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