1use core::fmt::Debug;
8use core::num::NonZeroU16;
9use core::ops::{ControlFlow, RangeInclusive};
10
11use derivative::Derivative;
12use log::{error, warn};
13use net_types::ip::IpVersionMarker;
14use net_types::SpecifiedAddr;
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.into()),
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.into()),
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.into()),
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.into()),
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.gen_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 packet::{InnerPacketBuilder, Serializer};
1319 use packet_formats::ip::{IpPacketBuilder, IpProto};
1320 use packet_formats::udp::UdpPacketBuilder;
1321 use test_case::{test_case, test_matrix};
1322
1323 use super::*;
1324 use crate::conntrack::Tuple;
1325 use crate::context::testutil::{
1326 FakeBindingsCtx, FakeDeviceClass, FakeNatCtx, FakePrimaryAddressId, FakeWeakAddressId,
1327 };
1328 use crate::matchers::testutil::{ethernet_interface, FakeDeviceId};
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().clone()
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.into()
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.into()
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 = FakeNatCtx::new([(ethernet_interface(), assigned_addr)]);
1612 let packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1613 let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
1614
1615 let hook = Hook {
1616 routines: vec![
1617 Routine {
1618 rules: vec![
1619 Rule::new(PacketMatcher::default(), Action::Masquerade { src_port: None }),
1621 ],
1622 },
1623 Routine {
1624 rules: vec![
1625 Rule::new(PacketMatcher::default(), Action::Drop),
1627 ],
1628 },
1629 ],
1630 };
1631
1632 assert_matches!(
1633 configure_nat::<EgressHook, _, _, _, _>(
1634 &mut core_ctx,
1635 &mut bindings_ctx,
1636 &conntrack,
1637 &mut conn,
1638 &hook,
1639 &packet,
1640 Interfaces { ingress: None, egress: Some(ðernet_interface()) },
1641 ),
1642 Verdict::Accept(NatConfigurationResult::Result(ShouldNat::Yes(Some(_))))
1643 );
1644 }
1645
1646 #[test]
1647 fn redirect_ingress_drops_packet_if_no_assigned_address() {
1648 let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
1649 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1650 let mut core_ctx = FakeNatCtx::default();
1651 let packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1652 let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
1653
1654 let hook = Hook {
1655 routines: vec![Routine {
1656 rules: vec![Rule::new(
1657 PacketMatcher::default(),
1658 Action::Redirect { dst_port: None },
1659 )],
1660 }],
1661 };
1662
1663 assert_eq!(
1664 configure_nat::<IngressHook, _, _, _, _>(
1665 &mut core_ctx,
1666 &mut bindings_ctx,
1667 &conntrack,
1668 &mut conn,
1669 &hook,
1670 &packet,
1671 Interfaces { ingress: Some(ðernet_interface()), egress: None },
1672 ),
1673 Verdict::Drop.into()
1674 );
1675 }
1676
1677 #[test]
1678 fn masquerade_egress_drops_packet_if_no_assigned_address() {
1679 let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
1680 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1681 let mut core_ctx = FakeNatCtx::default();
1682 let packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1683 let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
1684
1685 let hook = Hook {
1686 routines: vec![Routine {
1687 rules: vec![Rule::new(
1688 PacketMatcher::default(),
1689 Action::Masquerade { src_port: None },
1690 )],
1691 }],
1692 };
1693
1694 assert_eq!(
1695 configure_nat::<EgressHook, _, _, _, _>(
1696 &mut core_ctx,
1697 &mut bindings_ctx,
1698 &conntrack,
1699 &mut conn,
1700 &hook,
1701 &packet,
1702 Interfaces { ingress: None, egress: Some(ðernet_interface()) },
1703 ),
1704 Verdict::Drop.into()
1705 );
1706 }
1707
1708 trait NatHookExt<I: FilterIpExt>: NatHook<I, Verdict<()>: PartialEq> {
1709 fn interfaces<'a>(interface: &'a FakeDeviceId) -> Interfaces<'a, FakeDeviceId>;
1710 }
1711
1712 impl<I: FilterIpExt> NatHookExt<I> for IngressHook {
1713 fn interfaces<'a>(interface: &'a FakeDeviceId) -> Interfaces<'a, FakeDeviceId> {
1714 Interfaces { ingress: Some(interface), egress: None }
1715 }
1716 }
1717
1718 impl<I: FilterIpExt> NatHookExt<I> for LocalEgressHook {
1719 fn interfaces<'a>(interface: &'a FakeDeviceId) -> Interfaces<'a, FakeDeviceId> {
1720 Interfaces { ingress: None, egress: Some(interface) }
1721 }
1722 }
1723
1724 impl<I: FilterIpExt> NatHookExt<I> for EgressHook {
1725 fn interfaces<'a>(interface: &'a FakeDeviceId) -> Interfaces<'a, FakeDeviceId> {
1726 Interfaces { ingress: None, egress: Some(interface) }
1727 }
1728 }
1729
1730 const NAT_ENABLED_FOR_TESTS: bool = true;
1731
1732 #[ip_test(I)]
1733 fn nat_disabled_for_self_connected_flows<I: TestIpExt>() {
1734 let mut bindings_ctx = FakeBindingsCtx::<I>::new();
1735 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1736 let mut core_ctx = FakeNatCtx::default();
1737
1738 let mut packet = FakeIpPacket::<I, _> {
1739 src_ip: I::SRC_IP,
1740 dst_ip: I::SRC_IP,
1741 body: FakeUdpPacket { src_port: 22222, dst_port: 22222 },
1742 };
1743 let (mut conn, direction) = conntrack
1744 .get_connection_for_packet_and_update(
1745 &bindings_ctx,
1746 packet.conntrack_packet().expect("packet should be valid"),
1747 )
1748 .expect("packet should be valid")
1749 .expect("packet should be trackable");
1750
1751 let verdict = perform_nat::<LocalEgressHook, _, _, _, _>(
1755 &mut core_ctx,
1756 &mut bindings_ctx,
1757 NAT_ENABLED_FOR_TESTS,
1758 &conntrack,
1759 &mut conn,
1760 direction,
1761 &Hook {
1762 routines: vec![Routine {
1763 rules: vec![Rule::new(
1764 PacketMatcher::default(),
1765 Action::Redirect { dst_port: None },
1766 )],
1767 }],
1768 },
1769 &mut packet,
1770 <LocalEgressHook as NatHookExt<I>>::interfaces(ðernet_interface()),
1771 );
1772 assert_eq!(verdict, Verdict::Accept(()).into());
1773
1774 let verdict = perform_nat::<EgressHook, _, _, _, _>(
1775 &mut core_ctx,
1776 &mut bindings_ctx,
1777 NAT_ENABLED_FOR_TESTS,
1778 &conntrack,
1779 &mut conn,
1780 direction,
1781 &Hook {
1782 routines: vec![Routine {
1783 rules: vec![Rule::new(
1784 PacketMatcher::default(),
1785 Action::Masquerade { src_port: None },
1786 )],
1787 }],
1788 },
1789 &mut packet,
1790 <EgressHook as NatHookExt<I>>::interfaces(ðernet_interface()),
1791 );
1792 assert_eq!(verdict, Verdict::Accept(()).into());
1793
1794 assert_eq!(conn.external_data().destination.get(), Some(&ShouldNat::No));
1795 assert_eq!(conn.external_data().source.get(), Some(&ShouldNat::No));
1796 }
1797
1798 #[ip_test(I)]
1799 fn nat_disabled_if_not_configured_before_connection_finalized<I: TestIpExt>() {
1800 let mut bindings_ctx = FakeBindingsCtx::<I>::new();
1801 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1802 let mut core_ctx = FakeNatCtx::default();
1803
1804 let mut packet = FakeIpPacket::<I, FakeUdpPacket>::arbitrary_value();
1805 let (mut conn, direction) = conntrack
1806 .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
1807 .expect("packet should be valid")
1808 .expect("packet should be trackable");
1809
1810 let verdict = perform_nat::<LocalEgressHook, _, _, _, _>(
1812 &mut core_ctx,
1813 &mut bindings_ctx,
1814 false, &conntrack,
1816 &mut conn,
1817 direction,
1818 &Hook::default(),
1819 &mut packet,
1820 <LocalEgressHook as NatHookExt<I>>::interfaces(ðernet_interface()),
1821 );
1822 assert_eq!(verdict, Verdict::Accept(()).into());
1823 assert_eq!(conn.external_data().destination.get(), None);
1824 assert_eq!(conn.external_data().source.get(), None);
1825
1826 let verdict = perform_nat::<EgressHook, _, _, _, _>(
1828 &mut core_ctx,
1829 &mut bindings_ctx,
1830 false, &conntrack,
1832 &mut conn,
1833 direction,
1834 &Hook::default(),
1835 &mut packet,
1836 <EgressHook as NatHookExt<I>>::interfaces(ðernet_interface()),
1837 );
1838 assert_eq!(verdict, Verdict::Accept(()).into());
1839 assert_eq!(conn.external_data().destination.get(), None);
1840 assert_eq!(conn.external_data().source.get(), None);
1841
1842 let (inserted, _weak) = conntrack
1843 .finalize_connection(&mut bindings_ctx, conn)
1844 .expect("connection should not conflict");
1845 assert!(inserted);
1846
1847 let mut reply = packet.reply();
1850 let (mut conn, direction) = conntrack
1851 .get_connection_for_packet_and_update(&bindings_ctx, reply.conntrack_packet().unwrap())
1852 .expect("packet should be valid")
1853 .expect("packet should be trackable");
1854 let verdict = perform_nat::<IngressHook, _, _, _, _>(
1855 &mut core_ctx,
1856 &mut bindings_ctx,
1857 NAT_ENABLED_FOR_TESTS,
1858 &conntrack,
1859 &mut conn,
1860 direction,
1861 &Hook::default(),
1862 &mut reply,
1863 <IngressHook as NatHookExt<I>>::interfaces(ðernet_interface()),
1864 );
1865 assert_eq!(verdict, Verdict::Accept(()).into());
1866 assert_eq!(conn.external_data().destination.get(), None);
1867 assert_eq!(conn.external_data().source.get(), Some(&ShouldNat::No));
1868
1869 let verdict = perform_nat::<LocalIngressHook, _, _, _, _>(
1871 &mut core_ctx,
1872 &mut bindings_ctx,
1873 NAT_ENABLED_FOR_TESTS,
1874 &conntrack,
1875 &mut conn,
1876 direction,
1877 &Hook::default(),
1878 &mut reply,
1879 <IngressHook as NatHookExt<I>>::interfaces(ðernet_interface()),
1880 );
1881 assert_eq!(verdict, Verdict::Accept(()).into());
1882 assert_eq!(conn.external_data().destination.get(), Some(&ShouldNat::No));
1883 assert_eq!(conn.external_data().source.get(), Some(&ShouldNat::No));
1884 }
1885
1886 const LOCAL_PORT: NonZeroU16 = NonZeroU16::new(55555).unwrap();
1887
1888 #[ip_test(I)]
1889 #[test_case(
1890 PhantomData::<IngressHook>, PhantomData::<EgressHook>, None;
1891 "redirect INGRESS"
1892 )]
1893 #[test_case(
1894 PhantomData::<IngressHook>, PhantomData::<EgressHook>, Some(LOCAL_PORT);
1895 "redirect INGRESS to local port"
1896 )]
1897 #[test_case(
1898 PhantomData::<LocalEgressHook>, PhantomData::<EgressHook>, None;
1899 "redirect LOCAL_EGRESS"
1900 )]
1901 #[test_case(
1902 PhantomData::<LocalEgressHook>, PhantomData::<EgressHook>, Some(LOCAL_PORT);
1903 "redirect LOCAL_EGRESS to local port"
1904 )]
1905 fn redirect<I: TestIpExt, Original: NatHookExt<I>, Reply: NatHookExt<I>>(
1906 _original_nat_hook: PhantomData<Original>,
1907 _reply_nat_hook: PhantomData<Reply>,
1908 dst_port: Option<NonZeroU16>,
1909 ) {
1910 let mut bindings_ctx = FakeBindingsCtx::<I>::new();
1911 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1912 let mut core_ctx = FakeNatCtx::new([(
1913 ethernet_interface(),
1914 AddrSubnet::new(I::DST_IP_2, I::SUBNET.prefix()).unwrap(),
1915 )]);
1916
1917 let mut packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1919 let pre_nat_packet = packet.clone();
1920 let (mut conn, direction) = conntrack
1921 .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
1922 .expect("packet should be valid")
1923 .expect("packet should be trackable");
1924 let original = conn.original_tuple().clone();
1925
1926 let nat_routines = Hook {
1930 routines: vec![Routine {
1931 rules: vec![Rule::new(
1932 PacketMatcher::default(),
1933 Action::Redirect { dst_port: dst_port.map(|port| port..=port) },
1934 )],
1935 }],
1936 };
1937 let verdict = perform_nat::<Original, _, _, _, _>(
1938 &mut core_ctx,
1939 &mut bindings_ctx,
1940 NAT_ENABLED_FOR_TESTS,
1941 &conntrack,
1942 &mut conn,
1943 direction,
1944 &nat_routines,
1945 &mut packet,
1946 Original::interfaces(ðernet_interface()),
1947 );
1948 assert_eq!(verdict, Verdict::Accept(()).into());
1949
1950 let (redirect_addr, cached_addr) = Original::redirect_addr(
1954 &mut core_ctx,
1955 &packet,
1956 Original::interfaces(ðernet_interface()).ingress,
1957 )
1958 .expect("get redirect addr for NAT hook");
1959 let expected = FakeIpPacket::<_, FakeUdpPacket> {
1960 src_ip: packet.src_ip,
1961 dst_ip: redirect_addr,
1962 body: FakeUdpPacket {
1963 src_port: packet.body.src_port,
1964 dst_port: dst_port.map(NonZeroU16::get).unwrap_or(packet.body.dst_port),
1965 },
1966 };
1967 assert_eq!(packet, expected);
1968 assert_eq!(
1969 conn.external_data().destination.get().expect("DNAT should be configured"),
1970 &ShouldNat::Yes(cached_addr)
1971 );
1972 assert_eq!(conn.external_data().source.get(), None, "SNAT should not be configured");
1973 assert_eq!(conn.original_tuple(), &original);
1974 let mut reply = Tuple { src_addr: redirect_addr, ..original.invert() };
1975 if let Some(port) = dst_port {
1976 reply.src_port_or_id = port.get();
1977 }
1978 assert_eq!(conn.reply_tuple(), &reply);
1979
1980 let mut reply_packet = packet.reply();
1984 let nat_routines = Hook {
1988 routines: vec![Routine {
1989 rules: vec![Rule::new(PacketMatcher::default(), Action::Drop)],
1990 }],
1991 };
1992 let verdict = perform_nat::<Reply, _, _, _, _>(
1993 &mut core_ctx,
1994 &mut bindings_ctx,
1995 NAT_ENABLED_FOR_TESTS,
1996 &conntrack,
1997 &mut conn,
1998 ConnectionDirection::Reply,
1999 &nat_routines,
2000 &mut reply_packet,
2001 Reply::interfaces(ðernet_interface()),
2002 );
2003 assert_eq!(verdict, Verdict::Accept(()).into());
2004 assert_eq!(reply_packet, pre_nat_packet.reply());
2005 }
2006
2007 #[ip_test(I)]
2008 #[test_case(None; "masquerade")]
2009 #[test_case(Some(LOCAL_PORT); "masquerade to specified port")]
2010 fn masquerade<I: TestIpExt>(src_port: Option<NonZeroU16>) {
2011 let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2012 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2013 let assigned_addr = AddrSubnet::new(I::SRC_IP_2, I::SUBNET.prefix()).unwrap();
2014 let mut core_ctx = FakeNatCtx::new([(ethernet_interface(), assigned_addr)]);
2015
2016 let mut packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
2018 let pre_nat_packet = packet.clone();
2019 let (mut conn, direction) = conntrack
2020 .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2021 .expect("packet should be valid")
2022 .expect("packet should be trackable");
2023 let original = conn.original_tuple().clone();
2024
2025 let nat_routines = Hook {
2027 routines: vec![Routine {
2028 rules: vec![Rule::new(
2029 PacketMatcher::default(),
2030 Action::Masquerade { src_port: src_port.map(|port| port..=port) },
2031 )],
2032 }],
2033 };
2034 let verdict = perform_nat::<EgressHook, _, _, _, _>(
2035 &mut core_ctx,
2036 &mut bindings_ctx,
2037 NAT_ENABLED_FOR_TESTS,
2038 &conntrack,
2039 &mut conn,
2040 direction,
2041 &nat_routines,
2042 &mut packet,
2043 Interfaces { ingress: None, egress: Some(ðernet_interface()) },
2044 );
2045 assert_eq!(verdict, Verdict::Accept(()).into());
2046
2047 let expected = FakeIpPacket::<_, FakeUdpPacket> {
2051 src_ip: I::SRC_IP_2,
2052 dst_ip: packet.dst_ip,
2053 body: FakeUdpPacket {
2054 src_port: src_port.map(NonZeroU16::get).unwrap_or(packet.body.src_port),
2055 dst_port: packet.body.dst_port,
2056 },
2057 };
2058 assert_eq!(packet, expected);
2059 assert_matches!(
2060 conn.external_data().source.get().expect("SNAT should be configured"),
2061 &ShouldNat::Yes(Some(_))
2062 );
2063 assert_eq!(conn.external_data().destination.get(), None, "DNAT should not be configured");
2064 assert_eq!(conn.original_tuple(), &original);
2065 let mut reply = Tuple { dst_addr: I::SRC_IP_2, ..original.invert() };
2066 if let Some(port) = src_port {
2067 reply.dst_port_or_id = port.get();
2068 }
2069 assert_eq!(conn.reply_tuple(), &reply);
2070
2071 let mut reply_packet = packet.reply();
2075 let nat_routines = Hook {
2079 routines: vec![Routine {
2080 rules: vec![Rule::new(PacketMatcher::default(), Action::Drop)],
2081 }],
2082 };
2083 let verdict = perform_nat::<IngressHook, _, _, _, _>(
2084 &mut core_ctx,
2085 &mut bindings_ctx,
2086 NAT_ENABLED_FOR_TESTS,
2087 &conntrack,
2088 &mut conn,
2089 ConnectionDirection::Reply,
2090 &nat_routines,
2091 &mut reply_packet,
2092 Interfaces { ingress: Some(ðernet_interface()), egress: None },
2093 );
2094 assert_eq!(verdict, Verdict::Accept(()).into());
2095 assert_eq!(reply_packet, pre_nat_packet.reply());
2096 }
2097
2098 #[ip_test(I)]
2099 #[test_case(22, 1..=511)]
2100 #[test_case(853, 1..=1023)]
2101 #[test_case(11111, 1024..=u16::MAX)]
2102 fn masquerade_reply_tuple_dst_port_rewritten_even_if_target_range_unspecified<I: TestIpExt>(
2103 src_port: u16,
2104 expected_range: RangeInclusive<u16>,
2105 ) {
2106 let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2107 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2108 let assigned_addr = AddrSubnet::new(I::SRC_IP_2, I::SUBNET.prefix()).unwrap();
2109 let mut core_ctx = FakeNatCtx::new([(ethernet_interface(), assigned_addr)]);
2110 let packet = FakeIpPacket {
2111 body: FakeUdpPacket { src_port, ..ArbitraryValue::arbitrary_value() },
2112 ..ArbitraryValue::arbitrary_value()
2113 };
2114
2115 let reply = FakeIpPacket { src_ip: I::SRC_IP_2, ..packet.clone() };
2118 let (conn, _dir) = conntrack
2119 .get_connection_for_packet_and_update(&bindings_ctx, reply.conntrack_packet().unwrap())
2120 .expect("packet should be valid")
2121 .expect("packet should be trackable");
2122 assert_matches!(
2123 conntrack
2124 .finalize_connection(&mut bindings_ctx, conn)
2125 .expect("connection should not conflict"),
2126 (true, Some(_))
2127 );
2128
2129 let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
2133 let verdict = configure_masquerade_nat(
2134 &mut core_ctx,
2135 &mut bindings_ctx,
2136 &conntrack,
2137 &mut conn,
2138 &packet,
2139 &Interfaces { ingress: None, egress: Some(ðernet_interface()) },
2140 None,
2141 );
2142
2143 assert_matches!(
2149 verdict,
2150 Verdict::Accept(NatConfigurationResult::Result(ShouldNat::Yes(Some(_))))
2151 );
2152 let reply_tuple = conn.reply_tuple();
2153 assert_eq!(reply_tuple.dst_addr, I::SRC_IP_2);
2154 assert_ne!(reply_tuple.dst_port_or_id, src_port);
2155 assert!(expected_range.contains(&reply_tuple.dst_port_or_id));
2156 }
2157
2158 #[ip_test(I)]
2159 #[test_case(
2160 PhantomData::<IngressHook>, Action::Redirect { dst_port: None };
2161 "redirect in INGRESS"
2162 )]
2163 #[test_case(
2164 PhantomData::<EgressHook>, Action::Masquerade { src_port: None };
2165 "masquerade in EGRESS"
2166 )]
2167 fn assigned_addr_cached_and_validated<I: TestIpExt, N: NatHookExt<I>>(
2168 _nat_hook: PhantomData<N>,
2169 action: Action<I, FakeDeviceClass, ()>,
2170 ) {
2171 let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2172 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2173 let assigned_addr = AddrSubnet::new(I::SRC_IP_2, I::SUBNET.prefix()).unwrap();
2174 let mut core_ctx = FakeNatCtx::new([(ethernet_interface(), assigned_addr)]);
2175
2176 let mut packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
2178 let (mut conn, direction) = conntrack
2179 .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2180 .expect("packet should be valid")
2181 .expect("packet should be trackable");
2182
2183 let nat_routines = Hook {
2185 routines: vec![Routine { rules: vec![Rule::new(PacketMatcher::default(), action)] }],
2186 };
2187 let verdict = perform_nat::<N, _, _, _, _>(
2188 &mut core_ctx,
2189 &mut bindings_ctx,
2190 NAT_ENABLED_FOR_TESTS,
2191 &conntrack,
2192 &mut conn,
2193 direction,
2194 &nat_routines,
2195 &mut packet,
2196 N::interfaces(ðernet_interface()),
2197 );
2198 assert_eq!(verdict, Verdict::Accept(()).into());
2199
2200 let (nat, _nat_type) = conn.relevant_config(N::NAT_TYPE, ConnectionDirection::Original);
2203 let nat = nat.get().unwrap_or_else(|| panic!("{:?} NAT should be configured", N::NAT_TYPE));
2204 let id = assert_matches!(nat, ShouldNat::Yes(Some(CachedAddr { id, _marker })) => id);
2205 let id = id
2206 .lock()
2207 .as_ref()
2208 .expect("address ID should be cached in NAT config")
2209 .upgrade()
2210 .expect("address ID should be valid");
2211 assert_eq!(*id, assigned_addr);
2212 drop(id);
2213
2214 core_ctx.device_addrs.clear();
2218 let verdict = perform_nat::<N, _, _, _, _>(
2219 &mut core_ctx,
2220 &mut bindings_ctx,
2221 NAT_ENABLED_FOR_TESTS,
2222 &conntrack,
2223 &mut conn,
2224 ConnectionDirection::Original,
2225 &nat_routines,
2226 &mut FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value(),
2227 N::interfaces(ðernet_interface()),
2228 );
2229 assert_eq!(verdict, Verdict::Drop.into());
2230 let (nat, _nat_type) = conn.relevant_config(N::NAT_TYPE, ConnectionDirection::Original);
2231 let nat = nat.get().unwrap_or_else(|| panic!("{:?} NAT should be configured", N::NAT_TYPE));
2232 let id = assert_matches!(nat, ShouldNat::Yes(Some(CachedAddr { id, _marker })) => id);
2233 assert_eq!(*id.lock(), None, "cached weak address ID should be cleared");
2234
2235 assert_matches!(
2238 core_ctx
2239 .device_addrs
2240 .insert(ethernet_interface(), FakePrimaryAddressId(Arc::new(assigned_addr))),
2241 None
2242 );
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(ðernet_interface()),
2253 );
2254 assert_eq!(verdict, Verdict::Accept(()).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 let id = id
2259 .lock()
2260 .as_ref()
2261 .expect("address ID should be cached in NAT config")
2262 .upgrade()
2263 .expect("address Id should be valid");
2264 assert_eq!(*id, assigned_addr);
2265 }
2266
2267 #[test_case(ReplyTuplePort::Source)]
2268 #[test_case(ReplyTuplePort::Destination)]
2269 fn rewrite_port_noop_if_in_range(which: ReplyTuplePort) {
2270 let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
2271 let table = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2272 let mut conn =
2273 ConnectionExclusive::with_reply_tuple(&bindings_ctx, which, LOCAL_PORT.get());
2274
2275 let pre_nat = conn.reply_tuple().clone();
2278 let result = rewrite_reply_tuple_port(
2279 &mut bindings_ctx,
2280 &table,
2281 &mut conn,
2282 which,
2283 LOCAL_PORT..=LOCAL_PORT,
2284 true, ConflictStrategy::RewritePort,
2286 );
2287 assert_eq!(
2288 result,
2289 Verdict::Accept(NatConfigurationResult::Result(
2290 ShouldNat::<_, FakeWeakAddressId<Ipv4>>::No
2291 ))
2292 );
2293 assert_eq!(conn.reply_tuple(), &pre_nat);
2294 }
2295
2296 #[test_case(ReplyTuplePort::Source)]
2297 #[test_case(ReplyTuplePort::Destination)]
2298 fn rewrite_port_noop_if_no_conflict(which: ReplyTuplePort) {
2299 let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
2300 let table = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2301 let mut conn =
2302 ConnectionExclusive::with_reply_tuple(&bindings_ctx, which, LOCAL_PORT.get());
2303
2304 let pre_nat = conn.reply_tuple().clone();
2308 const NEW_PORT: NonZeroU16 = LOCAL_PORT.checked_add(1).unwrap();
2309 let result = rewrite_reply_tuple_port(
2310 &mut bindings_ctx,
2311 &table,
2312 &mut conn,
2313 which,
2314 NEW_PORT..=NEW_PORT,
2315 false, ConflictStrategy::RewritePort,
2317 );
2318 assert_eq!(
2319 result,
2320 Verdict::Accept(NatConfigurationResult::Result(
2321 ShouldNat::<_, FakeWeakAddressId<Ipv4>>::No
2322 ))
2323 );
2324 assert_eq!(conn.reply_tuple(), &pre_nat);
2325 }
2326
2327 #[test_case(ReplyTuplePort::Source)]
2328 #[test_case(ReplyTuplePort::Destination)]
2329 fn rewrite_port_succeeds_if_available_port_in_range(which: ReplyTuplePort) {
2330 let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
2331 let table = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2332 let mut conn =
2333 ConnectionExclusive::with_reply_tuple(&bindings_ctx, which, LOCAL_PORT.get());
2334
2335 const NEW_PORT: NonZeroU16 = LOCAL_PORT.checked_add(1).unwrap();
2338 let result = rewrite_reply_tuple_port(
2339 &mut bindings_ctx,
2340 &table,
2341 &mut conn,
2342 which,
2343 NEW_PORT..=NEW_PORT,
2344 true, ConflictStrategy::RewritePort,
2346 );
2347 assert_eq!(
2348 result,
2349 Verdict::Accept(NatConfigurationResult::Result(
2350 ShouldNat::<_, FakeWeakAddressId<Ipv4>>::Yes(None)
2351 ))
2352 );
2353 assert_eq!(conn.reply_tuple(), &tuple_with_port(which, NEW_PORT.get()));
2354 }
2355
2356 #[test_case(ReplyTuplePort::Source)]
2357 #[test_case(ReplyTuplePort::Destination)]
2358 fn rewrite_port_fails_if_no_available_port_in_range(which: ReplyTuplePort) {
2359 let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
2360 let table: Table<_, NatConfig<_, FakeWeakAddressId<Ipv4>>, _> =
2361 Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2362
2363 let packet = packet_with_port(which, LOCAL_PORT.get());
2367 let (conn, _dir) = table
2368 .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2369 .expect("packet should be valid")
2370 .expect("packet should be trackable");
2371 assert_matches!(
2372 table
2373 .finalize_connection(&mut bindings_ctx, conn)
2374 .expect("connection should not conflict"),
2375 (true, Some(_))
2376 );
2377
2378 let mut conn =
2379 ConnectionExclusive::with_reply_tuple(&bindings_ctx, which, LOCAL_PORT.get());
2380 let result = rewrite_reply_tuple_port(
2381 &mut bindings_ctx,
2382 &table,
2383 &mut conn,
2384 which,
2385 LOCAL_PORT..=LOCAL_PORT,
2386 true, ConflictStrategy::RewritePort,
2388 );
2389 assert_eq!(result, Verdict::Drop.into());
2390 }
2391
2392 #[test_case(ReplyTuplePort::Source)]
2393 #[test_case(ReplyTuplePort::Destination)]
2394 fn port_rewritten_to_ensure_unique_tuple_even_if_in_range(which: ReplyTuplePort) {
2395 let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
2396 let table = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2397
2398 const MAX_PORT: NonZeroU16 = NonZeroU16::new(LOCAL_PORT.get() + 100).unwrap();
2402 for port in LOCAL_PORT.get()..=MAX_PORT.get() {
2403 let packet = packet_with_port(which, port);
2404 let (conn, _dir) = table
2405 .get_connection_for_packet_and_update(
2406 &bindings_ctx,
2407 packet.conntrack_packet().unwrap(),
2408 )
2409 .expect("packet should be valid")
2410 .expect("packet should be trackable");
2411 assert_matches!(
2412 table
2413 .finalize_connection(&mut bindings_ctx, conn)
2414 .expect("connection should not conflict"),
2415 (true, Some(_))
2416 );
2417 }
2418
2419 let mut conn =
2423 ConnectionExclusive::with_reply_tuple(&bindings_ctx, which, LOCAL_PORT.get());
2424 const MIN_PORT: NonZeroU16 = NonZeroU16::new(LOCAL_PORT.get() - 1).unwrap();
2425 let result = rewrite_reply_tuple_port(
2426 &mut bindings_ctx,
2427 &table,
2428 &mut conn,
2429 which,
2430 MIN_PORT..=MAX_PORT,
2431 true, ConflictStrategy::RewritePort,
2433 );
2434 assert_eq!(
2435 result,
2436 Verdict::Accept(NatConfigurationResult::Result(
2437 ShouldNat::<_, FakeWeakAddressId<Ipv4>>::Yes(None)
2438 ))
2439 );
2440 assert_eq!(conn.reply_tuple(), &tuple_with_port(which, MIN_PORT.get()));
2441 }
2442
2443 #[test_case(ReplyTuplePort::Source)]
2444 #[test_case(ReplyTuplePort::Destination)]
2445 fn rewrite_port_skipped_if_existing_connection_can_be_adopted(which: ReplyTuplePort) {
2446 let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
2447 let table: Table<_, NatConfig<_, FakeWeakAddressId<Ipv4>>, _> =
2448 Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2449
2450 let packet = packet_with_port(which, LOCAL_PORT.get());
2455 let (conn, _dir) = table
2456 .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2457 .expect("packet should be valid")
2458 .expect("packet should be trackable");
2459 let existing = assert_matches!(
2460 table
2461 .finalize_connection(&mut bindings_ctx, conn)
2462 .expect("connection should not conflict"),
2463 (true, Some(conn)) => conn
2464 );
2465
2466 let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
2467 let result = rewrite_reply_tuple_port(
2468 &mut bindings_ctx,
2469 &table,
2470 &mut conn,
2471 which,
2472 NonZeroU16::MIN..=NonZeroU16::MAX,
2473 false, ConflictStrategy::AdoptExisting,
2475 );
2476 let conn = assert_matches!(
2477 result,
2478 Verdict::Accept(NatConfigurationResult::AdoptExisting(Connection::Shared(conn))) => conn
2479 );
2480 assert!(Arc::ptr_eq(&existing, &conn));
2481 }
2482
2483 trait IcmpErrorTestIpExt: TestIpExt {
2484 const NETSTACK: Self::Addr = Self::DST_IP;
2485 const ULTIMATE_SRC: Self::Addr = Self::SRC_IP;
2486 const ULTIMATE_DST: Self::Addr = Self::DST_IP_3;
2487 const ROUTER_SRC: Self::Addr = Self::SRC_IP_2;
2488 const ROUTER_DST: Self::Addr = Self::DST_IP_2;
2489 }
2490
2491 impl<I> IcmpErrorTestIpExt for I where I: TestIpExt {}
2492
2493 enum IcmpErrorSource {
2494 IntermediateRouter,
2495 EndHost,
2496 }
2497
2498 #[test_case(Icmpv4DestUnreachableError)]
2529 #[test_case(Icmpv6DestUnreachableError)]
2530 fn redirect_icmp_error_in_reply_direction<I: IcmpErrorTestIpExt, IE: IcmpErrorMessage<I>>(
2531 _icmp_error: IE,
2532 ) {
2533 let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2534 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2535 let mut core_ctx = FakeNatCtx::new([(
2536 ethernet_interface(),
2537 AddrSubnet::new(I::NETSTACK, I::SUBNET.prefix()).unwrap(),
2538 )]);
2539
2540 let mut packet = []
2542 .into_serializer()
2543 .encapsulate(UdpPacketBuilder::new(
2544 I::ULTIMATE_SRC,
2545 I::ULTIMATE_DST,
2546 Some(NonZeroU16::new(11111).unwrap()),
2547 NonZeroU16::new(22222).unwrap(),
2548 ))
2549 .encapsulate(I::PacketBuilder::new(
2550 I::ULTIMATE_SRC,
2551 I::ULTIMATE_DST,
2552 u8::MAX,
2553 IpProto::Udp.into(),
2554 ));
2555 let packet_pre_nat = packet.clone();
2556 let (mut conn, _) = conntrack
2557 .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2558 .expect("packet should be valid")
2559 .expect("packet should be trackable");
2560 let original_tuple = conn.original_tuple().clone();
2561
2562 let nat_routines = Hook {
2563 routines: vec![Routine {
2564 rules: vec![Rule::new(
2565 PacketMatcher::default(),
2566 Action::Redirect { dst_port: Some(LOCAL_PORT..=LOCAL_PORT) },
2567 )],
2568 }],
2569 };
2570 let verdict = perform_nat::<IngressHook, _, _, _, _>(
2571 &mut core_ctx,
2572 &mut bindings_ctx,
2573 NAT_ENABLED_FOR_TESTS,
2574 &conntrack,
2575 &mut conn,
2576 ConnectionDirection::Original,
2577 &nat_routines,
2578 &mut packet,
2579 <IngressHook as NatHookExt<I>>::interfaces(ðernet_interface()),
2580 );
2581 assert_eq!(verdict, Verdict::Accept(()).into());
2582
2583 let (redirect_addr, cached_addr) = IngressHook::redirect_addr(
2587 &mut core_ctx,
2588 &packet,
2589 <IngressHook as NatHookExt<I>>::interfaces(ðernet_interface()).ingress,
2590 )
2591 .expect("get redirect addr for NAT hook");
2592
2593 assert_eq!(redirect_addr, I::NETSTACK);
2596
2597 let expected = []
2599 .into_serializer()
2600 .encapsulate(UdpPacketBuilder::new(
2601 I::ULTIMATE_SRC,
2602 redirect_addr,
2603 packet.inner().outer().src_port(),
2604 LOCAL_PORT,
2605 ))
2606 .encapsulate(I::PacketBuilder::new(
2607 I::ULTIMATE_SRC,
2608 redirect_addr,
2609 u8::MAX,
2610 IpProto::Udp.into(),
2611 ));
2612 assert_eq!(packet, expected);
2613 assert_eq!(
2614 conn.external_data().destination.get().expect("DNAT should be configured"),
2615 &ShouldNat::Yes(cached_addr)
2616 );
2617 assert_eq!(conn.external_data().source.get(), None, "SNAT should not be configured");
2618 assert_eq!(conn.original_tuple(), &original_tuple);
2619
2620 let reply_tuple = Tuple {
2621 src_addr: redirect_addr,
2622 src_port_or_id: LOCAL_PORT.get(),
2623 ..original_tuple.invert()
2624 };
2625 assert_eq!(conn.reply_tuple(), &reply_tuple);
2626
2627 let mut error_packet = IE::make_serializer(
2628 redirect_addr,
2629 I::ULTIMATE_SRC,
2630 packet.clone().serialize_vec_outer().unwrap().unwrap_b().into_inner(),
2631 )
2632 .encapsulate(I::PacketBuilder::new(
2633 redirect_addr,
2634 I::ULTIMATE_SRC,
2635 u8::MAX,
2636 IE::proto(),
2637 ));
2638
2639 let verdict = perform_nat::<EgressHook, _, _, _, _>(
2640 &mut core_ctx,
2641 &mut bindings_ctx,
2642 NAT_ENABLED_FOR_TESTS,
2643 &conntrack,
2644 &mut conn,
2645 ConnectionDirection::Reply,
2646 &nat_routines,
2647 &mut error_packet,
2648 <EgressHook as NatHookExt<I>>::interfaces(ðernet_interface()),
2649 );
2650 assert_eq!(verdict, Verdict::Accept(()).into());
2651
2652 let error_packet_expected = IE::make_serializer(
2653 I::ULTIMATE_DST,
2656 I::ULTIMATE_SRC,
2657 packet_pre_nat.clone().serialize_vec_outer().unwrap().unwrap_b().into_inner(),
2658 )
2659 .encapsulate(I::PacketBuilder::new(
2660 I::ULTIMATE_DST,
2661 I::ULTIMATE_SRC,
2662 u8::MAX,
2663 IE::proto(),
2664 ));
2665
2666 assert_eq!(error_packet, error_packet_expected);
2667 }
2668
2669 #[test_matrix(
2670 [
2671 Icmpv4DestUnreachableError,
2672 Icmpv6DestUnreachableError,
2673 ],
2674 [
2675 IcmpErrorSource::IntermediateRouter,
2676 IcmpErrorSource::EndHost,
2677 ]
2678 )]
2679 fn redirect_icmp_error_in_original_direction<I: IcmpErrorTestIpExt, IE: IcmpErrorMessage<I>>(
2680 _icmp_error: IE,
2681 icmp_error_source: IcmpErrorSource,
2682 ) {
2683 let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2684 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2685 let mut core_ctx = FakeNatCtx::new([(
2686 ethernet_interface(),
2687 AddrSubnet::new(I::NETSTACK, I::SUBNET.prefix()).unwrap(),
2688 )]);
2689
2690 let mut packet = []
2692 .into_serializer()
2693 .encapsulate(UdpPacketBuilder::new(
2694 I::ULTIMATE_SRC,
2695 I::ULTIMATE_DST,
2696 Some(NonZeroU16::new(11111).unwrap()),
2697 NonZeroU16::new(22222).unwrap(),
2698 ))
2699 .encapsulate(I::PacketBuilder::new(
2700 I::ULTIMATE_SRC,
2701 I::ULTIMATE_DST,
2702 u8::MAX,
2703 IpProto::Udp.into(),
2704 ));
2705 let (mut conn, direction) = conntrack
2706 .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2707 .expect("packet should be valid")
2708 .expect("packet should be trackable");
2709 let original_tuple = conn.original_tuple().clone();
2710
2711 let nat_routines = Hook {
2715 routines: vec![Routine {
2716 rules: vec![Rule::new(
2717 PacketMatcher::default(),
2718 Action::Redirect { dst_port: Some(LOCAL_PORT..=LOCAL_PORT) },
2719 )],
2720 }],
2721 };
2722 let verdict = perform_nat::<IngressHook, _, _, _, _>(
2723 &mut core_ctx,
2724 &mut bindings_ctx,
2725 NAT_ENABLED_FOR_TESTS,
2726 &conntrack,
2727 &mut conn,
2728 direction,
2729 &nat_routines,
2730 &mut packet,
2731 <IngressHook as NatHookExt<I>>::interfaces(ðernet_interface()),
2732 );
2733 assert_eq!(verdict, Verdict::Accept(()).into());
2734
2735 let (redirect_addr, cached_addr) = IngressHook::redirect_addr(
2739 &mut core_ctx,
2740 &packet,
2741 <IngressHook as NatHookExt<I>>::interfaces(ðernet_interface()).ingress,
2742 )
2743 .expect("get redirect addr for NAT hook");
2744
2745 assert_eq!(redirect_addr, I::NETSTACK);
2748
2749 let expected = []
2751 .into_serializer()
2752 .encapsulate(UdpPacketBuilder::new(
2753 I::ULTIMATE_SRC,
2754 redirect_addr,
2755 packet.inner().outer().src_port(),
2756 LOCAL_PORT,
2757 ))
2758 .encapsulate(I::PacketBuilder::new(
2759 I::ULTIMATE_SRC,
2760 redirect_addr,
2761 u8::MAX,
2762 IpProto::Udp.into(),
2763 ));
2764 assert_eq!(packet, expected);
2765 assert_eq!(
2766 conn.external_data().destination.get().expect("DNAT should be configured"),
2767 &ShouldNat::Yes(cached_addr)
2768 );
2769 assert_eq!(conn.external_data().source.get(), None, "SNAT should not be configured");
2770 assert_eq!(conn.original_tuple(), &original_tuple);
2771 let reply_tuple = Tuple {
2772 src_addr: redirect_addr,
2773 src_port_or_id: LOCAL_PORT.get(),
2774 ..original_tuple.invert()
2775 };
2776 assert_eq!(conn.reply_tuple(), &reply_tuple);
2777
2778 let mut reply_packet = []
2779 .into_serializer()
2780 .encapsulate(UdpPacketBuilder::new(
2781 reply_tuple.src_addr,
2782 reply_tuple.dst_addr,
2783 Some(NonZeroU16::new(reply_tuple.src_port_or_id).unwrap()),
2784 NonZeroU16::new(reply_tuple.dst_port_or_id).unwrap(),
2785 ))
2786 .encapsulate(I::PacketBuilder::new(
2787 reply_tuple.src_addr,
2788 reply_tuple.dst_addr,
2789 u8::MAX,
2790 IpProto::Udp.into(),
2791 ));
2792 let reply_packet_pre_nat = reply_packet.clone();
2793
2794 let verdict = perform_nat::<EgressHook, _, _, _, _>(
2795 &mut core_ctx,
2796 &mut bindings_ctx,
2797 NAT_ENABLED_FOR_TESTS,
2798 &conntrack,
2799 &mut conn,
2800 ConnectionDirection::Reply,
2801 &nat_routines,
2802 &mut reply_packet,
2803 <EgressHook as NatHookExt<I>>::interfaces(ðernet_interface()),
2804 );
2805 assert_eq!(verdict, Verdict::Accept(()).into());
2806
2807 let reply_packet_expected = []
2808 .into_serializer()
2809 .encapsulate(UdpPacketBuilder::new(
2810 I::ULTIMATE_DST,
2811 I::ULTIMATE_SRC,
2812 Some(NonZeroU16::new(22222).unwrap()),
2813 NonZeroU16::new(11111).unwrap(),
2814 ))
2815 .encapsulate(I::PacketBuilder::new(
2816 I::ULTIMATE_DST,
2817 I::ULTIMATE_SRC,
2818 u8::MAX,
2819 IpProto::Udp.into(),
2820 ));
2821 assert_eq!(reply_packet, reply_packet_expected);
2822
2823 let error_src_addr = match icmp_error_source {
2826 IcmpErrorSource::IntermediateRouter => I::ROUTER_SRC,
2827 IcmpErrorSource::EndHost => I::ULTIMATE_SRC,
2828 };
2829
2830 let mut error_packet = IE::make_serializer(
2831 error_src_addr,
2832 I::ULTIMATE_DST,
2833 reply_packet.clone().serialize_vec_outer().unwrap().unwrap_b().into_inner(),
2834 )
2835 .encapsulate(I::PacketBuilder::new(
2836 error_src_addr,
2837 I::ULTIMATE_DST,
2838 u8::MAX,
2839 IE::proto(),
2840 ));
2841
2842 let verdict = perform_nat::<IngressHook, _, _, _, _>(
2843 &mut core_ctx,
2844 &mut bindings_ctx,
2845 NAT_ENABLED_FOR_TESTS,
2846 &conntrack,
2847 &mut conn,
2848 direction,
2849 &nat_routines,
2850 &mut error_packet,
2851 <IngressHook as NatHookExt<I>>::interfaces(ðernet_interface()),
2852 );
2853 assert_eq!(verdict, Verdict::Accept(()).into());
2854
2855 let error_packet_expected = IE::make_serializer(
2856 error_src_addr,
2859 redirect_addr,
2860 reply_packet_pre_nat.clone().serialize_vec_outer().unwrap().unwrap_b().into_inner(),
2861 )
2862 .encapsulate(I::PacketBuilder::new(
2863 error_src_addr,
2864 redirect_addr,
2865 u8::MAX,
2866 IE::proto(),
2867 ));
2868
2869 assert_eq!(error_packet, error_packet_expected);
2870 }
2871
2872 #[test_matrix(
2900 [
2901 Icmpv4DestUnreachableError,
2902 Icmpv6DestUnreachableError,
2903 ],
2904 [
2905 IcmpErrorSource::IntermediateRouter,
2906 IcmpErrorSource::EndHost,
2907 ]
2908 )]
2909 fn masquerade_icmp_error_in_reply_direction<I: TestIpExt, IE: IcmpErrorMessage<I>>(
2910 _icmp_error: IE,
2911 icmp_error_source: IcmpErrorSource,
2912 ) {
2913 let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2914 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2915 let assigned_addr = AddrSubnet::new(I::NETSTACK, I::SUBNET.prefix()).unwrap();
2916 let mut core_ctx = FakeNatCtx::new([(ethernet_interface(), assigned_addr)]);
2917
2918 let mut packet = []
2920 .into_serializer()
2921 .encapsulate(UdpPacketBuilder::new(
2922 I::ULTIMATE_SRC,
2923 I::ULTIMATE_DST,
2924 Some(NonZeroU16::new(11111).unwrap()),
2925 NonZeroU16::new(22222).unwrap(),
2926 ))
2927 .encapsulate(I::PacketBuilder::new(
2928 I::ULTIMATE_SRC,
2929 I::ULTIMATE_DST,
2930 u8::MAX,
2931 IpProto::Udp.into(),
2932 ));
2933 let packet_pre_nat = packet.clone();
2934 let (mut conn, direction) = conntrack
2935 .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2936 .expect("packet should be valid")
2937 .expect("packet should be trackable");
2938 let original_tuple = conn.original_tuple().clone();
2939
2940 let nat_routines = Hook {
2942 routines: vec![Routine {
2943 rules: vec![Rule::new(
2944 PacketMatcher::default(),
2945 Action::Masquerade { src_port: Some(LOCAL_PORT..=LOCAL_PORT) },
2946 )],
2947 }],
2948 };
2949 let verdict = perform_nat::<EgressHook, _, _, _, _>(
2950 &mut core_ctx,
2951 &mut bindings_ctx,
2952 NAT_ENABLED_FOR_TESTS,
2953 &conntrack,
2954 &mut conn,
2955 direction,
2956 &nat_routines,
2957 &mut packet,
2958 Interfaces { ingress: None, egress: Some(ðernet_interface()) },
2959 );
2960 assert_eq!(verdict, Verdict::Accept(()).into());
2961
2962 let expected = []
2966 .into_serializer()
2967 .encapsulate(UdpPacketBuilder::new(
2968 I::NETSTACK,
2969 I::ULTIMATE_DST,
2970 Some(LOCAL_PORT),
2971 packet.inner().outer().dst_port().unwrap(),
2972 ))
2973 .encapsulate(I::PacketBuilder::new(
2974 I::NETSTACK,
2975 I::ULTIMATE_DST,
2976 u8::MAX,
2977 IpProto::Udp.into(),
2978 ));
2979 assert_eq!(packet, expected);
2980 assert_matches!(
2981 conn.external_data().source.get().expect("SNAT should be configured"),
2982 &ShouldNat::Yes(Some(_))
2983 );
2984 assert_eq!(conn.external_data().destination.get(), None, "DNAT should not be configured");
2985 assert_eq!(conn.original_tuple(), &original_tuple);
2986 let reply_tuple = Tuple {
2987 dst_addr: I::NETSTACK,
2988 dst_port_or_id: LOCAL_PORT.get(),
2989 ..original_tuple.invert()
2990 };
2991 assert_eq!(conn.reply_tuple(), &reply_tuple);
2992
2993 let error_src_addr = match icmp_error_source {
2996 IcmpErrorSource::IntermediateRouter => I::ROUTER_DST,
2997 IcmpErrorSource::EndHost => I::ULTIMATE_DST,
2998 };
2999
3000 let mut error_packet = IE::make_serializer(
3001 error_src_addr,
3002 I::NETSTACK,
3003 packet.clone().serialize_vec_outer().unwrap().unwrap_b().into_inner(),
3004 )
3005 .encapsulate(I::PacketBuilder::new(
3006 error_src_addr,
3007 I::NETSTACK,
3008 u8::MAX,
3009 IE::proto(),
3010 ));
3011
3012 let verdict = perform_nat::<IngressHook, _, _, _, _>(
3013 &mut core_ctx,
3014 &mut bindings_ctx,
3015 NAT_ENABLED_FOR_TESTS,
3016 &conntrack,
3017 &mut conn,
3018 ConnectionDirection::Reply,
3019 &nat_routines,
3020 &mut error_packet,
3021 Interfaces { ingress: Some(ðernet_interface()), egress: None },
3022 );
3023 assert_eq!(verdict, Verdict::Accept(()).into());
3024
3025 let error_packet_expected = IE::make_serializer(
3026 error_src_addr,
3029 I::ULTIMATE_SRC,
3030 packet_pre_nat.clone().serialize_vec_outer().unwrap().unwrap_b().into_inner(),
3031 )
3032 .encapsulate(I::PacketBuilder::new(
3033 error_src_addr,
3034 I::ULTIMATE_SRC,
3035 u8::MAX,
3036 IE::proto(),
3037 ));
3038
3039 assert_eq!(error_packet, error_packet_expected);
3040 }
3041
3042 #[test_matrix(
3043 [
3044 Icmpv4DestUnreachableError,
3045 Icmpv6DestUnreachableError,
3046 ],
3047 [
3048 IcmpErrorSource::IntermediateRouter,
3049 IcmpErrorSource::EndHost,
3050 ]
3051 )]
3052 fn masquerade_icmp_error_in_original_direction<I: TestIpExt, IE: IcmpErrorMessage<I>>(
3053 _icmp_error: IE,
3054 icmp_error_source: IcmpErrorSource,
3055 ) {
3056 let mut bindings_ctx = FakeBindingsCtx::<I>::new();
3057 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
3058 let assigned_addr = AddrSubnet::new(I::NETSTACK, I::SUBNET.prefix()).unwrap();
3059 let mut core_ctx = FakeNatCtx::new([(ethernet_interface(), assigned_addr)]);
3060
3061 let mut packet = []
3063 .into_serializer()
3064 .encapsulate(UdpPacketBuilder::new(
3065 I::ULTIMATE_SRC,
3066 I::ULTIMATE_DST,
3067 Some(NonZeroU16::new(11111).unwrap()),
3068 NonZeroU16::new(22222).unwrap(),
3069 ))
3070 .encapsulate(I::PacketBuilder::new(
3071 I::ULTIMATE_SRC,
3072 I::ULTIMATE_DST,
3073 u8::MAX,
3074 IpProto::Udp.into(),
3075 ));
3076 let (mut conn, direction) = conntrack
3077 .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
3078 .expect("packet should be valid")
3079 .expect("packet should be trackable");
3080 let original_tuple = conn.original_tuple().clone();
3081
3082 let nat_routines = Hook {
3084 routines: vec![Routine {
3085 rules: vec![Rule::new(
3086 PacketMatcher::default(),
3087 Action::Masquerade { src_port: Some(LOCAL_PORT..=LOCAL_PORT) },
3088 )],
3089 }],
3090 };
3091 let verdict = perform_nat::<EgressHook, _, _, _, _>(
3092 &mut core_ctx,
3093 &mut bindings_ctx,
3094 NAT_ENABLED_FOR_TESTS,
3095 &conntrack,
3096 &mut conn,
3097 direction,
3098 &nat_routines,
3099 &mut packet,
3100 Interfaces { ingress: None, egress: Some(ðernet_interface()) },
3101 );
3102 assert_eq!(verdict, Verdict::Accept(()).into());
3103
3104 let expected = []
3108 .into_serializer()
3109 .encapsulate(UdpPacketBuilder::new(
3110 I::NETSTACK,
3111 I::ULTIMATE_DST,
3112 Some(LOCAL_PORT),
3113 packet.inner().outer().dst_port().unwrap(),
3114 ))
3115 .encapsulate(I::PacketBuilder::new(
3116 I::NETSTACK,
3117 I::ULTIMATE_DST,
3118 u8::MAX,
3119 IpProto::Udp.into(),
3120 ));
3121 assert_eq!(packet, expected);
3122 assert_matches!(
3123 conn.external_data().source.get().expect("SNAT should be configured"),
3124 &ShouldNat::Yes(Some(_))
3125 );
3126 assert_eq!(conn.external_data().destination.get(), None, "DNAT should not be configured");
3127 assert_eq!(conn.original_tuple(), &original_tuple);
3128 let reply_tuple = Tuple {
3129 dst_addr: I::NETSTACK,
3130 dst_port_or_id: LOCAL_PORT.get(),
3131 ..original_tuple.invert()
3132 };
3133 assert_eq!(conn.reply_tuple(), &reply_tuple);
3134
3135 let mut reply_packet = []
3139 .into_serializer()
3140 .encapsulate(UdpPacketBuilder::new(
3141 reply_tuple.src_addr,
3142 reply_tuple.dst_addr,
3143 Some(NonZeroU16::new(reply_tuple.src_port_or_id).unwrap()),
3144 NonZeroU16::new(reply_tuple.dst_port_or_id).unwrap(),
3145 ))
3146 .encapsulate(I::PacketBuilder::new(
3147 reply_tuple.src_addr,
3148 reply_tuple.dst_addr,
3149 u8::MAX,
3150 IpProto::Udp.into(),
3151 ));
3152 let reply_packet_pre_nat = reply_packet.clone();
3153
3154 let verdict = perform_nat::<IngressHook, _, _, _, _>(
3155 &mut core_ctx,
3156 &mut bindings_ctx,
3157 NAT_ENABLED_FOR_TESTS,
3158 &conntrack,
3159 &mut conn,
3160 ConnectionDirection::Reply,
3161 &nat_routines,
3162 &mut reply_packet,
3163 Interfaces { ingress: Some(ðernet_interface()), egress: None },
3164 );
3165 assert_eq!(verdict, Verdict::Accept(()).into());
3166
3167 let reply_packet_expected = []
3168 .into_serializer()
3169 .encapsulate(UdpPacketBuilder::new(
3170 I::ULTIMATE_DST,
3171 I::ULTIMATE_SRC,
3172 Some(NonZeroU16::new(22222).unwrap()),
3173 NonZeroU16::new(11111).unwrap(),
3174 ))
3175 .encapsulate(I::PacketBuilder::new(
3176 I::ULTIMATE_DST,
3177 I::ULTIMATE_SRC,
3178 u8::MAX,
3179 IpProto::Udp.into(),
3180 ));
3181 assert_eq!(reply_packet, reply_packet_expected);
3182
3183 let error_src_addr = match icmp_error_source {
3184 IcmpErrorSource::IntermediateRouter => I::ROUTER_SRC,
3185 IcmpErrorSource::EndHost => I::ULTIMATE_SRC,
3186 };
3187
3188 let mut error_packet = IE::make_serializer(
3189 error_src_addr,
3190 I::ULTIMATE_DST,
3191 reply_packet.clone().serialize_vec_outer().unwrap().unwrap_b().into_inner(),
3192 )
3193 .encapsulate(I::PacketBuilder::new(
3194 error_src_addr,
3195 I::ULTIMATE_DST,
3196 u8::MAX,
3197 IE::proto(),
3198 ));
3199
3200 let verdict = perform_nat::<EgressHook, _, _, _, _>(
3201 &mut core_ctx,
3202 &mut bindings_ctx,
3203 NAT_ENABLED_FOR_TESTS,
3204 &conntrack,
3205 &mut conn,
3206 direction,
3207 &nat_routines,
3208 &mut error_packet,
3209 Interfaces { ingress: None, egress: Some(ðernet_interface()) },
3210 );
3211 assert_eq!(verdict, Verdict::Accept(()).into());
3212
3213 let error_packet_expected = IE::make_serializer(
3214 I::NETSTACK,
3217 I::ULTIMATE_DST,
3218 reply_packet_pre_nat.clone().serialize_vec_outer().unwrap().unwrap_b().into_inner(),
3219 )
3220 .encapsulate(I::PacketBuilder::new(
3221 I::NETSTACK,
3222 I::ULTIMATE_DST,
3223 u8::MAX,
3224 IE::proto(),
3225 ));
3226
3227 assert_eq!(error_packet, error_packet_expected);
3228 }
3229}