1use core::fmt::Debug;
8use core::num::NonZeroU16;
9use core::ops::{ControlFlow, RangeInclusive};
10
11use derivative::Derivative;
12use log::{error, warn};
13use net_types::SpecifiedAddr;
14use net_types::ip::IpVersionMarker;
15use netstack3_base::sync::Mutex;
16use netstack3_base::{
17 Inspectable, InspectableValue, Inspector as _, IpAddressId as _, IpDeviceAddr, MarkDomain,
18 WeakIpAddressId,
19};
20use once_cell::sync::OnceCell;
21use packet_formats::ip::IpExt;
22use rand::Rng as _;
23
24use crate::actions::MarkAction;
25use crate::conntrack::{
26 CompatibleWith, Connection, ConnectionDirection, ConnectionExclusive, Table, TransportProtocol,
27 Tuple,
28};
29use crate::context::{FilterBindingsContext, FilterBindingsTypes, NatContext};
30use crate::logic::{IngressVerdict, Interfaces, RoutineResult, Verdict};
31use crate::packets::{
32 FilterIpExt, IcmpErrorMut, IpPacket, MaybeIcmpErrorMut as _, MaybeTransportPacketMut as _,
33 TransportPacketMut as _,
34};
35use crate::state::{FilterMarkMetadata, Hook};
36
37#[derive(Derivative)]
46#[derivative(Default(bound = ""), Debug(bound = "A: Debug"), PartialEq(bound = ""))]
47pub struct NatConfig<I: IpExt, A> {
48 destination: OnceCell<ShouldNat<I, A>>,
49 source: OnceCell<ShouldNat<I, A>>,
50}
51
52#[derive(Derivative)]
55#[derivative(Debug(bound = "A: Debug"), PartialEq(bound = ""))]
56pub(crate) enum ShouldNat<I: IpExt, A> {
57 Yes(#[derivative(PartialEq = "ignore")] Option<CachedAddr<I, A>>),
62 No,
64}
65
66impl<I: IpExt, A: PartialEq> CompatibleWith for NatConfig<I, A> {
67 fn compatible_with(&self, other: &Self) -> bool {
68 self.source.get().unwrap_or(&ShouldNat::No) == other.source.get().unwrap_or(&ShouldNat::No)
74 && self.destination.get().unwrap_or(&ShouldNat::No)
75 == other.destination.get().unwrap_or(&ShouldNat::No)
76 }
77}
78
79impl<I: IpExt, A, BT: FilterBindingsTypes> Connection<I, NatConfig<I, A>, BT> {
80 pub fn destination_nat(&self) -> bool {
81 match self.external_data().destination.get() {
82 Some(ShouldNat::Yes(_)) => true,
83 Some(ShouldNat::No) | None => false,
84 }
85 }
86}
87
88impl<I: IpExt, A: InspectableValue> Inspectable for NatConfig<I, A> {
89 fn record<Inspector: netstack3_base::Inspector>(&self, inspector: &mut Inspector) {
90 fn record_nat_status<
91 I: IpExt,
92 A: InspectableValue,
93 Inspector: netstack3_base::Inspector,
94 >(
95 inspector: &mut Inspector,
96 config: &OnceCell<ShouldNat<I, A>>,
97 ) {
98 let status = match config.get() {
99 None => "Unconfigured",
100 Some(ShouldNat::No) => "No-op",
101 Some(ShouldNat::Yes(cached_addr)) => {
102 if let Some(CachedAddr { id, _marker }) = cached_addr {
103 match &*id.lock() {
104 Some(id) => inspector.record_inspectable_value("To", id),
105 None => inspector.record_str("To", "InvalidAddress"),
106 }
107 }
108 "NAT"
109 }
110 };
111 inspector.record_str("Status", status);
112 }
113
114 let Self { source, destination } = self;
115 inspector.record_child("NAT", |inspector| {
116 inspector
117 .record_child("Destination", |inspector| record_nat_status(inspector, destination));
118 inspector.record_child("Source", |inspector| record_nat_status(inspector, source));
119 });
120 }
121}
122
123#[derive(Derivative)]
126#[derivative(Debug(bound = "A: Debug"))]
127pub(crate) struct CachedAddr<I: IpExt, A> {
128 id: Mutex<Option<A>>,
129 _marker: IpVersionMarker<I>,
130}
131
132impl<I: IpExt, A> CachedAddr<I, A> {
133 fn new(id: A) -> Self {
134 Self { id: Mutex::new(Some(id)), _marker: IpVersionMarker::new() }
135 }
136}
137
138impl<I: IpExt, A: WeakIpAddressId<I::Addr>> CachedAddr<I, A> {
139 fn validate_or_replace<CC, BT>(
144 &self,
145 core_ctx: &mut CC,
146 device: &CC::DeviceId,
147 addr: I::Addr,
148 ) -> Verdict
149 where
150 CC: NatContext<I, BT, WeakAddressId = A>,
151 BT: FilterBindingsContext,
152 {
153 let Self { id, _marker } = self;
154
155 {
157 if id.lock().as_ref().map(|id| id.is_assigned()).unwrap_or(false) {
158 return Verdict::Accept(());
159 }
160 }
161
162 match IpDeviceAddr::new(addr).and_then(|addr| core_ctx.get_address_id(device, addr)) {
173 Some(new) => {
174 *id.lock() = Some(new.downgrade());
175 Verdict::Accept(())
176 }
177 None => {
182 *id.lock() = None;
183 Verdict::Drop
184 }
185 }
186 }
187}
188
189#[derive(Debug, Clone, Copy, PartialEq)]
191pub enum NatType {
192 Destination,
194 Source,
196}
197
198pub(crate) trait NatHook<I: FilterIpExt> {
199 type Verdict<R: Debug>: FilterVerdict<R> + Debug;
200
201 fn verdict_behavior<R: Debug>(v: Self::Verdict<R>) -> ControlFlow<Self::Verdict<()>, R>;
202
203 const NAT_TYPE: NatType;
204
205 fn evaluate_result<P, CC, BC>(
208 core_ctx: &mut CC,
209 bindings_ctx: &mut BC,
210 table: &Table<I, NatConfig<I, CC::WeakAddressId>, BC>,
211 conn: &mut ConnectionExclusive<I, NatConfig<I, CC::WeakAddressId>, BC>,
212 packet: &P,
213 interfaces: &Interfaces<'_, CC::DeviceId>,
214 result: RoutineResult<I>,
215 ) -> ControlFlow<Self::Verdict<NatConfigurationResult<I, CC::WeakAddressId, BC>>>
216 where
217 P: IpPacket<I>,
218 CC: NatContext<I, BC>,
219 BC: FilterBindingsContext;
220
221 fn redirect_addr<P, CC, BT>(
229 core_ctx: &mut CC,
230 packet: &P,
231 ingress: Option<&CC::DeviceId>,
232 ) -> Option<(I::Addr, Option<CachedAddr<I, CC::WeakAddressId>>)>
233 where
234 P: IpPacket<I>,
235 CC: NatContext<I, BT>,
236 BT: FilterBindingsTypes;
237
238 fn interface<DeviceId>(interfaces: Interfaces<'_, DeviceId>) -> &DeviceId;
245}
246
247pub(crate) trait FilterVerdict<R>: From<Verdict<R>> {
248 fn accept(&self) -> Option<&R>;
249}
250
251impl<R> FilterVerdict<R> for Verdict<R> {
252 fn accept(&self) -> Option<&R> {
253 match self {
254 Self::Accept(result) => Some(result),
255 Self::Drop => None,
256 }
257 }
258}
259
260impl<R> Verdict<R> {
261 fn into_behavior(self) -> ControlFlow<Verdict<()>, R> {
262 match self {
263 Self::Accept(result) => ControlFlow::Continue(result),
264 Self::Drop => ControlFlow::Break(Verdict::Drop),
265 }
266 }
267}
268
269#[derive(Derivative)]
270#[derivative(Debug(bound = "A: Debug"))]
271pub(crate) enum NatConfigurationResult<I: IpExt, A, BT: FilterBindingsTypes> {
272 Result(ShouldNat<I, A>),
273 AdoptExisting(Connection<I, NatConfig<I, A>, BT>),
274}
275
276pub(crate) enum IngressHook {}
277
278impl<I: FilterIpExt> NatHook<I> for IngressHook {
279 type Verdict<R: Debug> = IngressVerdict<I, R>;
280
281 fn verdict_behavior<R: Debug>(v: Self::Verdict<R>) -> ControlFlow<Self::Verdict<()>, R> {
282 v.into_behavior()
283 }
284
285 const NAT_TYPE: NatType = NatType::Destination;
286
287 fn evaluate_result<P, CC, BC>(
288 core_ctx: &mut CC,
289 bindings_ctx: &mut BC,
290 table: &Table<I, NatConfig<I, CC::WeakAddressId>, BC>,
291 conn: &mut ConnectionExclusive<I, NatConfig<I, CC::WeakAddressId>, BC>,
292 packet: &P,
293 interfaces: &Interfaces<'_, CC::DeviceId>,
294 result: RoutineResult<I>,
295 ) -> ControlFlow<Self::Verdict<NatConfigurationResult<I, CC::WeakAddressId, BC>>>
296 where
297 P: IpPacket<I>,
298 CC: NatContext<I, BC>,
299 BC: FilterBindingsContext,
300 {
301 match result {
302 RoutineResult::Accept | RoutineResult::Return => ControlFlow::Continue(()),
303 RoutineResult::Drop => ControlFlow::Break(Verdict::Drop.into()),
304 RoutineResult::TransparentLocalDelivery { addr, port } => {
305 ControlFlow::Break(IngressVerdict::TransparentLocalDelivery { addr, port })
306 }
307 RoutineResult::Redirect { dst_port } => {
308 ControlFlow::Break(configure_redirect_nat::<Self, _, _, _, _>(
309 core_ctx,
310 bindings_ctx,
311 table,
312 conn,
313 packet,
314 interfaces,
315 dst_port,
316 ))
317 }
318 result @ RoutineResult::Masquerade { .. } => {
319 unreachable!("SNAT not supported in INGRESS; got {result:?}")
320 }
321 }
322 }
323
324 fn redirect_addr<P, CC, BT>(
325 core_ctx: &mut CC,
326 packet: &P,
327 ingress: Option<&CC::DeviceId>,
328 ) -> Option<(I::Addr, Option<CachedAddr<I, CC::WeakAddressId>>)>
329 where
330 P: IpPacket<I>,
331 CC: NatContext<I, BT>,
332 BT: FilterBindingsTypes,
333 {
334 let interface = ingress.expect("must have ingress interface in ingress hook");
335 let addr_id = core_ctx
336 .get_local_addr_for_remote(interface, SpecifiedAddr::new(packet.src_addr()))
337 .or_else(|| {
338 warn!(
339 "cannot redirect because there is no address assigned to the incoming \
340 interface {interface:?}; dropping packet",
341 );
342 None
343 })?;
344 let addr = addr_id.addr();
345 Some((addr.addr(), Some(CachedAddr::new(addr_id.downgrade()))))
346 }
347
348 fn interface<DeviceId>(interfaces: Interfaces<'_, DeviceId>) -> &DeviceId {
349 let Interfaces { ingress, egress: _ } = interfaces;
350 ingress.expect("ingress interface must be provided to INGRESS hook")
351 }
352}
353
354impl<I: IpExt, R> FilterVerdict<R> for IngressVerdict<I, R> {
355 fn accept(&self) -> Option<&R> {
356 match self {
357 Self::Verdict(Verdict::Accept(result)) => Some(result),
358 Self::Verdict(Verdict::Drop) | Self::TransparentLocalDelivery { .. } => None,
359 }
360 }
361}
362
363impl<I: IpExt, R> IngressVerdict<I, R> {
364 fn into_behavior(self) -> ControlFlow<IngressVerdict<I, ()>, R> {
365 match self {
366 Self::Verdict(v) => match v.into_behavior() {
367 ControlFlow::Continue(r) => ControlFlow::Continue(r),
368 ControlFlow::Break(v) => ControlFlow::Break(v.into()),
369 },
370 Self::TransparentLocalDelivery { addr, port } => {
371 ControlFlow::Break(IngressVerdict::TransparentLocalDelivery { addr, port })
372 }
373 }
374 }
375}
376
377pub(crate) enum LocalEgressHook {}
378
379impl<I: FilterIpExt> NatHook<I> for LocalEgressHook {
380 type Verdict<R: Debug> = Verdict<R>;
381
382 fn verdict_behavior<R: Debug>(v: Self::Verdict<R>) -> ControlFlow<Self::Verdict<()>, R> {
383 v.into_behavior()
384 }
385
386 const NAT_TYPE: NatType = NatType::Destination;
387
388 fn evaluate_result<P, CC, BC>(
389 core_ctx: &mut CC,
390 bindings_ctx: &mut BC,
391 table: &Table<I, NatConfig<I, CC::WeakAddressId>, BC>,
392 conn: &mut ConnectionExclusive<I, NatConfig<I, CC::WeakAddressId>, BC>,
393 packet: &P,
394 interfaces: &Interfaces<'_, CC::DeviceId>,
395 result: RoutineResult<I>,
396 ) -> ControlFlow<Self::Verdict<NatConfigurationResult<I, CC::WeakAddressId, BC>>>
397 where
398 P: IpPacket<I>,
399 CC: NatContext<I, BC>,
400 BC: FilterBindingsContext,
401 {
402 match result {
403 RoutineResult::Accept | RoutineResult::Return => ControlFlow::Continue(()),
404 RoutineResult::Drop => ControlFlow::Break(Verdict::Drop),
405 result @ RoutineResult::TransparentLocalDelivery { .. } => {
406 unreachable!(
407 "transparent local delivery is only valid in INGRESS hook; got {result:?}"
408 )
409 }
410 result @ RoutineResult::Masquerade { .. } => {
411 unreachable!("SNAT not supported in LOCAL_EGRESS; got {result:?}")
412 }
413 RoutineResult::Redirect { dst_port } => {
414 ControlFlow::Break(configure_redirect_nat::<Self, _, _, _, _>(
415 core_ctx,
416 bindings_ctx,
417 table,
418 conn,
419 packet,
420 interfaces,
421 dst_port,
422 ))
423 }
424 }
425 }
426
427 fn redirect_addr<P, CC, BT>(
428 _: &mut CC,
429 _: &P,
430 _: Option<&CC::DeviceId>,
431 ) -> Option<(I::Addr, Option<CachedAddr<I, CC::WeakAddressId>>)>
432 where
433 P: IpPacket<I>,
434 CC: NatContext<I, BT>,
435 BT: FilterBindingsTypes,
436 {
437 Some((*I::LOOPBACK_ADDRESS, None))
438 }
439
440 fn interface<DeviceId>(interfaces: Interfaces<'_, DeviceId>) -> &DeviceId {
441 let Interfaces { ingress: _, egress } = interfaces;
442 egress.expect("egress interface must be provided to LOCAL_EGRESS hook")
443 }
444}
445
446pub(crate) enum LocalIngressHook {}
447
448impl<I: FilterIpExt> NatHook<I> for LocalIngressHook {
449 type Verdict<R: Debug> = Verdict<R>;
450
451 fn verdict_behavior<R: Debug>(v: Self::Verdict<R>) -> ControlFlow<Self::Verdict<()>, R> {
452 v.into_behavior()
453 }
454
455 const NAT_TYPE: NatType = NatType::Source;
456
457 fn evaluate_result<P, CC, BC>(
458 _core_ctx: &mut CC,
459 _bindings_ctx: &mut BC,
460 _table: &Table<I, NatConfig<I, CC::WeakAddressId>, BC>,
461 _conn: &mut ConnectionExclusive<I, NatConfig<I, CC::WeakAddressId>, BC>,
462 _packet: &P,
463 _interfaces: &Interfaces<'_, CC::DeviceId>,
464 result: RoutineResult<I>,
465 ) -> ControlFlow<Self::Verdict<NatConfigurationResult<I, CC::WeakAddressId, BC>>>
466 where
467 P: IpPacket<I>,
468 CC: NatContext<I, BC>,
469 BC: FilterBindingsContext,
470 {
471 match result {
472 RoutineResult::Accept | RoutineResult::Return => ControlFlow::Continue(()),
473 RoutineResult::Drop => ControlFlow::Break(Verdict::Drop),
474 result @ RoutineResult::Masquerade { .. } => {
475 unreachable!("Masquerade not supported in LOCAL_INGRESS; got {result:?}")
476 }
477 result @ RoutineResult::TransparentLocalDelivery { .. } => {
478 unreachable!(
479 "transparent local delivery is only valid in INGRESS hook; got {result:?}"
480 )
481 }
482 result @ RoutineResult::Redirect { .. } => {
483 unreachable!("DNAT not supported in LOCAL_INGRESS; got {result:?}")
484 }
485 }
486 }
487
488 fn redirect_addr<P, CC, BT>(
489 _: &mut CC,
490 _: &P,
491 _: Option<&CC::DeviceId>,
492 ) -> Option<(I::Addr, Option<CachedAddr<I, CC::WeakAddressId>>)>
493 where
494 P: IpPacket<I>,
495 CC: NatContext<I, BT>,
496 BT: FilterBindingsTypes,
497 {
498 unreachable!("DNAT not supported in LOCAL_INGRESS; cannot perform redirect action")
499 }
500
501 fn interface<DeviceId>(interfaces: Interfaces<'_, DeviceId>) -> &DeviceId {
502 let Interfaces { ingress, egress: _ } = interfaces;
503 ingress.expect("ingress interface must be provided to LOCAL_INGRESS hook")
504 }
505}
506
507pub(crate) enum EgressHook {}
508
509impl<I: FilterIpExt> NatHook<I> for EgressHook {
510 type Verdict<R: Debug> = Verdict<R>;
511
512 fn verdict_behavior<R: Debug>(v: Self::Verdict<R>) -> ControlFlow<Self::Verdict<()>, R> {
513 v.into_behavior()
514 }
515
516 const NAT_TYPE: NatType = NatType::Source;
517
518 fn evaluate_result<P, CC, BC>(
519 core_ctx: &mut CC,
520 bindings_ctx: &mut BC,
521 table: &Table<I, NatConfig<I, CC::WeakAddressId>, BC>,
522 conn: &mut ConnectionExclusive<I, NatConfig<I, CC::WeakAddressId>, BC>,
523 packet: &P,
524 interfaces: &Interfaces<'_, CC::DeviceId>,
525 result: RoutineResult<I>,
526 ) -> ControlFlow<Self::Verdict<NatConfigurationResult<I, CC::WeakAddressId, BC>>>
527 where
528 P: IpPacket<I>,
529 CC: NatContext<I, BC>,
530 BC: FilterBindingsContext,
531 {
532 match result {
533 RoutineResult::Accept | RoutineResult::Return => ControlFlow::Continue(()),
534 RoutineResult::Drop => ControlFlow::Break(Verdict::Drop),
535 RoutineResult::Masquerade { src_port } => {
536 ControlFlow::Break(configure_masquerade_nat::<_, _, _, _>(
537 core_ctx,
538 bindings_ctx,
539 table,
540 conn,
541 packet,
542 interfaces,
543 src_port,
544 ))
545 }
546 result @ RoutineResult::TransparentLocalDelivery { .. } => {
547 unreachable!(
548 "transparent local delivery is only valid in INGRESS hook; got {result:?}"
549 )
550 }
551 result @ RoutineResult::Redirect { .. } => {
552 unreachable!("DNAT not supported in EGRESS; got {result:?}")
553 }
554 }
555 }
556
557 fn redirect_addr<P, CC, BT>(
558 _: &mut CC,
559 _: &P,
560 _: Option<&CC::DeviceId>,
561 ) -> Option<(I::Addr, Option<CachedAddr<I, CC::WeakAddressId>>)>
562 where
563 P: IpPacket<I>,
564 CC: NatContext<I, BT>,
565 BT: FilterBindingsTypes,
566 {
567 unreachable!("DNAT not supported in EGRESS; cannot perform redirect action")
568 }
569
570 fn interface<DeviceId>(interfaces: Interfaces<'_, DeviceId>) -> &DeviceId {
571 let Interfaces { ingress: _, egress } = interfaces;
572 egress.expect("egress interface must be provided to EGRESS hook")
573 }
574}
575
576impl<I: IpExt, A, BT: FilterBindingsTypes> Connection<I, NatConfig<I, A>, BT> {
577 fn relevant_config(
578 &self,
579 hook_nat_type: NatType,
580 direction: ConnectionDirection,
581 ) -> (&OnceCell<ShouldNat<I, A>>, NatType) {
582 let NatConfig { source, destination } = self.external_data();
583 match (hook_nat_type, direction) {
584 (NatType::Destination, ConnectionDirection::Original)
589 | (NatType::Source, ConnectionDirection::Reply) => (destination, NatType::Destination),
590 (NatType::Source, ConnectionDirection::Original)
595 | (NatType::Destination, ConnectionDirection::Reply) => (source, NatType::Source),
596 }
597 }
598
599 fn relevant_reply_tuple_addr(&self, nat_type: NatType) -> I::Addr {
600 match nat_type {
601 NatType::Destination => self.reply_tuple().src_addr,
602 NatType::Source => self.reply_tuple().dst_addr,
603 }
604 }
605
606 fn nat_config(
610 &self,
611 hook_nat_type: NatType,
612 direction: ConnectionDirection,
613 ) -> Option<&ShouldNat<I, A>> {
614 let (config, _nat_type) = self.relevant_config(hook_nat_type, direction);
615 config.get()
616 }
617
618 fn set_nat_config(
626 &self,
627 hook_nat_type: NatType,
628 direction: ConnectionDirection,
629 value: ShouldNat<I, A>,
630 ) -> Result<&ShouldNat<I, A>, (ShouldNat<I, A>, NatType)> {
631 let (config, nat_type) = self.relevant_config(hook_nat_type, direction);
632 let mut value = Some(value);
633 let config = config.get_or_init(|| value.take().unwrap());
634 match value {
635 None => Ok(config),
636 Some(value) => Err((value, nat_type)),
637 }
638 }
639}
640
641pub(crate) fn perform_nat<N, I, P, CC, BC>(
647 core_ctx: &mut CC,
648 bindings_ctx: &mut BC,
649 nat_installed: bool,
650 table: &Table<I, NatConfig<I, CC::WeakAddressId>, BC>,
651 conn: &mut Connection<I, NatConfig<I, CC::WeakAddressId>, BC>,
652 direction: ConnectionDirection,
653 hook: &Hook<I, BC::DeviceClass, ()>,
654 packet: &mut P,
655 interfaces: Interfaces<'_, CC::DeviceId>,
656) -> N::Verdict<()>
657where
658 N: NatHook<I>,
659 I: FilterIpExt,
660 P: IpPacket<I>,
661 CC: NatContext<I, BC>,
662 BC: FilterBindingsContext,
663{
664 if !nat_installed {
665 return Verdict::Accept(()).into();
666 }
667
668 let nat_config = if let Some(nat) = conn.nat_config(N::NAT_TYPE, direction) {
669 nat
670 } else {
671 let (verdict, exclusive) = match (&mut *conn, direction) {
674 (Connection::Exclusive(_), ConnectionDirection::Reply) => {
675 (Verdict::Accept(NatConfigurationResult::Result(ShouldNat::No)).into(), true)
686 }
687 (Connection::Shared(_), _) => {
688 (Verdict::Accept(NatConfigurationResult::Result(ShouldNat::No)).into(), false)
696 }
697 (Connection::Exclusive(conn), ConnectionDirection::Original) => {
698 let verdict = configure_nat::<N, _, _, _, _>(
699 core_ctx,
700 bindings_ctx,
701 table,
702 conn,
703 hook,
704 packet,
705 interfaces.clone(),
706 );
707 let verdict = if matches!(
712 verdict.accept(),
713 Some(&NatConfigurationResult::Result(ShouldNat::No))
714 ) && N::NAT_TYPE == NatType::Source
715 {
716 configure_snat_port(
717 bindings_ctx,
718 table,
719 conn,
720 None, ConflictStrategy::AdoptExisting,
722 )
723 .into()
724 } else {
725 verdict
726 };
727 (verdict, true)
728 }
729 };
730
731 let result = match N::verdict_behavior(verdict) {
732 ControlFlow::Break(verdict) => {
733 return verdict;
734 }
735 ControlFlow::Continue(result) => result,
736 };
737 let new_nat_config = match result {
738 NatConfigurationResult::Result(config) => Some(config),
739 NatConfigurationResult::AdoptExisting(existing) => {
740 *conn = existing;
741 None
742 }
743 };
744 if let Some(config) = new_nat_config {
745 conn.set_nat_config(N::NAT_TYPE, direction, config).unwrap_or_else(
746 |(value, nat_type)| {
747 if exclusive {
752 unreachable!(
753 "{nat_type:?} NAT should not have been configured yet, but found \
754 {value:?}"
755 );
756 }
757 &ShouldNat::No
758 },
759 )
760 } else {
761 conn.nat_config(N::NAT_TYPE, direction).unwrap_or(&ShouldNat::No)
762 }
763 };
764
765 match nat_config {
766 ShouldNat::No => return Verdict::Accept(()).into(),
767 ShouldNat::Yes(None) => {}
771 ShouldNat::Yes(Some(cached_addr)) => {
774 if direction == ConnectionDirection::Original {
779 match cached_addr.validate_or_replace(
780 core_ctx,
781 N::interface(interfaces),
782 conn.relevant_reply_tuple_addr(N::NAT_TYPE),
783 ) {
784 Verdict::Accept(()) => {}
785 Verdict::Drop => return Verdict::Drop.into(),
786 }
787 }
788 }
789 }
790 rewrite_packet(conn, direction, N::NAT_TYPE, packet).into()
791}
792
793struct NatMetadata {}
794
795impl FilterMarkMetadata for NatMetadata {
796 fn apply_mark_action(&mut self, domain: MarkDomain, action: MarkAction) {
797 unreachable!("nat is not expected to configure packet marks, got {domain:?} -> {action:?}");
798 }
799}
800
801fn configure_nat<N, I, P, CC, BC>(
808 core_ctx: &mut CC,
809 bindings_ctx: &mut BC,
810 table: &Table<I, NatConfig<I, CC::WeakAddressId>, BC>,
811 conn: &mut ConnectionExclusive<I, NatConfig<I, CC::WeakAddressId>, BC>,
812 hook: &Hook<I, BC::DeviceClass, ()>,
813 packet: &P,
814 interfaces: Interfaces<'_, CC::DeviceId>,
815) -> N::Verdict<NatConfigurationResult<I, CC::WeakAddressId, BC>>
816where
817 N: NatHook<I>,
818 I: FilterIpExt,
819 P: IpPacket<I>,
820 CC: NatContext<I, BC>,
821 BC: FilterBindingsContext,
822{
823 let Hook { routines } = hook;
824 for routine in routines {
825 let result = super::check_routine(&routine, packet, &interfaces, &mut NatMetadata {});
826 match N::evaluate_result(core_ctx, bindings_ctx, table, conn, packet, &interfaces, result) {
827 ControlFlow::Break(result) => return result,
828 ControlFlow::Continue(()) => {}
829 }
830 }
831 Verdict::Accept(NatConfigurationResult::Result(ShouldNat::No)).into()
832}
833
834fn configure_redirect_nat<N, I, P, CC, BC>(
837 core_ctx: &mut CC,
838 bindings_ctx: &mut BC,
839 table: &Table<I, NatConfig<I, CC::WeakAddressId>, BC>,
840 conn: &mut ConnectionExclusive<I, NatConfig<I, CC::WeakAddressId>, BC>,
841 packet: &P,
842 interfaces: &Interfaces<'_, CC::DeviceId>,
843 dst_port_range: Option<RangeInclusive<NonZeroU16>>,
844) -> N::Verdict<NatConfigurationResult<I, CC::WeakAddressId, BC>>
845where
846 N: NatHook<I>,
847 I: FilterIpExt,
848 P: IpPacket<I>,
849 CC: NatContext<I, BC>,
850 BC: FilterBindingsContext,
851{
852 match N::NAT_TYPE {
853 NatType::Source => panic!("DNAT action called from SNAT-only hook"),
854 NatType::Destination => {}
855 }
856
857 let Some((addr, cached_addr)) = N::redirect_addr(core_ctx, packet, interfaces.ingress) else {
864 return Verdict::Drop.into();
865 };
866 conn.rewrite_reply_src_addr(addr);
867
868 let Some(range) = dst_port_range else {
869 return Verdict::Accept(NatConfigurationResult::Result(ShouldNat::Yes(cached_addr))).into();
870 };
871 match rewrite_reply_tuple_port(
872 bindings_ctx,
873 table,
874 conn,
875 ReplyTuplePort::Source,
876 range,
877 true, ConflictStrategy::RewritePort,
879 ) {
880 Verdict::Accept(
883 NatConfigurationResult::Result(ShouldNat::Yes(_))
884 | NatConfigurationResult::Result(ShouldNat::No),
885 ) => Verdict::Accept(NatConfigurationResult::Result(ShouldNat::Yes(cached_addr))),
886 Verdict::Accept(NatConfigurationResult::AdoptExisting(_)) => {
887 unreachable!("cannot adopt existing connection")
888 }
889 Verdict::Drop => Verdict::Drop,
890 }
891 .into()
892}
893
894fn configure_masquerade_nat<I, P, CC, BC>(
898 core_ctx: &mut CC,
899 bindings_ctx: &mut BC,
900 table: &Table<I, NatConfig<I, CC::WeakAddressId>, BC>,
901 conn: &mut ConnectionExclusive<I, NatConfig<I, CC::WeakAddressId>, BC>,
902 packet: &P,
903 interfaces: &Interfaces<'_, CC::DeviceId>,
904 src_port_range: Option<RangeInclusive<NonZeroU16>>,
905) -> Verdict<NatConfigurationResult<I, CC::WeakAddressId, BC>>
906where
907 I: FilterIpExt,
908 P: IpPacket<I>,
909 CC: NatContext<I, BC>,
910 BC: FilterBindingsContext,
911{
912 let interface = interfaces.egress.expect(
916 "must have egress interface in EGRESS hook; Masquerade NAT is only valid in EGRESS",
917 );
918 let Some(addr) =
919 core_ctx.get_local_addr_for_remote(interface, SpecifiedAddr::new(packet.dst_addr()))
920 else {
921 warn!(
923 "cannot masquerade because there is no address assigned to the outgoing interface \
924 {interface:?}; dropping packet",
925 );
926 return Verdict::Drop;
927 };
928 conn.rewrite_reply_dst_addr(addr.addr().addr());
929
930 match configure_snat_port(
933 bindings_ctx,
934 table,
935 conn,
936 src_port_range,
937 ConflictStrategy::RewritePort,
938 ) {
939 Verdict::Accept(
942 NatConfigurationResult::Result(ShouldNat::Yes(_))
943 | NatConfigurationResult::Result(ShouldNat::No),
944 ) => Verdict::Accept(NatConfigurationResult::Result(ShouldNat::Yes(Some(
945 CachedAddr::new(addr.downgrade()),
946 )))),
947 Verdict::Accept(NatConfigurationResult::AdoptExisting(_)) => {
948 unreachable!("cannot adopt existing connection")
949 }
950 Verdict::Drop => Verdict::Drop,
951 }
952}
953
954fn configure_snat_port<I, A, BC>(
955 bindings_ctx: &mut BC,
956 table: &Table<I, NatConfig<I, A>, BC>,
957 conn: &mut ConnectionExclusive<I, NatConfig<I, A>, BC>,
958 src_port_range: Option<RangeInclusive<NonZeroU16>>,
959 conflict_strategy: ConflictStrategy,
960) -> Verdict<NatConfigurationResult<I, A, BC>>
961where
962 I: IpExt,
963 BC: FilterBindingsContext,
964 A: PartialEq,
965{
966 let (range, ensure_port_in_range) = if let Some(range) = src_port_range {
971 (range, true)
972 } else {
973 let reply_tuple = conn.reply_tuple();
974 let Some(range) =
975 similar_port_or_id_range(reply_tuple.protocol, reply_tuple.dst_port_or_id)
976 else {
977 return Verdict::Drop;
978 };
979 (range, false)
980 };
981 rewrite_reply_tuple_port(
982 bindings_ctx,
983 table,
984 conn,
985 ReplyTuplePort::Destination,
986 range,
987 ensure_port_in_range,
988 conflict_strategy,
989 )
990}
991
992fn similar_port_or_id_range(
999 protocol: TransportProtocol,
1000 port_or_id: u16,
1001) -> Option<RangeInclusive<NonZeroU16>> {
1002 match protocol {
1003 TransportProtocol::Tcp | TransportProtocol::Udp => Some(match port_or_id {
1004 _ if port_or_id < 512 => NonZeroU16::MIN..=NonZeroU16::new(511).unwrap(),
1005 _ if port_or_id < 1024 => NonZeroU16::MIN..=NonZeroU16::new(1023).unwrap(),
1006 _ => NonZeroU16::new(1024).unwrap()..=NonZeroU16::MAX,
1007 }),
1008 TransportProtocol::Icmp => Some(NonZeroU16::MIN..=NonZeroU16::MAX),
1010 TransportProtocol::Other(p) => {
1011 error!(
1012 "cannot rewrite port or ID of unsupported transport protocol {p}; dropping packet"
1013 );
1014 None
1015 }
1016 }
1017}
1018
1019#[derive(Clone, Copy)]
1020enum ReplyTuplePort {
1021 Source,
1022 Destination,
1023}
1024
1025enum ConflictStrategy {
1026 AdoptExisting,
1027 RewritePort,
1028}
1029
1030fn rewrite_reply_tuple_port<I: IpExt, BC: FilterBindingsContext, A: PartialEq>(
1034 bindings_ctx: &mut BC,
1035 table: &Table<I, NatConfig<I, A>, BC>,
1036 conn: &mut ConnectionExclusive<I, NatConfig<I, A>, BC>,
1037 which_port: ReplyTuplePort,
1038 port_range: RangeInclusive<NonZeroU16>,
1039 ensure_port_in_range: bool,
1040 conflict_strategy: ConflictStrategy,
1041) -> Verdict<NatConfigurationResult<I, A, BC>> {
1042 let current_port = match which_port {
1046 ReplyTuplePort::Source => conn.reply_tuple().src_port_or_id,
1047 ReplyTuplePort::Destination => conn.reply_tuple().dst_port_or_id,
1048 };
1049 let already_in_range = !ensure_port_in_range
1050 || NonZeroU16::new(current_port).map(|port| port_range.contains(&port)).unwrap_or(false);
1051 if already_in_range {
1052 match table.get_shared_connection(conn.reply_tuple()) {
1053 None => return Verdict::Accept(NatConfigurationResult::Result(ShouldNat::No)),
1054 Some(conflict) => match conflict_strategy {
1055 ConflictStrategy::AdoptExisting => {
1056 if conflict.compatible_with(&*conn) {
1060 return Verdict::Accept(NatConfigurationResult::AdoptExisting(
1061 Connection::Shared(conflict),
1062 ));
1063 }
1064 }
1065 ConflictStrategy::RewritePort => {}
1066 },
1067 }
1068 }
1069
1070 const MAX_ATTEMPTS: u16 = 128;
1079 let len = port_range.end().get() - port_range.start().get() + 1;
1080 let mut rng = bindings_ctx.rng();
1081 let start = rng.random_range(port_range.start().get()..=port_range.end().get());
1082 for i in 0..core::cmp::min(MAX_ATTEMPTS, len) {
1083 let offset = (start + i) % len;
1086 let new_port = port_range.start().checked_add(offset).unwrap();
1087 match which_port {
1088 ReplyTuplePort::Source => conn.rewrite_reply_src_port_or_id(new_port.get()),
1089 ReplyTuplePort::Destination => conn.rewrite_reply_dst_port_or_id(new_port.get()),
1090 };
1091 if !table.contains_tuple(conn.reply_tuple()) {
1092 return Verdict::Accept(NatConfigurationResult::Result(ShouldNat::Yes(None)));
1093 }
1094 }
1095
1096 Verdict::Drop
1097}
1098
1099fn rewrite_packet_for_dst_nat<I, P>(
1100 packet: &mut P,
1101 new_dst_addr: I::Addr,
1102 new_dst_port: u16,
1103) -> Verdict
1104where
1105 I: FilterIpExt,
1106 P: IpPacket<I>,
1107{
1108 packet.set_dst_addr(new_dst_addr);
1109 let Some(proto) = packet.protocol() else {
1110 return Verdict::Accept(());
1111 };
1112 let mut transport = packet.transport_packet_mut();
1113 let Some(mut transport) = transport.transport_packet_mut() else {
1114 return Verdict::Accept(());
1115 };
1116 let Some(new_dst_port) = NonZeroU16::new(new_dst_port) else {
1117 error!("cannot rewrite dst port to unspecified; dropping {proto} packet");
1120 return Verdict::Drop;
1121 };
1122 transport.set_dst_port(new_dst_port);
1123
1124 Verdict::Accept(())
1125}
1126
1127fn rewrite_packet_for_src_nat<I, P>(
1128 packet: &mut P,
1129 new_src_addr: I::Addr,
1130 new_src_port: u16,
1131) -> Verdict
1132where
1133 I: FilterIpExt,
1134 P: IpPacket<I>,
1135{
1136 packet.set_src_addr(new_src_addr);
1137 let Some(proto) = packet.protocol() else {
1138 return Verdict::Accept(());
1139 };
1140 let mut transport = packet.transport_packet_mut();
1141 let Some(mut transport) = transport.transport_packet_mut() else {
1142 return Verdict::Accept(());
1143 };
1144 let Some(new_src_port) = NonZeroU16::new(new_src_port) else {
1145 error!("cannot rewrite src port to unspecified; dropping {proto} packet");
1148 return Verdict::Drop;
1149 };
1150 transport.set_src_port(new_src_port);
1151
1152 Verdict::Accept(())
1153}
1154
1155fn rewrite_icmp_error_payload<I, E>(icmp_error: &mut E, nat: NatType, tuple: &Tuple<I>) -> Verdict
1156where
1157 I: FilterIpExt,
1158 E: IcmpErrorMut<I>,
1159{
1160 let should_recalculate_checksum = match icmp_error.inner_packet() {
1232 Some(mut inner_packet) => {
1233 let verdict = match nat {
1234 NatType::Destination => rewrite_packet_for_src_nat(
1235 &mut inner_packet,
1236 tuple.src_addr,
1237 tuple.src_port_or_id,
1238 ),
1239 NatType::Source => rewrite_packet_for_dst_nat(
1240 &mut inner_packet,
1241 tuple.dst_addr,
1242 tuple.dst_port_or_id,
1243 ),
1244 };
1245
1246 match verdict {
1247 Verdict::Accept(_) => true,
1248 Verdict::Drop => return Verdict::Drop,
1249 }
1250 }
1251 None => false,
1252 };
1253
1254 if should_recalculate_checksum {
1255 if !icmp_error.recalculate_checksum() {
1261 return Verdict::Drop;
1262 }
1263 }
1264
1265 Verdict::Accept(())
1266}
1267
1268fn rewrite_packet<I, P, A, BT>(
1271 conn: &Connection<I, NatConfig<I, A>, BT>,
1272 direction: ConnectionDirection,
1273 nat: NatType,
1274 packet: &mut P,
1275) -> Verdict
1276where
1277 I: FilterIpExt,
1278 P: IpPacket<I>,
1279 BT: FilterBindingsTypes,
1280{
1281 let tuple = match direction {
1289 ConnectionDirection::Original => conn.reply_tuple(),
1290 ConnectionDirection::Reply => conn.original_tuple(),
1291 };
1292
1293 if let Some(mut icmp_error) = packet.icmp_error_mut().icmp_error_mut() {
1294 match rewrite_icmp_error_payload(&mut icmp_error, nat, tuple) {
1295 Verdict::Accept(_) => (),
1296 Verdict::Drop => return Verdict::Drop,
1297 }
1298 }
1299
1300 match nat {
1301 NatType::Destination => {
1302 rewrite_packet_for_dst_nat(packet, tuple.src_addr, tuple.src_port_or_id)
1303 }
1304 NatType::Source => rewrite_packet_for_src_nat(packet, tuple.dst_addr, tuple.dst_port_or_id),
1305 }
1306}
1307
1308#[cfg(test)]
1309mod tests {
1310 use alloc::sync::Arc;
1311 use alloc::vec;
1312 use core::marker::PhantomData;
1313
1314 use assert_matches::assert_matches;
1315 use ip_test_macro::ip_test;
1316 use net_types::ip::{AddrSubnet, Ipv4};
1317 use netstack3_base::IntoCoreTimerCtx;
1318 use netstack3_base::testutil::{FakeDeviceClass, FakeMatcherDeviceId};
1319 use packet::{EmptyBuf, PacketBuilder, Serializer};
1320 use packet_formats::ip::{IpPacketBuilder, IpProto};
1321 use packet_formats::udp::UdpPacketBuilder;
1322 use test_case::{test_case, test_matrix};
1323
1324 use super::*;
1325 use crate::conntrack::Tuple;
1326 use crate::context::testutil::{
1327 FakeBindingsCtx, FakeNatCtx, FakePrimaryAddressId, FakeWeakAddressId,
1328 };
1329 use crate::matchers::PacketMatcher;
1330 use crate::packets::testutil::internal::{
1331 ArbitraryValue, FakeIpPacket, FakeUdpPacket, IcmpErrorMessage, Icmpv4DestUnreachableError,
1332 Icmpv6DestUnreachableError,
1333 };
1334 use crate::state::{Action, Routine, Rule, TransparentProxy};
1335 use crate::testutil::TestIpExt;
1336
1337 impl<I: IpExt, A: PartialEq, BT: FilterBindingsTypes> PartialEq
1338 for NatConfigurationResult<I, A, BT>
1339 {
1340 fn eq(&self, other: &Self) -> bool {
1341 match (self, other) {
1342 (Self::Result(lhs), Self::Result(rhs)) => lhs == rhs,
1343 (Self::AdoptExisting(_), Self::AdoptExisting(_)) => {
1344 panic!("equality check for connections is not supported")
1345 }
1346 _ => false,
1347 }
1348 }
1349 }
1350
1351 impl<I: FilterIpExt, A, BC: FilterBindingsContext> ConnectionExclusive<I, NatConfig<I, A>, BC> {
1352 fn from_packet<P: IpPacket<I>>(bindings_ctx: &BC, packet: &P) -> Self {
1353 ConnectionExclusive::from_deconstructed_packet(
1354 bindings_ctx,
1355 &packet.conntrack_packet().unwrap(),
1356 )
1357 .expect("create conntrack entry")
1358 }
1359 }
1360
1361 impl<A, BC: FilterBindingsContext> ConnectionExclusive<Ipv4, NatConfig<Ipv4, A>, BC> {
1362 fn with_reply_tuple(bindings_ctx: &BC, which: ReplyTuplePort, port: u16) -> Self {
1363 Self::from_packet(bindings_ctx, &packet_with_port(which, port).reply())
1364 }
1365 }
1366
1367 fn packet_with_port(which: ReplyTuplePort, port: u16) -> FakeIpPacket<Ipv4, FakeUdpPacket> {
1368 let mut packet = FakeIpPacket::<Ipv4, FakeUdpPacket>::arbitrary_value();
1369 match which {
1370 ReplyTuplePort::Source => packet.body.src_port = port,
1371 ReplyTuplePort::Destination => packet.body.dst_port = port,
1372 }
1373 packet
1374 }
1375
1376 fn tuple_with_port(which: ReplyTuplePort, port: u16) -> Tuple<Ipv4> {
1377 packet_with_port(which, port).conntrack_packet().unwrap().tuple()
1378 }
1379
1380 #[test]
1381 fn accept_by_default_if_no_matching_rules_in_hook() {
1382 let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
1383 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1384 let mut core_ctx = FakeNatCtx::default();
1385 let packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1386 let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
1387
1388 assert_eq!(
1389 configure_nat::<LocalEgressHook, _, _, _, _>(
1390 &mut core_ctx,
1391 &mut bindings_ctx,
1392 &conntrack,
1393 &mut conn,
1394 &Hook::default(),
1395 &packet,
1396 Interfaces { ingress: None, egress: None },
1397 ),
1398 Verdict::Accept(NatConfigurationResult::Result(ShouldNat::No))
1399 );
1400 }
1401
1402 #[test]
1403 fn accept_by_default_if_return_from_routine() {
1404 let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
1405 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1406 let mut core_ctx = FakeNatCtx::default();
1407 let packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1408 let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
1409
1410 let hook = Hook {
1411 routines: vec![Routine {
1412 rules: vec![Rule::new(PacketMatcher::default(), Action::Return)],
1413 }],
1414 };
1415 assert_eq!(
1416 configure_nat::<LocalEgressHook, _, _, _, _>(
1417 &mut core_ctx,
1418 &mut bindings_ctx,
1419 &conntrack,
1420 &mut conn,
1421 &hook,
1422 &packet,
1423 Interfaces { ingress: None, egress: None },
1424 ),
1425 Verdict::Accept(NatConfigurationResult::Result(ShouldNat::No))
1426 );
1427 }
1428
1429 #[test]
1430 fn accept_terminal_for_installed_routine() {
1431 let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
1432 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1433 let mut core_ctx = FakeNatCtx::default();
1434 let packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1435 let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
1436
1437 let routine = Routine {
1439 rules: vec![
1440 Rule::new(PacketMatcher::default(), Action::Accept),
1442 Rule::new(PacketMatcher::default(), Action::Drop),
1444 ],
1445 };
1446 assert_eq!(
1447 configure_nat::<LocalEgressHook, _, _, _, _>(
1448 &mut core_ctx,
1449 &mut bindings_ctx,
1450 &conntrack,
1451 &mut conn,
1452 &Hook { routines: vec![routine.clone()] },
1453 &packet,
1454 Interfaces { ingress: None, egress: None },
1455 ),
1456 Verdict::Accept(NatConfigurationResult::Result(ShouldNat::No))
1457 );
1458
1459 let hook = Hook {
1462 routines: vec![
1463 routine,
1464 Routine {
1465 rules: vec![
1466 Rule::new(PacketMatcher::default(), Action::Drop),
1468 ],
1469 },
1470 ],
1471 };
1472 assert_eq!(
1473 configure_nat::<LocalEgressHook, _, _, _, _>(
1474 &mut core_ctx,
1475 &mut bindings_ctx,
1476 &conntrack,
1477 &mut conn,
1478 &hook,
1479 &packet,
1480 Interfaces { ingress: None, egress: None },
1481 ),
1482 Verdict::Drop
1483 );
1484 }
1485
1486 #[test]
1487 fn drop_terminal_for_entire_hook() {
1488 let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
1489 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1490 let mut core_ctx = FakeNatCtx::default();
1491 let packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1492 let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
1493
1494 let hook = Hook {
1495 routines: vec![
1496 Routine {
1497 rules: vec![
1498 Rule::new(PacketMatcher::default(), Action::Drop),
1500 ],
1501 },
1502 Routine {
1503 rules: vec![
1504 Rule::new(PacketMatcher::default(), Action::Accept),
1506 ],
1507 },
1508 ],
1509 };
1510
1511 assert_eq!(
1512 configure_nat::<LocalEgressHook, _, _, _, _>(
1513 &mut core_ctx,
1514 &mut bindings_ctx,
1515 &conntrack,
1516 &mut conn,
1517 &hook,
1518 &packet,
1519 Interfaces { ingress: None, egress: None },
1520 ),
1521 Verdict::Drop
1522 );
1523 }
1524
1525 #[test]
1526 fn transparent_proxy_terminal_for_entire_hook() {
1527 let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
1528 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1529 let mut core_ctx = FakeNatCtx::default();
1530 let packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1531 let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
1532
1533 let ingress = Hook {
1534 routines: vec![
1535 Routine {
1536 rules: vec![Rule::new(
1537 PacketMatcher::default(),
1538 Action::TransparentProxy(TransparentProxy::LocalPort(LOCAL_PORT)),
1539 )],
1540 },
1541 Routine {
1542 rules: vec![
1543 Rule::new(PacketMatcher::default(), Action::Accept),
1545 ],
1546 },
1547 ],
1548 };
1549
1550 assert_eq!(
1551 configure_nat::<IngressHook, _, _, _, _>(
1552 &mut core_ctx,
1553 &mut bindings_ctx,
1554 &conntrack,
1555 &mut conn,
1556 &ingress,
1557 &packet,
1558 Interfaces { ingress: None, egress: None },
1559 ),
1560 IngressVerdict::TransparentLocalDelivery {
1561 addr: <Ipv4 as crate::packets::testutil::internal::TestIpExt>::DST_IP,
1562 port: LOCAL_PORT
1563 }
1564 );
1565 }
1566
1567 #[test]
1568 fn redirect_terminal_for_entire_hook() {
1569 let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
1570 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1571 let mut core_ctx = FakeNatCtx::default();
1572 let packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1573 let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
1574
1575 let hook = Hook {
1576 routines: vec![
1577 Routine {
1578 rules: vec![
1579 Rule::new(PacketMatcher::default(), Action::Redirect { dst_port: None }),
1581 ],
1582 },
1583 Routine {
1584 rules: vec![
1585 Rule::new(PacketMatcher::default(), Action::Drop),
1587 ],
1588 },
1589 ],
1590 };
1591
1592 assert_eq!(
1593 configure_nat::<LocalEgressHook, _, _, _, _>(
1594 &mut core_ctx,
1595 &mut bindings_ctx,
1596 &conntrack,
1597 &mut conn,
1598 &hook,
1599 &packet,
1600 Interfaces { ingress: None, egress: None },
1601 ),
1602 Verdict::Accept(NatConfigurationResult::Result(ShouldNat::Yes(None)))
1603 );
1604 }
1605
1606 #[ip_test(I)]
1607 fn masquerade_terminal_for_entire_hook<I: TestIpExt>() {
1608 let mut bindings_ctx = FakeBindingsCtx::<I>::new();
1609 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1610 let assigned_addr = AddrSubnet::new(I::SRC_IP_2, I::SUBNET.prefix()).unwrap();
1611 let mut core_ctx =
1612 FakeNatCtx::new([(FakeMatcherDeviceId::ethernet_interface(), assigned_addr)]);
1613 let packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1614 let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
1615
1616 let hook = Hook {
1617 routines: vec![
1618 Routine {
1619 rules: vec![
1620 Rule::new(PacketMatcher::default(), Action::Masquerade { src_port: None }),
1622 ],
1623 },
1624 Routine {
1625 rules: vec![
1626 Rule::new(PacketMatcher::default(), Action::Drop),
1628 ],
1629 },
1630 ],
1631 };
1632
1633 assert_matches!(
1634 configure_nat::<EgressHook, _, _, _, _>(
1635 &mut core_ctx,
1636 &mut bindings_ctx,
1637 &conntrack,
1638 &mut conn,
1639 &hook,
1640 &packet,
1641 Interfaces {
1642 ingress: None,
1643 egress: Some(&FakeMatcherDeviceId::ethernet_interface())
1644 },
1645 ),
1646 Verdict::Accept(NatConfigurationResult::Result(ShouldNat::Yes(Some(_))))
1647 );
1648 }
1649
1650 #[test]
1651 fn redirect_ingress_drops_packet_if_no_assigned_address() {
1652 let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
1653 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1654 let mut core_ctx = FakeNatCtx::default();
1655 let packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1656 let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
1657
1658 let hook = Hook {
1659 routines: vec![Routine {
1660 rules: vec![Rule::new(
1661 PacketMatcher::default(),
1662 Action::Redirect { dst_port: None },
1663 )],
1664 }],
1665 };
1666
1667 assert_eq!(
1668 configure_nat::<IngressHook, _, _, _, _>(
1669 &mut core_ctx,
1670 &mut bindings_ctx,
1671 &conntrack,
1672 &mut conn,
1673 &hook,
1674 &packet,
1675 Interfaces {
1676 ingress: Some(&FakeMatcherDeviceId::ethernet_interface()),
1677 egress: None
1678 },
1679 ),
1680 Verdict::Drop.into()
1681 );
1682 }
1683
1684 #[test]
1685 fn masquerade_egress_drops_packet_if_no_assigned_address() {
1686 let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
1687 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1688 let mut core_ctx = FakeNatCtx::default();
1689 let packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1690 let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
1691
1692 let hook = Hook {
1693 routines: vec![Routine {
1694 rules: vec![Rule::new(
1695 PacketMatcher::default(),
1696 Action::Masquerade { src_port: None },
1697 )],
1698 }],
1699 };
1700
1701 assert_eq!(
1702 configure_nat::<EgressHook, _, _, _, _>(
1703 &mut core_ctx,
1704 &mut bindings_ctx,
1705 &conntrack,
1706 &mut conn,
1707 &hook,
1708 &packet,
1709 Interfaces {
1710 ingress: None,
1711 egress: Some(&FakeMatcherDeviceId::ethernet_interface())
1712 },
1713 ),
1714 Verdict::Drop
1715 );
1716 }
1717
1718 trait NatHookExt<I: FilterIpExt>: NatHook<I, Verdict<()>: PartialEq> {
1719 fn interfaces<'a>(
1720 interface: &'a FakeMatcherDeviceId,
1721 ) -> Interfaces<'a, FakeMatcherDeviceId>;
1722 }
1723
1724 impl<I: FilterIpExt> NatHookExt<I> for IngressHook {
1725 fn interfaces<'a>(
1726 interface: &'a FakeMatcherDeviceId,
1727 ) -> Interfaces<'a, FakeMatcherDeviceId> {
1728 Interfaces { ingress: Some(interface), egress: None }
1729 }
1730 }
1731
1732 impl<I: FilterIpExt> NatHookExt<I> for LocalEgressHook {
1733 fn interfaces<'a>(
1734 interface: &'a FakeMatcherDeviceId,
1735 ) -> Interfaces<'a, FakeMatcherDeviceId> {
1736 Interfaces { ingress: None, egress: Some(interface) }
1737 }
1738 }
1739
1740 impl<I: FilterIpExt> NatHookExt<I> for EgressHook {
1741 fn interfaces<'a>(
1742 interface: &'a FakeMatcherDeviceId,
1743 ) -> Interfaces<'a, FakeMatcherDeviceId> {
1744 Interfaces { ingress: None, egress: Some(interface) }
1745 }
1746 }
1747
1748 const NAT_ENABLED_FOR_TESTS: bool = true;
1749
1750 #[ip_test(I)]
1751 fn nat_disabled_for_self_connected_flows<I: TestIpExt>() {
1752 let mut bindings_ctx = FakeBindingsCtx::<I>::new();
1753 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1754 let mut core_ctx = FakeNatCtx::default();
1755
1756 let mut packet = FakeIpPacket::<I, _> {
1757 src_ip: I::SRC_IP,
1758 dst_ip: I::SRC_IP,
1759 body: FakeUdpPacket { src_port: 22222, dst_port: 22222 },
1760 };
1761 let (mut conn, direction) = conntrack
1762 .get_connection_for_packet_and_update(
1763 &bindings_ctx,
1764 packet.conntrack_packet().expect("packet should be valid"),
1765 )
1766 .expect("packet should be valid")
1767 .expect("packet should be trackable");
1768
1769 let verdict = perform_nat::<LocalEgressHook, _, _, _, _>(
1773 &mut core_ctx,
1774 &mut bindings_ctx,
1775 NAT_ENABLED_FOR_TESTS,
1776 &conntrack,
1777 &mut conn,
1778 direction,
1779 &Hook {
1780 routines: vec![Routine {
1781 rules: vec![Rule::new(
1782 PacketMatcher::default(),
1783 Action::Redirect { dst_port: None },
1784 )],
1785 }],
1786 },
1787 &mut packet,
1788 <LocalEgressHook as NatHookExt<I>>::interfaces(
1789 &FakeMatcherDeviceId::ethernet_interface(),
1790 ),
1791 );
1792 assert_eq!(verdict, Verdict::Accept(()));
1793
1794 let verdict = perform_nat::<EgressHook, _, _, _, _>(
1795 &mut core_ctx,
1796 &mut bindings_ctx,
1797 NAT_ENABLED_FOR_TESTS,
1798 &conntrack,
1799 &mut conn,
1800 direction,
1801 &Hook {
1802 routines: vec![Routine {
1803 rules: vec![Rule::new(
1804 PacketMatcher::default(),
1805 Action::Masquerade { src_port: None },
1806 )],
1807 }],
1808 },
1809 &mut packet,
1810 <EgressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
1811 );
1812 assert_eq!(verdict, Verdict::Accept(()));
1813
1814 assert_eq!(conn.external_data().destination.get(), Some(&ShouldNat::No));
1815 assert_eq!(conn.external_data().source.get(), Some(&ShouldNat::No));
1816 }
1817
1818 #[ip_test(I)]
1819 fn nat_disabled_if_not_configured_before_connection_finalized<I: TestIpExt>() {
1820 let mut bindings_ctx = FakeBindingsCtx::<I>::new();
1821 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1822 let mut core_ctx = FakeNatCtx::default();
1823
1824 let mut packet = FakeIpPacket::<I, FakeUdpPacket>::arbitrary_value();
1825 let (mut conn, direction) = conntrack
1826 .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
1827 .expect("packet should be valid")
1828 .expect("packet should be trackable");
1829
1830 let verdict = perform_nat::<LocalEgressHook, _, _, _, _>(
1832 &mut core_ctx,
1833 &mut bindings_ctx,
1834 false, &conntrack,
1836 &mut conn,
1837 direction,
1838 &Hook::default(),
1839 &mut packet,
1840 <LocalEgressHook as NatHookExt<I>>::interfaces(
1841 &FakeMatcherDeviceId::ethernet_interface(),
1842 ),
1843 );
1844 assert_eq!(verdict, Verdict::Accept(()));
1845 assert_eq!(conn.external_data().destination.get(), None);
1846 assert_eq!(conn.external_data().source.get(), None);
1847
1848 let verdict = perform_nat::<EgressHook, _, _, _, _>(
1850 &mut core_ctx,
1851 &mut bindings_ctx,
1852 false, &conntrack,
1854 &mut conn,
1855 direction,
1856 &Hook::default(),
1857 &mut packet,
1858 <EgressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
1859 );
1860 assert_eq!(verdict, Verdict::Accept(()));
1861 assert_eq!(conn.external_data().destination.get(), None);
1862 assert_eq!(conn.external_data().source.get(), None);
1863
1864 let (inserted, _weak) = conntrack
1865 .finalize_connection(&mut bindings_ctx, conn)
1866 .expect("connection should not conflict");
1867 assert!(inserted);
1868
1869 let mut reply = packet.reply();
1872 let (mut conn, direction) = conntrack
1873 .get_connection_for_packet_and_update(&bindings_ctx, reply.conntrack_packet().unwrap())
1874 .expect("packet should be valid")
1875 .expect("packet should be trackable");
1876 let verdict = perform_nat::<IngressHook, _, _, _, _>(
1877 &mut core_ctx,
1878 &mut bindings_ctx,
1879 NAT_ENABLED_FOR_TESTS,
1880 &conntrack,
1881 &mut conn,
1882 direction,
1883 &Hook::default(),
1884 &mut reply,
1885 <IngressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
1886 );
1887 assert_eq!(verdict, Verdict::Accept(()).into());
1888 assert_eq!(conn.external_data().destination.get(), None);
1889 assert_eq!(conn.external_data().source.get(), Some(&ShouldNat::No));
1890
1891 let verdict = perform_nat::<LocalIngressHook, _, _, _, _>(
1893 &mut core_ctx,
1894 &mut bindings_ctx,
1895 NAT_ENABLED_FOR_TESTS,
1896 &conntrack,
1897 &mut conn,
1898 direction,
1899 &Hook::default(),
1900 &mut reply,
1901 <IngressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
1902 );
1903 assert_eq!(verdict, Verdict::Accept(()));
1904 assert_eq!(conn.external_data().destination.get(), Some(&ShouldNat::No));
1905 assert_eq!(conn.external_data().source.get(), Some(&ShouldNat::No));
1906 }
1907
1908 const LOCAL_PORT: NonZeroU16 = NonZeroU16::new(55555).unwrap();
1909
1910 #[ip_test(I)]
1911 #[test_case(
1912 PhantomData::<IngressHook>, PhantomData::<EgressHook>, None;
1913 "redirect INGRESS"
1914 )]
1915 #[test_case(
1916 PhantomData::<IngressHook>, PhantomData::<EgressHook>, Some(LOCAL_PORT);
1917 "redirect INGRESS to local port"
1918 )]
1919 #[test_case(
1920 PhantomData::<LocalEgressHook>, PhantomData::<EgressHook>, None;
1921 "redirect LOCAL_EGRESS"
1922 )]
1923 #[test_case(
1924 PhantomData::<LocalEgressHook>, PhantomData::<EgressHook>, Some(LOCAL_PORT);
1925 "redirect LOCAL_EGRESS to local port"
1926 )]
1927 fn redirect<I: TestIpExt, Original: NatHookExt<I>, Reply: NatHookExt<I>>(
1928 _original_nat_hook: PhantomData<Original>,
1929 _reply_nat_hook: PhantomData<Reply>,
1930 dst_port: Option<NonZeroU16>,
1931 ) {
1932 let mut bindings_ctx = FakeBindingsCtx::<I>::new();
1933 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1934 let mut core_ctx = FakeNatCtx::new([(
1935 FakeMatcherDeviceId::ethernet_interface(),
1936 AddrSubnet::new(I::DST_IP_2, I::SUBNET.prefix()).unwrap(),
1937 )]);
1938
1939 let mut packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1941 let pre_nat_packet = packet.clone();
1942 let (mut conn, direction) = conntrack
1943 .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
1944 .expect("packet should be valid")
1945 .expect("packet should be trackable");
1946 let original = conn.original_tuple().clone();
1947
1948 let nat_routines = Hook {
1952 routines: vec![Routine {
1953 rules: vec![Rule::new(
1954 PacketMatcher::default(),
1955 Action::Redirect { dst_port: dst_port.map(|port| port..=port) },
1956 )],
1957 }],
1958 };
1959 let verdict = perform_nat::<Original, _, _, _, _>(
1960 &mut core_ctx,
1961 &mut bindings_ctx,
1962 NAT_ENABLED_FOR_TESTS,
1963 &conntrack,
1964 &mut conn,
1965 direction,
1966 &nat_routines,
1967 &mut packet,
1968 Original::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
1969 );
1970 assert_eq!(verdict, Verdict::Accept(()).into());
1971
1972 let (redirect_addr, cached_addr) = Original::redirect_addr(
1976 &mut core_ctx,
1977 &packet,
1978 Original::interfaces(&FakeMatcherDeviceId::ethernet_interface()).ingress,
1979 )
1980 .expect("get redirect addr for NAT hook");
1981 let expected = FakeIpPacket::<_, FakeUdpPacket> {
1982 src_ip: packet.src_ip,
1983 dst_ip: redirect_addr,
1984 body: FakeUdpPacket {
1985 src_port: packet.body.src_port,
1986 dst_port: dst_port.map(NonZeroU16::get).unwrap_or(packet.body.dst_port),
1987 },
1988 };
1989 assert_eq!(packet, expected);
1990 assert_eq!(
1991 conn.external_data().destination.get().expect("DNAT should be configured"),
1992 &ShouldNat::Yes(cached_addr)
1993 );
1994 assert_eq!(conn.external_data().source.get(), None, "SNAT should not be configured");
1995 assert_eq!(conn.original_tuple(), &original);
1996 let mut reply = Tuple { src_addr: redirect_addr, ..original.invert() };
1997 if let Some(port) = dst_port {
1998 reply.src_port_or_id = port.get();
1999 }
2000 assert_eq!(conn.reply_tuple(), &reply);
2001
2002 let mut reply_packet = packet.reply();
2006 let nat_routines = Hook {
2010 routines: vec![Routine {
2011 rules: vec![Rule::new(PacketMatcher::default(), Action::Drop)],
2012 }],
2013 };
2014 let verdict = perform_nat::<Reply, _, _, _, _>(
2015 &mut core_ctx,
2016 &mut bindings_ctx,
2017 NAT_ENABLED_FOR_TESTS,
2018 &conntrack,
2019 &mut conn,
2020 ConnectionDirection::Reply,
2021 &nat_routines,
2022 &mut reply_packet,
2023 Reply::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
2024 );
2025 assert_eq!(verdict, Verdict::Accept(()).into());
2026 assert_eq!(reply_packet, pre_nat_packet.reply());
2027 }
2028
2029 #[ip_test(I)]
2030 #[test_case(None; "masquerade")]
2031 #[test_case(Some(LOCAL_PORT); "masquerade to specified port")]
2032 fn masquerade<I: TestIpExt>(src_port: Option<NonZeroU16>) {
2033 let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2034 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2035 let assigned_addr = AddrSubnet::new(I::SRC_IP_2, I::SUBNET.prefix()).unwrap();
2036 let mut core_ctx =
2037 FakeNatCtx::new([(FakeMatcherDeviceId::ethernet_interface(), assigned_addr)]);
2038
2039 let mut packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
2041 let pre_nat_packet = packet.clone();
2042 let (mut conn, direction) = conntrack
2043 .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2044 .expect("packet should be valid")
2045 .expect("packet should be trackable");
2046 let original = conn.original_tuple().clone();
2047
2048 let nat_routines = Hook {
2050 routines: vec![Routine {
2051 rules: vec![Rule::new(
2052 PacketMatcher::default(),
2053 Action::Masquerade { src_port: src_port.map(|port| port..=port) },
2054 )],
2055 }],
2056 };
2057 let verdict = perform_nat::<EgressHook, _, _, _, _>(
2058 &mut core_ctx,
2059 &mut bindings_ctx,
2060 NAT_ENABLED_FOR_TESTS,
2061 &conntrack,
2062 &mut conn,
2063 direction,
2064 &nat_routines,
2065 &mut packet,
2066 Interfaces { ingress: None, egress: Some(&FakeMatcherDeviceId::ethernet_interface()) },
2067 );
2068 assert_eq!(verdict, Verdict::Accept(()));
2069
2070 let expected = FakeIpPacket::<_, FakeUdpPacket> {
2074 src_ip: I::SRC_IP_2,
2075 dst_ip: packet.dst_ip,
2076 body: FakeUdpPacket {
2077 src_port: src_port.map(NonZeroU16::get).unwrap_or(packet.body.src_port),
2078 dst_port: packet.body.dst_port,
2079 },
2080 };
2081 assert_eq!(packet, expected);
2082 assert_matches!(
2083 conn.external_data().source.get().expect("SNAT should be configured"),
2084 &ShouldNat::Yes(Some(_))
2085 );
2086 assert_eq!(conn.external_data().destination.get(), None, "DNAT should not be configured");
2087 assert_eq!(conn.original_tuple(), &original);
2088 let mut reply = Tuple { dst_addr: I::SRC_IP_2, ..original.invert() };
2089 if let Some(port) = src_port {
2090 reply.dst_port_or_id = port.get();
2091 }
2092 assert_eq!(conn.reply_tuple(), &reply);
2093
2094 let mut reply_packet = packet.reply();
2098 let nat_routines = Hook {
2102 routines: vec![Routine {
2103 rules: vec![Rule::new(PacketMatcher::default(), Action::Drop)],
2104 }],
2105 };
2106 let verdict = perform_nat::<IngressHook, _, _, _, _>(
2107 &mut core_ctx,
2108 &mut bindings_ctx,
2109 NAT_ENABLED_FOR_TESTS,
2110 &conntrack,
2111 &mut conn,
2112 ConnectionDirection::Reply,
2113 &nat_routines,
2114 &mut reply_packet,
2115 Interfaces { ingress: Some(&FakeMatcherDeviceId::ethernet_interface()), egress: None },
2116 );
2117 assert_eq!(verdict, Verdict::Accept(()).into());
2118 assert_eq!(reply_packet, pre_nat_packet.reply());
2119 }
2120
2121 #[ip_test(I)]
2122 #[test_case(22, 1..=511)]
2123 #[test_case(853, 1..=1023)]
2124 #[test_case(11111, 1024..=u16::MAX)]
2125 fn masquerade_reply_tuple_dst_port_rewritten_even_if_target_range_unspecified<I: TestIpExt>(
2126 src_port: u16,
2127 expected_range: RangeInclusive<u16>,
2128 ) {
2129 let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2130 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2131 let assigned_addr = AddrSubnet::new(I::SRC_IP_2, I::SUBNET.prefix()).unwrap();
2132 let mut core_ctx =
2133 FakeNatCtx::new([(FakeMatcherDeviceId::ethernet_interface(), assigned_addr)]);
2134 let packet = FakeIpPacket {
2135 body: FakeUdpPacket { src_port, ..ArbitraryValue::arbitrary_value() },
2136 ..ArbitraryValue::arbitrary_value()
2137 };
2138
2139 let reply = FakeIpPacket { src_ip: I::SRC_IP_2, ..packet.clone() };
2142 let (conn, _dir) = conntrack
2143 .get_connection_for_packet_and_update(&bindings_ctx, reply.conntrack_packet().unwrap())
2144 .expect("packet should be valid")
2145 .expect("packet should be trackable");
2146 assert_matches!(
2147 conntrack
2148 .finalize_connection(&mut bindings_ctx, conn)
2149 .expect("connection should not conflict"),
2150 (true, Some(_))
2151 );
2152
2153 let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
2157 let verdict = configure_masquerade_nat(
2158 &mut core_ctx,
2159 &mut bindings_ctx,
2160 &conntrack,
2161 &mut conn,
2162 &packet,
2163 &Interfaces { ingress: None, egress: Some(&FakeMatcherDeviceId::ethernet_interface()) },
2164 None,
2165 );
2166
2167 assert_matches!(
2173 verdict,
2174 Verdict::Accept(NatConfigurationResult::Result(ShouldNat::Yes(Some(_))))
2175 );
2176 let reply_tuple = conn.reply_tuple();
2177 assert_eq!(reply_tuple.dst_addr, I::SRC_IP_2);
2178 assert_ne!(reply_tuple.dst_port_or_id, src_port);
2179 assert!(expected_range.contains(&reply_tuple.dst_port_or_id));
2180 }
2181
2182 #[ip_test(I)]
2183 #[test_case(
2184 PhantomData::<IngressHook>, Action::Redirect { dst_port: None };
2185 "redirect in INGRESS"
2186 )]
2187 #[test_case(
2188 PhantomData::<EgressHook>, Action::Masquerade { src_port: None };
2189 "masquerade in EGRESS"
2190 )]
2191 fn assigned_addr_cached_and_validated<I: TestIpExt, N: NatHookExt<I>>(
2192 _nat_hook: PhantomData<N>,
2193 action: Action<I, FakeDeviceClass, ()>,
2194 ) {
2195 let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2196 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2197 let assigned_addr = AddrSubnet::new(I::SRC_IP_2, I::SUBNET.prefix()).unwrap();
2198 let mut core_ctx =
2199 FakeNatCtx::new([(FakeMatcherDeviceId::ethernet_interface(), assigned_addr)]);
2200
2201 let mut packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
2203 let (mut conn, direction) = conntrack
2204 .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2205 .expect("packet should be valid")
2206 .expect("packet should be trackable");
2207
2208 let nat_routines = Hook {
2210 routines: vec![Routine { rules: vec![Rule::new(PacketMatcher::default(), action)] }],
2211 };
2212 let verdict = perform_nat::<N, _, _, _, _>(
2213 &mut core_ctx,
2214 &mut bindings_ctx,
2215 NAT_ENABLED_FOR_TESTS,
2216 &conntrack,
2217 &mut conn,
2218 direction,
2219 &nat_routines,
2220 &mut packet,
2221 N::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
2222 );
2223 assert_eq!(verdict, Verdict::Accept(()).into());
2224
2225 let (nat, _nat_type) = conn.relevant_config(N::NAT_TYPE, ConnectionDirection::Original);
2228 let nat = nat.get().unwrap_or_else(|| panic!("{:?} NAT should be configured", N::NAT_TYPE));
2229 let id = assert_matches!(nat, ShouldNat::Yes(Some(CachedAddr { id, _marker })) => id);
2230 let id = id
2231 .lock()
2232 .as_ref()
2233 .expect("address ID should be cached in NAT config")
2234 .upgrade()
2235 .expect("address ID should be valid");
2236 assert_eq!(*id, assigned_addr);
2237 drop(id);
2238
2239 core_ctx.device_addrs.clear();
2243 let verdict = perform_nat::<N, _, _, _, _>(
2244 &mut core_ctx,
2245 &mut bindings_ctx,
2246 NAT_ENABLED_FOR_TESTS,
2247 &conntrack,
2248 &mut conn,
2249 ConnectionDirection::Original,
2250 &nat_routines,
2251 &mut FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value(),
2252 N::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
2253 );
2254 assert_eq!(verdict, Verdict::Drop.into());
2255 let (nat, _nat_type) = conn.relevant_config(N::NAT_TYPE, ConnectionDirection::Original);
2256 let nat = nat.get().unwrap_or_else(|| panic!("{:?} NAT should be configured", N::NAT_TYPE));
2257 let id = assert_matches!(nat, ShouldNat::Yes(Some(CachedAddr { id, _marker })) => id);
2258 assert_eq!(*id.lock(), None, "cached weak address ID should be cleared");
2259
2260 assert_matches!(
2263 core_ctx.device_addrs.insert(
2264 FakeMatcherDeviceId::ethernet_interface(),
2265 FakePrimaryAddressId(Arc::new(assigned_addr))
2266 ),
2267 None
2268 );
2269 let verdict = perform_nat::<N, _, _, _, _>(
2270 &mut core_ctx,
2271 &mut bindings_ctx,
2272 NAT_ENABLED_FOR_TESTS,
2273 &conntrack,
2274 &mut conn,
2275 ConnectionDirection::Original,
2276 &nat_routines,
2277 &mut FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value(),
2278 N::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
2279 );
2280 assert_eq!(verdict, Verdict::Accept(()).into());
2281 let (nat, _nat_type) = conn.relevant_config(N::NAT_TYPE, ConnectionDirection::Original);
2282 let nat = nat.get().unwrap_or_else(|| panic!("{:?} NAT should be configured", N::NAT_TYPE));
2283 let id = assert_matches!(nat, ShouldNat::Yes(Some(CachedAddr { id, _marker })) => id);
2284 let id = id
2285 .lock()
2286 .as_ref()
2287 .expect("address ID should be cached in NAT config")
2288 .upgrade()
2289 .expect("address Id should be valid");
2290 assert_eq!(*id, assigned_addr);
2291 }
2292
2293 #[test_case(ReplyTuplePort::Source)]
2294 #[test_case(ReplyTuplePort::Destination)]
2295 fn rewrite_port_noop_if_in_range(which: ReplyTuplePort) {
2296 let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
2297 let table = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2298 let mut conn =
2299 ConnectionExclusive::with_reply_tuple(&bindings_ctx, which, LOCAL_PORT.get());
2300
2301 let pre_nat = conn.reply_tuple().clone();
2304 let result = rewrite_reply_tuple_port(
2305 &mut bindings_ctx,
2306 &table,
2307 &mut conn,
2308 which,
2309 LOCAL_PORT..=LOCAL_PORT,
2310 true, ConflictStrategy::RewritePort,
2312 );
2313 assert_eq!(
2314 result,
2315 Verdict::Accept(NatConfigurationResult::Result(
2316 ShouldNat::<_, FakeWeakAddressId<Ipv4>>::No
2317 ))
2318 );
2319 assert_eq!(conn.reply_tuple(), &pre_nat);
2320 }
2321
2322 #[test_case(ReplyTuplePort::Source)]
2323 #[test_case(ReplyTuplePort::Destination)]
2324 fn rewrite_port_noop_if_no_conflict(which: ReplyTuplePort) {
2325 let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
2326 let table = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2327 let mut conn =
2328 ConnectionExclusive::with_reply_tuple(&bindings_ctx, which, LOCAL_PORT.get());
2329
2330 let pre_nat = conn.reply_tuple().clone();
2334 const NEW_PORT: NonZeroU16 = LOCAL_PORT.checked_add(1).unwrap();
2335 let result = rewrite_reply_tuple_port(
2336 &mut bindings_ctx,
2337 &table,
2338 &mut conn,
2339 which,
2340 NEW_PORT..=NEW_PORT,
2341 false, ConflictStrategy::RewritePort,
2343 );
2344 assert_eq!(
2345 result,
2346 Verdict::Accept(NatConfigurationResult::Result(
2347 ShouldNat::<_, FakeWeakAddressId<Ipv4>>::No
2348 ))
2349 );
2350 assert_eq!(conn.reply_tuple(), &pre_nat);
2351 }
2352
2353 #[test_case(ReplyTuplePort::Source)]
2354 #[test_case(ReplyTuplePort::Destination)]
2355 fn rewrite_port_succeeds_if_available_port_in_range(which: ReplyTuplePort) {
2356 let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
2357 let table = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2358 let mut conn =
2359 ConnectionExclusive::with_reply_tuple(&bindings_ctx, which, LOCAL_PORT.get());
2360
2361 const NEW_PORT: NonZeroU16 = LOCAL_PORT.checked_add(1).unwrap();
2364 let result = rewrite_reply_tuple_port(
2365 &mut bindings_ctx,
2366 &table,
2367 &mut conn,
2368 which,
2369 NEW_PORT..=NEW_PORT,
2370 true, ConflictStrategy::RewritePort,
2372 );
2373 assert_eq!(
2374 result,
2375 Verdict::Accept(NatConfigurationResult::Result(
2376 ShouldNat::<_, FakeWeakAddressId<Ipv4>>::Yes(None)
2377 ))
2378 );
2379 assert_eq!(conn.reply_tuple(), &tuple_with_port(which, NEW_PORT.get()));
2380 }
2381
2382 #[test_case(ReplyTuplePort::Source)]
2383 #[test_case(ReplyTuplePort::Destination)]
2384 fn rewrite_port_fails_if_no_available_port_in_range(which: ReplyTuplePort) {
2385 let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
2386 let table: Table<_, NatConfig<_, FakeWeakAddressId<Ipv4>>, _> =
2387 Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2388
2389 let packet = packet_with_port(which, LOCAL_PORT.get());
2393 let (conn, _dir) = table
2394 .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2395 .expect("packet should be valid")
2396 .expect("packet should be trackable");
2397 assert_matches!(
2398 table
2399 .finalize_connection(&mut bindings_ctx, conn)
2400 .expect("connection should not conflict"),
2401 (true, Some(_))
2402 );
2403
2404 let mut conn =
2405 ConnectionExclusive::with_reply_tuple(&bindings_ctx, which, LOCAL_PORT.get());
2406 let result = rewrite_reply_tuple_port(
2407 &mut bindings_ctx,
2408 &table,
2409 &mut conn,
2410 which,
2411 LOCAL_PORT..=LOCAL_PORT,
2412 true, ConflictStrategy::RewritePort,
2414 );
2415 assert_eq!(result, Verdict::Drop);
2416 }
2417
2418 #[test_case(ReplyTuplePort::Source)]
2419 #[test_case(ReplyTuplePort::Destination)]
2420 fn port_rewritten_to_ensure_unique_tuple_even_if_in_range(which: ReplyTuplePort) {
2421 let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
2422 let table = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2423
2424 const MAX_PORT: NonZeroU16 = NonZeroU16::new(LOCAL_PORT.get() + 100).unwrap();
2428 for port in LOCAL_PORT.get()..=MAX_PORT.get() {
2429 let packet = packet_with_port(which, port);
2430 let (conn, _dir) = table
2431 .get_connection_for_packet_and_update(
2432 &bindings_ctx,
2433 packet.conntrack_packet().unwrap(),
2434 )
2435 .expect("packet should be valid")
2436 .expect("packet should be trackable");
2437 assert_matches!(
2438 table
2439 .finalize_connection(&mut bindings_ctx, conn)
2440 .expect("connection should not conflict"),
2441 (true, Some(_))
2442 );
2443 }
2444
2445 let mut conn =
2449 ConnectionExclusive::with_reply_tuple(&bindings_ctx, which, LOCAL_PORT.get());
2450 const MIN_PORT: NonZeroU16 = NonZeroU16::new(LOCAL_PORT.get() - 1).unwrap();
2451 let result = rewrite_reply_tuple_port(
2452 &mut bindings_ctx,
2453 &table,
2454 &mut conn,
2455 which,
2456 MIN_PORT..=MAX_PORT,
2457 true, ConflictStrategy::RewritePort,
2459 );
2460 assert_eq!(
2461 result,
2462 Verdict::Accept(NatConfigurationResult::Result(
2463 ShouldNat::<_, FakeWeakAddressId<Ipv4>>::Yes(None)
2464 ))
2465 );
2466 assert_eq!(conn.reply_tuple(), &tuple_with_port(which, MIN_PORT.get()));
2467 }
2468
2469 #[test_case(ReplyTuplePort::Source)]
2470 #[test_case(ReplyTuplePort::Destination)]
2471 fn rewrite_port_skipped_if_existing_connection_can_be_adopted(which: ReplyTuplePort) {
2472 let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
2473 let table: Table<_, NatConfig<_, FakeWeakAddressId<Ipv4>>, _> =
2474 Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2475
2476 let packet = packet_with_port(which, LOCAL_PORT.get());
2481 let (conn, _dir) = table
2482 .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2483 .expect("packet should be valid")
2484 .expect("packet should be trackable");
2485 let existing = assert_matches!(
2486 table
2487 .finalize_connection(&mut bindings_ctx, conn)
2488 .expect("connection should not conflict"),
2489 (true, Some(conn)) => conn
2490 );
2491
2492 let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
2493 let result = rewrite_reply_tuple_port(
2494 &mut bindings_ctx,
2495 &table,
2496 &mut conn,
2497 which,
2498 NonZeroU16::MIN..=NonZeroU16::MAX,
2499 false, ConflictStrategy::AdoptExisting,
2501 );
2502 let conn = assert_matches!(
2503 result,
2504 Verdict::Accept(NatConfigurationResult::AdoptExisting(Connection::Shared(conn))) => conn
2505 );
2506 assert!(Arc::ptr_eq(&existing, &conn));
2507 }
2508
2509 trait IcmpErrorTestIpExt: TestIpExt {
2510 const NETSTACK: Self::Addr = Self::DST_IP;
2511 const ULTIMATE_SRC: Self::Addr = Self::SRC_IP;
2512 const ULTIMATE_DST: Self::Addr = Self::DST_IP_3;
2513 const ROUTER_SRC: Self::Addr = Self::SRC_IP_2;
2514 const ROUTER_DST: Self::Addr = Self::DST_IP_2;
2515 }
2516
2517 impl<I> IcmpErrorTestIpExt for I where I: TestIpExt {}
2518
2519 enum IcmpErrorSource {
2520 IntermediateRouter,
2521 EndHost,
2522 }
2523
2524 #[test_case(Icmpv4DestUnreachableError)]
2555 #[test_case(Icmpv6DestUnreachableError)]
2556 fn redirect_icmp_error_in_reply_direction<I: IcmpErrorTestIpExt, IE: IcmpErrorMessage<I>>(
2557 _icmp_error: IE,
2558 ) {
2559 let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2560 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2561 let mut core_ctx = FakeNatCtx::new([(
2562 FakeMatcherDeviceId::ethernet_interface(),
2563 AddrSubnet::new(I::NETSTACK, I::SUBNET.prefix()).unwrap(),
2564 )]);
2565
2566 let mut packet = EmptyBuf
2568 .wrap_in(UdpPacketBuilder::new(
2569 I::ULTIMATE_SRC,
2570 I::ULTIMATE_DST,
2571 Some(NonZeroU16::new(11111).unwrap()),
2572 NonZeroU16::new(22222).unwrap(),
2573 ))
2574 .wrap_in(I::PacketBuilder::new(
2575 I::ULTIMATE_SRC,
2576 I::ULTIMATE_DST,
2577 u8::MAX,
2578 IpProto::Udp.into(),
2579 ));
2580 let packet_pre_nat = packet.clone();
2581 let (mut conn, _) = conntrack
2582 .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2583 .expect("packet should be valid")
2584 .expect("packet should be trackable");
2585 let original_tuple = conn.original_tuple().clone();
2586
2587 let nat_routines = Hook {
2588 routines: vec![Routine {
2589 rules: vec![Rule::new(
2590 PacketMatcher::default(),
2591 Action::Redirect { dst_port: Some(LOCAL_PORT..=LOCAL_PORT) },
2592 )],
2593 }],
2594 };
2595 let verdict = perform_nat::<IngressHook, _, _, _, _>(
2596 &mut core_ctx,
2597 &mut bindings_ctx,
2598 NAT_ENABLED_FOR_TESTS,
2599 &conntrack,
2600 &mut conn,
2601 ConnectionDirection::Original,
2602 &nat_routines,
2603 &mut packet,
2604 <IngressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
2605 );
2606 assert_eq!(verdict, Verdict::Accept(()).into());
2607
2608 let (redirect_addr, cached_addr) = IngressHook::redirect_addr(
2612 &mut core_ctx,
2613 &packet,
2614 <IngressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface())
2615 .ingress,
2616 )
2617 .expect("get redirect addr for NAT hook");
2618
2619 assert_eq!(redirect_addr, I::NETSTACK);
2622
2623 let expected = EmptyBuf
2625 .wrap_in(UdpPacketBuilder::new(
2626 I::ULTIMATE_SRC,
2627 redirect_addr,
2628 packet.inner().outer().src_port(),
2629 LOCAL_PORT,
2630 ))
2631 .wrap_in(I::PacketBuilder::new(
2632 I::ULTIMATE_SRC,
2633 redirect_addr,
2634 u8::MAX,
2635 IpProto::Udp.into(),
2636 ));
2637 assert_eq!(packet, expected);
2638 assert_eq!(
2639 conn.external_data().destination.get().expect("DNAT should be configured"),
2640 &ShouldNat::Yes(cached_addr)
2641 );
2642 assert_eq!(conn.external_data().source.get(), None, "SNAT should not be configured");
2643 assert_eq!(conn.original_tuple(), &original_tuple);
2644
2645 let reply_tuple = Tuple {
2646 src_addr: redirect_addr,
2647 src_port_or_id: LOCAL_PORT.get(),
2648 ..original_tuple.invert()
2649 };
2650 assert_eq!(conn.reply_tuple(), &reply_tuple);
2651
2652 let mut error_packet = IE::make_serializer(
2653 redirect_addr,
2654 I::ULTIMATE_SRC,
2655 packet.clone().serialize_vec_outer().unwrap().unwrap_b().into_inner(),
2656 )
2657 .wrap_in(I::PacketBuilder::new(
2658 redirect_addr,
2659 I::ULTIMATE_SRC,
2660 u8::MAX,
2661 IE::proto(),
2662 ));
2663
2664 let verdict = perform_nat::<EgressHook, _, _, _, _>(
2665 &mut core_ctx,
2666 &mut bindings_ctx,
2667 NAT_ENABLED_FOR_TESTS,
2668 &conntrack,
2669 &mut conn,
2670 ConnectionDirection::Reply,
2671 &nat_routines,
2672 &mut error_packet,
2673 <EgressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
2674 );
2675 assert_eq!(verdict, Verdict::Accept(()));
2676
2677 let error_packet_expected = IE::make_serializer(
2678 I::ULTIMATE_DST,
2681 I::ULTIMATE_SRC,
2682 packet_pre_nat.serialize_vec_outer().unwrap().unwrap_b().into_inner(),
2683 )
2684 .wrap_in(I::PacketBuilder::new(
2685 I::ULTIMATE_DST,
2686 I::ULTIMATE_SRC,
2687 u8::MAX,
2688 IE::proto(),
2689 ));
2690
2691 assert_eq!(error_packet, error_packet_expected);
2692 }
2693
2694 #[test_matrix(
2695 [
2696 Icmpv4DestUnreachableError,
2697 Icmpv6DestUnreachableError,
2698 ],
2699 [
2700 IcmpErrorSource::IntermediateRouter,
2701 IcmpErrorSource::EndHost,
2702 ]
2703 )]
2704 fn redirect_icmp_error_in_original_direction<I: IcmpErrorTestIpExt, IE: IcmpErrorMessage<I>>(
2705 _icmp_error: IE,
2706 icmp_error_source: IcmpErrorSource,
2707 ) {
2708 let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2709 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2710 let mut core_ctx = FakeNatCtx::new([(
2711 FakeMatcherDeviceId::ethernet_interface(),
2712 AddrSubnet::new(I::NETSTACK, I::SUBNET.prefix()).unwrap(),
2713 )]);
2714
2715 let mut packet = EmptyBuf
2717 .wrap_in(UdpPacketBuilder::new(
2718 I::ULTIMATE_SRC,
2719 I::ULTIMATE_DST,
2720 Some(NonZeroU16::new(11111).unwrap()),
2721 NonZeroU16::new(22222).unwrap(),
2722 ))
2723 .wrap_in(I::PacketBuilder::new(
2724 I::ULTIMATE_SRC,
2725 I::ULTIMATE_DST,
2726 u8::MAX,
2727 IpProto::Udp.into(),
2728 ));
2729 let (mut conn, direction) = conntrack
2730 .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2731 .expect("packet should be valid")
2732 .expect("packet should be trackable");
2733 let original_tuple = conn.original_tuple().clone();
2734
2735 let nat_routines = Hook {
2739 routines: vec![Routine {
2740 rules: vec![Rule::new(
2741 PacketMatcher::default(),
2742 Action::Redirect { dst_port: Some(LOCAL_PORT..=LOCAL_PORT) },
2743 )],
2744 }],
2745 };
2746 let verdict = perform_nat::<IngressHook, _, _, _, _>(
2747 &mut core_ctx,
2748 &mut bindings_ctx,
2749 NAT_ENABLED_FOR_TESTS,
2750 &conntrack,
2751 &mut conn,
2752 direction,
2753 &nat_routines,
2754 &mut packet,
2755 <IngressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
2756 );
2757 assert_eq!(verdict, Verdict::Accept(()).into());
2758
2759 let (redirect_addr, cached_addr) = IngressHook::redirect_addr(
2763 &mut core_ctx,
2764 &packet,
2765 <IngressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface())
2766 .ingress,
2767 )
2768 .expect("get redirect addr for NAT hook");
2769
2770 assert_eq!(redirect_addr, I::NETSTACK);
2773
2774 let expected = EmptyBuf
2776 .wrap_in(UdpPacketBuilder::new(
2777 I::ULTIMATE_SRC,
2778 redirect_addr,
2779 packet.inner().outer().src_port(),
2780 LOCAL_PORT,
2781 ))
2782 .wrap_in(I::PacketBuilder::new(
2783 I::ULTIMATE_SRC,
2784 redirect_addr,
2785 u8::MAX,
2786 IpProto::Udp.into(),
2787 ));
2788 assert_eq!(packet, expected);
2789 assert_eq!(
2790 conn.external_data().destination.get().expect("DNAT should be configured"),
2791 &ShouldNat::Yes(cached_addr)
2792 );
2793 assert_eq!(conn.external_data().source.get(), None, "SNAT should not be configured");
2794 assert_eq!(conn.original_tuple(), &original_tuple);
2795 let reply_tuple = Tuple {
2796 src_addr: redirect_addr,
2797 src_port_or_id: LOCAL_PORT.get(),
2798 ..original_tuple.invert()
2799 };
2800 assert_eq!(conn.reply_tuple(), &reply_tuple);
2801
2802 let mut reply_packet = EmptyBuf
2803 .wrap_in(UdpPacketBuilder::new(
2804 reply_tuple.src_addr,
2805 reply_tuple.dst_addr,
2806 Some(NonZeroU16::new(reply_tuple.src_port_or_id).unwrap()),
2807 NonZeroU16::new(reply_tuple.dst_port_or_id).unwrap(),
2808 ))
2809 .wrap_in(I::PacketBuilder::new(
2810 reply_tuple.src_addr,
2811 reply_tuple.dst_addr,
2812 u8::MAX,
2813 IpProto::Udp.into(),
2814 ));
2815 let reply_packet_pre_nat = reply_packet.clone();
2816
2817 let verdict = perform_nat::<EgressHook, _, _, _, _>(
2818 &mut core_ctx,
2819 &mut bindings_ctx,
2820 NAT_ENABLED_FOR_TESTS,
2821 &conntrack,
2822 &mut conn,
2823 ConnectionDirection::Reply,
2824 &nat_routines,
2825 &mut reply_packet,
2826 <EgressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
2827 );
2828 assert_eq!(verdict, Verdict::Accept(()));
2829
2830 let reply_packet_expected = EmptyBuf
2831 .wrap_in(UdpPacketBuilder::new(
2832 I::ULTIMATE_DST,
2833 I::ULTIMATE_SRC,
2834 Some(NonZeroU16::new(22222).unwrap()),
2835 NonZeroU16::new(11111).unwrap(),
2836 ))
2837 .wrap_in(I::PacketBuilder::new(
2838 I::ULTIMATE_DST,
2839 I::ULTIMATE_SRC,
2840 u8::MAX,
2841 IpProto::Udp.into(),
2842 ));
2843 assert_eq!(reply_packet, reply_packet_expected);
2844
2845 let error_src_addr = match icmp_error_source {
2848 IcmpErrorSource::IntermediateRouter => I::ROUTER_SRC,
2849 IcmpErrorSource::EndHost => I::ULTIMATE_SRC,
2850 };
2851
2852 let mut error_packet = IE::make_serializer(
2853 error_src_addr,
2854 I::ULTIMATE_DST,
2855 reply_packet.clone().serialize_vec_outer().unwrap().unwrap_b().into_inner(),
2856 )
2857 .wrap_in(I::PacketBuilder::new(
2858 error_src_addr,
2859 I::ULTIMATE_DST,
2860 u8::MAX,
2861 IE::proto(),
2862 ));
2863
2864 let verdict = perform_nat::<IngressHook, _, _, _, _>(
2865 &mut core_ctx,
2866 &mut bindings_ctx,
2867 NAT_ENABLED_FOR_TESTS,
2868 &conntrack,
2869 &mut conn,
2870 direction,
2871 &nat_routines,
2872 &mut error_packet,
2873 <IngressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
2874 );
2875 assert_eq!(verdict, Verdict::Accept(()).into());
2876
2877 let error_packet_expected = IE::make_serializer(
2878 error_src_addr,
2881 redirect_addr,
2882 reply_packet_pre_nat.serialize_vec_outer().unwrap().unwrap_b().into_inner(),
2883 )
2884 .wrap_in(I::PacketBuilder::new(
2885 error_src_addr,
2886 redirect_addr,
2887 u8::MAX,
2888 IE::proto(),
2889 ));
2890
2891 assert_eq!(error_packet, error_packet_expected);
2892 }
2893
2894 #[test_matrix(
2922 [
2923 Icmpv4DestUnreachableError,
2924 Icmpv6DestUnreachableError,
2925 ],
2926 [
2927 IcmpErrorSource::IntermediateRouter,
2928 IcmpErrorSource::EndHost,
2929 ]
2930 )]
2931 fn masquerade_icmp_error_in_reply_direction<I: TestIpExt, IE: IcmpErrorMessage<I>>(
2932 _icmp_error: IE,
2933 icmp_error_source: IcmpErrorSource,
2934 ) {
2935 let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2936 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2937 let assigned_addr = AddrSubnet::new(I::NETSTACK, I::SUBNET.prefix()).unwrap();
2938 let mut core_ctx =
2939 FakeNatCtx::new([(FakeMatcherDeviceId::ethernet_interface(), assigned_addr)]);
2940
2941 let mut packet = EmptyBuf
2943 .wrap_in(UdpPacketBuilder::new(
2944 I::ULTIMATE_SRC,
2945 I::ULTIMATE_DST,
2946 Some(NonZeroU16::new(11111).unwrap()),
2947 NonZeroU16::new(22222).unwrap(),
2948 ))
2949 .wrap_in(I::PacketBuilder::new(
2950 I::ULTIMATE_SRC,
2951 I::ULTIMATE_DST,
2952 u8::MAX,
2953 IpProto::Udp.into(),
2954 ));
2955 let packet_pre_nat = packet.clone();
2956 let (mut conn, direction) = conntrack
2957 .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2958 .expect("packet should be valid")
2959 .expect("packet should be trackable");
2960 let original_tuple = conn.original_tuple().clone();
2961
2962 let nat_routines = Hook {
2964 routines: vec![Routine {
2965 rules: vec![Rule::new(
2966 PacketMatcher::default(),
2967 Action::Masquerade { src_port: Some(LOCAL_PORT..=LOCAL_PORT) },
2968 )],
2969 }],
2970 };
2971 let verdict = perform_nat::<EgressHook, _, _, _, _>(
2972 &mut core_ctx,
2973 &mut bindings_ctx,
2974 NAT_ENABLED_FOR_TESTS,
2975 &conntrack,
2976 &mut conn,
2977 direction,
2978 &nat_routines,
2979 &mut packet,
2980 Interfaces { ingress: None, egress: Some(&FakeMatcherDeviceId::ethernet_interface()) },
2981 );
2982 assert_eq!(verdict, Verdict::Accept(()));
2983
2984 let expected = EmptyBuf
2988 .wrap_in(UdpPacketBuilder::new(
2989 I::NETSTACK,
2990 I::ULTIMATE_DST,
2991 Some(LOCAL_PORT),
2992 packet.inner().outer().dst_port().unwrap(),
2993 ))
2994 .wrap_in(I::PacketBuilder::new(
2995 I::NETSTACK,
2996 I::ULTIMATE_DST,
2997 u8::MAX,
2998 IpProto::Udp.into(),
2999 ));
3000 assert_eq!(packet, expected);
3001 assert_matches!(
3002 conn.external_data().source.get().expect("SNAT should be configured"),
3003 &ShouldNat::Yes(Some(_))
3004 );
3005 assert_eq!(conn.external_data().destination.get(), None, "DNAT should not be configured");
3006 assert_eq!(conn.original_tuple(), &original_tuple);
3007 let reply_tuple = Tuple {
3008 dst_addr: I::NETSTACK,
3009 dst_port_or_id: LOCAL_PORT.get(),
3010 ..original_tuple.invert()
3011 };
3012 assert_eq!(conn.reply_tuple(), &reply_tuple);
3013
3014 let error_src_addr = match icmp_error_source {
3017 IcmpErrorSource::IntermediateRouter => I::ROUTER_DST,
3018 IcmpErrorSource::EndHost => I::ULTIMATE_DST,
3019 };
3020
3021 let mut error_packet = IE::make_serializer(
3022 error_src_addr,
3023 I::NETSTACK,
3024 packet.clone().serialize_vec_outer().unwrap().unwrap_b().into_inner(),
3025 )
3026 .wrap_in(I::PacketBuilder::new(error_src_addr, I::NETSTACK, u8::MAX, IE::proto()));
3027
3028 let verdict = perform_nat::<IngressHook, _, _, _, _>(
3029 &mut core_ctx,
3030 &mut bindings_ctx,
3031 NAT_ENABLED_FOR_TESTS,
3032 &conntrack,
3033 &mut conn,
3034 ConnectionDirection::Reply,
3035 &nat_routines,
3036 &mut error_packet,
3037 Interfaces { ingress: Some(&FakeMatcherDeviceId::ethernet_interface()), egress: None },
3038 );
3039 assert_eq!(verdict, Verdict::Accept(()).into());
3040
3041 let error_packet_expected = IE::make_serializer(
3042 error_src_addr,
3045 I::ULTIMATE_SRC,
3046 packet_pre_nat.serialize_vec_outer().unwrap().unwrap_b().into_inner(),
3047 )
3048 .wrap_in(I::PacketBuilder::new(
3049 error_src_addr,
3050 I::ULTIMATE_SRC,
3051 u8::MAX,
3052 IE::proto(),
3053 ));
3054
3055 assert_eq!(error_packet, error_packet_expected);
3056 }
3057
3058 #[test_matrix(
3059 [
3060 Icmpv4DestUnreachableError,
3061 Icmpv6DestUnreachableError,
3062 ],
3063 [
3064 IcmpErrorSource::IntermediateRouter,
3065 IcmpErrorSource::EndHost,
3066 ]
3067 )]
3068 fn masquerade_icmp_error_in_original_direction<I: TestIpExt, IE: IcmpErrorMessage<I>>(
3069 _icmp_error: IE,
3070 icmp_error_source: IcmpErrorSource,
3071 ) {
3072 let mut bindings_ctx = FakeBindingsCtx::<I>::new();
3073 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
3074 let assigned_addr = AddrSubnet::new(I::NETSTACK, I::SUBNET.prefix()).unwrap();
3075 let mut core_ctx =
3076 FakeNatCtx::new([(FakeMatcherDeviceId::ethernet_interface(), assigned_addr)]);
3077
3078 let mut packet = EmptyBuf
3080 .wrap_in(UdpPacketBuilder::new(
3081 I::ULTIMATE_SRC,
3082 I::ULTIMATE_DST,
3083 Some(NonZeroU16::new(11111).unwrap()),
3084 NonZeroU16::new(22222).unwrap(),
3085 ))
3086 .wrap_in(I::PacketBuilder::new(
3087 I::ULTIMATE_SRC,
3088 I::ULTIMATE_DST,
3089 u8::MAX,
3090 IpProto::Udp.into(),
3091 ));
3092 let (mut conn, direction) = conntrack
3093 .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
3094 .expect("packet should be valid")
3095 .expect("packet should be trackable");
3096 let original_tuple = conn.original_tuple().clone();
3097
3098 let nat_routines = Hook {
3100 routines: vec![Routine {
3101 rules: vec![Rule::new(
3102 PacketMatcher::default(),
3103 Action::Masquerade { src_port: Some(LOCAL_PORT..=LOCAL_PORT) },
3104 )],
3105 }],
3106 };
3107 let verdict = perform_nat::<EgressHook, _, _, _, _>(
3108 &mut core_ctx,
3109 &mut bindings_ctx,
3110 NAT_ENABLED_FOR_TESTS,
3111 &conntrack,
3112 &mut conn,
3113 direction,
3114 &nat_routines,
3115 &mut packet,
3116 Interfaces { ingress: None, egress: Some(&FakeMatcherDeviceId::ethernet_interface()) },
3117 );
3118 assert_eq!(verdict, Verdict::Accept(()));
3119
3120 let expected = EmptyBuf
3124 .wrap_in(UdpPacketBuilder::new(
3125 I::NETSTACK,
3126 I::ULTIMATE_DST,
3127 Some(LOCAL_PORT),
3128 packet.inner().outer().dst_port().unwrap(),
3129 ))
3130 .wrap_in(I::PacketBuilder::new(
3131 I::NETSTACK,
3132 I::ULTIMATE_DST,
3133 u8::MAX,
3134 IpProto::Udp.into(),
3135 ));
3136 assert_eq!(packet, expected);
3137 assert_matches!(
3138 conn.external_data().source.get().expect("SNAT should be configured"),
3139 &ShouldNat::Yes(Some(_))
3140 );
3141 assert_eq!(conn.external_data().destination.get(), None, "DNAT should not be configured");
3142 assert_eq!(conn.original_tuple(), &original_tuple);
3143 let reply_tuple = Tuple {
3144 dst_addr: I::NETSTACK,
3145 dst_port_or_id: LOCAL_PORT.get(),
3146 ..original_tuple.invert()
3147 };
3148 assert_eq!(conn.reply_tuple(), &reply_tuple);
3149
3150 let mut reply_packet = EmptyBuf
3154 .wrap_in(UdpPacketBuilder::new(
3155 reply_tuple.src_addr,
3156 reply_tuple.dst_addr,
3157 Some(NonZeroU16::new(reply_tuple.src_port_or_id).unwrap()),
3158 NonZeroU16::new(reply_tuple.dst_port_or_id).unwrap(),
3159 ))
3160 .wrap_in(I::PacketBuilder::new(
3161 reply_tuple.src_addr,
3162 reply_tuple.dst_addr,
3163 u8::MAX,
3164 IpProto::Udp.into(),
3165 ));
3166 let reply_packet_pre_nat = reply_packet.clone();
3167
3168 let verdict = perform_nat::<IngressHook, _, _, _, _>(
3169 &mut core_ctx,
3170 &mut bindings_ctx,
3171 NAT_ENABLED_FOR_TESTS,
3172 &conntrack,
3173 &mut conn,
3174 ConnectionDirection::Reply,
3175 &nat_routines,
3176 &mut reply_packet,
3177 Interfaces { ingress: Some(&FakeMatcherDeviceId::ethernet_interface()), egress: None },
3178 );
3179 assert_eq!(verdict, Verdict::Accept(()).into());
3180
3181 let reply_packet_expected = EmptyBuf
3182 .wrap_in(UdpPacketBuilder::new(
3183 I::ULTIMATE_DST,
3184 I::ULTIMATE_SRC,
3185 Some(NonZeroU16::new(22222).unwrap()),
3186 NonZeroU16::new(11111).unwrap(),
3187 ))
3188 .wrap_in(I::PacketBuilder::new(
3189 I::ULTIMATE_DST,
3190 I::ULTIMATE_SRC,
3191 u8::MAX,
3192 IpProto::Udp.into(),
3193 ));
3194 assert_eq!(reply_packet, reply_packet_expected);
3195
3196 let error_src_addr = match icmp_error_source {
3197 IcmpErrorSource::IntermediateRouter => I::ROUTER_SRC,
3198 IcmpErrorSource::EndHost => I::ULTIMATE_SRC,
3199 };
3200
3201 let mut error_packet = IE::make_serializer(
3202 error_src_addr,
3203 I::ULTIMATE_DST,
3204 reply_packet.clone().serialize_vec_outer().unwrap().unwrap_b().into_inner(),
3205 )
3206 .wrap_in(I::PacketBuilder::new(
3207 error_src_addr,
3208 I::ULTIMATE_DST,
3209 u8::MAX,
3210 IE::proto(),
3211 ));
3212
3213 let verdict = perform_nat::<EgressHook, _, _, _, _>(
3214 &mut core_ctx,
3215 &mut bindings_ctx,
3216 NAT_ENABLED_FOR_TESTS,
3217 &conntrack,
3218 &mut conn,
3219 direction,
3220 &nat_routines,
3221 &mut error_packet,
3222 Interfaces { ingress: None, egress: Some(&FakeMatcherDeviceId::ethernet_interface()) },
3223 );
3224 assert_eq!(verdict, Verdict::Accept(()));
3225
3226 let error_packet_expected =
3227 I::PacketBuilder::new(I::NETSTACK, I::ULTIMATE_DST, u8::MAX, IE::proto()).wrap_body(
3228 IE::make_serializer(
3229 I::NETSTACK,
3232 I::ULTIMATE_DST,
3233 reply_packet_pre_nat.serialize_vec_outer().unwrap().unwrap_b().into_inner(),
3234 ),
3235 );
3236
3237 assert_eq!(error_packet, error_packet_expected);
3238 }
3239}