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::{Accept, DropPacket, 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<DropPacket>
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::Proceed(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::Proceed(Accept)
175 }
176 None => {
181 *id.lock() = None;
182 Verdict::Stop(DropPacket)
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 const NAT_TYPE: NatType;
199
200 fn evaluate_result<P, CC, BC>(
203 core_ctx: &mut CC,
204 bindings_ctx: &mut BC,
205 table: &Table<I, NatConfig<I, CC::WeakAddressId>, BC>,
206 conn: &mut ConnectionExclusive<I, NatConfig<I, CC::WeakAddressId>, BC>,
207 packet: &P,
208 interfaces: &Interfaces<'_, CC::DeviceId>,
209 result: RoutineResult<I>,
210 ) -> ControlFlow<Verdict<DropPacket, NatConfigurationResult<I, CC::WeakAddressId, BC>>>
211 where
212 P: FilterIpPacket<I>,
213 CC: NatContext<I, BC>,
214 BC: FilterBindingsContext<CC::DeviceId>;
215
216 fn redirect_addr<P, CC, BT>(
224 core_ctx: &mut CC,
225 packet: &P,
226 ingress: Option<&CC::DeviceId>,
227 ) -> Option<(I::Addr, Option<CachedAddr<I, CC::WeakAddressId>>)>
228 where
229 P: IpPacket<I>,
230 CC: NatContext<I, BT>,
231 BT: FilterBindingsTypes;
232
233 fn interface<DeviceId>(interfaces: Interfaces<'_, DeviceId>) -> &DeviceId;
240}
241
242#[derive(Derivative)]
243#[derivative(Debug(bound = "A: Debug"))]
244pub(crate) enum NatConfigurationResult<I: IpExt, A, BT: FilterBindingsTypes> {
245 Result(ShouldNat<I, A>),
246 AdoptExisting(Connection<I, NatConfig<I, A>, BT>),
247}
248
249pub(crate) enum IngressHook {}
250
251impl<I: FilterIpExt> NatHook<I> for IngressHook {
252 const NAT_TYPE: NatType = NatType::Destination;
253
254 fn evaluate_result<P, CC, BC>(
255 core_ctx: &mut CC,
256 bindings_ctx: &mut BC,
257 table: &Table<I, NatConfig<I, CC::WeakAddressId>, BC>,
258 conn: &mut ConnectionExclusive<I, NatConfig<I, CC::WeakAddressId>, BC>,
259 packet: &P,
260 interfaces: &Interfaces<'_, CC::DeviceId>,
261 result: RoutineResult<I>,
262 ) -> ControlFlow<Verdict<DropPacket, NatConfigurationResult<I, CC::WeakAddressId, BC>>>
263 where
264 P: FilterIpPacket<I>,
265 CC: NatContext<I, BC>,
266 BC: FilterBindingsContext<CC::DeviceId>,
267 {
268 match result {
269 RoutineResult::Accept | RoutineResult::Return => ControlFlow::Continue(()),
270 RoutineResult::Drop => ControlFlow::Break(Verdict::Stop(DropPacket)),
271 RoutineResult::Redirect { dst_port } => {
272 ControlFlow::Break(configure_redirect_nat::<Self, _, _, _, _>(
273 core_ctx,
274 bindings_ctx,
275 table,
276 conn,
277 packet,
278 interfaces,
279 dst_port,
280 ))
281 }
282 result @ RoutineResult::Masquerade { .. } => {
283 unreachable!("SNAT not supported in INGRESS; got {result:?}")
284 }
285 RoutineResult::Reject(_reject_type) => {
286 unreachable!("REJECT not supported in NAT hooks")
287 }
288 RoutineResult::TransparentLocalDelivery { .. } => {
289 unreachable!("Transparent proxy not supported in NAT hooks")
290 }
291 }
292 }
293
294 fn redirect_addr<P, CC, BT>(
295 core_ctx: &mut CC,
296 packet: &P,
297 ingress: Option<&CC::DeviceId>,
298 ) -> Option<(I::Addr, Option<CachedAddr<I, CC::WeakAddressId>>)>
299 where
300 P: IpPacket<I>,
301 CC: NatContext<I, BT>,
302 BT: FilterBindingsTypes,
303 {
304 let interface = ingress.expect("must have ingress interface in ingress hook");
305 let addr_id = core_ctx
306 .get_local_addr_for_remote(interface, SpecifiedAddr::new(packet.src_addr()))
307 .or_else(|| {
308 warn!(
309 "cannot redirect because there is no address assigned to the incoming \
310 interface {interface:?}; dropping packet",
311 );
312 None
313 })?;
314 let addr = addr_id.addr();
315 Some((addr.addr(), Some(CachedAddr::new(addr_id.downgrade()))))
316 }
317
318 fn interface<DeviceId>(interfaces: Interfaces<'_, DeviceId>) -> &DeviceId {
319 let Interfaces { ingress, egress: _ } = interfaces;
320 ingress.expect("ingress interface must be provided to INGRESS hook")
321 }
322}
323
324pub(crate) enum LocalEgressHook {}
325
326impl<I: FilterIpExt> NatHook<I> for LocalEgressHook {
327 const NAT_TYPE: NatType = NatType::Destination;
328
329 fn evaluate_result<P, CC, BC>(
330 core_ctx: &mut CC,
331 bindings_ctx: &mut BC,
332 table: &Table<I, NatConfig<I, CC::WeakAddressId>, BC>,
333 conn: &mut ConnectionExclusive<I, NatConfig<I, CC::WeakAddressId>, BC>,
334 packet: &P,
335 interfaces: &Interfaces<'_, CC::DeviceId>,
336 result: RoutineResult<I>,
337 ) -> ControlFlow<Verdict<DropPacket, NatConfigurationResult<I, CC::WeakAddressId, BC>>>
338 where
339 P: FilterIpPacket<I>,
340 CC: NatContext<I, BC>,
341 BC: FilterBindingsContext<CC::DeviceId>,
342 {
343 match result {
344 RoutineResult::Accept | RoutineResult::Return => ControlFlow::Continue(()),
345 RoutineResult::Drop => ControlFlow::Break(Verdict::Stop(DropPacket)),
346 result @ RoutineResult::TransparentLocalDelivery { .. } => {
347 unreachable!(
348 "transparent local delivery is only valid in INGRESS hook; got {result:?}"
349 )
350 }
351 result @ RoutineResult::Masquerade { .. } => {
352 unreachable!("SNAT not supported in LOCAL_EGRESS; got {result:?}")
353 }
354 RoutineResult::Redirect { dst_port } => {
355 ControlFlow::Break(configure_redirect_nat::<Self, _, _, _, _>(
356 core_ctx,
357 bindings_ctx,
358 table,
359 conn,
360 packet,
361 interfaces,
362 dst_port,
363 ))
364 }
365 RoutineResult::Reject(_) => {
366 unreachable!("REJECT not supported in NAT hooks")
367 }
368 }
369 }
370
371 fn redirect_addr<P, CC, BT>(
372 _: &mut CC,
373 _: &P,
374 _: Option<&CC::DeviceId>,
375 ) -> Option<(I::Addr, Option<CachedAddr<I, CC::WeakAddressId>>)>
376 where
377 P: IpPacket<I>,
378 CC: NatContext<I, BT>,
379 BT: FilterBindingsTypes,
380 {
381 Some((*I::LOOPBACK_ADDRESS, None))
382 }
383
384 fn interface<DeviceId>(interfaces: Interfaces<'_, DeviceId>) -> &DeviceId {
385 let Interfaces { ingress: _, egress } = interfaces;
386 egress.expect("egress interface must be provided to LOCAL_EGRESS hook")
387 }
388}
389
390pub(crate) enum LocalIngressHook {}
391
392impl<I: FilterIpExt> NatHook<I> for LocalIngressHook {
393 const NAT_TYPE: NatType = NatType::Source;
394
395 fn evaluate_result<P, CC, BC>(
396 _core_ctx: &mut CC,
397 _bindings_ctx: &mut BC,
398 _table: &Table<I, NatConfig<I, CC::WeakAddressId>, BC>,
399 _conn: &mut ConnectionExclusive<I, NatConfig<I, CC::WeakAddressId>, BC>,
400 _packet: &P,
401 _interfaces: &Interfaces<'_, CC::DeviceId>,
402 result: RoutineResult<I>,
403 ) -> ControlFlow<Verdict<DropPacket, NatConfigurationResult<I, CC::WeakAddressId, BC>>>
404 where
405 P: IpPacket<I>,
406 CC: NatContext<I, BC>,
407 BC: FilterBindingsContext<CC::DeviceId>,
408 {
409 match result {
410 RoutineResult::Accept | RoutineResult::Return => ControlFlow::Continue(()),
411 RoutineResult::Drop => ControlFlow::Break(Verdict::Stop(DropPacket)),
412 result @ RoutineResult::Masquerade { .. } => {
413 unreachable!("Masquerade not supported in LOCAL_INGRESS; got {result:?}")
414 }
415 result @ RoutineResult::TransparentLocalDelivery { .. } => {
416 unreachable!(
417 "transparent local delivery is only valid in INGRESS hook; got {result:?}"
418 )
419 }
420 result @ RoutineResult::Redirect { .. } => {
421 unreachable!("DNAT not supported in LOCAL_INGRESS; got {result:?}")
422 }
423 RoutineResult::Reject(_) => {
424 unreachable!("REJECT not supported in NAT hooks")
425 }
426 }
427 }
428
429 fn redirect_addr<P, CC, BT>(
430 _: &mut CC,
431 _: &P,
432 _: Option<&CC::DeviceId>,
433 ) -> Option<(I::Addr, Option<CachedAddr<I, CC::WeakAddressId>>)>
434 where
435 P: IpPacket<I>,
436 CC: NatContext<I, BT>,
437 BT: FilterBindingsTypes,
438 {
439 unreachable!("DNAT not supported in LOCAL_INGRESS; cannot perform redirect action")
440 }
441
442 fn interface<DeviceId>(interfaces: Interfaces<'_, DeviceId>) -> &DeviceId {
443 let Interfaces { ingress, egress: _ } = interfaces;
444 ingress.expect("ingress interface must be provided to LOCAL_INGRESS hook")
445 }
446}
447
448pub(crate) enum EgressHook {}
449
450impl<I: FilterIpExt> NatHook<I> for EgressHook {
451 const NAT_TYPE: NatType = NatType::Source;
452
453 fn evaluate_result<P, CC, BC>(
454 core_ctx: &mut CC,
455 bindings_ctx: &mut BC,
456 table: &Table<I, NatConfig<I, CC::WeakAddressId>, BC>,
457 conn: &mut ConnectionExclusive<I, NatConfig<I, CC::WeakAddressId>, BC>,
458 packet: &P,
459 interfaces: &Interfaces<'_, CC::DeviceId>,
460 result: RoutineResult<I>,
461 ) -> ControlFlow<Verdict<DropPacket, NatConfigurationResult<I, CC::WeakAddressId, BC>>>
462 where
463 P: FilterIpPacket<I>,
464 CC: NatContext<I, BC>,
465 BC: FilterBindingsContext<CC::DeviceId>,
466 {
467 match result {
468 RoutineResult::Accept | RoutineResult::Return => ControlFlow::Continue(()),
469 RoutineResult::Drop => ControlFlow::Break(Verdict::Stop(DropPacket)),
470 RoutineResult::Masquerade { src_port } => {
471 ControlFlow::Break(configure_masquerade_nat::<_, _, _, _>(
472 core_ctx,
473 bindings_ctx,
474 table,
475 conn,
476 packet,
477 interfaces,
478 src_port,
479 ))
480 }
481 result @ RoutineResult::TransparentLocalDelivery { .. } => {
482 unreachable!(
483 "transparent local delivery is only valid in INGRESS hook; got {result:?}"
484 )
485 }
486 result @ RoutineResult::Redirect { .. } => {
487 unreachable!("DNAT not supported in EGRESS; got {result:?}")
488 }
489 RoutineResult::Reject(_) => {
490 unreachable!("REJECT not supported in NAT hooks")
491 }
492 }
493 }
494
495 fn redirect_addr<P, CC, BT>(
496 _: &mut CC,
497 _: &P,
498 _: Option<&CC::DeviceId>,
499 ) -> Option<(I::Addr, Option<CachedAddr<I, CC::WeakAddressId>>)>
500 where
501 P: IpPacket<I>,
502 CC: NatContext<I, BT>,
503 BT: FilterBindingsTypes,
504 {
505 unreachable!("DNAT not supported in EGRESS; cannot perform redirect action")
506 }
507
508 fn interface<DeviceId>(interfaces: Interfaces<'_, DeviceId>) -> &DeviceId {
509 let Interfaces { ingress: _, egress } = interfaces;
510 egress.expect("egress interface must be provided to EGRESS hook")
511 }
512}
513
514impl<I: IpExt, A, BT: FilterBindingsTypes> Connection<I, NatConfig<I, A>, BT> {
515 fn relevant_config(
516 &self,
517 hook_nat_type: NatType,
518 direction: ConnectionDirection,
519 ) -> (&OnceCell<ShouldNat<I, A>>, NatType) {
520 let NatConfig { source, destination } = self.external_data();
521 match (hook_nat_type, direction) {
522 (NatType::Destination, ConnectionDirection::Original)
527 | (NatType::Source, ConnectionDirection::Reply) => (destination, NatType::Destination),
528 (NatType::Source, ConnectionDirection::Original)
533 | (NatType::Destination, ConnectionDirection::Reply) => (source, NatType::Source),
534 }
535 }
536
537 fn relevant_reply_tuple_addr(&self, nat_type: NatType) -> I::Addr {
538 match nat_type {
539 NatType::Destination => self.reply_tuple().src_addr,
540 NatType::Source => self.reply_tuple().dst_addr,
541 }
542 }
543
544 fn nat_config(
548 &self,
549 hook_nat_type: NatType,
550 direction: ConnectionDirection,
551 ) -> Option<&ShouldNat<I, A>> {
552 let (config, _nat_type) = self.relevant_config(hook_nat_type, direction);
553 config.get()
554 }
555
556 fn set_nat_config(
564 &self,
565 hook_nat_type: NatType,
566 direction: ConnectionDirection,
567 value: ShouldNat<I, A>,
568 ) -> Result<&ShouldNat<I, A>, (ShouldNat<I, A>, NatType)> {
569 let (config, nat_type) = self.relevant_config(hook_nat_type, direction);
570 let mut value = Some(value);
571 let config = config.get_or_init(|| value.take().unwrap());
572 match value {
573 None => Ok(config),
574 Some(value) => Err((value, nat_type)),
575 }
576 }
577}
578
579pub(crate) fn perform_nat<N, I, P, CC, BC>(
585 core_ctx: &mut CC,
586 bindings_ctx: &mut BC,
587 nat_installed: bool,
588 table: &Table<I, NatConfig<I, CC::WeakAddressId>, BC>,
589 conn: &mut Connection<I, NatConfig<I, CC::WeakAddressId>, BC>,
590 direction: ConnectionDirection,
591 hook: &Hook<I, BC, ()>,
592 packet: &mut P,
593 interfaces: Interfaces<'_, CC::DeviceId>,
594) -> Verdict<DropPacket>
595where
596 N: NatHook<I>,
597 I: FilterIpExt,
598 P: FilterIpPacket<I>,
599 CC: NatContext<I, BC>,
600 BC: FilterBindingsContext<CC::DeviceId>,
601{
602 if !nat_installed {
603 return Verdict::Proceed(Accept);
604 }
605
606 let nat_config = if let Some(nat) = conn.nat_config(N::NAT_TYPE, direction) {
607 nat
608 } else {
609 let (verdict, exclusive) = match (&mut *conn, direction) {
612 (Connection::Exclusive(_), ConnectionDirection::Reply) => {
613 (Verdict::Proceed(NatConfigurationResult::Result(ShouldNat::No)), true)
624 }
625 (Connection::Shared(_), _) => {
626 (Verdict::Proceed(NatConfigurationResult::Result(ShouldNat::No)), false)
634 }
635 (Connection::Exclusive(conn), ConnectionDirection::Original) => {
636 let verdict = configure_nat::<N, _, _, _, _>(
637 core_ctx,
638 bindings_ctx,
639 table,
640 conn,
641 hook,
642 packet,
643 interfaces.clone(),
644 );
645 let verdict = if matches!(
650 verdict,
651 Verdict::Proceed(NatConfigurationResult::Result(ShouldNat::No))
652 ) && N::NAT_TYPE == NatType::Source
653 {
654 configure_snat_port(
655 bindings_ctx,
656 table,
657 conn,
658 None, ConflictStrategy::AdoptExisting,
660 )
661 } else {
662 verdict
663 };
664 (verdict, true)
665 }
666 };
667
668 let result = match verdict {
669 Verdict::Proceed(result) => result,
670 Verdict::Stop(reason) => return Verdict::Stop(reason),
671 };
672 let new_nat_config = match result {
673 NatConfigurationResult::Result(config) => Some(config),
674 NatConfigurationResult::AdoptExisting(existing) => {
675 *conn = existing;
676 None
677 }
678 };
679 if let Some(config) = new_nat_config {
680 conn.set_nat_config(N::NAT_TYPE, direction, config).unwrap_or_else(
681 |(value, nat_type)| {
682 if exclusive {
687 unreachable!(
688 "{nat_type:?} NAT should not have been configured yet, but found \
689 {value:?}"
690 );
691 }
692 &ShouldNat::No
693 },
694 )
695 } else {
696 conn.nat_config(N::NAT_TYPE, direction).unwrap_or(&ShouldNat::No)
697 }
698 };
699
700 match nat_config {
701 ShouldNat::No => return Verdict::Proceed(Accept),
702 ShouldNat::Yes(None) => {}
706 ShouldNat::Yes(Some(cached_addr)) => {
709 if direction == ConnectionDirection::Original {
714 match cached_addr.validate_or_replace(
715 core_ctx,
716 N::interface(interfaces),
717 conn.relevant_reply_tuple_addr(N::NAT_TYPE),
718 ) {
719 Verdict::Proceed(Accept) => {}
720 v @ Verdict::Stop(_) => return v,
721 }
722 }
723 }
724 }
725 rewrite_packet(conn, direction, N::NAT_TYPE, packet)
726}
727
728fn configure_nat<N, I, P, CC, BC>(
735 core_ctx: &mut CC,
736 bindings_ctx: &mut BC,
737 table: &Table<I, NatConfig<I, CC::WeakAddressId>, BC>,
738 conn: &mut ConnectionExclusive<I, NatConfig<I, CC::WeakAddressId>, BC>,
739 hook: &Hook<I, BC, ()>,
740 packet: &P,
741 interfaces: Interfaces<'_, CC::DeviceId>,
742) -> Verdict<DropPacket, NatConfigurationResult<I, CC::WeakAddressId, BC>>
743where
744 N: NatHook<I>,
745 I: FilterIpExt,
746 P: FilterIpPacket<I>,
747 CC: NatContext<I, BC>,
748 BC: FilterBindingsContext<CC::DeviceId>,
749{
750 let Hook { routines } = hook;
751 for routine in routines {
752 let result =
753 super::check_routine(&routine, packet, interfaces, &mut FakePacketMetadata::default());
754 match N::evaluate_result(core_ctx, bindings_ctx, table, conn, packet, &interfaces, result) {
755 ControlFlow::Break(result) => return result,
756 ControlFlow::Continue(()) => {}
757 }
758 }
759 Verdict::Proceed(NatConfigurationResult::Result(ShouldNat::No))
760}
761
762fn configure_redirect_nat<N, I, P, CC, BC>(
765 core_ctx: &mut CC,
766 bindings_ctx: &mut BC,
767 table: &Table<I, NatConfig<I, CC::WeakAddressId>, BC>,
768 conn: &mut ConnectionExclusive<I, NatConfig<I, CC::WeakAddressId>, BC>,
769 packet: &P,
770 interfaces: &Interfaces<'_, CC::DeviceId>,
771 dst_port_range: Option<RangeInclusive<NonZeroU16>>,
772) -> Verdict<DropPacket, NatConfigurationResult<I, CC::WeakAddressId, BC>>
773where
774 N: NatHook<I>,
775 I: FilterIpExt,
776 P: FilterIpPacket<I>,
777 CC: NatContext<I, BC>,
778 BC: FilterBindingsContext<CC::DeviceId>,
779{
780 match N::NAT_TYPE {
781 NatType::Source => panic!("DNAT action called from SNAT-only hook"),
782 NatType::Destination => {}
783 }
784
785 let Some((addr, cached_addr)) = N::redirect_addr(core_ctx, packet, interfaces.ingress) else {
792 return Verdict::Stop(DropPacket);
793 };
794 conn.rewrite_reply_src_addr(addr);
795
796 let Some(range) = dst_port_range else {
797 return Verdict::Proceed(NatConfigurationResult::Result(ShouldNat::Yes(cached_addr)));
798 };
799 match rewrite_reply_tuple_port(
800 bindings_ctx,
801 table,
802 conn,
803 ReplyTuplePort::Source,
804 range,
805 true, ConflictStrategy::RewritePort,
807 ) {
808 Verdict::Proceed(
811 NatConfigurationResult::Result(ShouldNat::Yes(_))
812 | NatConfigurationResult::Result(ShouldNat::No),
813 ) => Verdict::Proceed(NatConfigurationResult::Result(ShouldNat::Yes(cached_addr))),
814 Verdict::Proceed(NatConfigurationResult::AdoptExisting(_)) => {
815 unreachable!("cannot adopt existing connection")
816 }
817 Verdict::Stop(DropPacket) => Verdict::Stop(DropPacket),
818 }
819}
820
821fn configure_masquerade_nat<I, P, CC, BC>(
825 core_ctx: &mut CC,
826 bindings_ctx: &mut BC,
827 table: &Table<I, NatConfig<I, CC::WeakAddressId>, BC>,
828 conn: &mut ConnectionExclusive<I, NatConfig<I, CC::WeakAddressId>, BC>,
829 packet: &P,
830 interfaces: &Interfaces<'_, CC::DeviceId>,
831 src_port_range: Option<RangeInclusive<NonZeroU16>>,
832) -> Verdict<DropPacket, NatConfigurationResult<I, CC::WeakAddressId, BC>>
833where
834 I: FilterIpExt,
835 P: FilterIpPacket<I>,
836 CC: NatContext<I, BC>,
837 BC: FilterBindingsContext<CC::DeviceId>,
838{
839 let interface = interfaces.egress.expect(
843 "must have egress interface in EGRESS hook; Masquerade NAT is only valid in EGRESS",
844 );
845 let Some(addr) =
846 core_ctx.get_local_addr_for_remote(interface, SpecifiedAddr::new(packet.dst_addr()))
847 else {
848 warn!(
850 "cannot masquerade because there is no address assigned to the outgoing interface \
851 {interface:?}; dropping packet",
852 );
853 return Verdict::Stop(DropPacket);
854 };
855 conn.rewrite_reply_dst_addr(addr.addr().addr());
856
857 match configure_snat_port(
860 bindings_ctx,
861 table,
862 conn,
863 src_port_range,
864 ConflictStrategy::RewritePort,
865 ) {
866 Verdict::Proceed(
869 NatConfigurationResult::Result(ShouldNat::Yes(_))
870 | NatConfigurationResult::Result(ShouldNat::No),
871 ) => Verdict::Proceed(NatConfigurationResult::Result(ShouldNat::Yes(Some(
872 CachedAddr::new(addr.downgrade()),
873 )))),
874 Verdict::Proceed(NatConfigurationResult::AdoptExisting(_)) => {
875 unreachable!("cannot adopt existing connection")
876 }
877 Verdict::Stop(DropPacket) => Verdict::Stop(DropPacket),
878 }
879}
880
881fn configure_snat_port<I, A, BC, D>(
882 bindings_ctx: &mut BC,
883 table: &Table<I, NatConfig<I, A>, BC>,
884 conn: &mut ConnectionExclusive<I, NatConfig<I, A>, BC>,
885 src_port_range: Option<RangeInclusive<NonZeroU16>>,
886 conflict_strategy: ConflictStrategy,
887) -> Verdict<DropPacket, NatConfigurationResult<I, A, BC>>
888where
889 I: IpExt,
890 BC: FilterBindingsContext<D>,
891 A: PartialEq,
892{
893 let (range, ensure_port_in_range) = if let Some(range) = src_port_range {
898 (range, true)
899 } else {
900 let reply_tuple = conn.reply_tuple();
901 let Some(range) =
902 similar_port_or_id_range(reply_tuple.protocol, reply_tuple.dst_port_or_id)
903 else {
904 return Verdict::Stop(DropPacket);
905 };
906 (range, false)
907 };
908 rewrite_reply_tuple_port(
909 bindings_ctx,
910 table,
911 conn,
912 ReplyTuplePort::Destination,
913 range,
914 ensure_port_in_range,
915 conflict_strategy,
916 )
917}
918
919fn similar_port_or_id_range(
926 protocol: TransportProtocol,
927 port_or_id: u16,
928) -> Option<RangeInclusive<NonZeroU16>> {
929 match protocol {
930 TransportProtocol::Tcp | TransportProtocol::Udp => Some(match port_or_id {
931 _ if port_or_id < 512 => NonZeroU16::MIN..=NonZeroU16::new(511).unwrap(),
932 _ if port_or_id < 1024 => NonZeroU16::MIN..=NonZeroU16::new(1023).unwrap(),
933 _ => NonZeroU16::new(1024).unwrap()..=NonZeroU16::MAX,
934 }),
935 TransportProtocol::Icmp => Some(NonZeroU16::MIN..=NonZeroU16::MAX),
937 TransportProtocol::Other(p) => {
938 error!(
939 "cannot rewrite port or ID of unsupported transport protocol {p}; dropping packet"
940 );
941 None
942 }
943 }
944}
945
946#[derive(Clone, Copy)]
947enum ReplyTuplePort {
948 Source,
949 Destination,
950}
951
952enum ConflictStrategy {
953 AdoptExisting,
954 RewritePort,
955}
956
957fn rewrite_reply_tuple_port<I, A, BC, D>(
961 bindings_ctx: &mut BC,
962 table: &Table<I, NatConfig<I, A>, BC>,
963 conn: &mut ConnectionExclusive<I, NatConfig<I, A>, BC>,
964 which_port: ReplyTuplePort,
965 port_range: RangeInclusive<NonZeroU16>,
966 ensure_port_in_range: bool,
967 conflict_strategy: ConflictStrategy,
968) -> Verdict<DropPacket, NatConfigurationResult<I, A, BC>>
969where
970 I: IpExt,
971 A: PartialEq,
972 BC: FilterBindingsContext<D>,
973{
974 let current_port = match which_port {
978 ReplyTuplePort::Source => conn.reply_tuple().src_port_or_id,
979 ReplyTuplePort::Destination => conn.reply_tuple().dst_port_or_id,
980 };
981 let already_in_range = !ensure_port_in_range
982 || NonZeroU16::new(current_port).map(|port| port_range.contains(&port)).unwrap_or(false);
983 if already_in_range {
984 match table.get_shared_connection(conn.reply_tuple()) {
985 None => return Verdict::Proceed(NatConfigurationResult::Result(ShouldNat::No)),
986 Some(conflict) => match conflict_strategy {
987 ConflictStrategy::AdoptExisting => {
988 if conflict.compatible_with(&*conn) {
992 return Verdict::Proceed(NatConfigurationResult::AdoptExisting(
993 Connection::Shared(conflict),
994 ));
995 }
996 }
997 ConflictStrategy::RewritePort => {}
998 },
999 }
1000 }
1001
1002 const MAX_ATTEMPTS: u16 = 128;
1011 let len = port_range.end().get() - port_range.start().get() + 1;
1012 let mut rng = bindings_ctx.rng();
1013 let start = rng.random_range(port_range.start().get()..=port_range.end().get());
1014 for i in 0..core::cmp::min(MAX_ATTEMPTS, len) {
1015 let offset = (start + i) % len;
1018 let new_port = port_range.start().checked_add(offset).unwrap();
1019 match which_port {
1020 ReplyTuplePort::Source => conn.rewrite_reply_src_port_or_id(new_port.get()),
1021 ReplyTuplePort::Destination => conn.rewrite_reply_dst_port_or_id(new_port.get()),
1022 };
1023 if !table.contains_tuple(conn.reply_tuple()) {
1024 return Verdict::Proceed(NatConfigurationResult::Result(ShouldNat::Yes(None)));
1025 }
1026 }
1027
1028 Verdict::Stop(DropPacket)
1029}
1030
1031fn rewrite_packet_for_dst_nat<I, P>(
1032 packet: &mut P,
1033 new_dst_addr: I::Addr,
1034 new_dst_port: u16,
1035) -> Verdict<DropPacket>
1036where
1037 I: FilterIpExt,
1038 P: IpPacket<I>,
1039{
1040 packet.set_dst_addr(new_dst_addr);
1041 let Some(proto) = packet.protocol() else {
1042 return Verdict::Proceed(Accept);
1043 };
1044 let mut transport = packet.transport_packet_mut();
1045 let Some(mut transport) = transport.transport_packet_mut() else {
1046 return Verdict::Proceed(Accept);
1047 };
1048 let Some(new_dst_port) = NonZeroU16::new(new_dst_port) else {
1049 error!("cannot rewrite dst port to unspecified; dropping {proto} packet");
1052 return Verdict::Stop(DropPacket);
1053 };
1054 transport.set_dst_port(new_dst_port);
1055
1056 Verdict::Proceed(Accept)
1057}
1058
1059fn rewrite_packet_for_src_nat<I, P>(
1060 packet: &mut P,
1061 new_src_addr: I::Addr,
1062 new_src_port: u16,
1063) -> Verdict<DropPacket>
1064where
1065 I: FilterIpExt,
1066 P: IpPacket<I>,
1067{
1068 packet.set_src_addr(new_src_addr);
1069 let Some(proto) = packet.protocol() else {
1070 return Verdict::Proceed(Accept);
1071 };
1072 let mut transport = packet.transport_packet_mut();
1073 let Some(mut transport) = transport.transport_packet_mut() else {
1074 return Verdict::Proceed(Accept);
1075 };
1076 let Some(new_src_port) = NonZeroU16::new(new_src_port) else {
1077 error!("cannot rewrite src port to unspecified; dropping {proto} packet");
1080 return Verdict::Stop(DropPacket);
1081 };
1082 transport.set_src_port(new_src_port);
1083
1084 Verdict::Proceed(Accept)
1085}
1086
1087fn rewrite_icmp_error_payload<I, E>(
1088 icmp_error: &mut E,
1089 nat: NatType,
1090 tuple: &Tuple<I>,
1091) -> Verdict<DropPacket>
1092where
1093 I: FilterIpExt,
1094 E: IcmpErrorMut<I>,
1095{
1096 let should_recalculate_checksum = match icmp_error.inner_packet() {
1168 Some(mut inner_packet) => {
1169 let verdict = match nat {
1170 NatType::Destination => rewrite_packet_for_src_nat(
1171 &mut inner_packet,
1172 tuple.src_addr,
1173 tuple.src_port_or_id,
1174 ),
1175 NatType::Source => rewrite_packet_for_dst_nat(
1176 &mut inner_packet,
1177 tuple.dst_addr,
1178 tuple.dst_port_or_id,
1179 ),
1180 };
1181
1182 match verdict {
1183 Verdict::Proceed(Accept) => true,
1184 Verdict::Stop(DropPacket) => return Verdict::Stop(DropPacket),
1185 }
1186 }
1187 None => false,
1188 };
1189
1190 if should_recalculate_checksum {
1191 if !icmp_error.recalculate_checksum() {
1197 return Verdict::Stop(DropPacket);
1198 }
1199 }
1200
1201 Verdict::Proceed(Accept)
1202}
1203
1204fn rewrite_packet<I, P, A, BT>(
1207 conn: &Connection<I, NatConfig<I, A>, BT>,
1208 direction: ConnectionDirection,
1209 nat: NatType,
1210 packet: &mut P,
1211) -> Verdict<DropPacket>
1212where
1213 I: FilterIpExt,
1214 P: IpPacket<I>,
1215 BT: FilterBindingsTypes,
1216{
1217 let tuple = match direction {
1225 ConnectionDirection::Original => conn.reply_tuple(),
1226 ConnectionDirection::Reply => conn.original_tuple(),
1227 };
1228
1229 if let Some(mut icmp_error) = packet.icmp_error_mut().icmp_error_mut() {
1230 match rewrite_icmp_error_payload(&mut icmp_error, nat, tuple) {
1231 Verdict::Proceed(Accept) => (),
1232 Verdict::Stop(DropPacket) => return Verdict::Stop(DropPacket),
1233 }
1234 }
1235
1236 match nat {
1237 NatType::Destination => {
1238 rewrite_packet_for_dst_nat(packet, tuple.src_addr, tuple.src_port_or_id)
1239 }
1240 NatType::Source => rewrite_packet_for_src_nat(packet, tuple.dst_addr, tuple.dst_port_or_id),
1241 }
1242}
1243
1244#[cfg(test)]
1245mod tests {
1246 use alloc::sync::Arc;
1247 use alloc::vec;
1248 use core::marker::PhantomData;
1249
1250 use assert_matches::assert_matches;
1251 use ip_test_macro::ip_test;
1252 use net_types::ip::{AddrSubnet, Ipv4};
1253 use netstack3_base::testutil::FakeMatcherDeviceId;
1254 use netstack3_base::{IntoCoreTimerCtx, TimerContext};
1255 use packet::{EmptyBuf, PacketBuilder, Serializer};
1256 use packet_formats::ip::{IpPacketBuilder, IpProto};
1257 use packet_formats::udp::UdpPacketBuilder;
1258 use test_case::{test_case, test_matrix};
1259
1260 use super::*;
1261 use crate::conntrack::Tuple;
1262 use crate::context::testutil::{
1263 FakeBindingsCtx, FakeNatCtx, FakePrimaryAddressId, FakeWeakAddressId,
1264 };
1265 use crate::matchers::PacketMatcher;
1266 use crate::packets::testutil::internal::{
1267 ArbitraryValue, FakeIpPacket, FakeUdpPacket, IcmpErrorMessage, Icmpv4DestUnreachableError,
1268 Icmpv6DestUnreachableError,
1269 };
1270 use crate::state::{Action, Routine, Rule};
1271 use crate::testutil::TestIpExt;
1272
1273 impl<I: IpExt, A: PartialEq, BT: FilterBindingsTypes> PartialEq
1274 for NatConfigurationResult<I, A, BT>
1275 {
1276 fn eq(&self, other: &Self) -> bool {
1277 match (self, other) {
1278 (Self::Result(lhs), Self::Result(rhs)) => lhs == rhs,
1279 (Self::AdoptExisting(_), Self::AdoptExisting(_)) => {
1280 panic!("equality check for connections is not supported")
1281 }
1282 _ => false,
1283 }
1284 }
1285 }
1286
1287 impl<I, A, BC> ConnectionExclusive<I, NatConfig<I, A>, BC>
1288 where
1289 BC: FilterBindingsTypes + TimerContext,
1290 I: FilterIpExt,
1291 {
1292 fn from_packet<P: IpPacket<I>>(bindings_ctx: &BC, packet: &P) -> Self {
1293 ConnectionExclusive::from_deconstructed_packet(
1294 bindings_ctx,
1295 &packet.conntrack_packet().unwrap(),
1296 )
1297 .expect("create conntrack entry")
1298 }
1299 }
1300
1301 impl<A, BC> ConnectionExclusive<Ipv4, NatConfig<Ipv4, A>, BC>
1302 where
1303 BC: FilterBindingsTypes + TimerContext,
1304 {
1305 fn with_reply_tuple(bindings_ctx: &BC, which: ReplyTuplePort, port: u16) -> Self {
1306 Self::from_packet(bindings_ctx, &packet_with_port(which, port).reply())
1307 }
1308 }
1309
1310 fn packet_with_port(which: ReplyTuplePort, port: u16) -> FakeIpPacket<Ipv4, FakeUdpPacket> {
1311 let mut packet = FakeIpPacket::<Ipv4, FakeUdpPacket>::arbitrary_value();
1312 match which {
1313 ReplyTuplePort::Source => packet.body.src_port = port,
1314 ReplyTuplePort::Destination => packet.body.dst_port = port,
1315 }
1316 packet
1317 }
1318
1319 fn tuple_with_port(which: ReplyTuplePort, port: u16) -> Tuple<Ipv4> {
1320 packet_with_port(which, port).conntrack_packet().unwrap().tuple()
1321 }
1322
1323 #[test]
1324 fn accept_by_default_if_no_matching_rules_in_hook() {
1325 let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
1326 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1327 let mut core_ctx = FakeNatCtx::default();
1328 let packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1329 let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
1330
1331 assert_eq!(
1332 configure_nat::<LocalEgressHook, _, _, _, _>(
1333 &mut core_ctx,
1334 &mut bindings_ctx,
1335 &conntrack,
1336 &mut conn,
1337 &Hook::default(),
1338 &packet,
1339 Interfaces { ingress: None, egress: None },
1340 ),
1341 Verdict::Proceed(NatConfigurationResult::Result(ShouldNat::No))
1342 );
1343 }
1344
1345 #[test]
1346 fn accept_by_default_if_return_from_routine() {
1347 let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
1348 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1349 let mut core_ctx = FakeNatCtx::default();
1350 let packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1351 let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
1352
1353 let hook = Hook {
1354 routines: vec![Routine {
1355 rules: vec![Rule::new(PacketMatcher::default(), Action::Return)],
1356 }],
1357 };
1358 assert_eq!(
1359 configure_nat::<LocalEgressHook, _, _, _, _>(
1360 &mut core_ctx,
1361 &mut bindings_ctx,
1362 &conntrack,
1363 &mut conn,
1364 &hook,
1365 &packet,
1366 Interfaces { ingress: None, egress: None },
1367 ),
1368 Verdict::Proceed(NatConfigurationResult::Result(ShouldNat::No))
1369 );
1370 }
1371
1372 #[test]
1373 fn accept_terminal_for_installed_routine() {
1374 let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
1375 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1376 let mut core_ctx = FakeNatCtx::default();
1377 let packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1378 let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
1379
1380 let routine = Routine {
1382 rules: vec![
1383 Rule::new(PacketMatcher::default(), Action::Accept),
1385 Rule::new(PacketMatcher::default(), Action::Drop),
1387 ],
1388 };
1389 assert_eq!(
1390 configure_nat::<LocalEgressHook, _, _, _, _>(
1391 &mut core_ctx,
1392 &mut bindings_ctx,
1393 &conntrack,
1394 &mut conn,
1395 &Hook { routines: vec![routine.clone()] },
1396 &packet,
1397 Interfaces { ingress: None, egress: None },
1398 ),
1399 Verdict::Proceed(NatConfigurationResult::Result(ShouldNat::No))
1400 );
1401
1402 let hook = Hook {
1405 routines: vec![
1406 routine,
1407 Routine {
1408 rules: vec![
1409 Rule::new(PacketMatcher::default(), Action::Drop),
1411 ],
1412 },
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::Stop(DropPacket)
1426 );
1427 }
1428
1429 #[test]
1430 fn drop_terminal_for_entire_hook() {
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 hook = Hook {
1438 routines: vec![
1439 Routine {
1440 rules: vec![
1441 Rule::new(PacketMatcher::default(), Action::Drop),
1443 ],
1444 },
1445 Routine {
1446 rules: vec![
1447 Rule::new(PacketMatcher::default(), Action::Accept),
1449 ],
1450 },
1451 ],
1452 };
1453
1454 assert_eq!(
1455 configure_nat::<LocalEgressHook, _, _, _, _>(
1456 &mut core_ctx,
1457 &mut bindings_ctx,
1458 &conntrack,
1459 &mut conn,
1460 &hook,
1461 &packet,
1462 Interfaces { ingress: None, egress: None },
1463 ),
1464 Verdict::Stop(DropPacket)
1465 );
1466 }
1467
1468 #[test]
1469 fn redirect_terminal_for_entire_hook() {
1470 let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
1471 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1472 let mut core_ctx = FakeNatCtx::default();
1473 let packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1474 let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
1475
1476 let hook = Hook {
1477 routines: vec![
1478 Routine {
1479 rules: vec![
1480 Rule::new(PacketMatcher::default(), Action::Redirect { dst_port: None }),
1482 ],
1483 },
1484 Routine {
1485 rules: vec![
1486 Rule::new(PacketMatcher::default(), Action::Drop),
1488 ],
1489 },
1490 ],
1491 };
1492
1493 assert_eq!(
1494 configure_nat::<LocalEgressHook, _, _, _, _>(
1495 &mut core_ctx,
1496 &mut bindings_ctx,
1497 &conntrack,
1498 &mut conn,
1499 &hook,
1500 &packet,
1501 Interfaces { ingress: None, egress: None },
1502 ),
1503 Verdict::Proceed(NatConfigurationResult::Result(ShouldNat::Yes(None)))
1504 );
1505 }
1506
1507 #[ip_test(I)]
1508 fn masquerade_terminal_for_entire_hook<I: TestIpExt>() {
1509 let mut bindings_ctx = FakeBindingsCtx::<I>::new();
1510 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1511 let assigned_addr = AddrSubnet::new(I::SRC_IP_2, I::SUBNET.prefix()).unwrap();
1512 let mut core_ctx =
1513 FakeNatCtx::new([(FakeMatcherDeviceId::ethernet_interface(), assigned_addr)]);
1514 let packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1515 let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
1516
1517 let hook = Hook {
1518 routines: vec![
1519 Routine {
1520 rules: vec![
1521 Rule::new(PacketMatcher::default(), Action::Masquerade { src_port: None }),
1523 ],
1524 },
1525 Routine {
1526 rules: vec![
1527 Rule::new(PacketMatcher::default(), Action::Drop),
1529 ],
1530 },
1531 ],
1532 };
1533
1534 assert_matches!(
1535 configure_nat::<EgressHook, _, _, _, _>(
1536 &mut core_ctx,
1537 &mut bindings_ctx,
1538 &conntrack,
1539 &mut conn,
1540 &hook,
1541 &packet,
1542 Interfaces {
1543 ingress: None,
1544 egress: Some(&FakeMatcherDeviceId::ethernet_interface())
1545 },
1546 ),
1547 Verdict::Proceed(NatConfigurationResult::Result(ShouldNat::Yes(Some(_))))
1548 );
1549 }
1550
1551 #[test]
1552 fn redirect_ingress_drops_packet_if_no_assigned_address() {
1553 let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
1554 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1555 let mut core_ctx = FakeNatCtx::default();
1556 let packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1557 let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
1558
1559 let hook = Hook {
1560 routines: vec![Routine {
1561 rules: vec![Rule::new(
1562 PacketMatcher::default(),
1563 Action::Redirect { dst_port: None },
1564 )],
1565 }],
1566 };
1567
1568 assert_eq!(
1569 configure_nat::<IngressHook, _, _, _, _>(
1570 &mut core_ctx,
1571 &mut bindings_ctx,
1572 &conntrack,
1573 &mut conn,
1574 &hook,
1575 &packet,
1576 Interfaces {
1577 ingress: Some(&FakeMatcherDeviceId::ethernet_interface()),
1578 egress: None
1579 },
1580 ),
1581 Verdict::Stop(DropPacket)
1582 );
1583 }
1584
1585 #[test]
1586 fn masquerade_egress_drops_packet_if_no_assigned_address() {
1587 let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
1588 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1589 let mut core_ctx = FakeNatCtx::default();
1590 let packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1591 let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
1592
1593 let hook = Hook {
1594 routines: vec![Routine {
1595 rules: vec![Rule::new(
1596 PacketMatcher::default(),
1597 Action::Masquerade { src_port: None },
1598 )],
1599 }],
1600 };
1601
1602 assert_eq!(
1603 configure_nat::<EgressHook, _, _, _, _>(
1604 &mut core_ctx,
1605 &mut bindings_ctx,
1606 &conntrack,
1607 &mut conn,
1608 &hook,
1609 &packet,
1610 Interfaces {
1611 ingress: None,
1612 egress: Some(&FakeMatcherDeviceId::ethernet_interface())
1613 },
1614 ),
1615 Verdict::Stop(DropPacket)
1616 );
1617 }
1618
1619 trait NatHookExt<I: FilterIpExt>: NatHook<I> {
1620 fn interfaces<'a>(
1621 interface: &'a FakeMatcherDeviceId,
1622 ) -> Interfaces<'a, FakeMatcherDeviceId>;
1623 }
1624
1625 impl<I: FilterIpExt> NatHookExt<I> for IngressHook {
1626 fn interfaces<'a>(
1627 interface: &'a FakeMatcherDeviceId,
1628 ) -> Interfaces<'a, FakeMatcherDeviceId> {
1629 Interfaces { ingress: Some(interface), egress: None }
1630 }
1631 }
1632
1633 impl<I: FilterIpExt> NatHookExt<I> for LocalEgressHook {
1634 fn interfaces<'a>(
1635 interface: &'a FakeMatcherDeviceId,
1636 ) -> Interfaces<'a, FakeMatcherDeviceId> {
1637 Interfaces { ingress: None, egress: Some(interface) }
1638 }
1639 }
1640
1641 impl<I: FilterIpExt> NatHookExt<I> for EgressHook {
1642 fn interfaces<'a>(
1643 interface: &'a FakeMatcherDeviceId,
1644 ) -> Interfaces<'a, FakeMatcherDeviceId> {
1645 Interfaces { ingress: None, egress: Some(interface) }
1646 }
1647 }
1648
1649 const NAT_ENABLED_FOR_TESTS: bool = true;
1650
1651 #[ip_test(I)]
1652 fn nat_disabled_for_self_connected_flows<I: TestIpExt>() {
1653 let mut bindings_ctx = FakeBindingsCtx::<I>::new();
1654 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1655 let mut core_ctx = FakeNatCtx::default();
1656
1657 let mut packet = FakeIpPacket::<I, _> {
1658 src_ip: I::SRC_IP,
1659 dst_ip: I::SRC_IP,
1660 body: FakeUdpPacket { src_port: 22222, dst_port: 22222 },
1661 };
1662 let (mut conn, direction) = conntrack
1663 .get_connection_for_packet_and_update(
1664 &bindings_ctx,
1665 packet.conntrack_packet().expect("packet should be valid"),
1666 )
1667 .expect("packet should be valid")
1668 .expect("packet should be trackable");
1669
1670 let verdict = perform_nat::<LocalEgressHook, _, _, _, _>(
1674 &mut core_ctx,
1675 &mut bindings_ctx,
1676 NAT_ENABLED_FOR_TESTS,
1677 &conntrack,
1678 &mut conn,
1679 direction,
1680 &Hook {
1681 routines: vec![Routine {
1682 rules: vec![Rule::new(
1683 PacketMatcher::default(),
1684 Action::Redirect { dst_port: None },
1685 )],
1686 }],
1687 },
1688 &mut packet,
1689 <LocalEgressHook as NatHookExt<I>>::interfaces(
1690 &FakeMatcherDeviceId::ethernet_interface(),
1691 ),
1692 );
1693 assert_eq!(verdict, Verdict::Proceed(Accept));
1694
1695 let verdict = perform_nat::<EgressHook, _, _, _, _>(
1696 &mut core_ctx,
1697 &mut bindings_ctx,
1698 NAT_ENABLED_FOR_TESTS,
1699 &conntrack,
1700 &mut conn,
1701 direction,
1702 &Hook {
1703 routines: vec![Routine {
1704 rules: vec![Rule::new(
1705 PacketMatcher::default(),
1706 Action::Masquerade { src_port: None },
1707 )],
1708 }],
1709 },
1710 &mut packet,
1711 <EgressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
1712 );
1713 assert_eq!(verdict, Verdict::Proceed(Accept));
1714
1715 assert_eq!(conn.external_data().destination.get(), Some(&ShouldNat::No));
1716 assert_eq!(conn.external_data().source.get(), Some(&ShouldNat::No));
1717 }
1718
1719 #[ip_test(I)]
1720 fn nat_disabled_if_not_configured_before_connection_finalized<I: TestIpExt>() {
1721 let mut bindings_ctx = FakeBindingsCtx::<I>::new();
1722 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1723 let mut core_ctx = FakeNatCtx::default();
1724
1725 let mut packet = FakeIpPacket::<I, FakeUdpPacket>::arbitrary_value();
1726 let (mut conn, direction) = conntrack
1727 .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
1728 .expect("packet should be valid")
1729 .expect("packet should be trackable");
1730
1731 let verdict = perform_nat::<LocalEgressHook, _, _, _, _>(
1733 &mut core_ctx,
1734 &mut bindings_ctx,
1735 false, &conntrack,
1737 &mut conn,
1738 direction,
1739 &Hook::default(),
1740 &mut packet,
1741 <LocalEgressHook as NatHookExt<I>>::interfaces(
1742 &FakeMatcherDeviceId::ethernet_interface(),
1743 ),
1744 );
1745 assert_eq!(verdict, Verdict::Proceed(Accept));
1746 assert_eq!(conn.external_data().destination.get(), None);
1747 assert_eq!(conn.external_data().source.get(), None);
1748
1749 let verdict = perform_nat::<EgressHook, _, _, _, _>(
1751 &mut core_ctx,
1752 &mut bindings_ctx,
1753 false, &conntrack,
1755 &mut conn,
1756 direction,
1757 &Hook::default(),
1758 &mut packet,
1759 <EgressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
1760 );
1761 assert_eq!(verdict, Verdict::Proceed(Accept));
1762 assert_eq!(conn.external_data().destination.get(), None);
1763 assert_eq!(conn.external_data().source.get(), None);
1764
1765 let (inserted, _weak) = conntrack
1766 .finalize_connection(&mut bindings_ctx, conn)
1767 .expect("connection should not conflict");
1768 assert!(inserted);
1769
1770 let mut reply = packet.reply();
1773 let (mut conn, direction) = conntrack
1774 .get_connection_for_packet_and_update(&bindings_ctx, reply.conntrack_packet().unwrap())
1775 .expect("packet should be valid")
1776 .expect("packet should be trackable");
1777 let verdict = perform_nat::<IngressHook, _, _, _, _>(
1778 &mut core_ctx,
1779 &mut bindings_ctx,
1780 NAT_ENABLED_FOR_TESTS,
1781 &conntrack,
1782 &mut conn,
1783 direction,
1784 &Hook::default(),
1785 &mut reply,
1786 <IngressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
1787 );
1788 assert_eq!(verdict, Verdict::Proceed(Accept));
1789 assert_eq!(conn.external_data().destination.get(), None);
1790 assert_eq!(conn.external_data().source.get(), Some(&ShouldNat::No));
1791
1792 let verdict = perform_nat::<LocalIngressHook, _, _, _, _>(
1794 &mut core_ctx,
1795 &mut bindings_ctx,
1796 NAT_ENABLED_FOR_TESTS,
1797 &conntrack,
1798 &mut conn,
1799 direction,
1800 &Hook::default(),
1801 &mut reply,
1802 <IngressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
1803 );
1804 assert_eq!(verdict, Verdict::Proceed(Accept));
1805 assert_eq!(conn.external_data().destination.get(), Some(&ShouldNat::No));
1806 assert_eq!(conn.external_data().source.get(), Some(&ShouldNat::No));
1807 }
1808
1809 const LOCAL_PORT: NonZeroU16 = NonZeroU16::new(55555).unwrap();
1810
1811 #[ip_test(I)]
1812 #[test_case(
1813 PhantomData::<IngressHook>, PhantomData::<EgressHook>, None;
1814 "redirect INGRESS"
1815 )]
1816 #[test_case(
1817 PhantomData::<IngressHook>, PhantomData::<EgressHook>, Some(LOCAL_PORT);
1818 "redirect INGRESS to local port"
1819 )]
1820 #[test_case(
1821 PhantomData::<LocalEgressHook>, PhantomData::<EgressHook>, None;
1822 "redirect LOCAL_EGRESS"
1823 )]
1824 #[test_case(
1825 PhantomData::<LocalEgressHook>, PhantomData::<EgressHook>, Some(LOCAL_PORT);
1826 "redirect LOCAL_EGRESS to local port"
1827 )]
1828 fn redirect<I: TestIpExt, Original: NatHookExt<I>, Reply: NatHookExt<I>>(
1829 _original_nat_hook: PhantomData<Original>,
1830 _reply_nat_hook: PhantomData<Reply>,
1831 dst_port: Option<NonZeroU16>,
1832 ) {
1833 let mut bindings_ctx = FakeBindingsCtx::<I>::new();
1834 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1835 let mut core_ctx = FakeNatCtx::new([(
1836 FakeMatcherDeviceId::ethernet_interface(),
1837 AddrSubnet::new(I::DST_IP_2, I::SUBNET.prefix()).unwrap(),
1838 )]);
1839
1840 let mut packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1842 let pre_nat_packet = packet.clone();
1843 let (mut conn, direction) = conntrack
1844 .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
1845 .expect("packet should be valid")
1846 .expect("packet should be trackable");
1847 let original = conn.original_tuple().clone();
1848
1849 let nat_routines = Hook {
1853 routines: vec![Routine {
1854 rules: vec![Rule::new(
1855 PacketMatcher::default(),
1856 Action::Redirect { dst_port: dst_port.map(|port| port..=port) },
1857 )],
1858 }],
1859 };
1860 let verdict = perform_nat::<Original, _, _, _, _>(
1861 &mut core_ctx,
1862 &mut bindings_ctx,
1863 NAT_ENABLED_FOR_TESTS,
1864 &conntrack,
1865 &mut conn,
1866 direction,
1867 &nat_routines,
1868 &mut packet,
1869 Original::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
1870 );
1871 assert_eq!(verdict, Verdict::Proceed(Accept));
1872
1873 let (redirect_addr, cached_addr) = Original::redirect_addr(
1877 &mut core_ctx,
1878 &packet,
1879 Original::interfaces(&FakeMatcherDeviceId::ethernet_interface()).ingress,
1880 )
1881 .expect("get redirect addr for NAT hook");
1882 let expected = FakeIpPacket::<_, FakeUdpPacket> {
1883 src_ip: packet.src_ip,
1884 dst_ip: redirect_addr,
1885 body: FakeUdpPacket {
1886 src_port: packet.body.src_port,
1887 dst_port: dst_port.map(NonZeroU16::get).unwrap_or(packet.body.dst_port),
1888 },
1889 };
1890 assert_eq!(packet, expected);
1891 assert_eq!(
1892 conn.external_data().destination.get().expect("DNAT should be configured"),
1893 &ShouldNat::Yes(cached_addr)
1894 );
1895 assert_eq!(conn.external_data().source.get(), None, "SNAT should not be configured");
1896 assert_eq!(conn.original_tuple(), &original);
1897 let mut reply = Tuple { src_addr: redirect_addr, ..original.invert() };
1898 if let Some(port) = dst_port {
1899 reply.src_port_or_id = port.get();
1900 }
1901 assert_eq!(conn.reply_tuple(), &reply);
1902
1903 let mut reply_packet = packet.reply();
1907 let nat_routines = Hook {
1911 routines: vec![Routine {
1912 rules: vec![Rule::new(PacketMatcher::default(), Action::Drop)],
1913 }],
1914 };
1915 let verdict = perform_nat::<Reply, _, _, _, _>(
1916 &mut core_ctx,
1917 &mut bindings_ctx,
1918 NAT_ENABLED_FOR_TESTS,
1919 &conntrack,
1920 &mut conn,
1921 ConnectionDirection::Reply,
1922 &nat_routines,
1923 &mut reply_packet,
1924 Reply::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
1925 );
1926 assert_eq!(verdict, Verdict::Proceed(Accept));
1927 assert_eq!(reply_packet, pre_nat_packet.reply());
1928 }
1929
1930 #[ip_test(I)]
1931 #[test_case(None; "masquerade")]
1932 #[test_case(Some(LOCAL_PORT); "masquerade to specified port")]
1933 fn masquerade<I: TestIpExt>(src_port: Option<NonZeroU16>) {
1934 let mut bindings_ctx = FakeBindingsCtx::<I>::new();
1935 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1936 let assigned_addr = AddrSubnet::new(I::SRC_IP_2, I::SUBNET.prefix()).unwrap();
1937 let mut core_ctx =
1938 FakeNatCtx::new([(FakeMatcherDeviceId::ethernet_interface(), assigned_addr)]);
1939
1940 let mut packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1942 let pre_nat_packet = packet.clone();
1943 let (mut conn, direction) = conntrack
1944 .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
1945 .expect("packet should be valid")
1946 .expect("packet should be trackable");
1947 let original = conn.original_tuple().clone();
1948
1949 let nat_routines = Hook {
1951 routines: vec![Routine {
1952 rules: vec![Rule::new(
1953 PacketMatcher::default(),
1954 Action::Masquerade { src_port: src_port.map(|port| port..=port) },
1955 )],
1956 }],
1957 };
1958 let verdict = perform_nat::<EgressHook, _, _, _, _>(
1959 &mut core_ctx,
1960 &mut bindings_ctx,
1961 NAT_ENABLED_FOR_TESTS,
1962 &conntrack,
1963 &mut conn,
1964 direction,
1965 &nat_routines,
1966 &mut packet,
1967 Interfaces { ingress: None, egress: Some(&FakeMatcherDeviceId::ethernet_interface()) },
1968 );
1969 assert_eq!(verdict, Verdict::Proceed(Accept));
1970
1971 let expected = FakeIpPacket::<_, FakeUdpPacket> {
1975 src_ip: I::SRC_IP_2,
1976 dst_ip: packet.dst_ip,
1977 body: FakeUdpPacket {
1978 src_port: src_port.map(NonZeroU16::get).unwrap_or(packet.body.src_port),
1979 dst_port: packet.body.dst_port,
1980 },
1981 };
1982 assert_eq!(packet, expected);
1983 assert_matches!(
1984 conn.external_data().source.get().expect("SNAT should be configured"),
1985 &ShouldNat::Yes(Some(_))
1986 );
1987 assert_eq!(conn.external_data().destination.get(), None, "DNAT should not be configured");
1988 assert_eq!(conn.original_tuple(), &original);
1989 let mut reply = Tuple { dst_addr: I::SRC_IP_2, ..original.invert() };
1990 if let Some(port) = src_port {
1991 reply.dst_port_or_id = port.get();
1992 }
1993 assert_eq!(conn.reply_tuple(), &reply);
1994
1995 let mut reply_packet = packet.reply();
1999 let nat_routines = Hook {
2003 routines: vec![Routine {
2004 rules: vec![Rule::new(PacketMatcher::default(), Action::Drop)],
2005 }],
2006 };
2007 let verdict = perform_nat::<IngressHook, _, _, _, _>(
2008 &mut core_ctx,
2009 &mut bindings_ctx,
2010 NAT_ENABLED_FOR_TESTS,
2011 &conntrack,
2012 &mut conn,
2013 ConnectionDirection::Reply,
2014 &nat_routines,
2015 &mut reply_packet,
2016 Interfaces { ingress: Some(&FakeMatcherDeviceId::ethernet_interface()), egress: None },
2017 );
2018 assert_eq!(verdict, Verdict::Proceed(Accept));
2019 assert_eq!(reply_packet, pre_nat_packet.reply());
2020 }
2021
2022 #[ip_test(I)]
2023 #[test_case(22, 1..=511)]
2024 #[test_case(853, 1..=1023)]
2025 #[test_case(11111, 1024..=u16::MAX)]
2026 fn masquerade_reply_tuple_dst_port_rewritten_even_if_target_range_unspecified<I: TestIpExt>(
2027 src_port: u16,
2028 expected_range: RangeInclusive<u16>,
2029 ) {
2030 let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2031 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2032 let assigned_addr = AddrSubnet::new(I::SRC_IP_2, I::SUBNET.prefix()).unwrap();
2033 let mut core_ctx =
2034 FakeNatCtx::new([(FakeMatcherDeviceId::ethernet_interface(), assigned_addr)]);
2035 let packet = FakeIpPacket {
2036 body: FakeUdpPacket { src_port, ..ArbitraryValue::arbitrary_value() },
2037 ..ArbitraryValue::arbitrary_value()
2038 };
2039
2040 let reply = FakeIpPacket { src_ip: I::SRC_IP_2, ..packet.clone() };
2043 let (conn, _dir) = conntrack
2044 .get_connection_for_packet_and_update(&bindings_ctx, reply.conntrack_packet().unwrap())
2045 .expect("packet should be valid")
2046 .expect("packet should be trackable");
2047 assert_matches!(
2048 conntrack
2049 .finalize_connection(&mut bindings_ctx, conn)
2050 .expect("connection should not conflict"),
2051 (true, Some(_))
2052 );
2053
2054 let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
2058 let verdict = configure_masquerade_nat(
2059 &mut core_ctx,
2060 &mut bindings_ctx,
2061 &conntrack,
2062 &mut conn,
2063 &packet,
2064 &Interfaces { ingress: None, egress: Some(&FakeMatcherDeviceId::ethernet_interface()) },
2065 None,
2066 );
2067
2068 assert_matches!(
2074 verdict,
2075 Verdict::Proceed(NatConfigurationResult::Result(ShouldNat::Yes(Some(_))))
2076 );
2077 let reply_tuple = conn.reply_tuple();
2078 assert_eq!(reply_tuple.dst_addr, I::SRC_IP_2);
2079 assert_ne!(reply_tuple.dst_port_or_id, src_port);
2080 assert!(expected_range.contains(&reply_tuple.dst_port_or_id));
2081 }
2082
2083 #[ip_test(I)]
2084 #[test_case(
2085 PhantomData::<IngressHook>, Action::Redirect { dst_port: None };
2086 "redirect in INGRESS"
2087 )]
2088 #[test_case(
2089 PhantomData::<EgressHook>, Action::Masquerade { src_port: None };
2090 "masquerade in EGRESS"
2091 )]
2092 fn assigned_addr_cached_and_validated<I: TestIpExt, N: NatHookExt<I>>(
2093 _nat_hook: PhantomData<N>,
2094 action: Action<I, FakeBindingsCtx<I>, ()>,
2095 ) {
2096 let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2097 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2098 let assigned_addr = AddrSubnet::new(I::SRC_IP_2, I::SUBNET.prefix()).unwrap();
2099 let mut core_ctx =
2100 FakeNatCtx::new([(FakeMatcherDeviceId::ethernet_interface(), assigned_addr)]);
2101
2102 let mut packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
2104 let (mut conn, direction) = conntrack
2105 .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2106 .expect("packet should be valid")
2107 .expect("packet should be trackable");
2108
2109 let nat_routines = Hook {
2111 routines: vec![Routine { rules: vec![Rule::new(PacketMatcher::default(), action)] }],
2112 };
2113 let verdict = perform_nat::<N, _, _, _, _>(
2114 &mut core_ctx,
2115 &mut bindings_ctx,
2116 NAT_ENABLED_FOR_TESTS,
2117 &conntrack,
2118 &mut conn,
2119 direction,
2120 &nat_routines,
2121 &mut packet,
2122 N::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
2123 );
2124 assert_eq!(verdict, Verdict::Proceed(Accept));
2125
2126 let (nat, _nat_type) = conn.relevant_config(N::NAT_TYPE, ConnectionDirection::Original);
2129 let nat = nat.get().unwrap_or_else(|| panic!("{:?} NAT should be configured", N::NAT_TYPE));
2130 let id = assert_matches!(nat, ShouldNat::Yes(Some(CachedAddr { id, _marker })) => id);
2131 let id = id
2132 .lock()
2133 .as_ref()
2134 .expect("address ID should be cached in NAT config")
2135 .upgrade()
2136 .expect("address ID should be valid");
2137 assert_eq!(*id, assigned_addr);
2138 drop(id);
2139
2140 core_ctx.device_addrs.clear();
2144 let verdict = perform_nat::<N, _, _, _, _>(
2145 &mut core_ctx,
2146 &mut bindings_ctx,
2147 NAT_ENABLED_FOR_TESTS,
2148 &conntrack,
2149 &mut conn,
2150 ConnectionDirection::Original,
2151 &nat_routines,
2152 &mut FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value(),
2153 N::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
2154 );
2155 assert_eq!(verdict, Verdict::Stop(DropPacket));
2156 let (nat, _nat_type) = conn.relevant_config(N::NAT_TYPE, ConnectionDirection::Original);
2157 let nat = nat.get().unwrap_or_else(|| panic!("{:?} NAT should be configured", N::NAT_TYPE));
2158 let id = assert_matches!(nat, ShouldNat::Yes(Some(CachedAddr { id, _marker })) => id);
2159 assert_eq!(*id.lock(), None, "cached weak address ID should be cleared");
2160
2161 assert_matches!(
2164 core_ctx.device_addrs.insert(
2165 FakeMatcherDeviceId::ethernet_interface(),
2166 FakePrimaryAddressId(Arc::new(assigned_addr))
2167 ),
2168 None
2169 );
2170 let verdict = perform_nat::<N, _, _, _, _>(
2171 &mut core_ctx,
2172 &mut bindings_ctx,
2173 NAT_ENABLED_FOR_TESTS,
2174 &conntrack,
2175 &mut conn,
2176 ConnectionDirection::Original,
2177 &nat_routines,
2178 &mut FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value(),
2179 N::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
2180 );
2181 assert_eq!(verdict, Verdict::Proceed(Accept));
2182 let (nat, _nat_type) = conn.relevant_config(N::NAT_TYPE, ConnectionDirection::Original);
2183 let nat = nat.get().unwrap_or_else(|| panic!("{:?} NAT should be configured", N::NAT_TYPE));
2184 let id = assert_matches!(nat, ShouldNat::Yes(Some(CachedAddr { id, _marker })) => id);
2185 let id = id
2186 .lock()
2187 .as_ref()
2188 .expect("address ID should be cached in NAT config")
2189 .upgrade()
2190 .expect("address Id should be valid");
2191 assert_eq!(*id, assigned_addr);
2192 }
2193
2194 #[test_case(ReplyTuplePort::Source)]
2195 #[test_case(ReplyTuplePort::Destination)]
2196 fn rewrite_port_noop_if_in_range(which: ReplyTuplePort) {
2197 let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
2198 let table = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2199 let mut conn =
2200 ConnectionExclusive::with_reply_tuple(&bindings_ctx, which, LOCAL_PORT.get());
2201
2202 let pre_nat = conn.reply_tuple().clone();
2205 let result = rewrite_reply_tuple_port(
2206 &mut bindings_ctx,
2207 &table,
2208 &mut conn,
2209 which,
2210 LOCAL_PORT..=LOCAL_PORT,
2211 true, ConflictStrategy::RewritePort,
2213 );
2214 assert_eq!(
2215 result,
2216 Verdict::Proceed(NatConfigurationResult::Result(
2217 ShouldNat::<_, FakeWeakAddressId<Ipv4>>::No
2218 ))
2219 );
2220 assert_eq!(conn.reply_tuple(), &pre_nat);
2221 }
2222
2223 #[test_case(ReplyTuplePort::Source)]
2224 #[test_case(ReplyTuplePort::Destination)]
2225 fn rewrite_port_noop_if_no_conflict(which: ReplyTuplePort) {
2226 let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
2227 let table = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2228 let mut conn =
2229 ConnectionExclusive::with_reply_tuple(&bindings_ctx, which, LOCAL_PORT.get());
2230
2231 let pre_nat = conn.reply_tuple().clone();
2235 const NEW_PORT: NonZeroU16 = LOCAL_PORT.checked_add(1).unwrap();
2236 let result = rewrite_reply_tuple_port(
2237 &mut bindings_ctx,
2238 &table,
2239 &mut conn,
2240 which,
2241 NEW_PORT..=NEW_PORT,
2242 false, ConflictStrategy::RewritePort,
2244 );
2245 assert_eq!(
2246 result,
2247 Verdict::Proceed(NatConfigurationResult::Result(
2248 ShouldNat::<_, FakeWeakAddressId<Ipv4>>::No
2249 ))
2250 );
2251 assert_eq!(conn.reply_tuple(), &pre_nat);
2252 }
2253
2254 #[test_case(ReplyTuplePort::Source)]
2255 #[test_case(ReplyTuplePort::Destination)]
2256 fn rewrite_port_succeeds_if_available_port_in_range(which: ReplyTuplePort) {
2257 let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
2258 let table = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2259 let mut conn =
2260 ConnectionExclusive::with_reply_tuple(&bindings_ctx, which, LOCAL_PORT.get());
2261
2262 const NEW_PORT: NonZeroU16 = LOCAL_PORT.checked_add(1).unwrap();
2265 let result = rewrite_reply_tuple_port(
2266 &mut bindings_ctx,
2267 &table,
2268 &mut conn,
2269 which,
2270 NEW_PORT..=NEW_PORT,
2271 true, ConflictStrategy::RewritePort,
2273 );
2274 assert_eq!(
2275 result,
2276 Verdict::Proceed(NatConfigurationResult::Result(
2277 ShouldNat::<_, FakeWeakAddressId<Ipv4>>::Yes(None)
2278 ))
2279 );
2280 assert_eq!(conn.reply_tuple(), &tuple_with_port(which, NEW_PORT.get()));
2281 }
2282
2283 #[test_case(ReplyTuplePort::Source)]
2284 #[test_case(ReplyTuplePort::Destination)]
2285 fn rewrite_port_fails_if_no_available_port_in_range(which: ReplyTuplePort) {
2286 let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
2287 let table: Table<_, NatConfig<_, FakeWeakAddressId<Ipv4>>, _> =
2288 Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2289
2290 let packet = packet_with_port(which, LOCAL_PORT.get());
2294 let (conn, _dir) = table
2295 .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2296 .expect("packet should be valid")
2297 .expect("packet should be trackable");
2298 assert_matches!(
2299 table
2300 .finalize_connection(&mut bindings_ctx, conn)
2301 .expect("connection should not conflict"),
2302 (true, Some(_))
2303 );
2304
2305 let mut conn =
2306 ConnectionExclusive::with_reply_tuple(&bindings_ctx, which, LOCAL_PORT.get());
2307 let result = rewrite_reply_tuple_port(
2308 &mut bindings_ctx,
2309 &table,
2310 &mut conn,
2311 which,
2312 LOCAL_PORT..=LOCAL_PORT,
2313 true, ConflictStrategy::RewritePort,
2315 );
2316 assert_eq!(result, Verdict::Stop(DropPacket));
2317 }
2318
2319 #[test_case(ReplyTuplePort::Source)]
2320 #[test_case(ReplyTuplePort::Destination)]
2321 fn port_rewritten_to_ensure_unique_tuple_even_if_in_range(which: ReplyTuplePort) {
2322 let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
2323 let table = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2324
2325 const MAX_PORT: NonZeroU16 = NonZeroU16::new(LOCAL_PORT.get() + 100).unwrap();
2329 for port in LOCAL_PORT.get()..=MAX_PORT.get() {
2330 let packet = packet_with_port(which, port);
2331 let (conn, _dir) = table
2332 .get_connection_for_packet_and_update(
2333 &bindings_ctx,
2334 packet.conntrack_packet().unwrap(),
2335 )
2336 .expect("packet should be valid")
2337 .expect("packet should be trackable");
2338 assert_matches!(
2339 table
2340 .finalize_connection(&mut bindings_ctx, conn)
2341 .expect("connection should not conflict"),
2342 (true, Some(_))
2343 );
2344 }
2345
2346 let mut conn =
2350 ConnectionExclusive::with_reply_tuple(&bindings_ctx, which, LOCAL_PORT.get());
2351 const MIN_PORT: NonZeroU16 = NonZeroU16::new(LOCAL_PORT.get() - 1).unwrap();
2352 let result = rewrite_reply_tuple_port(
2353 &mut bindings_ctx,
2354 &table,
2355 &mut conn,
2356 which,
2357 MIN_PORT..=MAX_PORT,
2358 true, ConflictStrategy::RewritePort,
2360 );
2361 assert_eq!(
2362 result,
2363 Verdict::Proceed(NatConfigurationResult::Result(
2364 ShouldNat::<_, FakeWeakAddressId<Ipv4>>::Yes(None)
2365 ))
2366 );
2367 assert_eq!(conn.reply_tuple(), &tuple_with_port(which, MIN_PORT.get()));
2368 }
2369
2370 #[test_case(ReplyTuplePort::Source)]
2371 #[test_case(ReplyTuplePort::Destination)]
2372 fn rewrite_port_skipped_if_existing_connection_can_be_adopted(which: ReplyTuplePort) {
2373 let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
2374 let table: Table<_, NatConfig<_, FakeWeakAddressId<Ipv4>>, _> =
2375 Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2376
2377 let packet = packet_with_port(which, LOCAL_PORT.get());
2382 let (conn, _dir) = table
2383 .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2384 .expect("packet should be valid")
2385 .expect("packet should be trackable");
2386 let existing = assert_matches!(
2387 table
2388 .finalize_connection(&mut bindings_ctx, conn)
2389 .expect("connection should not conflict"),
2390 (true, Some(conn)) => conn
2391 );
2392
2393 let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
2394 let result = rewrite_reply_tuple_port(
2395 &mut bindings_ctx,
2396 &table,
2397 &mut conn,
2398 which,
2399 NonZeroU16::MIN..=NonZeroU16::MAX,
2400 false, ConflictStrategy::AdoptExisting,
2402 );
2403 let conn = assert_matches!(
2404 result,
2405 Verdict::Proceed(NatConfigurationResult::AdoptExisting(Connection::Shared(conn))) => conn
2406 );
2407 assert!(Arc::ptr_eq(&existing, &conn));
2408 }
2409
2410 trait IcmpErrorTestIpExt: TestIpExt {
2411 const NETSTACK: Self::Addr = Self::DST_IP;
2412 const ULTIMATE_SRC: Self::Addr = Self::SRC_IP;
2413 const ULTIMATE_DST: Self::Addr = Self::DST_IP_3;
2414 const ROUTER_SRC: Self::Addr = Self::SRC_IP_2;
2415 const ROUTER_DST: Self::Addr = Self::DST_IP_2;
2416 }
2417
2418 impl<I> IcmpErrorTestIpExt for I where I: TestIpExt {}
2419
2420 enum IcmpErrorSource {
2421 IntermediateRouter,
2422 EndHost,
2423 }
2424
2425 #[test_case(Icmpv4DestUnreachableError)]
2456 #[test_case(Icmpv6DestUnreachableError)]
2457 fn redirect_icmp_error_in_reply_direction<I: IcmpErrorTestIpExt, IE: IcmpErrorMessage<I>>(
2458 _icmp_error: IE,
2459 ) {
2460 let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2461 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2462 let mut core_ctx = FakeNatCtx::new([(
2463 FakeMatcherDeviceId::ethernet_interface(),
2464 AddrSubnet::new(I::NETSTACK, I::SUBNET.prefix()).unwrap(),
2465 )]);
2466
2467 let mut packet = EmptyBuf
2469 .wrap_in(UdpPacketBuilder::new(
2470 I::ULTIMATE_SRC,
2471 I::ULTIMATE_DST,
2472 Some(NonZeroU16::new(11111).unwrap()),
2473 NonZeroU16::new(22222).unwrap(),
2474 ))
2475 .wrap_in(I::PacketBuilder::new(
2476 I::ULTIMATE_SRC,
2477 I::ULTIMATE_DST,
2478 u8::MAX,
2479 IpProto::Udp.into(),
2480 ));
2481 let packet_pre_nat = packet.clone();
2482 let (mut conn, _) = conntrack
2483 .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2484 .expect("packet should be valid")
2485 .expect("packet should be trackable");
2486 let original_tuple = conn.original_tuple().clone();
2487
2488 let nat_routines = Hook {
2489 routines: vec![Routine {
2490 rules: vec![Rule::new(
2491 PacketMatcher::default(),
2492 Action::Redirect { dst_port: Some(LOCAL_PORT..=LOCAL_PORT) },
2493 )],
2494 }],
2495 };
2496 let verdict = perform_nat::<IngressHook, _, _, _, _>(
2497 &mut core_ctx,
2498 &mut bindings_ctx,
2499 NAT_ENABLED_FOR_TESTS,
2500 &conntrack,
2501 &mut conn,
2502 ConnectionDirection::Original,
2503 &nat_routines,
2504 &mut packet,
2505 <IngressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
2506 );
2507 assert_eq!(verdict, Verdict::Proceed(Accept));
2508
2509 let (redirect_addr, cached_addr) = IngressHook::redirect_addr(
2513 &mut core_ctx,
2514 &packet,
2515 <IngressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface())
2516 .ingress,
2517 )
2518 .expect("get redirect addr for NAT hook");
2519
2520 assert_eq!(redirect_addr, I::NETSTACK);
2523
2524 let expected = EmptyBuf
2526 .wrap_in(UdpPacketBuilder::new(
2527 I::ULTIMATE_SRC,
2528 redirect_addr,
2529 packet.inner().outer().src_port(),
2530 LOCAL_PORT,
2531 ))
2532 .wrap_in(I::PacketBuilder::new(
2533 I::ULTIMATE_SRC,
2534 redirect_addr,
2535 u8::MAX,
2536 IpProto::Udp.into(),
2537 ));
2538 assert_eq!(packet, expected);
2539 assert_eq!(
2540 conn.external_data().destination.get().expect("DNAT should be configured"),
2541 &ShouldNat::Yes(cached_addr)
2542 );
2543 assert_eq!(conn.external_data().source.get(), None, "SNAT should not be configured");
2544 assert_eq!(conn.original_tuple(), &original_tuple);
2545
2546 let reply_tuple = Tuple {
2547 src_addr: redirect_addr,
2548 src_port_or_id: LOCAL_PORT.get(),
2549 ..original_tuple.invert()
2550 };
2551 assert_eq!(conn.reply_tuple(), &reply_tuple);
2552
2553 let mut error_packet = IE::make_serializer(
2554 redirect_addr,
2555 I::ULTIMATE_SRC,
2556 packet.clone().serialize_vec_outer().unwrap().unwrap_b().into_inner(),
2557 )
2558 .wrap_in(I::PacketBuilder::new(
2559 redirect_addr,
2560 I::ULTIMATE_SRC,
2561 u8::MAX,
2562 IE::proto(),
2563 ));
2564
2565 let verdict = perform_nat::<EgressHook, _, _, _, _>(
2566 &mut core_ctx,
2567 &mut bindings_ctx,
2568 NAT_ENABLED_FOR_TESTS,
2569 &conntrack,
2570 &mut conn,
2571 ConnectionDirection::Reply,
2572 &nat_routines,
2573 &mut error_packet,
2574 <EgressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
2575 );
2576 assert_eq!(verdict, Verdict::Proceed(Accept));
2577
2578 let error_packet_expected = IE::make_serializer(
2579 I::ULTIMATE_DST,
2582 I::ULTIMATE_SRC,
2583 packet_pre_nat.serialize_vec_outer().unwrap().unwrap_b().into_inner(),
2584 )
2585 .wrap_in(I::PacketBuilder::new(
2586 I::ULTIMATE_DST,
2587 I::ULTIMATE_SRC,
2588 u8::MAX,
2589 IE::proto(),
2590 ));
2591
2592 assert_eq!(error_packet, error_packet_expected);
2593 }
2594
2595 #[test_matrix(
2596 [
2597 Icmpv4DestUnreachableError,
2598 Icmpv6DestUnreachableError,
2599 ],
2600 [
2601 IcmpErrorSource::IntermediateRouter,
2602 IcmpErrorSource::EndHost,
2603 ]
2604 )]
2605 fn redirect_icmp_error_in_original_direction<I: IcmpErrorTestIpExt, IE: IcmpErrorMessage<I>>(
2606 _icmp_error: IE,
2607 icmp_error_source: IcmpErrorSource,
2608 ) {
2609 let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2610 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2611 let mut core_ctx = FakeNatCtx::new([(
2612 FakeMatcherDeviceId::ethernet_interface(),
2613 AddrSubnet::new(I::NETSTACK, I::SUBNET.prefix()).unwrap(),
2614 )]);
2615
2616 let mut packet = EmptyBuf
2618 .wrap_in(UdpPacketBuilder::new(
2619 I::ULTIMATE_SRC,
2620 I::ULTIMATE_DST,
2621 Some(NonZeroU16::new(11111).unwrap()),
2622 NonZeroU16::new(22222).unwrap(),
2623 ))
2624 .wrap_in(I::PacketBuilder::new(
2625 I::ULTIMATE_SRC,
2626 I::ULTIMATE_DST,
2627 u8::MAX,
2628 IpProto::Udp.into(),
2629 ));
2630 let (mut conn, direction) = conntrack
2631 .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2632 .expect("packet should be valid")
2633 .expect("packet should be trackable");
2634 let original_tuple = conn.original_tuple().clone();
2635
2636 let nat_routines = Hook {
2640 routines: vec![Routine {
2641 rules: vec![Rule::new(
2642 PacketMatcher::default(),
2643 Action::Redirect { dst_port: Some(LOCAL_PORT..=LOCAL_PORT) },
2644 )],
2645 }],
2646 };
2647 let verdict = perform_nat::<IngressHook, _, _, _, _>(
2648 &mut core_ctx,
2649 &mut bindings_ctx,
2650 NAT_ENABLED_FOR_TESTS,
2651 &conntrack,
2652 &mut conn,
2653 direction,
2654 &nat_routines,
2655 &mut packet,
2656 <IngressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
2657 );
2658 assert_eq!(verdict, Verdict::Proceed(Accept));
2659
2660 let (redirect_addr, cached_addr) = IngressHook::redirect_addr(
2664 &mut core_ctx,
2665 &packet,
2666 <IngressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface())
2667 .ingress,
2668 )
2669 .expect("get redirect addr for NAT hook");
2670
2671 assert_eq!(redirect_addr, I::NETSTACK);
2674
2675 let expected = EmptyBuf
2677 .wrap_in(UdpPacketBuilder::new(
2678 I::ULTIMATE_SRC,
2679 redirect_addr,
2680 packet.inner().outer().src_port(),
2681 LOCAL_PORT,
2682 ))
2683 .wrap_in(I::PacketBuilder::new(
2684 I::ULTIMATE_SRC,
2685 redirect_addr,
2686 u8::MAX,
2687 IpProto::Udp.into(),
2688 ));
2689 assert_eq!(packet, expected);
2690 assert_eq!(
2691 conn.external_data().destination.get().expect("DNAT should be configured"),
2692 &ShouldNat::Yes(cached_addr)
2693 );
2694 assert_eq!(conn.external_data().source.get(), None, "SNAT should not be configured");
2695 assert_eq!(conn.original_tuple(), &original_tuple);
2696 let reply_tuple = Tuple {
2697 src_addr: redirect_addr,
2698 src_port_or_id: LOCAL_PORT.get(),
2699 ..original_tuple.invert()
2700 };
2701 assert_eq!(conn.reply_tuple(), &reply_tuple);
2702
2703 let mut reply_packet = EmptyBuf
2704 .wrap_in(UdpPacketBuilder::new(
2705 reply_tuple.src_addr,
2706 reply_tuple.dst_addr,
2707 Some(NonZeroU16::new(reply_tuple.src_port_or_id).unwrap()),
2708 NonZeroU16::new(reply_tuple.dst_port_or_id).unwrap(),
2709 ))
2710 .wrap_in(I::PacketBuilder::new(
2711 reply_tuple.src_addr,
2712 reply_tuple.dst_addr,
2713 u8::MAX,
2714 IpProto::Udp.into(),
2715 ));
2716 let reply_packet_pre_nat = reply_packet.clone();
2717
2718 let verdict = perform_nat::<EgressHook, _, _, _, _>(
2719 &mut core_ctx,
2720 &mut bindings_ctx,
2721 NAT_ENABLED_FOR_TESTS,
2722 &conntrack,
2723 &mut conn,
2724 ConnectionDirection::Reply,
2725 &nat_routines,
2726 &mut reply_packet,
2727 <EgressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
2728 );
2729 assert_eq!(verdict, Verdict::Proceed(Accept));
2730
2731 let reply_packet_expected = EmptyBuf
2732 .wrap_in(UdpPacketBuilder::new(
2733 I::ULTIMATE_DST,
2734 I::ULTIMATE_SRC,
2735 Some(NonZeroU16::new(22222).unwrap()),
2736 NonZeroU16::new(11111).unwrap(),
2737 ))
2738 .wrap_in(I::PacketBuilder::new(
2739 I::ULTIMATE_DST,
2740 I::ULTIMATE_SRC,
2741 u8::MAX,
2742 IpProto::Udp.into(),
2743 ));
2744 assert_eq!(reply_packet, reply_packet_expected);
2745
2746 let error_src_addr = match icmp_error_source {
2749 IcmpErrorSource::IntermediateRouter => I::ROUTER_SRC,
2750 IcmpErrorSource::EndHost => I::ULTIMATE_SRC,
2751 };
2752
2753 let mut error_packet = IE::make_serializer(
2754 error_src_addr,
2755 I::ULTIMATE_DST,
2756 reply_packet.clone().serialize_vec_outer().unwrap().unwrap_b().into_inner(),
2757 )
2758 .wrap_in(I::PacketBuilder::new(
2759 error_src_addr,
2760 I::ULTIMATE_DST,
2761 u8::MAX,
2762 IE::proto(),
2763 ));
2764
2765 let verdict = perform_nat::<IngressHook, _, _, _, _>(
2766 &mut core_ctx,
2767 &mut bindings_ctx,
2768 NAT_ENABLED_FOR_TESTS,
2769 &conntrack,
2770 &mut conn,
2771 direction,
2772 &nat_routines,
2773 &mut error_packet,
2774 <IngressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
2775 );
2776 assert_eq!(verdict, Verdict::Proceed(Accept));
2777
2778 let error_packet_expected = IE::make_serializer(
2779 error_src_addr,
2782 redirect_addr,
2783 reply_packet_pre_nat.serialize_vec_outer().unwrap().unwrap_b().into_inner(),
2784 )
2785 .wrap_in(I::PacketBuilder::new(
2786 error_src_addr,
2787 redirect_addr,
2788 u8::MAX,
2789 IE::proto(),
2790 ));
2791
2792 assert_eq!(error_packet, error_packet_expected);
2793 }
2794
2795 #[test_matrix(
2823 [
2824 Icmpv4DestUnreachableError,
2825 Icmpv6DestUnreachableError,
2826 ],
2827 [
2828 IcmpErrorSource::IntermediateRouter,
2829 IcmpErrorSource::EndHost,
2830 ]
2831 )]
2832 fn masquerade_icmp_error_in_reply_direction<I: TestIpExt, IE: IcmpErrorMessage<I>>(
2833 _icmp_error: IE,
2834 icmp_error_source: IcmpErrorSource,
2835 ) {
2836 let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2837 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2838 let assigned_addr = AddrSubnet::new(I::NETSTACK, I::SUBNET.prefix()).unwrap();
2839 let mut core_ctx =
2840 FakeNatCtx::new([(FakeMatcherDeviceId::ethernet_interface(), assigned_addr)]);
2841
2842 let mut packet = EmptyBuf
2844 .wrap_in(UdpPacketBuilder::new(
2845 I::ULTIMATE_SRC,
2846 I::ULTIMATE_DST,
2847 Some(NonZeroU16::new(11111).unwrap()),
2848 NonZeroU16::new(22222).unwrap(),
2849 ))
2850 .wrap_in(I::PacketBuilder::new(
2851 I::ULTIMATE_SRC,
2852 I::ULTIMATE_DST,
2853 u8::MAX,
2854 IpProto::Udp.into(),
2855 ));
2856 let packet_pre_nat = packet.clone();
2857 let (mut conn, direction) = conntrack
2858 .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2859 .expect("packet should be valid")
2860 .expect("packet should be trackable");
2861 let original_tuple = conn.original_tuple().clone();
2862
2863 let nat_routines = Hook {
2865 routines: vec![Routine {
2866 rules: vec![Rule::new(
2867 PacketMatcher::default(),
2868 Action::Masquerade { src_port: Some(LOCAL_PORT..=LOCAL_PORT) },
2869 )],
2870 }],
2871 };
2872 let verdict = perform_nat::<EgressHook, _, _, _, _>(
2873 &mut core_ctx,
2874 &mut bindings_ctx,
2875 NAT_ENABLED_FOR_TESTS,
2876 &conntrack,
2877 &mut conn,
2878 direction,
2879 &nat_routines,
2880 &mut packet,
2881 Interfaces { ingress: None, egress: Some(&FakeMatcherDeviceId::ethernet_interface()) },
2882 );
2883 assert_eq!(verdict, Verdict::Proceed(Accept));
2884
2885 let expected = EmptyBuf
2889 .wrap_in(UdpPacketBuilder::new(
2890 I::NETSTACK,
2891 I::ULTIMATE_DST,
2892 Some(LOCAL_PORT),
2893 packet.inner().outer().dst_port().unwrap(),
2894 ))
2895 .wrap_in(I::PacketBuilder::new(
2896 I::NETSTACK,
2897 I::ULTIMATE_DST,
2898 u8::MAX,
2899 IpProto::Udp.into(),
2900 ));
2901 assert_eq!(packet, expected);
2902 assert_matches!(
2903 conn.external_data().source.get().expect("SNAT should be configured"),
2904 &ShouldNat::Yes(Some(_))
2905 );
2906 assert_eq!(conn.external_data().destination.get(), None, "DNAT should not be configured");
2907 assert_eq!(conn.original_tuple(), &original_tuple);
2908 let reply_tuple = Tuple {
2909 dst_addr: I::NETSTACK,
2910 dst_port_or_id: LOCAL_PORT.get(),
2911 ..original_tuple.invert()
2912 };
2913 assert_eq!(conn.reply_tuple(), &reply_tuple);
2914
2915 let error_src_addr = match icmp_error_source {
2918 IcmpErrorSource::IntermediateRouter => I::ROUTER_DST,
2919 IcmpErrorSource::EndHost => I::ULTIMATE_DST,
2920 };
2921
2922 let mut error_packet = IE::make_serializer(
2923 error_src_addr,
2924 I::NETSTACK,
2925 packet.clone().serialize_vec_outer().unwrap().unwrap_b().into_inner(),
2926 )
2927 .wrap_in(I::PacketBuilder::new(error_src_addr, I::NETSTACK, u8::MAX, IE::proto()));
2928
2929 let verdict = perform_nat::<IngressHook, _, _, _, _>(
2930 &mut core_ctx,
2931 &mut bindings_ctx,
2932 NAT_ENABLED_FOR_TESTS,
2933 &conntrack,
2934 &mut conn,
2935 ConnectionDirection::Reply,
2936 &nat_routines,
2937 &mut error_packet,
2938 Interfaces { ingress: Some(&FakeMatcherDeviceId::ethernet_interface()), egress: None },
2939 );
2940 assert_eq!(verdict, Verdict::Proceed(Accept));
2941
2942 let error_packet_expected = IE::make_serializer(
2943 error_src_addr,
2946 I::ULTIMATE_SRC,
2947 packet_pre_nat.serialize_vec_outer().unwrap().unwrap_b().into_inner(),
2948 )
2949 .wrap_in(I::PacketBuilder::new(
2950 error_src_addr,
2951 I::ULTIMATE_SRC,
2952 u8::MAX,
2953 IE::proto(),
2954 ));
2955
2956 assert_eq!(error_packet, error_packet_expected);
2957 }
2958
2959 #[test_matrix(
2960 [
2961 Icmpv4DestUnreachableError,
2962 Icmpv6DestUnreachableError,
2963 ],
2964 [
2965 IcmpErrorSource::IntermediateRouter,
2966 IcmpErrorSource::EndHost,
2967 ]
2968 )]
2969 fn masquerade_icmp_error_in_original_direction<I: TestIpExt, IE: IcmpErrorMessage<I>>(
2970 _icmp_error: IE,
2971 icmp_error_source: IcmpErrorSource,
2972 ) {
2973 let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2974 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2975 let assigned_addr = AddrSubnet::new(I::NETSTACK, I::SUBNET.prefix()).unwrap();
2976 let mut core_ctx =
2977 FakeNatCtx::new([(FakeMatcherDeviceId::ethernet_interface(), assigned_addr)]);
2978
2979 let mut packet = EmptyBuf
2981 .wrap_in(UdpPacketBuilder::new(
2982 I::ULTIMATE_SRC,
2983 I::ULTIMATE_DST,
2984 Some(NonZeroU16::new(11111).unwrap()),
2985 NonZeroU16::new(22222).unwrap(),
2986 ))
2987 .wrap_in(I::PacketBuilder::new(
2988 I::ULTIMATE_SRC,
2989 I::ULTIMATE_DST,
2990 u8::MAX,
2991 IpProto::Udp.into(),
2992 ));
2993 let (mut conn, direction) = conntrack
2994 .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2995 .expect("packet should be valid")
2996 .expect("packet should be trackable");
2997 let original_tuple = conn.original_tuple().clone();
2998
2999 let nat_routines = Hook {
3001 routines: vec![Routine {
3002 rules: vec![Rule::new(
3003 PacketMatcher::default(),
3004 Action::Masquerade { src_port: Some(LOCAL_PORT..=LOCAL_PORT) },
3005 )],
3006 }],
3007 };
3008 let verdict = perform_nat::<EgressHook, _, _, _, _>(
3009 &mut core_ctx,
3010 &mut bindings_ctx,
3011 NAT_ENABLED_FOR_TESTS,
3012 &conntrack,
3013 &mut conn,
3014 direction,
3015 &nat_routines,
3016 &mut packet,
3017 Interfaces { ingress: None, egress: Some(&FakeMatcherDeviceId::ethernet_interface()) },
3018 );
3019 assert_eq!(verdict, Verdict::Proceed(Accept));
3020
3021 let expected = EmptyBuf
3025 .wrap_in(UdpPacketBuilder::new(
3026 I::NETSTACK,
3027 I::ULTIMATE_DST,
3028 Some(LOCAL_PORT),
3029 packet.inner().outer().dst_port().unwrap(),
3030 ))
3031 .wrap_in(I::PacketBuilder::new(
3032 I::NETSTACK,
3033 I::ULTIMATE_DST,
3034 u8::MAX,
3035 IpProto::Udp.into(),
3036 ));
3037 assert_eq!(packet, expected);
3038 assert_matches!(
3039 conn.external_data().source.get().expect("SNAT should be configured"),
3040 &ShouldNat::Yes(Some(_))
3041 );
3042 assert_eq!(conn.external_data().destination.get(), None, "DNAT should not be configured");
3043 assert_eq!(conn.original_tuple(), &original_tuple);
3044 let reply_tuple = Tuple {
3045 dst_addr: I::NETSTACK,
3046 dst_port_or_id: LOCAL_PORT.get(),
3047 ..original_tuple.invert()
3048 };
3049 assert_eq!(conn.reply_tuple(), &reply_tuple);
3050
3051 let mut reply_packet = EmptyBuf
3055 .wrap_in(UdpPacketBuilder::new(
3056 reply_tuple.src_addr,
3057 reply_tuple.dst_addr,
3058 Some(NonZeroU16::new(reply_tuple.src_port_or_id).unwrap()),
3059 NonZeroU16::new(reply_tuple.dst_port_or_id).unwrap(),
3060 ))
3061 .wrap_in(I::PacketBuilder::new(
3062 reply_tuple.src_addr,
3063 reply_tuple.dst_addr,
3064 u8::MAX,
3065 IpProto::Udp.into(),
3066 ));
3067 let reply_packet_pre_nat = reply_packet.clone();
3068
3069 let verdict = perform_nat::<IngressHook, _, _, _, _>(
3070 &mut core_ctx,
3071 &mut bindings_ctx,
3072 NAT_ENABLED_FOR_TESTS,
3073 &conntrack,
3074 &mut conn,
3075 ConnectionDirection::Reply,
3076 &nat_routines,
3077 &mut reply_packet,
3078 Interfaces { ingress: Some(&FakeMatcherDeviceId::ethernet_interface()), egress: None },
3079 );
3080 assert_eq!(verdict, Verdict::Proceed(Accept));
3081
3082 let reply_packet_expected = EmptyBuf
3083 .wrap_in(UdpPacketBuilder::new(
3084 I::ULTIMATE_DST,
3085 I::ULTIMATE_SRC,
3086 Some(NonZeroU16::new(22222).unwrap()),
3087 NonZeroU16::new(11111).unwrap(),
3088 ))
3089 .wrap_in(I::PacketBuilder::new(
3090 I::ULTIMATE_DST,
3091 I::ULTIMATE_SRC,
3092 u8::MAX,
3093 IpProto::Udp.into(),
3094 ));
3095 assert_eq!(reply_packet, reply_packet_expected);
3096
3097 let error_src_addr = match icmp_error_source {
3098 IcmpErrorSource::IntermediateRouter => I::ROUTER_SRC,
3099 IcmpErrorSource::EndHost => I::ULTIMATE_SRC,
3100 };
3101
3102 let mut error_packet = IE::make_serializer(
3103 error_src_addr,
3104 I::ULTIMATE_DST,
3105 reply_packet.clone().serialize_vec_outer().unwrap().unwrap_b().into_inner(),
3106 )
3107 .wrap_in(I::PacketBuilder::new(
3108 error_src_addr,
3109 I::ULTIMATE_DST,
3110 u8::MAX,
3111 IE::proto(),
3112 ));
3113
3114 let verdict = perform_nat::<EgressHook, _, _, _, _>(
3115 &mut core_ctx,
3116 &mut bindings_ctx,
3117 NAT_ENABLED_FOR_TESTS,
3118 &conntrack,
3119 &mut conn,
3120 direction,
3121 &nat_routines,
3122 &mut error_packet,
3123 Interfaces { ingress: None, egress: Some(&FakeMatcherDeviceId::ethernet_interface()) },
3124 );
3125 assert_eq!(verdict, Verdict::Proceed(Accept));
3126
3127 let error_packet_expected =
3128 I::PacketBuilder::new(I::NETSTACK, I::ULTIMATE_DST, u8::MAX, IE::proto()).wrap_body(
3129 IE::make_serializer(
3130 I::NETSTACK,
3133 I::ULTIMATE_DST,
3134 reply_packet_pre_nat.serialize_vec_outer().unwrap().unwrap_b().into_inner(),
3135 ),
3136 );
3137
3138 assert_eq!(error_packet, error_packet_expected);
3139 }
3140}