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, NetworkSerializationContext, TimerContext};
1255 use packet::{EmptyBuf, NestablePacketBuilder as _, NestableSerializer as _, 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
2557 .clone()
2558 .serialize_vec_outer(&mut NetworkSerializationContext::default())
2559 .unwrap()
2560 .unwrap_b()
2561 .into_inner(),
2562 )
2563 .wrap_in(I::PacketBuilder::new(
2564 redirect_addr,
2565 I::ULTIMATE_SRC,
2566 u8::MAX,
2567 IE::proto(),
2568 ));
2569
2570 let verdict = perform_nat::<EgressHook, _, _, _, _>(
2571 &mut core_ctx,
2572 &mut bindings_ctx,
2573 NAT_ENABLED_FOR_TESTS,
2574 &conntrack,
2575 &mut conn,
2576 ConnectionDirection::Reply,
2577 &nat_routines,
2578 &mut error_packet,
2579 <EgressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
2580 );
2581 assert_eq!(verdict, Verdict::Proceed(Accept));
2582
2583 let error_packet_expected = IE::make_serializer(
2584 I::ULTIMATE_DST,
2587 I::ULTIMATE_SRC,
2588 packet_pre_nat
2589 .serialize_vec_outer(&mut NetworkSerializationContext::default())
2590 .unwrap()
2591 .unwrap_b()
2592 .into_inner(),
2593 )
2594 .wrap_in(I::PacketBuilder::new(
2595 I::ULTIMATE_DST,
2596 I::ULTIMATE_SRC,
2597 u8::MAX,
2598 IE::proto(),
2599 ));
2600
2601 assert_eq!(error_packet, error_packet_expected);
2602 }
2603
2604 #[test_matrix(
2605 [
2606 Icmpv4DestUnreachableError,
2607 Icmpv6DestUnreachableError,
2608 ],
2609 [
2610 IcmpErrorSource::IntermediateRouter,
2611 IcmpErrorSource::EndHost,
2612 ]
2613 )]
2614 fn redirect_icmp_error_in_original_direction<I: IcmpErrorTestIpExt, IE: IcmpErrorMessage<I>>(
2615 _icmp_error: IE,
2616 icmp_error_source: IcmpErrorSource,
2617 ) {
2618 let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2619 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2620 let mut core_ctx = FakeNatCtx::new([(
2621 FakeMatcherDeviceId::ethernet_interface(),
2622 AddrSubnet::new(I::NETSTACK, I::SUBNET.prefix()).unwrap(),
2623 )]);
2624
2625 let mut packet = EmptyBuf
2627 .wrap_in(UdpPacketBuilder::new(
2628 I::ULTIMATE_SRC,
2629 I::ULTIMATE_DST,
2630 Some(NonZeroU16::new(11111).unwrap()),
2631 NonZeroU16::new(22222).unwrap(),
2632 ))
2633 .wrap_in(I::PacketBuilder::new(
2634 I::ULTIMATE_SRC,
2635 I::ULTIMATE_DST,
2636 u8::MAX,
2637 IpProto::Udp.into(),
2638 ));
2639 let (mut conn, direction) = conntrack
2640 .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2641 .expect("packet should be valid")
2642 .expect("packet should be trackable");
2643 let original_tuple = conn.original_tuple().clone();
2644
2645 let nat_routines = Hook {
2649 routines: vec![Routine {
2650 rules: vec![Rule::new(
2651 PacketMatcher::default(),
2652 Action::Redirect { dst_port: Some(LOCAL_PORT..=LOCAL_PORT) },
2653 )],
2654 }],
2655 };
2656 let verdict = perform_nat::<IngressHook, _, _, _, _>(
2657 &mut core_ctx,
2658 &mut bindings_ctx,
2659 NAT_ENABLED_FOR_TESTS,
2660 &conntrack,
2661 &mut conn,
2662 direction,
2663 &nat_routines,
2664 &mut packet,
2665 <IngressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
2666 );
2667 assert_eq!(verdict, Verdict::Proceed(Accept));
2668
2669 let (redirect_addr, cached_addr) = IngressHook::redirect_addr(
2673 &mut core_ctx,
2674 &packet,
2675 <IngressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface())
2676 .ingress,
2677 )
2678 .expect("get redirect addr for NAT hook");
2679
2680 assert_eq!(redirect_addr, I::NETSTACK);
2683
2684 let expected = EmptyBuf
2686 .wrap_in(UdpPacketBuilder::new(
2687 I::ULTIMATE_SRC,
2688 redirect_addr,
2689 packet.inner().outer().src_port(),
2690 LOCAL_PORT,
2691 ))
2692 .wrap_in(I::PacketBuilder::new(
2693 I::ULTIMATE_SRC,
2694 redirect_addr,
2695 u8::MAX,
2696 IpProto::Udp.into(),
2697 ));
2698 assert_eq!(packet, expected);
2699 assert_eq!(
2700 conn.external_data().destination.get().expect("DNAT should be configured"),
2701 &ShouldNat::Yes(cached_addr)
2702 );
2703 assert_eq!(conn.external_data().source.get(), None, "SNAT should not be configured");
2704 assert_eq!(conn.original_tuple(), &original_tuple);
2705 let reply_tuple = Tuple {
2706 src_addr: redirect_addr,
2707 src_port_or_id: LOCAL_PORT.get(),
2708 ..original_tuple.invert()
2709 };
2710 assert_eq!(conn.reply_tuple(), &reply_tuple);
2711
2712 let mut reply_packet = EmptyBuf
2713 .wrap_in(UdpPacketBuilder::new(
2714 reply_tuple.src_addr,
2715 reply_tuple.dst_addr,
2716 Some(NonZeroU16::new(reply_tuple.src_port_or_id).unwrap()),
2717 NonZeroU16::new(reply_tuple.dst_port_or_id).unwrap(),
2718 ))
2719 .wrap_in(I::PacketBuilder::new(
2720 reply_tuple.src_addr,
2721 reply_tuple.dst_addr,
2722 u8::MAX,
2723 IpProto::Udp.into(),
2724 ));
2725 let reply_packet_pre_nat = reply_packet.clone();
2726
2727 let verdict = perform_nat::<EgressHook, _, _, _, _>(
2728 &mut core_ctx,
2729 &mut bindings_ctx,
2730 NAT_ENABLED_FOR_TESTS,
2731 &conntrack,
2732 &mut conn,
2733 ConnectionDirection::Reply,
2734 &nat_routines,
2735 &mut reply_packet,
2736 <EgressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
2737 );
2738 assert_eq!(verdict, Verdict::Proceed(Accept));
2739
2740 let reply_packet_expected = EmptyBuf
2741 .wrap_in(UdpPacketBuilder::new(
2742 I::ULTIMATE_DST,
2743 I::ULTIMATE_SRC,
2744 Some(NonZeroU16::new(22222).unwrap()),
2745 NonZeroU16::new(11111).unwrap(),
2746 ))
2747 .wrap_in(I::PacketBuilder::new(
2748 I::ULTIMATE_DST,
2749 I::ULTIMATE_SRC,
2750 u8::MAX,
2751 IpProto::Udp.into(),
2752 ));
2753 assert_eq!(reply_packet, reply_packet_expected);
2754
2755 let error_src_addr = match icmp_error_source {
2758 IcmpErrorSource::IntermediateRouter => I::ROUTER_SRC,
2759 IcmpErrorSource::EndHost => I::ULTIMATE_SRC,
2760 };
2761
2762 let mut error_packet = IE::make_serializer(
2763 error_src_addr,
2764 I::ULTIMATE_DST,
2765 reply_packet
2766 .clone()
2767 .serialize_vec_outer(&mut NetworkSerializationContext::default())
2768 .unwrap()
2769 .unwrap_b()
2770 .into_inner(),
2771 )
2772 .wrap_in(I::PacketBuilder::new(
2773 error_src_addr,
2774 I::ULTIMATE_DST,
2775 u8::MAX,
2776 IE::proto(),
2777 ));
2778
2779 let verdict = perform_nat::<IngressHook, _, _, _, _>(
2780 &mut core_ctx,
2781 &mut bindings_ctx,
2782 NAT_ENABLED_FOR_TESTS,
2783 &conntrack,
2784 &mut conn,
2785 direction,
2786 &nat_routines,
2787 &mut error_packet,
2788 <IngressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
2789 );
2790 assert_eq!(verdict, Verdict::Proceed(Accept));
2791
2792 let error_packet_expected = IE::make_serializer(
2793 error_src_addr,
2796 redirect_addr,
2797 reply_packet_pre_nat
2798 .serialize_vec_outer(&mut NetworkSerializationContext::default())
2799 .unwrap()
2800 .unwrap_b()
2801 .into_inner(),
2802 )
2803 .wrap_in(I::PacketBuilder::new(
2804 error_src_addr,
2805 redirect_addr,
2806 u8::MAX,
2807 IE::proto(),
2808 ));
2809
2810 assert_eq!(error_packet, error_packet_expected);
2811 }
2812
2813 #[test_matrix(
2841 [
2842 Icmpv4DestUnreachableError,
2843 Icmpv6DestUnreachableError,
2844 ],
2845 [
2846 IcmpErrorSource::IntermediateRouter,
2847 IcmpErrorSource::EndHost,
2848 ]
2849 )]
2850 fn masquerade_icmp_error_in_reply_direction<I: TestIpExt, IE: IcmpErrorMessage<I>>(
2851 _icmp_error: IE,
2852 icmp_error_source: IcmpErrorSource,
2853 ) {
2854 let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2855 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2856 let assigned_addr = AddrSubnet::new(I::NETSTACK, I::SUBNET.prefix()).unwrap();
2857 let mut core_ctx =
2858 FakeNatCtx::new([(FakeMatcherDeviceId::ethernet_interface(), assigned_addr)]);
2859
2860 let mut packet = EmptyBuf
2862 .wrap_in(UdpPacketBuilder::new(
2863 I::ULTIMATE_SRC,
2864 I::ULTIMATE_DST,
2865 Some(NonZeroU16::new(11111).unwrap()),
2866 NonZeroU16::new(22222).unwrap(),
2867 ))
2868 .wrap_in(I::PacketBuilder::new(
2869 I::ULTIMATE_SRC,
2870 I::ULTIMATE_DST,
2871 u8::MAX,
2872 IpProto::Udp.into(),
2873 ));
2874 let packet_pre_nat = packet.clone();
2875 let (mut conn, direction) = conntrack
2876 .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2877 .expect("packet should be valid")
2878 .expect("packet should be trackable");
2879 let original_tuple = conn.original_tuple().clone();
2880
2881 let nat_routines = Hook {
2883 routines: vec![Routine {
2884 rules: vec![Rule::new(
2885 PacketMatcher::default(),
2886 Action::Masquerade { src_port: Some(LOCAL_PORT..=LOCAL_PORT) },
2887 )],
2888 }],
2889 };
2890 let verdict = perform_nat::<EgressHook, _, _, _, _>(
2891 &mut core_ctx,
2892 &mut bindings_ctx,
2893 NAT_ENABLED_FOR_TESTS,
2894 &conntrack,
2895 &mut conn,
2896 direction,
2897 &nat_routines,
2898 &mut packet,
2899 Interfaces { ingress: None, egress: Some(&FakeMatcherDeviceId::ethernet_interface()) },
2900 );
2901 assert_eq!(verdict, Verdict::Proceed(Accept));
2902
2903 let expected = EmptyBuf
2907 .wrap_in(UdpPacketBuilder::new(
2908 I::NETSTACK,
2909 I::ULTIMATE_DST,
2910 Some(LOCAL_PORT),
2911 packet.inner().outer().dst_port().unwrap(),
2912 ))
2913 .wrap_in(I::PacketBuilder::new(
2914 I::NETSTACK,
2915 I::ULTIMATE_DST,
2916 u8::MAX,
2917 IpProto::Udp.into(),
2918 ));
2919 assert_eq!(packet, expected);
2920 assert_matches!(
2921 conn.external_data().source.get().expect("SNAT should be configured"),
2922 &ShouldNat::Yes(Some(_))
2923 );
2924 assert_eq!(conn.external_data().destination.get(), None, "DNAT should not be configured");
2925 assert_eq!(conn.original_tuple(), &original_tuple);
2926 let reply_tuple = Tuple {
2927 dst_addr: I::NETSTACK,
2928 dst_port_or_id: LOCAL_PORT.get(),
2929 ..original_tuple.invert()
2930 };
2931 assert_eq!(conn.reply_tuple(), &reply_tuple);
2932
2933 let error_src_addr = match icmp_error_source {
2936 IcmpErrorSource::IntermediateRouter => I::ROUTER_DST,
2937 IcmpErrorSource::EndHost => I::ULTIMATE_DST,
2938 };
2939
2940 let mut error_packet = IE::make_serializer(
2941 error_src_addr,
2942 I::NETSTACK,
2943 packet
2944 .clone()
2945 .serialize_vec_outer(&mut NetworkSerializationContext::default())
2946 .unwrap()
2947 .unwrap_b()
2948 .into_inner(),
2949 )
2950 .wrap_in(I::PacketBuilder::new(error_src_addr, I::NETSTACK, u8::MAX, IE::proto()));
2951
2952 let verdict = perform_nat::<IngressHook, _, _, _, _>(
2953 &mut core_ctx,
2954 &mut bindings_ctx,
2955 NAT_ENABLED_FOR_TESTS,
2956 &conntrack,
2957 &mut conn,
2958 ConnectionDirection::Reply,
2959 &nat_routines,
2960 &mut error_packet,
2961 Interfaces { ingress: Some(&FakeMatcherDeviceId::ethernet_interface()), egress: None },
2962 );
2963 assert_eq!(verdict, Verdict::Proceed(Accept));
2964
2965 let error_packet_expected = IE::make_serializer(
2966 error_src_addr,
2969 I::ULTIMATE_SRC,
2970 packet_pre_nat
2971 .serialize_vec_outer(&mut NetworkSerializationContext::default())
2972 .unwrap()
2973 .unwrap_b()
2974 .into_inner(),
2975 )
2976 .wrap_in(I::PacketBuilder::new(
2977 error_src_addr,
2978 I::ULTIMATE_SRC,
2979 u8::MAX,
2980 IE::proto(),
2981 ));
2982
2983 assert_eq!(error_packet, error_packet_expected);
2984 }
2985
2986 #[test_matrix(
2987 [
2988 Icmpv4DestUnreachableError,
2989 Icmpv6DestUnreachableError,
2990 ],
2991 [
2992 IcmpErrorSource::IntermediateRouter,
2993 IcmpErrorSource::EndHost,
2994 ]
2995 )]
2996 fn masquerade_icmp_error_in_original_direction<I: TestIpExt, IE: IcmpErrorMessage<I>>(
2997 _icmp_error: IE,
2998 icmp_error_source: IcmpErrorSource,
2999 ) {
3000 let mut bindings_ctx = FakeBindingsCtx::<I>::new();
3001 let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
3002 let assigned_addr = AddrSubnet::new(I::NETSTACK, I::SUBNET.prefix()).unwrap();
3003 let mut core_ctx =
3004 FakeNatCtx::new([(FakeMatcherDeviceId::ethernet_interface(), assigned_addr)]);
3005
3006 let mut packet = EmptyBuf
3008 .wrap_in(UdpPacketBuilder::new(
3009 I::ULTIMATE_SRC,
3010 I::ULTIMATE_DST,
3011 Some(NonZeroU16::new(11111).unwrap()),
3012 NonZeroU16::new(22222).unwrap(),
3013 ))
3014 .wrap_in(I::PacketBuilder::new(
3015 I::ULTIMATE_SRC,
3016 I::ULTIMATE_DST,
3017 u8::MAX,
3018 IpProto::Udp.into(),
3019 ));
3020 let (mut conn, direction) = conntrack
3021 .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
3022 .expect("packet should be valid")
3023 .expect("packet should be trackable");
3024 let original_tuple = conn.original_tuple().clone();
3025
3026 let nat_routines = Hook {
3028 routines: vec![Routine {
3029 rules: vec![Rule::new(
3030 PacketMatcher::default(),
3031 Action::Masquerade { src_port: Some(LOCAL_PORT..=LOCAL_PORT) },
3032 )],
3033 }],
3034 };
3035 let verdict = perform_nat::<EgressHook, _, _, _, _>(
3036 &mut core_ctx,
3037 &mut bindings_ctx,
3038 NAT_ENABLED_FOR_TESTS,
3039 &conntrack,
3040 &mut conn,
3041 direction,
3042 &nat_routines,
3043 &mut packet,
3044 Interfaces { ingress: None, egress: Some(&FakeMatcherDeviceId::ethernet_interface()) },
3045 );
3046 assert_eq!(verdict, Verdict::Proceed(Accept));
3047
3048 let expected = EmptyBuf
3052 .wrap_in(UdpPacketBuilder::new(
3053 I::NETSTACK,
3054 I::ULTIMATE_DST,
3055 Some(LOCAL_PORT),
3056 packet.inner().outer().dst_port().unwrap(),
3057 ))
3058 .wrap_in(I::PacketBuilder::new(
3059 I::NETSTACK,
3060 I::ULTIMATE_DST,
3061 u8::MAX,
3062 IpProto::Udp.into(),
3063 ));
3064 assert_eq!(packet, expected);
3065 assert_matches!(
3066 conn.external_data().source.get().expect("SNAT should be configured"),
3067 &ShouldNat::Yes(Some(_))
3068 );
3069 assert_eq!(conn.external_data().destination.get(), None, "DNAT should not be configured");
3070 assert_eq!(conn.original_tuple(), &original_tuple);
3071 let reply_tuple = Tuple {
3072 dst_addr: I::NETSTACK,
3073 dst_port_or_id: LOCAL_PORT.get(),
3074 ..original_tuple.invert()
3075 };
3076 assert_eq!(conn.reply_tuple(), &reply_tuple);
3077
3078 let mut reply_packet = EmptyBuf
3082 .wrap_in(UdpPacketBuilder::new(
3083 reply_tuple.src_addr,
3084 reply_tuple.dst_addr,
3085 Some(NonZeroU16::new(reply_tuple.src_port_or_id).unwrap()),
3086 NonZeroU16::new(reply_tuple.dst_port_or_id).unwrap(),
3087 ))
3088 .wrap_in(I::PacketBuilder::new(
3089 reply_tuple.src_addr,
3090 reply_tuple.dst_addr,
3091 u8::MAX,
3092 IpProto::Udp.into(),
3093 ));
3094 let reply_packet_pre_nat = reply_packet.clone();
3095
3096 let verdict = perform_nat::<IngressHook, _, _, _, _>(
3097 &mut core_ctx,
3098 &mut bindings_ctx,
3099 NAT_ENABLED_FOR_TESTS,
3100 &conntrack,
3101 &mut conn,
3102 ConnectionDirection::Reply,
3103 &nat_routines,
3104 &mut reply_packet,
3105 Interfaces { ingress: Some(&FakeMatcherDeviceId::ethernet_interface()), egress: None },
3106 );
3107 assert_eq!(verdict, Verdict::Proceed(Accept));
3108
3109 let reply_packet_expected = EmptyBuf
3110 .wrap_in(UdpPacketBuilder::new(
3111 I::ULTIMATE_DST,
3112 I::ULTIMATE_SRC,
3113 Some(NonZeroU16::new(22222).unwrap()),
3114 NonZeroU16::new(11111).unwrap(),
3115 ))
3116 .wrap_in(I::PacketBuilder::new(
3117 I::ULTIMATE_DST,
3118 I::ULTIMATE_SRC,
3119 u8::MAX,
3120 IpProto::Udp.into(),
3121 ));
3122 assert_eq!(reply_packet, reply_packet_expected);
3123
3124 let error_src_addr = match icmp_error_source {
3125 IcmpErrorSource::IntermediateRouter => I::ROUTER_SRC,
3126 IcmpErrorSource::EndHost => I::ULTIMATE_SRC,
3127 };
3128
3129 let mut error_packet = IE::make_serializer(
3130 error_src_addr,
3131 I::ULTIMATE_DST,
3132 reply_packet
3133 .clone()
3134 .serialize_vec_outer(&mut NetworkSerializationContext::default())
3135 .unwrap()
3136 .unwrap_b()
3137 .into_inner(),
3138 )
3139 .wrap_in(I::PacketBuilder::new(
3140 error_src_addr,
3141 I::ULTIMATE_DST,
3142 u8::MAX,
3143 IE::proto(),
3144 ));
3145
3146 let verdict = perform_nat::<EgressHook, _, _, _, _>(
3147 &mut core_ctx,
3148 &mut bindings_ctx,
3149 NAT_ENABLED_FOR_TESTS,
3150 &conntrack,
3151 &mut conn,
3152 direction,
3153 &nat_routines,
3154 &mut error_packet,
3155 Interfaces { ingress: None, egress: Some(&FakeMatcherDeviceId::ethernet_interface()) },
3156 );
3157 assert_eq!(verdict, Verdict::Proceed(Accept));
3158
3159 let error_packet_expected =
3160 I::PacketBuilder::new(I::NETSTACK, I::ULTIMATE_DST, u8::MAX, IE::proto()).wrap_body(
3161 IE::make_serializer(
3162 I::NETSTACK,
3165 I::ULTIMATE_DST,
3166 reply_packet_pre_nat
3167 .serialize_vec_outer(&mut NetworkSerializationContext::default())
3168 .unwrap()
3169 .unwrap_b()
3170 .into_inner(),
3171 ),
3172 );
3173
3174 assert_eq!(error_packet, error_packet_expected);
3175 }
3176}