netstack3_filter/logic/
nat.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Network Address Translation.
6
7use core::fmt::Debug;
8use core::num::NonZeroU16;
9use core::ops::{ControlFlow, RangeInclusive};
10
11use derivative::Derivative;
12use log::{error, warn};
13use net_types::SpecifiedAddr;
14use net_types::ip::IpVersionMarker;
15use netstack3_base::sync::Mutex;
16use netstack3_base::{
17    Inspectable, InspectableValue, Inspector as _, IpAddressId as _, IpDeviceAddr, WeakIpAddressId,
18};
19use once_cell::sync::OnceCell;
20use packet_formats::ip::IpExt;
21use rand::Rng as _;
22
23use crate::FilterBindingsContext;
24use crate::conntrack::{
25    CompatibleWith, Connection, ConnectionDirection, ConnectionExclusive, Table, TransportProtocol,
26    Tuple,
27};
28use crate::context::{FilterBindingsTypes, NatContext};
29use crate::logic::{IngressVerdict, Interfaces, RoutineResult, Verdict};
30use crate::packets::{
31    FilterIpExt, FilterIpPacket, IcmpErrorMut, IpPacket, MaybeIcmpErrorMut as _,
32    MaybeTransportPacketMut as _, TransportPacketMut as _,
33};
34use crate::state::{FakePacketMetadata, Hook};
35
36/// The NAT configuration for a given conntrack connection.
37///
38/// Each type of NAT (source and destination) is configured exactly once for a
39/// given connection, for the first packet encountered on that connection. This
40/// is not to say that all connections are NATed: the configuration can be
41/// either `ShouldNat::Yes` or `ShouldNat::No`, but the `OnceCell` containing
42/// the configuration should, in most cases, be initialized by the time a
43/// connection is inserted in the conntrack table.
44#[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/// The NAT configuration for a given type of NAT (either source or
52/// destination).
53#[derive(Derivative)]
54#[derivative(Debug(bound = "A: Debug"), PartialEq(bound = ""))]
55pub(crate) enum ShouldNat<I: IpExt, A> {
56    /// NAT should be performed on this connection.
57    ///
58    /// Optionally contains a `CachedAddr`, which is a weak reference to the address
59    /// used for NAT, if it is a dynamically assigned IP address owned by the stack.
60    Yes(#[derivative(PartialEq = "ignore")] Option<CachedAddr<I, A>>),
61    /// NAT should not be performed on this connection.
62    No,
63}
64
65impl<I: IpExt, A: PartialEq> CompatibleWith for NatConfig<I, A> {
66    fn compatible_with(&self, other: &Self) -> bool {
67        // Check for both SNAT and DNAT that either NAT is configured with the same
68        // value, or that it is unconfigured. When determining whether two connections
69        // are compatible, if NAT is configured differently for both, they are not
70        // compatible, but if NAT is either unconfigured or matches, they are considered
71        // to be compatible.
72        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/// A weak handle to the address assigned to a device. Allows checking for
123/// validity when using the address for NAT.
124#[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    /// Check whether the provided IP address is still valid to be used for NAT.
139    ///
140    /// If the address is no longer valid, attempts to acquire a new handle to the
141    /// same address.
142    fn validate_or_replace<CC, BT>(
143        &self,
144        core_ctx: &mut CC,
145        device: &CC::DeviceId,
146        addr: I::Addr,
147    ) -> Verdict
148    where
149        CC: NatContext<I, BT, WeakAddressId = A>,
150        BT: FilterBindingsTypes,
151    {
152        let Self { id, _marker } = self;
153
154        // If the address ID is still valid, use the address for NAT.
155        {
156            if id.lock().as_ref().map(|id| id.is_assigned()).unwrap_or(false) {
157                return Verdict::Accept(());
158            }
159        }
160
161        // Otherwise, try to re-acquire a new handle to the same address in case it has
162        // since been re-added to the device.
163        //
164        // NB: we release the lock around the `Option<WeakIpAddressId>` while we request
165        // a new address ID and then reacquire it to assign it in order to avoid any
166        // possible lock ordering issues. This does mean that there is a potential race
167        // here between two packets in the same conntrack flow updating `id`, but `id`
168        // should be eventually consistent with the state of the device addresses given
169        // each subsequent packet will check for its validity and try to update it if
170        // necessary.
171        match IpDeviceAddr::new(addr).and_then(|addr| core_ctx.get_address_id(device, addr)) {
172            Some(new) => {
173                *id.lock() = Some(new.downgrade());
174                Verdict::Accept(())
175            }
176            // If either the address we are using for NAT is not a valid `IpDeviceAddr` or
177            // the address is no longer assigned to the device, drop the ID, both to avoid
178            // further futile attempts to upgrade the weak reference and to allow the
179            // backing memory to be deallocated.
180            None => {
181                *id.lock() = None;
182                Verdict::Drop
183            }
184        }
185    }
186}
187
188/// A type of NAT that is performed on a given conntrack connection.
189#[derive(Debug, Clone, Copy, PartialEq)]
190pub enum NatType {
191    /// Destination NAT is performed.
192    Destination,
193    /// Source NAT is performed.
194    Source,
195}
196
197pub(crate) trait NatHook<I: FilterIpExt> {
198    type Verdict<R: Debug>: FilterVerdict<R> + Debug;
199
200    fn verdict_behavior<R: Debug>(v: Self::Verdict<R>) -> ControlFlow<Self::Verdict<()>, R>;
201
202    const NAT_TYPE: NatType;
203
204    /// Evaluate the result of a given routine and returning the resulting control
205    /// flow.
206    fn evaluate_result<P, CC, BC>(
207        core_ctx: &mut CC,
208        bindings_ctx: &mut BC,
209        table: &Table<I, NatConfig<I, CC::WeakAddressId>, BC>,
210        conn: &mut ConnectionExclusive<I, NatConfig<I, CC::WeakAddressId>, BC>,
211        packet: &P,
212        interfaces: &Interfaces<'_, CC::DeviceId>,
213        result: RoutineResult<I>,
214    ) -> ControlFlow<Self::Verdict<NatConfigurationResult<I, CC::WeakAddressId, BC>>>
215    where
216        P: FilterIpPacket<I>,
217        CC: NatContext<I, BC>,
218        BC: FilterBindingsContext<CC::DeviceId>;
219
220    /// Return the IP address that should be used for Redirect NAT in this NAT hook
221    /// and, if applicable, a weakly-held reference to the address if it is assigned
222    /// to an interface owned by the stack.
223    ///
224    /// # Panics
225    ///
226    /// Panics if called on a hook that does not support DNAT.
227    fn redirect_addr<P, CC, BT>(
228        core_ctx: &mut CC,
229        packet: &P,
230        ingress: Option<&CC::DeviceId>,
231    ) -> Option<(I::Addr, Option<CachedAddr<I, CC::WeakAddressId>>)>
232    where
233        P: IpPacket<I>,
234        CC: NatContext<I, BT>,
235        BT: FilterBindingsTypes;
236
237    /// Extract the relevant interface for this NAT hook from `interfaces`.
238    ///
239    /// # Panics
240    ///
241    /// Panics if the required interface (ingress for ingress hooks and egress for
242    /// egress hooks) is not provided to the NAT hook.
243    fn interface<DeviceId>(interfaces: Interfaces<'_, DeviceId>) -> &DeviceId;
244}
245
246pub(crate) trait FilterVerdict<R>: From<Verdict<R>> {
247    fn accept(&self) -> Option<&R>;
248}
249
250impl<R> FilterVerdict<R> for Verdict<R> {
251    fn accept(&self) -> Option<&R> {
252        match self {
253            Self::Accept(result) => Some(result),
254            Self::Drop => None,
255        }
256    }
257}
258
259impl<R> Verdict<R> {
260    fn into_behavior(self) -> ControlFlow<Verdict<()>, R> {
261        match self {
262            Self::Accept(result) => ControlFlow::Continue(result),
263            Self::Drop => ControlFlow::Break(Verdict::Drop),
264        }
265    }
266}
267
268#[derive(Derivative)]
269#[derivative(Debug(bound = "A: Debug"))]
270pub(crate) enum NatConfigurationResult<I: IpExt, A, BT: FilterBindingsTypes> {
271    Result(ShouldNat<I, A>),
272    AdoptExisting(Connection<I, NatConfig<I, A>, BT>),
273}
274
275pub(crate) enum IngressHook {}
276
277impl<I: FilterIpExt> NatHook<I> for IngressHook {
278    type Verdict<R: Debug> = IngressVerdict<I, R>;
279
280    fn verdict_behavior<R: Debug>(v: Self::Verdict<R>) -> ControlFlow<Self::Verdict<()>, R> {
281        v.into_behavior()
282    }
283
284    const NAT_TYPE: NatType = NatType::Destination;
285
286    fn evaluate_result<P, CC, BC>(
287        core_ctx: &mut CC,
288        bindings_ctx: &mut BC,
289        table: &Table<I, NatConfig<I, CC::WeakAddressId>, BC>,
290        conn: &mut ConnectionExclusive<I, NatConfig<I, CC::WeakAddressId>, BC>,
291        packet: &P,
292        interfaces: &Interfaces<'_, CC::DeviceId>,
293        result: RoutineResult<I>,
294    ) -> ControlFlow<Self::Verdict<NatConfigurationResult<I, CC::WeakAddressId, BC>>>
295    where
296        P: FilterIpPacket<I>,
297        CC: NatContext<I, BC>,
298        BC: FilterBindingsContext<CC::DeviceId>,
299    {
300        match result {
301            RoutineResult::Accept | RoutineResult::Return => ControlFlow::Continue(()),
302            RoutineResult::Drop => ControlFlow::Break(Verdict::Drop.into()),
303            RoutineResult::TransparentLocalDelivery { addr, port } => {
304                ControlFlow::Break(IngressVerdict::TransparentLocalDelivery { addr, port })
305            }
306            RoutineResult::Redirect { dst_port } => {
307                ControlFlow::Break(configure_redirect_nat::<Self, _, _, _, _>(
308                    core_ctx,
309                    bindings_ctx,
310                    table,
311                    conn,
312                    packet,
313                    interfaces,
314                    dst_port,
315                ))
316            }
317            result @ RoutineResult::Masquerade { .. } => {
318                unreachable!("SNAT not supported in INGRESS; got {result:?}")
319            }
320        }
321    }
322
323    fn redirect_addr<P, CC, BT>(
324        core_ctx: &mut CC,
325        packet: &P,
326        ingress: Option<&CC::DeviceId>,
327    ) -> Option<(I::Addr, Option<CachedAddr<I, CC::WeakAddressId>>)>
328    where
329        P: IpPacket<I>,
330        CC: NatContext<I, BT>,
331        BT: FilterBindingsTypes,
332    {
333        let interface = ingress.expect("must have ingress interface in ingress hook");
334        let addr_id = core_ctx
335            .get_local_addr_for_remote(interface, SpecifiedAddr::new(packet.src_addr()))
336            .or_else(|| {
337                warn!(
338                    "cannot redirect because there is no address assigned to the incoming \
339                    interface {interface:?}; dropping packet",
340                );
341                None
342            })?;
343        let addr = addr_id.addr();
344        Some((addr.addr(), Some(CachedAddr::new(addr_id.downgrade()))))
345    }
346
347    fn interface<DeviceId>(interfaces: Interfaces<'_, DeviceId>) -> &DeviceId {
348        let Interfaces { ingress, egress: _ } = interfaces;
349        ingress.expect("ingress interface must be provided to INGRESS hook")
350    }
351}
352
353impl<I: IpExt, R> FilterVerdict<R> for IngressVerdict<I, R> {
354    fn accept(&self) -> Option<&R> {
355        match self {
356            Self::Verdict(Verdict::Accept(result)) => Some(result),
357            Self::Verdict(Verdict::Drop) | Self::TransparentLocalDelivery { .. } => None,
358        }
359    }
360}
361
362impl<I: IpExt, R> IngressVerdict<I, R> {
363    fn into_behavior(self) -> ControlFlow<IngressVerdict<I, ()>, R> {
364        match self {
365            Self::Verdict(v) => match v.into_behavior() {
366                ControlFlow::Continue(r) => ControlFlow::Continue(r),
367                ControlFlow::Break(v) => ControlFlow::Break(v.into()),
368            },
369            Self::TransparentLocalDelivery { addr, port } => {
370                ControlFlow::Break(IngressVerdict::TransparentLocalDelivery { addr, port })
371            }
372        }
373    }
374}
375
376pub(crate) enum LocalEgressHook {}
377
378impl<I: FilterIpExt> NatHook<I> for LocalEgressHook {
379    type Verdict<R: Debug> = Verdict<R>;
380
381    fn verdict_behavior<R: Debug>(v: Self::Verdict<R>) -> ControlFlow<Self::Verdict<()>, R> {
382        v.into_behavior()
383    }
384
385    const NAT_TYPE: NatType = NatType::Destination;
386
387    fn evaluate_result<P, CC, BC>(
388        core_ctx: &mut CC,
389        bindings_ctx: &mut BC,
390        table: &Table<I, NatConfig<I, CC::WeakAddressId>, BC>,
391        conn: &mut ConnectionExclusive<I, NatConfig<I, CC::WeakAddressId>, BC>,
392        packet: &P,
393        interfaces: &Interfaces<'_, CC::DeviceId>,
394        result: RoutineResult<I>,
395    ) -> ControlFlow<Self::Verdict<NatConfigurationResult<I, CC::WeakAddressId, BC>>>
396    where
397        P: FilterIpPacket<I>,
398        CC: NatContext<I, BC>,
399        BC: FilterBindingsContext<CC::DeviceId>,
400    {
401        match result {
402            RoutineResult::Accept | RoutineResult::Return => ControlFlow::Continue(()),
403            RoutineResult::Drop => ControlFlow::Break(Verdict::Drop),
404            result @ RoutineResult::TransparentLocalDelivery { .. } => {
405                unreachable!(
406                    "transparent local delivery is only valid in INGRESS hook; got {result:?}"
407                )
408            }
409            result @ RoutineResult::Masquerade { .. } => {
410                unreachable!("SNAT not supported in LOCAL_EGRESS; got {result:?}")
411            }
412            RoutineResult::Redirect { dst_port } => {
413                ControlFlow::Break(configure_redirect_nat::<Self, _, _, _, _>(
414                    core_ctx,
415                    bindings_ctx,
416                    table,
417                    conn,
418                    packet,
419                    interfaces,
420                    dst_port,
421                ))
422            }
423        }
424    }
425
426    fn redirect_addr<P, CC, BT>(
427        _: &mut CC,
428        _: &P,
429        _: Option<&CC::DeviceId>,
430    ) -> Option<(I::Addr, Option<CachedAddr<I, CC::WeakAddressId>>)>
431    where
432        P: IpPacket<I>,
433        CC: NatContext<I, BT>,
434        BT: FilterBindingsTypes,
435    {
436        Some((*I::LOOPBACK_ADDRESS, None))
437    }
438
439    fn interface<DeviceId>(interfaces: Interfaces<'_, DeviceId>) -> &DeviceId {
440        let Interfaces { ingress: _, egress } = interfaces;
441        egress.expect("egress interface must be provided to LOCAL_EGRESS hook")
442    }
443}
444
445pub(crate) enum LocalIngressHook {}
446
447impl<I: FilterIpExt> NatHook<I> for LocalIngressHook {
448    type Verdict<R: Debug> = Verdict<R>;
449
450    fn verdict_behavior<R: Debug>(v: Self::Verdict<R>) -> ControlFlow<Self::Verdict<()>, R> {
451        v.into_behavior()
452    }
453
454    const NAT_TYPE: NatType = NatType::Source;
455
456    fn evaluate_result<P, CC, BC>(
457        _core_ctx: &mut CC,
458        _bindings_ctx: &mut BC,
459        _table: &Table<I, NatConfig<I, CC::WeakAddressId>, BC>,
460        _conn: &mut ConnectionExclusive<I, NatConfig<I, CC::WeakAddressId>, BC>,
461        _packet: &P,
462        _interfaces: &Interfaces<'_, CC::DeviceId>,
463        result: RoutineResult<I>,
464    ) -> ControlFlow<Self::Verdict<NatConfigurationResult<I, CC::WeakAddressId, BC>>>
465    where
466        P: IpPacket<I>,
467        CC: NatContext<I, BC>,
468        BC: FilterBindingsContext<CC::DeviceId>,
469    {
470        match result {
471            RoutineResult::Accept | RoutineResult::Return => ControlFlow::Continue(()),
472            RoutineResult::Drop => ControlFlow::Break(Verdict::Drop),
473            result @ RoutineResult::Masquerade { .. } => {
474                unreachable!("Masquerade not supported in LOCAL_INGRESS; got {result:?}")
475            }
476            result @ RoutineResult::TransparentLocalDelivery { .. } => {
477                unreachable!(
478                    "transparent local delivery is only valid in INGRESS hook; got {result:?}"
479                )
480            }
481            result @ RoutineResult::Redirect { .. } => {
482                unreachable!("DNAT not supported in LOCAL_INGRESS; got {result:?}")
483            }
484        }
485    }
486
487    fn redirect_addr<P, CC, BT>(
488        _: &mut CC,
489        _: &P,
490        _: Option<&CC::DeviceId>,
491    ) -> Option<(I::Addr, Option<CachedAddr<I, CC::WeakAddressId>>)>
492    where
493        P: IpPacket<I>,
494        CC: NatContext<I, BT>,
495        BT: FilterBindingsTypes,
496    {
497        unreachable!("DNAT not supported in LOCAL_INGRESS; cannot perform redirect action")
498    }
499
500    fn interface<DeviceId>(interfaces: Interfaces<'_, DeviceId>) -> &DeviceId {
501        let Interfaces { ingress, egress: _ } = interfaces;
502        ingress.expect("ingress interface must be provided to LOCAL_INGRESS hook")
503    }
504}
505
506pub(crate) enum EgressHook {}
507
508impl<I: FilterIpExt> NatHook<I> for EgressHook {
509    type Verdict<R: Debug> = Verdict<R>;
510
511    fn verdict_behavior<R: Debug>(v: Self::Verdict<R>) -> ControlFlow<Self::Verdict<()>, R> {
512        v.into_behavior()
513    }
514
515    const NAT_TYPE: NatType = NatType::Source;
516
517    fn evaluate_result<P, CC, BC>(
518        core_ctx: &mut CC,
519        bindings_ctx: &mut BC,
520        table: &Table<I, NatConfig<I, CC::WeakAddressId>, BC>,
521        conn: &mut ConnectionExclusive<I, NatConfig<I, CC::WeakAddressId>, BC>,
522        packet: &P,
523        interfaces: &Interfaces<'_, CC::DeviceId>,
524        result: RoutineResult<I>,
525    ) -> ControlFlow<Self::Verdict<NatConfigurationResult<I, CC::WeakAddressId, BC>>>
526    where
527        P: FilterIpPacket<I>,
528        CC: NatContext<I, BC>,
529        BC: FilterBindingsContext<CC::DeviceId>,
530    {
531        match result {
532            RoutineResult::Accept | RoutineResult::Return => ControlFlow::Continue(()),
533            RoutineResult::Drop => ControlFlow::Break(Verdict::Drop),
534            RoutineResult::Masquerade { src_port } => {
535                ControlFlow::Break(configure_masquerade_nat::<_, _, _, _>(
536                    core_ctx,
537                    bindings_ctx,
538                    table,
539                    conn,
540                    packet,
541                    interfaces,
542                    src_port,
543                ))
544            }
545            result @ RoutineResult::TransparentLocalDelivery { .. } => {
546                unreachable!(
547                    "transparent local delivery is only valid in INGRESS hook; got {result:?}"
548                )
549            }
550            result @ RoutineResult::Redirect { .. } => {
551                unreachable!("DNAT not supported in EGRESS; got {result:?}")
552            }
553        }
554    }
555
556    fn redirect_addr<P, CC, BT>(
557        _: &mut CC,
558        _: &P,
559        _: Option<&CC::DeviceId>,
560    ) -> Option<(I::Addr, Option<CachedAddr<I, CC::WeakAddressId>>)>
561    where
562        P: IpPacket<I>,
563        CC: NatContext<I, BT>,
564        BT: FilterBindingsTypes,
565    {
566        unreachable!("DNAT not supported in EGRESS; cannot perform redirect action")
567    }
568
569    fn interface<DeviceId>(interfaces: Interfaces<'_, DeviceId>) -> &DeviceId {
570        let Interfaces { ingress: _, egress } = interfaces;
571        egress.expect("egress interface must be provided to EGRESS hook")
572    }
573}
574
575impl<I: IpExt, A, BT: FilterBindingsTypes> Connection<I, NatConfig<I, A>, BT> {
576    fn relevant_config(
577        &self,
578        hook_nat_type: NatType,
579        direction: ConnectionDirection,
580    ) -> (&OnceCell<ShouldNat<I, A>>, NatType) {
581        let NatConfig { source, destination } = self.external_data();
582        match (hook_nat_type, direction) {
583            // If either this is a DNAT hook and we are looking at a packet in the
584            // "original" direction, or this is an SNAT hook and we are looking at a reply
585            // packet, then we want to decide whether to NAT based on whether the connection
586            // has DNAT configured.
587            (NatType::Destination, ConnectionDirection::Original)
588            | (NatType::Source, ConnectionDirection::Reply) => (destination, NatType::Destination),
589            // If either this is an SNAT hook and we are looking at a packet in the
590            // "original" direction, or this is a DNAT hook and we are looking at a reply
591            // packet, then we want to decide whether to NAT based on whether the connection
592            // has SNAT configured.
593            (NatType::Source, ConnectionDirection::Original)
594            | (NatType::Destination, ConnectionDirection::Reply) => (source, NatType::Source),
595        }
596    }
597
598    fn relevant_reply_tuple_addr(&self, nat_type: NatType) -> I::Addr {
599        match nat_type {
600            NatType::Destination => self.reply_tuple().src_addr,
601            NatType::Source => self.reply_tuple().dst_addr,
602        }
603    }
604
605    /// Gets a reference to the appropriate NAT config depending on the NAT hook we
606    /// are in and the direction of the packet with respect to its conntrack
607    /// connection.
608    fn nat_config(
609        &self,
610        hook_nat_type: NatType,
611        direction: ConnectionDirection,
612    ) -> Option<&ShouldNat<I, A>> {
613        let (config, _nat_type) = self.relevant_config(hook_nat_type, direction);
614        config.get()
615    }
616
617    /// Sets the appropriate NAT config to the provided value. Which NAT config is
618    /// appropriate depends on the NAT hook we are in and the direction of the
619    /// packet with respect to its conntrack connection.
620    ///
621    /// Returns a reference to the value that was inserted, if the config had not
622    /// yet been initialized; otherwise, returns the existing value of the config,
623    /// along with the type of NAT it is (for diagnostic purposes).
624    fn set_nat_config(
625        &self,
626        hook_nat_type: NatType,
627        direction: ConnectionDirection,
628        value: ShouldNat<I, A>,
629    ) -> Result<&ShouldNat<I, A>, (ShouldNat<I, A>, NatType)> {
630        let (config, nat_type) = self.relevant_config(hook_nat_type, direction);
631        let mut value = Some(value);
632        let config = config.get_or_init(|| value.take().unwrap());
633        match value {
634            None => Ok(config),
635            Some(value) => Err((value, nat_type)),
636        }
637    }
638}
639
640/// The entry point for NAT logic from an IP layer filtering hook.
641///
642/// This function configures NAT, if it has not yet been configured for the
643/// connection, and performs NAT on the provided packet based on the hook and
644/// the connection's NAT configuration.
645pub(crate) fn perform_nat<N, I, P, CC, BC>(
646    core_ctx: &mut CC,
647    bindings_ctx: &mut BC,
648    nat_installed: bool,
649    table: &Table<I, NatConfig<I, CC::WeakAddressId>, BC>,
650    conn: &mut Connection<I, NatConfig<I, CC::WeakAddressId>, BC>,
651    direction: ConnectionDirection,
652    hook: &Hook<I, BC, ()>,
653    packet: &mut P,
654    interfaces: Interfaces<'_, CC::DeviceId>,
655) -> N::Verdict<()>
656where
657    N: NatHook<I>,
658    I: FilterIpExt,
659    P: FilterIpPacket<I>,
660    CC: NatContext<I, BC>,
661    BC: FilterBindingsContext<CC::DeviceId>,
662{
663    if !nat_installed {
664        return Verdict::Accept(()).into();
665    }
666
667    let nat_config = if let Some(nat) = conn.nat_config(N::NAT_TYPE, direction) {
668        nat
669    } else {
670        // NAT has not yet been configured for this connection; traverse the installed
671        // NAT routines in order to configure NAT.
672        let (verdict, exclusive) = match (&mut *conn, direction) {
673            (Connection::Exclusive(_), ConnectionDirection::Reply) => {
674                // This is the first packet in the flow (hence the connection being exclusive),
675                // yet the packet is determined to be in the "reply" direction. This means that
676                // this is a self-connected flow. When a connection's original tuple is the same
677                // as its reply tuple, every packet on the connection is considered to be in the
678                // reply direction, which is an implementation quirk that allows self-connected
679                // flows to be considered immediately "established".
680                //
681                // Handle this by just configuring the connection not to be NATed. It does not
682                // make sense to NAT a self-connected flow since the original and reply
683                // directions are indistinguishable.
684                (Verdict::Accept(NatConfigurationResult::Result(ShouldNat::No)).into(), true)
685            }
686            (Connection::Shared(_), _) => {
687                // In most scenarios, NAT should be configured for every connection before it is
688                // inserted in the conntrack table, at which point it becomes a shared
689                // connection. (This should apply whether or not NAT will actually be performed;
690                // the configuration could be `DoNotNat`.) However, as an optimization, when no
691                // NAT rules have been installed, performing NAT is skipped entirely, which can
692                // result in some connections being finalized without NAT being configured for
693                // them. To handle this, just don't NAT the connection.
694                (Verdict::Accept(NatConfigurationResult::Result(ShouldNat::No)).into(), false)
695            }
696            (Connection::Exclusive(conn), ConnectionDirection::Original) => {
697                let verdict = configure_nat::<N, _, _, _, _>(
698                    core_ctx,
699                    bindings_ctx,
700                    table,
701                    conn,
702                    hook,
703                    packet,
704                    interfaces.clone(),
705                );
706                // Configure source port remapping for a connection by default even if its first
707                // packet does not match any NAT rules, in order to ensure that source ports for
708                // locally-generated traffic do not clash with ports used by existing NATed
709                // connections (such as for forwarded masqueraded traffic).
710                let verdict = if matches!(
711                    verdict.accept(),
712                    Some(&NatConfigurationResult::Result(ShouldNat::No))
713                ) && N::NAT_TYPE == NatType::Source
714                {
715                    configure_snat_port(
716                        bindings_ctx,
717                        table,
718                        conn,
719                        None, /* src_port_range */
720                        ConflictStrategy::AdoptExisting,
721                    )
722                    .into()
723                } else {
724                    verdict
725                };
726                (verdict, true)
727            }
728        };
729
730        let result = match N::verdict_behavior(verdict) {
731            ControlFlow::Break(verdict) => {
732                return verdict;
733            }
734            ControlFlow::Continue(result) => result,
735        };
736        let new_nat_config = match result {
737            NatConfigurationResult::Result(config) => Some(config),
738            NatConfigurationResult::AdoptExisting(existing) => {
739                *conn = existing;
740                None
741            }
742        };
743        if let Some(config) = new_nat_config {
744            conn.set_nat_config(N::NAT_TYPE, direction, config).unwrap_or_else(
745                |(value, nat_type)| {
746                    // We can only assert that NAT has not been configured yet if we are configuring
747                    // NAT on an exclusive connection. If the connection has already been inserted
748                    // in the table and we are holding a shared reference to it, we could race with
749                    // another thread also configuring NAT for the connection.
750                    if exclusive {
751                        unreachable!(
752                            "{nat_type:?} NAT should not have been configured yet, but found \
753                            {value:?}"
754                        );
755                    }
756                    &ShouldNat::No
757                },
758            )
759        } else {
760            conn.nat_config(N::NAT_TYPE, direction).unwrap_or(&ShouldNat::No)
761        }
762    };
763
764    match nat_config {
765        ShouldNat::No => return Verdict::Accept(()).into(),
766        // If we are NATing this connection, but are not using an IP address that is
767        // dynamically assigned to an interface to do so, continue to rewrite the
768        // packet.
769        ShouldNat::Yes(None) => {}
770        // If we are NATing to an IP address assigned to an interface, ensure that the
771        // address is still valid before performing NAT.
772        ShouldNat::Yes(Some(cached_addr)) => {
773            // Only check the validity of the cached address if we are looking at a packet
774            // in the original direction. Packets in the reply direction are rewritten based
775            // on the original tuple, which is unchanged by NAT and therefore not necessary
776            // to check for validity.
777            if direction == ConnectionDirection::Original {
778                match cached_addr.validate_or_replace(
779                    core_ctx,
780                    N::interface(interfaces),
781                    conn.relevant_reply_tuple_addr(N::NAT_TYPE),
782                ) {
783                    Verdict::Accept(()) => {}
784                    Verdict::Drop => return Verdict::Drop.into(),
785                }
786            }
787        }
788    }
789    rewrite_packet(conn, direction, N::NAT_TYPE, packet).into()
790}
791
792/// Configure NAT by rewriting the provided reply tuple of a connection.
793///
794/// Evaluates the NAT routines at the provided hook and, on finding a rule that
795/// matches the provided packet, configures NAT based on the rule's action. Note
796/// that because NAT routines can contain a superset of the rules filter
797/// routines can, it's possible for this packet to hit a non-NAT action.
798fn configure_nat<N, I, P, CC, BC>(
799    core_ctx: &mut CC,
800    bindings_ctx: &mut BC,
801    table: &Table<I, NatConfig<I, CC::WeakAddressId>, BC>,
802    conn: &mut ConnectionExclusive<I, NatConfig<I, CC::WeakAddressId>, BC>,
803    hook: &Hook<I, BC, ()>,
804    packet: &P,
805    interfaces: Interfaces<'_, CC::DeviceId>,
806) -> N::Verdict<NatConfigurationResult<I, CC::WeakAddressId, BC>>
807where
808    N: NatHook<I>,
809    I: FilterIpExt,
810    P: FilterIpPacket<I>,
811    CC: NatContext<I, BC>,
812    BC: FilterBindingsContext<CC::DeviceId>,
813{
814    let Hook { routines } = hook;
815    for routine in routines {
816        let result =
817            super::check_routine(&routine, packet, interfaces, &mut FakePacketMetadata::default());
818        match N::evaluate_result(core_ctx, bindings_ctx, table, conn, packet, &interfaces, result) {
819            ControlFlow::Break(result) => return result,
820            ControlFlow::Continue(()) => {}
821        }
822    }
823    Verdict::Accept(NatConfigurationResult::Result(ShouldNat::No)).into()
824}
825
826/// Configure Redirect NAT, a special case of DNAT that redirects the packet to
827/// the local host.
828fn configure_redirect_nat<N, I, P, CC, BC>(
829    core_ctx: &mut CC,
830    bindings_ctx: &mut BC,
831    table: &Table<I, NatConfig<I, CC::WeakAddressId>, BC>,
832    conn: &mut ConnectionExclusive<I, NatConfig<I, CC::WeakAddressId>, BC>,
833    packet: &P,
834    interfaces: &Interfaces<'_, CC::DeviceId>,
835    dst_port_range: Option<RangeInclusive<NonZeroU16>>,
836) -> N::Verdict<NatConfigurationResult<I, CC::WeakAddressId, BC>>
837where
838    N: NatHook<I>,
839    I: FilterIpExt,
840    P: FilterIpPacket<I>,
841    CC: NatContext<I, BC>,
842    BC: FilterBindingsContext<CC::DeviceId>,
843{
844    match N::NAT_TYPE {
845        NatType::Source => panic!("DNAT action called from SNAT-only hook"),
846        NatType::Destination => {}
847    }
848
849    // Choose an appropriate new destination address and, optionally, port. Then
850    // rewrite the source address/port of the reply tuple for the connection to use
851    // as the guide for future packet rewriting.
852    //
853    // If we are in INGRESS, use the primary address of the incoming interface; if
854    // we are in LOCAL_EGRESS, use the loopback address.
855    let Some((addr, cached_addr)) = N::redirect_addr(core_ctx, packet, interfaces.ingress) else {
856        return Verdict::Drop.into();
857    };
858    conn.rewrite_reply_src_addr(addr);
859
860    let Some(range) = dst_port_range else {
861        return Verdict::Accept(NatConfigurationResult::Result(ShouldNat::Yes(cached_addr))).into();
862    };
863    match rewrite_reply_tuple_port(
864        bindings_ctx,
865        table,
866        conn,
867        ReplyTuplePort::Source,
868        range,
869        true, /* ensure_port_in_range */
870        ConflictStrategy::RewritePort,
871    ) {
872        // We are already NATing the address, so even if NATing the port is unnecessary,
873        // the connection as a whole still needs to be NATed.
874        Verdict::Accept(
875            NatConfigurationResult::Result(ShouldNat::Yes(_))
876            | NatConfigurationResult::Result(ShouldNat::No),
877        ) => Verdict::Accept(NatConfigurationResult::Result(ShouldNat::Yes(cached_addr))),
878        Verdict::Accept(NatConfigurationResult::AdoptExisting(_)) => {
879            unreachable!("cannot adopt existing connection")
880        }
881        Verdict::Drop => Verdict::Drop,
882    }
883    .into()
884}
885
886/// Configure Masquerade NAT, a special case of SNAT that rewrites the source IP
887/// address of the packet to an address that is assigned to the outgoing
888/// interface.
889fn configure_masquerade_nat<I, P, CC, BC>(
890    core_ctx: &mut CC,
891    bindings_ctx: &mut BC,
892    table: &Table<I, NatConfig<I, CC::WeakAddressId>, BC>,
893    conn: &mut ConnectionExclusive<I, NatConfig<I, CC::WeakAddressId>, BC>,
894    packet: &P,
895    interfaces: &Interfaces<'_, CC::DeviceId>,
896    src_port_range: Option<RangeInclusive<NonZeroU16>>,
897) -> Verdict<NatConfigurationResult<I, CC::WeakAddressId, BC>>
898where
899    I: FilterIpExt,
900    P: FilterIpPacket<I>,
901    CC: NatContext<I, BC>,
902    BC: FilterBindingsContext<CC::DeviceId>,
903{
904    // Choose an appropriate new source address and, optionally, port. Then rewrite
905    // the destination address/port of the reply tuple for the connection to use as
906    // the guide for future packet rewriting.
907    let interface = interfaces.egress.expect(
908        "must have egress interface in EGRESS hook; Masquerade NAT is only valid in EGRESS",
909    );
910    let Some(addr) =
911        core_ctx.get_local_addr_for_remote(interface, SpecifiedAddr::new(packet.dst_addr()))
912    else {
913        // TODO(https://fxbug.dev/372549231): add a counter for this scenario.
914        warn!(
915            "cannot masquerade because there is no address assigned to the outgoing interface \
916            {interface:?}; dropping packet",
917        );
918        return Verdict::Drop;
919    };
920    conn.rewrite_reply_dst_addr(addr.addr().addr());
921
922    // Rewrite the source port if necessary to avoid conflicting with existing
923    // tracked connections.
924    match configure_snat_port(
925        bindings_ctx,
926        table,
927        conn,
928        src_port_range,
929        ConflictStrategy::RewritePort,
930    ) {
931        // We are already NATing the address, so even if NATing the port is unnecessary,
932        // the connection as a whole still needs to be NATed.
933        Verdict::Accept(
934            NatConfigurationResult::Result(ShouldNat::Yes(_))
935            | NatConfigurationResult::Result(ShouldNat::No),
936        ) => Verdict::Accept(NatConfigurationResult::Result(ShouldNat::Yes(Some(
937            CachedAddr::new(addr.downgrade()),
938        )))),
939        Verdict::Accept(NatConfigurationResult::AdoptExisting(_)) => {
940            unreachable!("cannot adopt existing connection")
941        }
942        Verdict::Drop => Verdict::Drop,
943    }
944}
945
946fn configure_snat_port<I, A, BC, D>(
947    bindings_ctx: &mut BC,
948    table: &Table<I, NatConfig<I, A>, BC>,
949    conn: &mut ConnectionExclusive<I, NatConfig<I, A>, BC>,
950    src_port_range: Option<RangeInclusive<NonZeroU16>>,
951    conflict_strategy: ConflictStrategy,
952) -> Verdict<NatConfigurationResult<I, A, BC>>
953where
954    I: IpExt,
955    BC: FilterBindingsContext<D>,
956    A: PartialEq,
957{
958    // Rewrite the source port if necessary to avoid conflicting with existing
959    // tracked connections. If a source port range was specified, we also ensure the
960    // port is in that range; otherwise, we attempt to rewrite into a "similar"
961    // range to the current value, and only if required to avoid a conflict.
962    let (range, ensure_port_in_range) = if let Some(range) = src_port_range {
963        (range, true)
964    } else {
965        let reply_tuple = conn.reply_tuple();
966        let Some(range) =
967            similar_port_or_id_range(reply_tuple.protocol, reply_tuple.dst_port_or_id)
968        else {
969            return Verdict::Drop;
970        };
971        (range, false)
972    };
973    rewrite_reply_tuple_port(
974        bindings_ctx,
975        table,
976        conn,
977        ReplyTuplePort::Destination,
978        range,
979        ensure_port_in_range,
980        conflict_strategy,
981    )
982}
983
984/// Choose a range of "similar" values to which transport-layer port or ID can
985/// be rewritten -- that is, a value that is likely to be similar in terms of
986/// privilege, or lack thereof.
987///
988/// The heuristics used in this function are chosen to roughly match those used
989/// by Netstack2/gVisor and Linux.
990fn similar_port_or_id_range(
991    protocol: TransportProtocol,
992    port_or_id: u16,
993) -> Option<RangeInclusive<NonZeroU16>> {
994    match protocol {
995        TransportProtocol::Tcp | TransportProtocol::Udp => Some(match port_or_id {
996            _ if port_or_id < 512 => NonZeroU16::MIN..=NonZeroU16::new(511).unwrap(),
997            _ if port_or_id < 1024 => NonZeroU16::MIN..=NonZeroU16::new(1023).unwrap(),
998            _ => NonZeroU16::new(1024).unwrap()..=NonZeroU16::MAX,
999        }),
1000        // TODO(https://fxbug.dev/341128580): allow rewriting ICMP echo ID to zero.
1001        TransportProtocol::Icmp => Some(NonZeroU16::MIN..=NonZeroU16::MAX),
1002        TransportProtocol::Other(p) => {
1003            error!(
1004                "cannot rewrite port or ID of unsupported transport protocol {p}; dropping packet"
1005            );
1006            None
1007        }
1008    }
1009}
1010
1011#[derive(Clone, Copy)]
1012enum ReplyTuplePort {
1013    Source,
1014    Destination,
1015}
1016
1017enum ConflictStrategy {
1018    AdoptExisting,
1019    RewritePort,
1020}
1021
1022/// Attempt to rewrite the indicated port of the reply tuple of the provided
1023/// connection such that it results in a unique tuple, and, if
1024/// `ensure_port_in_range` is `true`, also that it fits in the specified range.
1025fn rewrite_reply_tuple_port<I, A, BC, D>(
1026    bindings_ctx: &mut BC,
1027    table: &Table<I, NatConfig<I, A>, BC>,
1028    conn: &mut ConnectionExclusive<I, NatConfig<I, A>, BC>,
1029    which_port: ReplyTuplePort,
1030    port_range: RangeInclusive<NonZeroU16>,
1031    ensure_port_in_range: bool,
1032    conflict_strategy: ConflictStrategy,
1033) -> Verdict<NatConfigurationResult<I, A, BC>>
1034where
1035    I: IpExt,
1036    A: PartialEq,
1037    BC: FilterBindingsContext<D>,
1038{
1039    // We only need to rewrite the port if the reply tuple of the connection
1040    // conflicts with another connection in the table, or if the port must be
1041    // rewritten to fall in the specified range.
1042    let current_port = match which_port {
1043        ReplyTuplePort::Source => conn.reply_tuple().src_port_or_id,
1044        ReplyTuplePort::Destination => conn.reply_tuple().dst_port_or_id,
1045    };
1046    let already_in_range = !ensure_port_in_range
1047        || NonZeroU16::new(current_port).map(|port| port_range.contains(&port)).unwrap_or(false);
1048    if already_in_range {
1049        match table.get_shared_connection(conn.reply_tuple()) {
1050            None => return Verdict::Accept(NatConfigurationResult::Result(ShouldNat::No)),
1051            Some(conflict) => match conflict_strategy {
1052                ConflictStrategy::AdoptExisting => {
1053                    // If this connection is identical to the conflicting one that's already in the
1054                    // table, including both its original and reply tuple and its NAT configuration,
1055                    // simply adopt the existing one rather than attempting port remapping.
1056                    if conflict.compatible_with(&*conn) {
1057                        return Verdict::Accept(NatConfigurationResult::AdoptExisting(
1058                            Connection::Shared(conflict),
1059                        ));
1060                    }
1061                }
1062                ConflictStrategy::RewritePort => {}
1063            },
1064        }
1065    }
1066
1067    // Attempt to find a new port in the provided range that results in a unique
1068    // tuple. Start by selecting a random offset into the target range, and from
1069    // there increment the candidate port, wrapping around until all ports in
1070    // the range have been checked (or MAX_ATTEMPTS has been exceeded).
1071    //
1072    // As soon as we find a port that would result in a unique tuple, stop and
1073    // accept the packet. If we search the entire range and fail to find a port
1074    // that creates a unique tuple, drop the packet.
1075    const MAX_ATTEMPTS: u16 = 128;
1076    let len = port_range.end().get() - port_range.start().get() + 1;
1077    let mut rng = bindings_ctx.rng();
1078    let start = rng.random_range(port_range.start().get()..=port_range.end().get());
1079    for i in 0..core::cmp::min(MAX_ATTEMPTS, len) {
1080        // `offset` is <= the size of `port_range`, which is a range of `NonZerou16`, so
1081        // `port_range.start()` + `offset` is guaranteed to fit in a `NonZeroU16`.
1082        let offset = (start + i) % len;
1083        let new_port = port_range.start().checked_add(offset).unwrap();
1084        match which_port {
1085            ReplyTuplePort::Source => conn.rewrite_reply_src_port_or_id(new_port.get()),
1086            ReplyTuplePort::Destination => conn.rewrite_reply_dst_port_or_id(new_port.get()),
1087        };
1088        if !table.contains_tuple(conn.reply_tuple()) {
1089            return Verdict::Accept(NatConfigurationResult::Result(ShouldNat::Yes(None)));
1090        }
1091    }
1092
1093    Verdict::Drop
1094}
1095
1096fn rewrite_packet_for_dst_nat<I, P>(
1097    packet: &mut P,
1098    new_dst_addr: I::Addr,
1099    new_dst_port: u16,
1100) -> Verdict
1101where
1102    I: FilterIpExt,
1103    P: IpPacket<I>,
1104{
1105    packet.set_dst_addr(new_dst_addr);
1106    let Some(proto) = packet.protocol() else {
1107        return Verdict::Accept(());
1108    };
1109    let mut transport = packet.transport_packet_mut();
1110    let Some(mut transport) = transport.transport_packet_mut() else {
1111        return Verdict::Accept(());
1112    };
1113    let Some(new_dst_port) = NonZeroU16::new(new_dst_port) else {
1114        // TODO(https://fxbug.dev/341128580): allow rewriting port to zero if
1115        // allowed by the transport-layer protocol.
1116        error!("cannot rewrite dst port to unspecified; dropping {proto} packet");
1117        return Verdict::Drop;
1118    };
1119    transport.set_dst_port(new_dst_port);
1120
1121    Verdict::Accept(())
1122}
1123
1124fn rewrite_packet_for_src_nat<I, P>(
1125    packet: &mut P,
1126    new_src_addr: I::Addr,
1127    new_src_port: u16,
1128) -> Verdict
1129where
1130    I: FilterIpExt,
1131    P: IpPacket<I>,
1132{
1133    packet.set_src_addr(new_src_addr);
1134    let Some(proto) = packet.protocol() else {
1135        return Verdict::Accept(());
1136    };
1137    let mut transport = packet.transport_packet_mut();
1138    let Some(mut transport) = transport.transport_packet_mut() else {
1139        return Verdict::Accept(());
1140    };
1141    let Some(new_src_port) = NonZeroU16::new(new_src_port) else {
1142        // TODO(https://fxbug.dev/341128580): allow rewriting port to zero if
1143        // allowed by the transport-layer protocol.
1144        error!("cannot rewrite src port to unspecified; dropping {proto} packet");
1145        return Verdict::Drop;
1146    };
1147    transport.set_src_port(new_src_port);
1148
1149    Verdict::Accept(())
1150}
1151
1152fn rewrite_icmp_error_payload<I, E>(icmp_error: &mut E, nat: NatType, tuple: &Tuple<I>) -> Verdict
1153where
1154    I: FilterIpExt,
1155    E: IcmpErrorMut<I>,
1156{
1157    // What follows is a justification for how ICMP error NAT decides what
1158    // addresses and ports to use.  We will only consider the address rewriting,
1159    // because ports necessarily must be rewritten in the same way.
1160    //
1161    // When rewriting the IP payload of ICMP errors, we know the following:
1162    //
1163    // 1. The payload was sent in the opposite direction from the error.
1164    // 2. The source of the payload is the same as the destination of the error.
1165    //
1166    // Imagine the following scenario:
1167    //
1168    // A --- R --- B
1169    //
1170    // Where SNAT is configured on R for packets originating at A. When packets
1171    // are sent in the original direction, their source will be rewritten from A
1172    // to R. The conntrack tuples will look like:
1173    //
1174    // Original: {
1175    //   src: A,
1176    //   dst: B,
1177    // }
1178    //
1179    // Reply: {
1180    //   src: B,
1181    //   dst: R,
1182    // }
1183    //
1184    // If B sends an error in response to a packet from A (meaning the reply
1185    // direction), it will look like:
1186    //
1187    //   B -> R | R -> B
1188    //
1189    // When performing SNAT, we need to perform the same rewrite for the
1190    // destination of the outer packet as well as the source of the inner
1191    // packet.
1192    //
1193    // In the opposite direction (original), if A sends an error in response to
1194    // a packet from B, it will look like:
1195    //
1196    //   A -> B | B -> A
1197    //
1198    // When performing SNAT, we need to perform the same rewrite to the source
1199    // of the outer packet and the destination of the inner packet.
1200    //
1201    // Conversely, let's look at DNAT. Imagine the case above was such that
1202    // R has DNAT configured to map traffic originally headed for R to B. The
1203    // tuples would look as follows:
1204    //
1205    // Original: {
1206    //   src: A,
1207    //   dst: R,
1208    // }
1209    //
1210    // Reply: {
1211    //   src: B,
1212    //   dst: A,
1213    // }
1214    //
1215    // An error response from B to A (reply) will look like:
1216    //
1217    // B -> A | A -> B
1218    //
1219    // An error response from A to B (original) will look like:
1220    //
1221    // A -> R | R -> A
1222    //
1223    // Note that we still need to rewrite the inner source the same as the outer
1224    // destination and the inner destination the same as the outer source. This
1225    // means we can use the same source and destination address and port for
1226    // rewriting the inner packet as well as the outer packet, but just need to
1227    // invert whether we're rewriting the source or destination information.
1228    let should_recalculate_checksum = match icmp_error.inner_packet() {
1229        Some(mut inner_packet) => {
1230            let verdict = match nat {
1231                NatType::Destination => rewrite_packet_for_src_nat(
1232                    &mut inner_packet,
1233                    tuple.src_addr,
1234                    tuple.src_port_or_id,
1235                ),
1236                NatType::Source => rewrite_packet_for_dst_nat(
1237                    &mut inner_packet,
1238                    tuple.dst_addr,
1239                    tuple.dst_port_or_id,
1240                ),
1241            };
1242
1243            match verdict {
1244                Verdict::Accept(_) => true,
1245                Verdict::Drop => return Verdict::Drop,
1246            }
1247        }
1248        None => false,
1249    };
1250
1251    if should_recalculate_checksum {
1252        // If updating the checksum fails, the packet will be left with the
1253        // original checksum, which will be incorrect. At the time of writing,
1254        // the only way for this to happen is if the length of an ICMPv6 is
1255        // larger than a u32, which is not possible for NS3 (nor most networks)
1256        // to handle.
1257        if !icmp_error.recalculate_checksum() {
1258            return Verdict::Drop;
1259        }
1260    }
1261
1262    Verdict::Accept(())
1263}
1264
1265/// Perform NAT on a packet, using its connection in the conntrack table as a
1266/// guide.
1267fn rewrite_packet<I, P, A, BT>(
1268    conn: &Connection<I, NatConfig<I, A>, BT>,
1269    direction: ConnectionDirection,
1270    nat: NatType,
1271    packet: &mut P,
1272) -> Verdict
1273where
1274    I: FilterIpExt,
1275    P: IpPacket<I>,
1276    BT: FilterBindingsTypes,
1277{
1278    // If this packet is in the "original" direction of the connection, rewrite its
1279    // address and port from the connection's reply tuple. If this is a reply
1280    // packet, rewrite it using the *original* tuple so the traffic is seen to
1281    // originate from or be destined to the expected peer.
1282    //
1283    // The reply tuple functions both as a way to mark what we expect to see coming
1284    // in, *and* a way to stash what the NAT remapping should be.
1285    let tuple = match direction {
1286        ConnectionDirection::Original => conn.reply_tuple(),
1287        ConnectionDirection::Reply => conn.original_tuple(),
1288    };
1289
1290    if let Some(mut icmp_error) = packet.icmp_error_mut().icmp_error_mut() {
1291        match rewrite_icmp_error_payload(&mut icmp_error, nat, tuple) {
1292            Verdict::Accept(_) => (),
1293            Verdict::Drop => return Verdict::Drop,
1294        }
1295    }
1296
1297    match nat {
1298        NatType::Destination => {
1299            rewrite_packet_for_dst_nat(packet, tuple.src_addr, tuple.src_port_or_id)
1300        }
1301        NatType::Source => rewrite_packet_for_src_nat(packet, tuple.dst_addr, tuple.dst_port_or_id),
1302    }
1303}
1304
1305#[cfg(test)]
1306mod tests {
1307    use alloc::sync::Arc;
1308    use alloc::vec;
1309    use core::marker::PhantomData;
1310
1311    use assert_matches::assert_matches;
1312    use ip_test_macro::ip_test;
1313    use net_types::ip::{AddrSubnet, Ipv4};
1314    use netstack3_base::testutil::FakeMatcherDeviceId;
1315    use netstack3_base::{IntoCoreTimerCtx, TimerContext};
1316    use packet::{EmptyBuf, PacketBuilder, Serializer};
1317    use packet_formats::ip::{IpPacketBuilder, IpProto};
1318    use packet_formats::udp::UdpPacketBuilder;
1319    use test_case::{test_case, test_matrix};
1320
1321    use super::*;
1322    use crate::conntrack::Tuple;
1323    use crate::context::testutil::{
1324        FakeBindingsCtx, FakeNatCtx, FakePrimaryAddressId, FakeWeakAddressId,
1325    };
1326    use crate::matchers::PacketMatcher;
1327    use crate::packets::testutil::internal::{
1328        ArbitraryValue, FakeIpPacket, FakeUdpPacket, IcmpErrorMessage, Icmpv4DestUnreachableError,
1329        Icmpv6DestUnreachableError,
1330    };
1331    use crate::state::{Action, Routine, Rule, TransparentProxy};
1332    use crate::testutil::TestIpExt;
1333
1334    impl<I: IpExt, A: PartialEq, BT: FilterBindingsTypes> PartialEq
1335        for NatConfigurationResult<I, A, BT>
1336    {
1337        fn eq(&self, other: &Self) -> bool {
1338            match (self, other) {
1339                (Self::Result(lhs), Self::Result(rhs)) => lhs == rhs,
1340                (Self::AdoptExisting(_), Self::AdoptExisting(_)) => {
1341                    panic!("equality check for connections is not supported")
1342                }
1343                _ => false,
1344            }
1345        }
1346    }
1347
1348    impl<I, A, BC> ConnectionExclusive<I, NatConfig<I, A>, BC>
1349    where
1350        BC: FilterBindingsTypes + TimerContext,
1351        I: FilterIpExt,
1352    {
1353        fn from_packet<P: IpPacket<I>>(bindings_ctx: &BC, packet: &P) -> Self {
1354            ConnectionExclusive::from_deconstructed_packet(
1355                bindings_ctx,
1356                &packet.conntrack_packet().unwrap(),
1357            )
1358            .expect("create conntrack entry")
1359        }
1360    }
1361
1362    impl<A, BC> ConnectionExclusive<Ipv4, NatConfig<Ipv4, A>, BC>
1363    where
1364        BC: FilterBindingsTypes + TimerContext,
1365    {
1366        fn with_reply_tuple(bindings_ctx: &BC, which: ReplyTuplePort, port: u16) -> Self {
1367            Self::from_packet(bindings_ctx, &packet_with_port(which, port).reply())
1368        }
1369    }
1370
1371    fn packet_with_port(which: ReplyTuplePort, port: u16) -> FakeIpPacket<Ipv4, FakeUdpPacket> {
1372        let mut packet = FakeIpPacket::<Ipv4, FakeUdpPacket>::arbitrary_value();
1373        match which {
1374            ReplyTuplePort::Source => packet.body.src_port = port,
1375            ReplyTuplePort::Destination => packet.body.dst_port = port,
1376        }
1377        packet
1378    }
1379
1380    fn tuple_with_port(which: ReplyTuplePort, port: u16) -> Tuple<Ipv4> {
1381        packet_with_port(which, port).conntrack_packet().unwrap().tuple()
1382    }
1383
1384    #[test]
1385    fn accept_by_default_if_no_matching_rules_in_hook() {
1386        let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
1387        let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1388        let mut core_ctx = FakeNatCtx::default();
1389        let packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1390        let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
1391
1392        assert_eq!(
1393            configure_nat::<LocalEgressHook, _, _, _, _>(
1394                &mut core_ctx,
1395                &mut bindings_ctx,
1396                &conntrack,
1397                &mut conn,
1398                &Hook::default(),
1399                &packet,
1400                Interfaces { ingress: None, egress: None },
1401            ),
1402            Verdict::Accept(NatConfigurationResult::Result(ShouldNat::No))
1403        );
1404    }
1405
1406    #[test]
1407    fn accept_by_default_if_return_from_routine() {
1408        let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
1409        let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1410        let mut core_ctx = FakeNatCtx::default();
1411        let packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1412        let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
1413
1414        let hook = Hook {
1415            routines: vec![Routine {
1416                rules: vec![Rule::new(PacketMatcher::default(), Action::Return)],
1417            }],
1418        };
1419        assert_eq!(
1420            configure_nat::<LocalEgressHook, _, _, _, _>(
1421                &mut core_ctx,
1422                &mut bindings_ctx,
1423                &conntrack,
1424                &mut conn,
1425                &hook,
1426                &packet,
1427                Interfaces { ingress: None, egress: None },
1428            ),
1429            Verdict::Accept(NatConfigurationResult::Result(ShouldNat::No))
1430        );
1431    }
1432
1433    #[test]
1434    fn accept_terminal_for_installed_routine() {
1435        let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
1436        let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1437        let mut core_ctx = FakeNatCtx::default();
1438        let packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1439        let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
1440
1441        // The first installed routine should terminate at its `Accept` result.
1442        let routine = Routine {
1443            rules: vec![
1444                // Accept all traffic.
1445                Rule::new(PacketMatcher::default(), Action::Accept),
1446                // Drop all traffic.
1447                Rule::new(PacketMatcher::default(), Action::Drop),
1448            ],
1449        };
1450        assert_eq!(
1451            configure_nat::<LocalEgressHook, _, _, _, _>(
1452                &mut core_ctx,
1453                &mut bindings_ctx,
1454                &conntrack,
1455                &mut conn,
1456                &Hook { routines: vec![routine.clone()] },
1457                &packet,
1458                Interfaces { ingress: None, egress: None },
1459            ),
1460            Verdict::Accept(NatConfigurationResult::Result(ShouldNat::No))
1461        );
1462
1463        // The first installed routine should terminate at its `Accept` result, but the
1464        // hook should terminate at the `Drop` result in the second routine.
1465        let hook = Hook {
1466            routines: vec![
1467                routine,
1468                Routine {
1469                    rules: vec![
1470                        // Drop all traffic.
1471                        Rule::new(PacketMatcher::default(), Action::Drop),
1472                    ],
1473                },
1474            ],
1475        };
1476        assert_eq!(
1477            configure_nat::<LocalEgressHook, _, _, _, _>(
1478                &mut core_ctx,
1479                &mut bindings_ctx,
1480                &conntrack,
1481                &mut conn,
1482                &hook,
1483                &packet,
1484                Interfaces { ingress: None, egress: None },
1485            ),
1486            Verdict::Drop
1487        );
1488    }
1489
1490    #[test]
1491    fn drop_terminal_for_entire_hook() {
1492        let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
1493        let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1494        let mut core_ctx = FakeNatCtx::default();
1495        let packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1496        let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
1497
1498        let hook = Hook {
1499            routines: vec![
1500                Routine {
1501                    rules: vec![
1502                        // Drop all traffic.
1503                        Rule::new(PacketMatcher::default(), Action::Drop),
1504                    ],
1505                },
1506                Routine {
1507                    rules: vec![
1508                        // Accept all traffic.
1509                        Rule::new(PacketMatcher::default(), Action::Accept),
1510                    ],
1511                },
1512            ],
1513        };
1514
1515        assert_eq!(
1516            configure_nat::<LocalEgressHook, _, _, _, _>(
1517                &mut core_ctx,
1518                &mut bindings_ctx,
1519                &conntrack,
1520                &mut conn,
1521                &hook,
1522                &packet,
1523                Interfaces { ingress: None, egress: None },
1524            ),
1525            Verdict::Drop
1526        );
1527    }
1528
1529    #[test]
1530    fn transparent_proxy_terminal_for_entire_hook() {
1531        let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
1532        let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1533        let mut core_ctx = FakeNatCtx::default();
1534        let packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1535        let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
1536
1537        let ingress = Hook {
1538            routines: vec![
1539                Routine {
1540                    rules: vec![Rule::new(
1541                        PacketMatcher::default(),
1542                        Action::TransparentProxy(TransparentProxy::LocalPort(LOCAL_PORT)),
1543                    )],
1544                },
1545                Routine {
1546                    rules: vec![
1547                        // Accept all traffic.
1548                        Rule::new(PacketMatcher::default(), Action::Accept),
1549                    ],
1550                },
1551            ],
1552        };
1553
1554        assert_eq!(
1555            configure_nat::<IngressHook, _, _, _, _>(
1556                &mut core_ctx,
1557                &mut bindings_ctx,
1558                &conntrack,
1559                &mut conn,
1560                &ingress,
1561                &packet,
1562                Interfaces { ingress: None, egress: None },
1563            ),
1564            IngressVerdict::TransparentLocalDelivery {
1565                addr: <Ipv4 as crate::packets::testutil::internal::TestIpExt>::DST_IP,
1566                port: LOCAL_PORT
1567            }
1568        );
1569    }
1570
1571    #[test]
1572    fn redirect_terminal_for_entire_hook() {
1573        let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
1574        let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1575        let mut core_ctx = FakeNatCtx::default();
1576        let packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1577        let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
1578
1579        let hook = Hook {
1580            routines: vec![
1581                Routine {
1582                    rules: vec![
1583                        // Redirect all traffic.
1584                        Rule::new(PacketMatcher::default(), Action::Redirect { dst_port: None }),
1585                    ],
1586                },
1587                Routine {
1588                    rules: vec![
1589                        // Drop all traffic.
1590                        Rule::new(PacketMatcher::default(), Action::Drop),
1591                    ],
1592                },
1593            ],
1594        };
1595
1596        assert_eq!(
1597            configure_nat::<LocalEgressHook, _, _, _, _>(
1598                &mut core_ctx,
1599                &mut bindings_ctx,
1600                &conntrack,
1601                &mut conn,
1602                &hook,
1603                &packet,
1604                Interfaces { ingress: None, egress: None },
1605            ),
1606            Verdict::Accept(NatConfigurationResult::Result(ShouldNat::Yes(None)))
1607        );
1608    }
1609
1610    #[ip_test(I)]
1611    fn masquerade_terminal_for_entire_hook<I: TestIpExt>() {
1612        let mut bindings_ctx = FakeBindingsCtx::<I>::new();
1613        let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1614        let assigned_addr = AddrSubnet::new(I::SRC_IP_2, I::SUBNET.prefix()).unwrap();
1615        let mut core_ctx =
1616            FakeNatCtx::new([(FakeMatcherDeviceId::ethernet_interface(), assigned_addr)]);
1617        let packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1618        let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
1619
1620        let hook = Hook {
1621            routines: vec![
1622                Routine {
1623                    rules: vec![
1624                        // Masquerade all traffic.
1625                        Rule::new(PacketMatcher::default(), Action::Masquerade { src_port: None }),
1626                    ],
1627                },
1628                Routine {
1629                    rules: vec![
1630                        // Drop all traffic.
1631                        Rule::new(PacketMatcher::default(), Action::Drop),
1632                    ],
1633                },
1634            ],
1635        };
1636
1637        assert_matches!(
1638            configure_nat::<EgressHook, _, _, _, _>(
1639                &mut core_ctx,
1640                &mut bindings_ctx,
1641                &conntrack,
1642                &mut conn,
1643                &hook,
1644                &packet,
1645                Interfaces {
1646                    ingress: None,
1647                    egress: Some(&FakeMatcherDeviceId::ethernet_interface())
1648                },
1649            ),
1650            Verdict::Accept(NatConfigurationResult::Result(ShouldNat::Yes(Some(_))))
1651        );
1652    }
1653
1654    #[test]
1655    fn redirect_ingress_drops_packet_if_no_assigned_address() {
1656        let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
1657        let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1658        let mut core_ctx = FakeNatCtx::default();
1659        let packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1660        let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
1661
1662        let hook = Hook {
1663            routines: vec![Routine {
1664                rules: vec![Rule::new(
1665                    PacketMatcher::default(),
1666                    Action::Redirect { dst_port: None },
1667                )],
1668            }],
1669        };
1670
1671        assert_eq!(
1672            configure_nat::<IngressHook, _, _, _, _>(
1673                &mut core_ctx,
1674                &mut bindings_ctx,
1675                &conntrack,
1676                &mut conn,
1677                &hook,
1678                &packet,
1679                Interfaces {
1680                    ingress: Some(&FakeMatcherDeviceId::ethernet_interface()),
1681                    egress: None
1682                },
1683            ),
1684            Verdict::Drop.into()
1685        );
1686    }
1687
1688    #[test]
1689    fn masquerade_egress_drops_packet_if_no_assigned_address() {
1690        let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
1691        let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1692        let mut core_ctx = FakeNatCtx::default();
1693        let packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1694        let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
1695
1696        let hook = Hook {
1697            routines: vec![Routine {
1698                rules: vec![Rule::new(
1699                    PacketMatcher::default(),
1700                    Action::Masquerade { src_port: None },
1701                )],
1702            }],
1703        };
1704
1705        assert_eq!(
1706            configure_nat::<EgressHook, _, _, _, _>(
1707                &mut core_ctx,
1708                &mut bindings_ctx,
1709                &conntrack,
1710                &mut conn,
1711                &hook,
1712                &packet,
1713                Interfaces {
1714                    ingress: None,
1715                    egress: Some(&FakeMatcherDeviceId::ethernet_interface())
1716                },
1717            ),
1718            Verdict::Drop
1719        );
1720    }
1721
1722    trait NatHookExt<I: FilterIpExt>: NatHook<I, Verdict<()>: PartialEq> {
1723        fn interfaces<'a>(
1724            interface: &'a FakeMatcherDeviceId,
1725        ) -> Interfaces<'a, FakeMatcherDeviceId>;
1726    }
1727
1728    impl<I: FilterIpExt> NatHookExt<I> for IngressHook {
1729        fn interfaces<'a>(
1730            interface: &'a FakeMatcherDeviceId,
1731        ) -> Interfaces<'a, FakeMatcherDeviceId> {
1732            Interfaces { ingress: Some(interface), egress: None }
1733        }
1734    }
1735
1736    impl<I: FilterIpExt> NatHookExt<I> for LocalEgressHook {
1737        fn interfaces<'a>(
1738            interface: &'a FakeMatcherDeviceId,
1739        ) -> Interfaces<'a, FakeMatcherDeviceId> {
1740            Interfaces { ingress: None, egress: Some(interface) }
1741        }
1742    }
1743
1744    impl<I: FilterIpExt> NatHookExt<I> for EgressHook {
1745        fn interfaces<'a>(
1746            interface: &'a FakeMatcherDeviceId,
1747        ) -> Interfaces<'a, FakeMatcherDeviceId> {
1748            Interfaces { ingress: None, egress: Some(interface) }
1749        }
1750    }
1751
1752    const NAT_ENABLED_FOR_TESTS: bool = true;
1753
1754    #[ip_test(I)]
1755    fn nat_disabled_for_self_connected_flows<I: TestIpExt>() {
1756        let mut bindings_ctx = FakeBindingsCtx::<I>::new();
1757        let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1758        let mut core_ctx = FakeNatCtx::default();
1759
1760        let mut packet = FakeIpPacket::<I, _> {
1761            src_ip: I::SRC_IP,
1762            dst_ip: I::SRC_IP,
1763            body: FakeUdpPacket { src_port: 22222, dst_port: 22222 },
1764        };
1765        let (mut conn, direction) = conntrack
1766            .get_connection_for_packet_and_update(
1767                &bindings_ctx,
1768                packet.conntrack_packet().expect("packet should be valid"),
1769            )
1770            .expect("packet should be valid")
1771            .expect("packet should be trackable");
1772
1773        // Even with a Redirect NAT rule in LOCAL_EGRESS, and a Masquerade NAT rule in
1774        // EGRESS, DNAT and SNAT should both be disabled for the connection because it
1775        // is self-connected.
1776        let verdict = perform_nat::<LocalEgressHook, _, _, _, _>(
1777            &mut core_ctx,
1778            &mut bindings_ctx,
1779            NAT_ENABLED_FOR_TESTS,
1780            &conntrack,
1781            &mut conn,
1782            direction,
1783            &Hook {
1784                routines: vec![Routine {
1785                    rules: vec![Rule::new(
1786                        PacketMatcher::default(),
1787                        Action::Redirect { dst_port: None },
1788                    )],
1789                }],
1790            },
1791            &mut packet,
1792            <LocalEgressHook as NatHookExt<I>>::interfaces(
1793                &FakeMatcherDeviceId::ethernet_interface(),
1794            ),
1795        );
1796        assert_eq!(verdict, Verdict::Accept(()));
1797
1798        let verdict = perform_nat::<EgressHook, _, _, _, _>(
1799            &mut core_ctx,
1800            &mut bindings_ctx,
1801            NAT_ENABLED_FOR_TESTS,
1802            &conntrack,
1803            &mut conn,
1804            direction,
1805            &Hook {
1806                routines: vec![Routine {
1807                    rules: vec![Rule::new(
1808                        PacketMatcher::default(),
1809                        Action::Masquerade { src_port: None },
1810                    )],
1811                }],
1812            },
1813            &mut packet,
1814            <EgressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
1815        );
1816        assert_eq!(verdict, Verdict::Accept(()));
1817
1818        assert_eq!(conn.external_data().destination.get(), Some(&ShouldNat::No));
1819        assert_eq!(conn.external_data().source.get(), Some(&ShouldNat::No));
1820    }
1821
1822    #[ip_test(I)]
1823    fn nat_disabled_if_not_configured_before_connection_finalized<I: TestIpExt>() {
1824        let mut bindings_ctx = FakeBindingsCtx::<I>::new();
1825        let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1826        let mut core_ctx = FakeNatCtx::default();
1827
1828        let mut packet = FakeIpPacket::<I, FakeUdpPacket>::arbitrary_value();
1829        let (mut conn, direction) = conntrack
1830            .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
1831            .expect("packet should be valid")
1832            .expect("packet should be trackable");
1833
1834        // Skip NAT so the connection is finalized without DNAT configured for it.
1835        let verdict = perform_nat::<LocalEgressHook, _, _, _, _>(
1836            &mut core_ctx,
1837            &mut bindings_ctx,
1838            false, /* nat_installed */
1839            &conntrack,
1840            &mut conn,
1841            direction,
1842            &Hook::default(),
1843            &mut packet,
1844            <LocalEgressHook as NatHookExt<I>>::interfaces(
1845                &FakeMatcherDeviceId::ethernet_interface(),
1846            ),
1847        );
1848        assert_eq!(verdict, Verdict::Accept(()));
1849        assert_eq!(conn.external_data().destination.get(), None);
1850        assert_eq!(conn.external_data().source.get(), None);
1851
1852        // Skip NAT so the connection is finalized without SNAT configured for it.
1853        let verdict = perform_nat::<EgressHook, _, _, _, _>(
1854            &mut core_ctx,
1855            &mut bindings_ctx,
1856            false, /* nat_installed */
1857            &conntrack,
1858            &mut conn,
1859            direction,
1860            &Hook::default(),
1861            &mut packet,
1862            <EgressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
1863        );
1864        assert_eq!(verdict, Verdict::Accept(()));
1865        assert_eq!(conn.external_data().destination.get(), None);
1866        assert_eq!(conn.external_data().source.get(), None);
1867
1868        let (inserted, _weak) = conntrack
1869            .finalize_connection(&mut bindings_ctx, conn)
1870            .expect("connection should not conflict");
1871        assert!(inserted);
1872
1873        // Now, when a reply comes in to INGRESS, expect that SNAT will be configured as
1874        // `DoNotNat` given it has not already been configured for the connection.
1875        let mut reply = packet.reply();
1876        let (mut conn, direction) = conntrack
1877            .get_connection_for_packet_and_update(&bindings_ctx, reply.conntrack_packet().unwrap())
1878            .expect("packet should be valid")
1879            .expect("packet should be trackable");
1880        let verdict = perform_nat::<IngressHook, _, _, _, _>(
1881            &mut core_ctx,
1882            &mut bindings_ctx,
1883            NAT_ENABLED_FOR_TESTS,
1884            &conntrack,
1885            &mut conn,
1886            direction,
1887            &Hook::default(),
1888            &mut reply,
1889            <IngressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
1890        );
1891        assert_eq!(verdict, Verdict::Accept(()).into());
1892        assert_eq!(conn.external_data().destination.get(), None);
1893        assert_eq!(conn.external_data().source.get(), Some(&ShouldNat::No));
1894
1895        // And finally, on LOCAL_INGRESS, DNAT should also be configured as `DoNotNat`.
1896        let verdict = perform_nat::<LocalIngressHook, _, _, _, _>(
1897            &mut core_ctx,
1898            &mut bindings_ctx,
1899            NAT_ENABLED_FOR_TESTS,
1900            &conntrack,
1901            &mut conn,
1902            direction,
1903            &Hook::default(),
1904            &mut reply,
1905            <IngressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
1906        );
1907        assert_eq!(verdict, Verdict::Accept(()));
1908        assert_eq!(conn.external_data().destination.get(), Some(&ShouldNat::No));
1909        assert_eq!(conn.external_data().source.get(), Some(&ShouldNat::No));
1910    }
1911
1912    const LOCAL_PORT: NonZeroU16 = NonZeroU16::new(55555).unwrap();
1913
1914    #[ip_test(I)]
1915    #[test_case(
1916        PhantomData::<IngressHook>, PhantomData::<EgressHook>, None;
1917        "redirect INGRESS"
1918    )]
1919    #[test_case(
1920        PhantomData::<IngressHook>, PhantomData::<EgressHook>, Some(LOCAL_PORT);
1921        "redirect INGRESS to local port"
1922    )]
1923    #[test_case(
1924        PhantomData::<LocalEgressHook>, PhantomData::<EgressHook>, None;
1925        "redirect LOCAL_EGRESS"
1926    )]
1927    #[test_case(
1928        PhantomData::<LocalEgressHook>, PhantomData::<EgressHook>, Some(LOCAL_PORT);
1929        "redirect LOCAL_EGRESS to local port"
1930    )]
1931    fn redirect<I: TestIpExt, Original: NatHookExt<I>, Reply: NatHookExt<I>>(
1932        _original_nat_hook: PhantomData<Original>,
1933        _reply_nat_hook: PhantomData<Reply>,
1934        dst_port: Option<NonZeroU16>,
1935    ) {
1936        let mut bindings_ctx = FakeBindingsCtx::<I>::new();
1937        let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1938        let mut core_ctx = FakeNatCtx::new([(
1939            FakeMatcherDeviceId::ethernet_interface(),
1940            AddrSubnet::new(I::DST_IP_2, I::SUBNET.prefix()).unwrap(),
1941        )]);
1942
1943        // Create a packet and get the corresponding connection from conntrack.
1944        let mut packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1945        let pre_nat_packet = packet.clone();
1946        let (mut conn, direction) = conntrack
1947            .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
1948            .expect("packet should be valid")
1949            .expect("packet should be trackable");
1950        let original = conn.original_tuple().clone();
1951
1952        // Perform NAT at the first hook where we'd encounter this packet (either
1953        // INGRESS, if it's an incoming packet, or LOCAL_EGRESS, if it's an outgoing
1954        // packet).
1955        let nat_routines = Hook {
1956            routines: vec![Routine {
1957                rules: vec![Rule::new(
1958                    PacketMatcher::default(),
1959                    Action::Redirect { dst_port: dst_port.map(|port| port..=port) },
1960                )],
1961            }],
1962        };
1963        let verdict = perform_nat::<Original, _, _, _, _>(
1964            &mut core_ctx,
1965            &mut bindings_ctx,
1966            NAT_ENABLED_FOR_TESTS,
1967            &conntrack,
1968            &mut conn,
1969            direction,
1970            &nat_routines,
1971            &mut packet,
1972            Original::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
1973        );
1974        assert_eq!(verdict, Verdict::Accept(()).into());
1975
1976        // The packet's destination should be rewritten, and DNAT should be configured
1977        // for the packet; the reply tuple's source should be rewritten to match the new
1978        // destination.
1979        let (redirect_addr, cached_addr) = Original::redirect_addr(
1980            &mut core_ctx,
1981            &packet,
1982            Original::interfaces(&FakeMatcherDeviceId::ethernet_interface()).ingress,
1983        )
1984        .expect("get redirect addr for NAT hook");
1985        let expected = FakeIpPacket::<_, FakeUdpPacket> {
1986            src_ip: packet.src_ip,
1987            dst_ip: redirect_addr,
1988            body: FakeUdpPacket {
1989                src_port: packet.body.src_port,
1990                dst_port: dst_port.map(NonZeroU16::get).unwrap_or(packet.body.dst_port),
1991            },
1992        };
1993        assert_eq!(packet, expected);
1994        assert_eq!(
1995            conn.external_data().destination.get().expect("DNAT should be configured"),
1996            &ShouldNat::Yes(cached_addr)
1997        );
1998        assert_eq!(conn.external_data().source.get(), None, "SNAT should not be configured");
1999        assert_eq!(conn.original_tuple(), &original);
2000        let mut reply = Tuple { src_addr: redirect_addr, ..original.invert() };
2001        if let Some(port) = dst_port {
2002            reply.src_port_or_id = port.get();
2003        }
2004        assert_eq!(conn.reply_tuple(), &reply);
2005
2006        // When a reply to the original packet arrives at the corresponding hook, it
2007        // should have reverse DNAT applied, i.e. its source should be rewritten to
2008        // match the original destination of the connection.
2009        let mut reply_packet = packet.reply();
2010        // Install a NAT routine that simply drops all packets. This should have no
2011        // effect, because only the first packet for a given connection traverses NAT
2012        // routines.
2013        let nat_routines = Hook {
2014            routines: vec![Routine {
2015                rules: vec![Rule::new(PacketMatcher::default(), Action::Drop)],
2016            }],
2017        };
2018        let verdict = perform_nat::<Reply, _, _, _, _>(
2019            &mut core_ctx,
2020            &mut bindings_ctx,
2021            NAT_ENABLED_FOR_TESTS,
2022            &conntrack,
2023            &mut conn,
2024            ConnectionDirection::Reply,
2025            &nat_routines,
2026            &mut reply_packet,
2027            Reply::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
2028        );
2029        assert_eq!(verdict, Verdict::Accept(()).into());
2030        assert_eq!(reply_packet, pre_nat_packet.reply());
2031    }
2032
2033    #[ip_test(I)]
2034    #[test_case(None; "masquerade")]
2035    #[test_case(Some(LOCAL_PORT); "masquerade to specified port")]
2036    fn masquerade<I: TestIpExt>(src_port: Option<NonZeroU16>) {
2037        let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2038        let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2039        let assigned_addr = AddrSubnet::new(I::SRC_IP_2, I::SUBNET.prefix()).unwrap();
2040        let mut core_ctx =
2041            FakeNatCtx::new([(FakeMatcherDeviceId::ethernet_interface(), assigned_addr)]);
2042
2043        // Create a packet and get the corresponding connection from conntrack.
2044        let mut packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
2045        let pre_nat_packet = packet.clone();
2046        let (mut conn, direction) = conntrack
2047            .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2048            .expect("packet should be valid")
2049            .expect("packet should be trackable");
2050        let original = conn.original_tuple().clone();
2051
2052        // Perform Masquerade NAT at EGRESS.
2053        let nat_routines = Hook {
2054            routines: vec![Routine {
2055                rules: vec![Rule::new(
2056                    PacketMatcher::default(),
2057                    Action::Masquerade { src_port: src_port.map(|port| port..=port) },
2058                )],
2059            }],
2060        };
2061        let verdict = perform_nat::<EgressHook, _, _, _, _>(
2062            &mut core_ctx,
2063            &mut bindings_ctx,
2064            NAT_ENABLED_FOR_TESTS,
2065            &conntrack,
2066            &mut conn,
2067            direction,
2068            &nat_routines,
2069            &mut packet,
2070            Interfaces { ingress: None, egress: Some(&FakeMatcherDeviceId::ethernet_interface()) },
2071        );
2072        assert_eq!(verdict, Verdict::Accept(()));
2073
2074        // The packet's source address should be rewritten, and SNAT should be
2075        // configured for the packet; the reply tuple's destination should be rewritten
2076        // to match the new source.
2077        let expected = FakeIpPacket::<_, FakeUdpPacket> {
2078            src_ip: I::SRC_IP_2,
2079            dst_ip: packet.dst_ip,
2080            body: FakeUdpPacket {
2081                src_port: src_port.map(NonZeroU16::get).unwrap_or(packet.body.src_port),
2082                dst_port: packet.body.dst_port,
2083            },
2084        };
2085        assert_eq!(packet, expected);
2086        assert_matches!(
2087            conn.external_data().source.get().expect("SNAT should be configured"),
2088            &ShouldNat::Yes(Some(_))
2089        );
2090        assert_eq!(conn.external_data().destination.get(), None, "DNAT should not be configured");
2091        assert_eq!(conn.original_tuple(), &original);
2092        let mut reply = Tuple { dst_addr: I::SRC_IP_2, ..original.invert() };
2093        if let Some(port) = src_port {
2094            reply.dst_port_or_id = port.get();
2095        }
2096        assert_eq!(conn.reply_tuple(), &reply);
2097
2098        // When a reply to the original packet arrives at INGRESS, it should have
2099        // reverse SNAT applied, i.e. its destination should be rewritten to match the
2100        // original source of the connection.
2101        let mut reply_packet = packet.reply();
2102        // Install a NAT routine that simply drops all packets. This should have no
2103        // effect, because only the first packet for a given connection traverses NAT
2104        // routines.
2105        let nat_routines = Hook {
2106            routines: vec![Routine {
2107                rules: vec![Rule::new(PacketMatcher::default(), Action::Drop)],
2108            }],
2109        };
2110        let verdict = perform_nat::<IngressHook, _, _, _, _>(
2111            &mut core_ctx,
2112            &mut bindings_ctx,
2113            NAT_ENABLED_FOR_TESTS,
2114            &conntrack,
2115            &mut conn,
2116            ConnectionDirection::Reply,
2117            &nat_routines,
2118            &mut reply_packet,
2119            Interfaces { ingress: Some(&FakeMatcherDeviceId::ethernet_interface()), egress: None },
2120        );
2121        assert_eq!(verdict, Verdict::Accept(()).into());
2122        assert_eq!(reply_packet, pre_nat_packet.reply());
2123    }
2124
2125    #[ip_test(I)]
2126    #[test_case(22, 1..=511)]
2127    #[test_case(853, 1..=1023)]
2128    #[test_case(11111, 1024..=u16::MAX)]
2129    fn masquerade_reply_tuple_dst_port_rewritten_even_if_target_range_unspecified<I: TestIpExt>(
2130        src_port: u16,
2131        expected_range: RangeInclusive<u16>,
2132    ) {
2133        let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2134        let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2135        let assigned_addr = AddrSubnet::new(I::SRC_IP_2, I::SUBNET.prefix()).unwrap();
2136        let mut core_ctx =
2137            FakeNatCtx::new([(FakeMatcherDeviceId::ethernet_interface(), assigned_addr)]);
2138        let packet = FakeIpPacket {
2139            body: FakeUdpPacket { src_port, ..ArbitraryValue::arbitrary_value() },
2140            ..ArbitraryValue::arbitrary_value()
2141        };
2142
2143        // First, insert a connection in conntrack with the same the source address the
2144        // packet will be masqueraded to, and the same source port, to cause a conflict.
2145        let reply = FakeIpPacket { src_ip: I::SRC_IP_2, ..packet.clone() };
2146        let (conn, _dir) = conntrack
2147            .get_connection_for_packet_and_update(&bindings_ctx, reply.conntrack_packet().unwrap())
2148            .expect("packet should be valid")
2149            .expect("packet should be trackable");
2150        assert_matches!(
2151            conntrack
2152                .finalize_connection(&mut bindings_ctx, conn)
2153                .expect("connection should not conflict"),
2154            (true, Some(_))
2155        );
2156
2157        // Now, configure Masquerade NAT for a new connection that conflicts with the
2158        // existing one, but do not specify a port range to which the source port should
2159        // be rewritten.
2160        let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
2161        let verdict = configure_masquerade_nat(
2162            &mut core_ctx,
2163            &mut bindings_ctx,
2164            &conntrack,
2165            &mut conn,
2166            &packet,
2167            &Interfaces { ingress: None, egress: Some(&FakeMatcherDeviceId::ethernet_interface()) },
2168            /* src_port */ None,
2169        );
2170
2171        // The destination address of the reply tuple should have been rewritten to the
2172        // new source address, and the destination port should also have been rewritten
2173        // (to a "similar" value), even though a rewrite range was not specified,
2174        // because otherwise it would conflict with the existing connection in the
2175        // table.
2176        assert_matches!(
2177            verdict,
2178            Verdict::Accept(NatConfigurationResult::Result(ShouldNat::Yes(Some(_))))
2179        );
2180        let reply_tuple = conn.reply_tuple();
2181        assert_eq!(reply_tuple.dst_addr, I::SRC_IP_2);
2182        assert_ne!(reply_tuple.dst_port_or_id, src_port);
2183        assert!(expected_range.contains(&reply_tuple.dst_port_or_id));
2184    }
2185
2186    #[ip_test(I)]
2187    #[test_case(
2188        PhantomData::<IngressHook>, Action::Redirect { dst_port: None };
2189        "redirect in INGRESS"
2190    )]
2191    #[test_case(
2192        PhantomData::<EgressHook>, Action::Masquerade { src_port: None };
2193        "masquerade in EGRESS"
2194    )]
2195    fn assigned_addr_cached_and_validated<I: TestIpExt, N: NatHookExt<I>>(
2196        _nat_hook: PhantomData<N>,
2197        action: Action<I, FakeBindingsCtx<I>, ()>,
2198    ) {
2199        let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2200        let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2201        let assigned_addr = AddrSubnet::new(I::SRC_IP_2, I::SUBNET.prefix()).unwrap();
2202        let mut core_ctx =
2203            FakeNatCtx::new([(FakeMatcherDeviceId::ethernet_interface(), assigned_addr)]);
2204
2205        // Create a packet and get the corresponding connection from conntrack.
2206        let mut packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
2207        let (mut conn, direction) = conntrack
2208            .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2209            .expect("packet should be valid")
2210            .expect("packet should be trackable");
2211
2212        // Perform NAT.
2213        let nat_routines = Hook {
2214            routines: vec![Routine { rules: vec![Rule::new(PacketMatcher::default(), action)] }],
2215        };
2216        let verdict = perform_nat::<N, _, _, _, _>(
2217            &mut core_ctx,
2218            &mut bindings_ctx,
2219            NAT_ENABLED_FOR_TESTS,
2220            &conntrack,
2221            &mut conn,
2222            direction,
2223            &nat_routines,
2224            &mut packet,
2225            N::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
2226        );
2227        assert_eq!(verdict, Verdict::Accept(()).into());
2228
2229        // A weakly-held reference to the address assigned to the interface should be
2230        // cached in the NAT config.
2231        let (nat, _nat_type) = conn.relevant_config(N::NAT_TYPE, ConnectionDirection::Original);
2232        let nat = nat.get().unwrap_or_else(|| panic!("{:?} NAT should be configured", N::NAT_TYPE));
2233        let id = assert_matches!(nat, ShouldNat::Yes(Some(CachedAddr { id, _marker })) => id);
2234        let id = id
2235            .lock()
2236            .as_ref()
2237            .expect("address ID should be cached in NAT config")
2238            .upgrade()
2239            .expect("address ID should be valid");
2240        assert_eq!(*id, assigned_addr);
2241        drop(id);
2242
2243        // Remove the assigned address; subsequent packets in the original direction on
2244        // the same flow should check the validity of the cached address, see that it's
2245        // invalid, and be dropped.
2246        core_ctx.device_addrs.clear();
2247        let verdict = perform_nat::<N, _, _, _, _>(
2248            &mut core_ctx,
2249            &mut bindings_ctx,
2250            NAT_ENABLED_FOR_TESTS,
2251            &conntrack,
2252            &mut conn,
2253            ConnectionDirection::Original,
2254            &nat_routines,
2255            &mut FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value(),
2256            N::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
2257        );
2258        assert_eq!(verdict, Verdict::Drop.into());
2259        let (nat, _nat_type) = conn.relevant_config(N::NAT_TYPE, ConnectionDirection::Original);
2260        let nat = nat.get().unwrap_or_else(|| panic!("{:?} NAT should be configured", N::NAT_TYPE));
2261        let id = assert_matches!(nat, ShouldNat::Yes(Some(CachedAddr { id, _marker })) => id);
2262        assert_eq!(*id.lock(), None, "cached weak address ID should be cleared");
2263
2264        // Reassign the address to the interface, and packet traversal should now
2265        // succeed, with NAT re-acquiring a handle to the address.
2266        assert_matches!(
2267            core_ctx.device_addrs.insert(
2268                FakeMatcherDeviceId::ethernet_interface(),
2269                FakePrimaryAddressId(Arc::new(assigned_addr))
2270            ),
2271            None
2272        );
2273        let verdict = perform_nat::<N, _, _, _, _>(
2274            &mut core_ctx,
2275            &mut bindings_ctx,
2276            NAT_ENABLED_FOR_TESTS,
2277            &conntrack,
2278            &mut conn,
2279            ConnectionDirection::Original,
2280            &nat_routines,
2281            &mut FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value(),
2282            N::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
2283        );
2284        assert_eq!(verdict, Verdict::Accept(()).into());
2285        let (nat, _nat_type) = conn.relevant_config(N::NAT_TYPE, ConnectionDirection::Original);
2286        let nat = nat.get().unwrap_or_else(|| panic!("{:?} NAT should be configured", N::NAT_TYPE));
2287        let id = assert_matches!(nat, ShouldNat::Yes(Some(CachedAddr { id, _marker })) => id);
2288        let id = id
2289            .lock()
2290            .as_ref()
2291            .expect("address ID should be cached in NAT config")
2292            .upgrade()
2293            .expect("address Id should be valid");
2294        assert_eq!(*id, assigned_addr);
2295    }
2296
2297    #[test_case(ReplyTuplePort::Source)]
2298    #[test_case(ReplyTuplePort::Destination)]
2299    fn rewrite_port_noop_if_in_range(which: ReplyTuplePort) {
2300        let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
2301        let table = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2302        let mut conn =
2303            ConnectionExclusive::with_reply_tuple(&bindings_ctx, which, LOCAL_PORT.get());
2304
2305        // If the port is already in the specified range, rewriting should succeed and
2306        // be a no-op.
2307        let pre_nat = conn.reply_tuple().clone();
2308        let result = rewrite_reply_tuple_port(
2309            &mut bindings_ctx,
2310            &table,
2311            &mut conn,
2312            which,
2313            LOCAL_PORT..=LOCAL_PORT,
2314            true, /* ensure_port_in_range */
2315            ConflictStrategy::RewritePort,
2316        );
2317        assert_eq!(
2318            result,
2319            Verdict::Accept(NatConfigurationResult::Result(
2320                ShouldNat::<_, FakeWeakAddressId<Ipv4>>::No
2321            ))
2322        );
2323        assert_eq!(conn.reply_tuple(), &pre_nat);
2324    }
2325
2326    #[test_case(ReplyTuplePort::Source)]
2327    #[test_case(ReplyTuplePort::Destination)]
2328    fn rewrite_port_noop_if_no_conflict(which: ReplyTuplePort) {
2329        let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
2330        let table = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2331        let mut conn =
2332            ConnectionExclusive::with_reply_tuple(&bindings_ctx, which, LOCAL_PORT.get());
2333
2334        // If there is no conflicting tuple in the table and we provide `false` for
2335        // `ensure_port_in_range` (as is done for implicit SNAT), then rewriting should
2336        // succeed and be a no-op, even if the port is not in the specified range,
2337        let pre_nat = conn.reply_tuple().clone();
2338        const NEW_PORT: NonZeroU16 = LOCAL_PORT.checked_add(1).unwrap();
2339        let result = rewrite_reply_tuple_port(
2340            &mut bindings_ctx,
2341            &table,
2342            &mut conn,
2343            which,
2344            NEW_PORT..=NEW_PORT,
2345            false, /* ensure_port_in_range */
2346            ConflictStrategy::RewritePort,
2347        );
2348        assert_eq!(
2349            result,
2350            Verdict::Accept(NatConfigurationResult::Result(
2351                ShouldNat::<_, FakeWeakAddressId<Ipv4>>::No
2352            ))
2353        );
2354        assert_eq!(conn.reply_tuple(), &pre_nat);
2355    }
2356
2357    #[test_case(ReplyTuplePort::Source)]
2358    #[test_case(ReplyTuplePort::Destination)]
2359    fn rewrite_port_succeeds_if_available_port_in_range(which: ReplyTuplePort) {
2360        let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
2361        let table = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2362        let mut conn =
2363            ConnectionExclusive::with_reply_tuple(&bindings_ctx, which, LOCAL_PORT.get());
2364
2365        // If the port is not in the specified range, but there is an available port,
2366        // rewriting should succeed.
2367        const NEW_PORT: NonZeroU16 = LOCAL_PORT.checked_add(1).unwrap();
2368        let result = rewrite_reply_tuple_port(
2369            &mut bindings_ctx,
2370            &table,
2371            &mut conn,
2372            which,
2373            NEW_PORT..=NEW_PORT,
2374            true, /* ensure_port_in_range */
2375            ConflictStrategy::RewritePort,
2376        );
2377        assert_eq!(
2378            result,
2379            Verdict::Accept(NatConfigurationResult::Result(
2380                ShouldNat::<_, FakeWeakAddressId<Ipv4>>::Yes(None)
2381            ))
2382        );
2383        assert_eq!(conn.reply_tuple(), &tuple_with_port(which, NEW_PORT.get()));
2384    }
2385
2386    #[test_case(ReplyTuplePort::Source)]
2387    #[test_case(ReplyTuplePort::Destination)]
2388    fn rewrite_port_fails_if_no_available_port_in_range(which: ReplyTuplePort) {
2389        let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
2390        let table: Table<_, NatConfig<_, FakeWeakAddressId<Ipv4>>, _> =
2391            Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2392
2393        // If there is no port available in the specified range that does not conflict
2394        // with a tuple already in the table, rewriting should fail and the packet
2395        // should be dropped.
2396        let packet = packet_with_port(which, LOCAL_PORT.get());
2397        let (conn, _dir) = table
2398            .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2399            .expect("packet should be valid")
2400            .expect("packet should be trackable");
2401        assert_matches!(
2402            table
2403                .finalize_connection(&mut bindings_ctx, conn)
2404                .expect("connection should not conflict"),
2405            (true, Some(_))
2406        );
2407
2408        let mut conn =
2409            ConnectionExclusive::with_reply_tuple(&bindings_ctx, which, LOCAL_PORT.get());
2410        let result = rewrite_reply_tuple_port(
2411            &mut bindings_ctx,
2412            &table,
2413            &mut conn,
2414            which,
2415            LOCAL_PORT..=LOCAL_PORT,
2416            true, /* ensure_port_in_range */
2417            ConflictStrategy::RewritePort,
2418        );
2419        assert_eq!(result, Verdict::Drop);
2420    }
2421
2422    #[test_case(ReplyTuplePort::Source)]
2423    #[test_case(ReplyTuplePort::Destination)]
2424    fn port_rewritten_to_ensure_unique_tuple_even_if_in_range(which: ReplyTuplePort) {
2425        let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
2426        let table = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2427
2428        // Fill the conntrack table with tuples such that there is only one tuple that
2429        // does not conflict with an existing one and which has a port in the specified
2430        // range.
2431        const MAX_PORT: NonZeroU16 = NonZeroU16::new(LOCAL_PORT.get() + 100).unwrap();
2432        for port in LOCAL_PORT.get()..=MAX_PORT.get() {
2433            let packet = packet_with_port(which, port);
2434            let (conn, _dir) = table
2435                .get_connection_for_packet_and_update(
2436                    &bindings_ctx,
2437                    packet.conntrack_packet().unwrap(),
2438                )
2439                .expect("packet should be valid")
2440                .expect("packet should be trackable");
2441            assert_matches!(
2442                table
2443                    .finalize_connection(&mut bindings_ctx, conn)
2444                    .expect("connection should not conflict"),
2445                (true, Some(_))
2446            );
2447        }
2448
2449        // If the port is in the specified range, but results in a non-unique tuple,
2450        // rewriting should succeed as long as some port exists in the range that
2451        // results in a unique tuple.
2452        let mut conn =
2453            ConnectionExclusive::with_reply_tuple(&bindings_ctx, which, LOCAL_PORT.get());
2454        const MIN_PORT: NonZeroU16 = NonZeroU16::new(LOCAL_PORT.get() - 1).unwrap();
2455        let result = rewrite_reply_tuple_port(
2456            &mut bindings_ctx,
2457            &table,
2458            &mut conn,
2459            which,
2460            MIN_PORT..=MAX_PORT,
2461            true, /* ensure_port_in_range */
2462            ConflictStrategy::RewritePort,
2463        );
2464        assert_eq!(
2465            result,
2466            Verdict::Accept(NatConfigurationResult::Result(
2467                ShouldNat::<_, FakeWeakAddressId<Ipv4>>::Yes(None)
2468            ))
2469        );
2470        assert_eq!(conn.reply_tuple(), &tuple_with_port(which, MIN_PORT.get()));
2471    }
2472
2473    #[test_case(ReplyTuplePort::Source)]
2474    #[test_case(ReplyTuplePort::Destination)]
2475    fn rewrite_port_skipped_if_existing_connection_can_be_adopted(which: ReplyTuplePort) {
2476        let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
2477        let table: Table<_, NatConfig<_, FakeWeakAddressId<Ipv4>>, _> =
2478            Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2479
2480        // If there is a conflicting connection already in the table, but the caller
2481        // specifies that existing connections can be adopted if they are identical to
2482        // the one we are NATing, and the one in the table is a match, the existing
2483        // connection should be adopted.
2484        let packet = packet_with_port(which, LOCAL_PORT.get());
2485        let (conn, _dir) = table
2486            .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2487            .expect("packet should be valid")
2488            .expect("packet should be trackable");
2489        let existing = assert_matches!(
2490            table
2491                .finalize_connection(&mut bindings_ctx, conn)
2492                .expect("connection should not conflict"),
2493            (true, Some(conn)) => conn
2494        );
2495
2496        let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
2497        let result = rewrite_reply_tuple_port(
2498            &mut bindings_ctx,
2499            &table,
2500            &mut conn,
2501            which,
2502            NonZeroU16::MIN..=NonZeroU16::MAX,
2503            false, /* ensure_port_in_range */
2504            ConflictStrategy::AdoptExisting,
2505        );
2506        let conn = assert_matches!(
2507            result,
2508            Verdict::Accept(NatConfigurationResult::AdoptExisting(Connection::Shared(conn))) => conn
2509        );
2510        assert!(Arc::ptr_eq(&existing, &conn));
2511    }
2512
2513    trait IcmpErrorTestIpExt: TestIpExt {
2514        const NETSTACK: Self::Addr = Self::DST_IP;
2515        const ULTIMATE_SRC: Self::Addr = Self::SRC_IP;
2516        const ULTIMATE_DST: Self::Addr = Self::DST_IP_3;
2517        const ROUTER_SRC: Self::Addr = Self::SRC_IP_2;
2518        const ROUTER_DST: Self::Addr = Self::DST_IP_2;
2519    }
2520
2521    impl<I> IcmpErrorTestIpExt for I where I: TestIpExt {}
2522
2523    enum IcmpErrorSource {
2524        IntermediateRouter,
2525        EndHost,
2526    }
2527
2528    // The next two test cases (redirect_icmp_error_in_reply_direction and
2529    // redirect_icmp_error_in_original_direction) require some explanation. The
2530    // tests are based on the `redirect` test above, but are validating that
2531    // ICMP error NAT works properly.
2532    //
2533    // The imaginary network used in these tests is set up as follows:
2534    //
2535    // D --- US --- SR --- S
2536    //
2537    // Where:
2538    //
2539    // - US: That's us :-)
2540    // - D:  The ultimate destination
2541    // - S:  The ultimate source
2542    // - SR: A router between us and the source
2543    //
2544    // The IPs that matter are:
2545    //
2546    // - D: ULTIMATE_DST
2547    // - US: NETSTACK
2548    // - S: ULTIMATE_SRC
2549    // - SR: ROUTER_SRC
2550    //
2551    // There is DNAT configured such that packets coming from S to D are
2552    // rewritten to instead go to US. Packets going from US -> S will have their
2553    // source rewritten to replace US with the address of D.
2554    //
2555    // Each test checks that an ICMP error traveling in either the original or
2556    // reply direction are rewritten appropriately.
2557
2558    #[test_case(Icmpv4DestUnreachableError)]
2559    #[test_case(Icmpv6DestUnreachableError)]
2560    fn redirect_icmp_error_in_reply_direction<I: IcmpErrorTestIpExt, IE: IcmpErrorMessage<I>>(
2561        _icmp_error: IE,
2562    ) {
2563        let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2564        let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2565        let mut core_ctx = FakeNatCtx::new([(
2566            FakeMatcherDeviceId::ethernet_interface(),
2567            AddrSubnet::new(I::NETSTACK, I::SUBNET.prefix()).unwrap(),
2568        )]);
2569
2570        // Create a packet and get the corresponding connection from conntrack.
2571        let mut packet = EmptyBuf
2572            .wrap_in(UdpPacketBuilder::new(
2573                I::ULTIMATE_SRC,
2574                I::ULTIMATE_DST,
2575                Some(NonZeroU16::new(11111).unwrap()),
2576                NonZeroU16::new(22222).unwrap(),
2577            ))
2578            .wrap_in(I::PacketBuilder::new(
2579                I::ULTIMATE_SRC,
2580                I::ULTIMATE_DST,
2581                u8::MAX,
2582                IpProto::Udp.into(),
2583            ));
2584        let packet_pre_nat = packet.clone();
2585        let (mut conn, _) = conntrack
2586            .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2587            .expect("packet should be valid")
2588            .expect("packet should be trackable");
2589        let original_tuple = conn.original_tuple().clone();
2590
2591        let nat_routines = Hook {
2592            routines: vec![Routine {
2593                rules: vec![Rule::new(
2594                    PacketMatcher::default(),
2595                    Action::Redirect { dst_port: Some(LOCAL_PORT..=LOCAL_PORT) },
2596                )],
2597            }],
2598        };
2599        let verdict = perform_nat::<IngressHook, _, _, _, _>(
2600            &mut core_ctx,
2601            &mut bindings_ctx,
2602            NAT_ENABLED_FOR_TESTS,
2603            &conntrack,
2604            &mut conn,
2605            ConnectionDirection::Original,
2606            &nat_routines,
2607            &mut packet,
2608            <IngressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
2609        );
2610        assert_eq!(verdict, Verdict::Accept(()).into());
2611
2612        // The packet's destination should be rewritten and DNAT should be
2613        // configured for the packet; the reply tuple's source should be
2614        // rewritten to match the new destination.
2615        let (redirect_addr, cached_addr) = IngressHook::redirect_addr(
2616            &mut core_ctx,
2617            &packet,
2618            <IngressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface())
2619                .ingress,
2620        )
2621        .expect("get redirect addr for NAT hook");
2622
2623        // Since this is in the INGRESS hook, redirect_addr should be the
2624        // address of the receiving interface.
2625        assert_eq!(redirect_addr, I::NETSTACK);
2626
2627        // The destination address and port were updated.
2628        let expected = EmptyBuf
2629            .wrap_in(UdpPacketBuilder::new(
2630                I::ULTIMATE_SRC,
2631                redirect_addr,
2632                packet.inner().outer().src_port(),
2633                LOCAL_PORT,
2634            ))
2635            .wrap_in(I::PacketBuilder::new(
2636                I::ULTIMATE_SRC,
2637                redirect_addr,
2638                u8::MAX,
2639                IpProto::Udp.into(),
2640            ));
2641        assert_eq!(packet, expected);
2642        assert_eq!(
2643            conn.external_data().destination.get().expect("DNAT should be configured"),
2644            &ShouldNat::Yes(cached_addr)
2645        );
2646        assert_eq!(conn.external_data().source.get(), None, "SNAT should not be configured");
2647        assert_eq!(conn.original_tuple(), &original_tuple);
2648
2649        let reply_tuple = Tuple {
2650            src_addr: redirect_addr,
2651            src_port_or_id: LOCAL_PORT.get(),
2652            ..original_tuple.invert()
2653        };
2654        assert_eq!(conn.reply_tuple(), &reply_tuple);
2655
2656        let mut error_packet = IE::make_serializer(
2657            redirect_addr,
2658            I::ULTIMATE_SRC,
2659            packet.clone().serialize_vec_outer().unwrap().unwrap_b().into_inner(),
2660        )
2661        .wrap_in(I::PacketBuilder::new(
2662            redirect_addr,
2663            I::ULTIMATE_SRC,
2664            u8::MAX,
2665            IE::proto(),
2666        ));
2667
2668        let verdict = perform_nat::<EgressHook, _, _, _, _>(
2669            &mut core_ctx,
2670            &mut bindings_ctx,
2671            NAT_ENABLED_FOR_TESTS,
2672            &conntrack,
2673            &mut conn,
2674            ConnectionDirection::Reply,
2675            &nat_routines,
2676            &mut error_packet,
2677            <EgressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
2678        );
2679        assert_eq!(verdict, Verdict::Accept(()));
2680
2681        let error_packet_expected = IE::make_serializer(
2682            // We expect this address to be rewritten since `redirect_addr`
2683            // shouldn't end up in the packet destined to the other host.
2684            I::ULTIMATE_DST,
2685            I::ULTIMATE_SRC,
2686            packet_pre_nat.serialize_vec_outer().unwrap().unwrap_b().into_inner(),
2687        )
2688        .wrap_in(I::PacketBuilder::new(
2689            I::ULTIMATE_DST,
2690            I::ULTIMATE_SRC,
2691            u8::MAX,
2692            IE::proto(),
2693        ));
2694
2695        assert_eq!(error_packet, error_packet_expected);
2696    }
2697
2698    #[test_matrix(
2699        [
2700            Icmpv4DestUnreachableError,
2701            Icmpv6DestUnreachableError,
2702        ],
2703        [
2704            IcmpErrorSource::IntermediateRouter,
2705            IcmpErrorSource::EndHost,
2706        ]
2707    )]
2708    fn redirect_icmp_error_in_original_direction<I: IcmpErrorTestIpExt, IE: IcmpErrorMessage<I>>(
2709        _icmp_error: IE,
2710        icmp_error_source: IcmpErrorSource,
2711    ) {
2712        let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2713        let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2714        let mut core_ctx = FakeNatCtx::new([(
2715            FakeMatcherDeviceId::ethernet_interface(),
2716            AddrSubnet::new(I::NETSTACK, I::SUBNET.prefix()).unwrap(),
2717        )]);
2718
2719        // Create a packet and get the corresponding connection from conntrack.
2720        let mut packet = EmptyBuf
2721            .wrap_in(UdpPacketBuilder::new(
2722                I::ULTIMATE_SRC,
2723                I::ULTIMATE_DST,
2724                Some(NonZeroU16::new(11111).unwrap()),
2725                NonZeroU16::new(22222).unwrap(),
2726            ))
2727            .wrap_in(I::PacketBuilder::new(
2728                I::ULTIMATE_SRC,
2729                I::ULTIMATE_DST,
2730                u8::MAX,
2731                IpProto::Udp.into(),
2732            ));
2733        let (mut conn, direction) = conntrack
2734            .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2735            .expect("packet should be valid")
2736            .expect("packet should be trackable");
2737        let original_tuple = conn.original_tuple().clone();
2738
2739        // Perform NAT at the first hook where we'd encounter this packet (either
2740        // INGRESS, if it's an incoming packet, or LOCAL_EGRESS, if it's an outgoing
2741        // packet).
2742        let nat_routines = Hook {
2743            routines: vec![Routine {
2744                rules: vec![Rule::new(
2745                    PacketMatcher::default(),
2746                    Action::Redirect { dst_port: Some(LOCAL_PORT..=LOCAL_PORT) },
2747                )],
2748            }],
2749        };
2750        let verdict = perform_nat::<IngressHook, _, _, _, _>(
2751            &mut core_ctx,
2752            &mut bindings_ctx,
2753            NAT_ENABLED_FOR_TESTS,
2754            &conntrack,
2755            &mut conn,
2756            direction,
2757            &nat_routines,
2758            &mut packet,
2759            <IngressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
2760        );
2761        assert_eq!(verdict, Verdict::Accept(()).into());
2762
2763        // The packet's destination should be rewritten, and DNAT should be configured
2764        // for the packet; the reply tuple's source should be rewritten to match the new
2765        // destination.
2766        let (redirect_addr, cached_addr) = IngressHook::redirect_addr(
2767            &mut core_ctx,
2768            &packet,
2769            <IngressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface())
2770                .ingress,
2771        )
2772        .expect("get redirect addr for NAT hook");
2773
2774        // Since this is in the INGRESS hook, redirect_addr should be the
2775        // address of the receiving interface.
2776        assert_eq!(redirect_addr, I::NETSTACK);
2777
2778        // The destination address and port were updated.
2779        let expected = EmptyBuf
2780            .wrap_in(UdpPacketBuilder::new(
2781                I::ULTIMATE_SRC,
2782                redirect_addr,
2783                packet.inner().outer().src_port(),
2784                LOCAL_PORT,
2785            ))
2786            .wrap_in(I::PacketBuilder::new(
2787                I::ULTIMATE_SRC,
2788                redirect_addr,
2789                u8::MAX,
2790                IpProto::Udp.into(),
2791            ));
2792        assert_eq!(packet, expected);
2793        assert_eq!(
2794            conn.external_data().destination.get().expect("DNAT should be configured"),
2795            &ShouldNat::Yes(cached_addr)
2796        );
2797        assert_eq!(conn.external_data().source.get(), None, "SNAT should not be configured");
2798        assert_eq!(conn.original_tuple(), &original_tuple);
2799        let reply_tuple = Tuple {
2800            src_addr: redirect_addr,
2801            src_port_or_id: LOCAL_PORT.get(),
2802            ..original_tuple.invert()
2803        };
2804        assert_eq!(conn.reply_tuple(), &reply_tuple);
2805
2806        let mut reply_packet = EmptyBuf
2807            .wrap_in(UdpPacketBuilder::new(
2808                reply_tuple.src_addr,
2809                reply_tuple.dst_addr,
2810                Some(NonZeroU16::new(reply_tuple.src_port_or_id).unwrap()),
2811                NonZeroU16::new(reply_tuple.dst_port_or_id).unwrap(),
2812            ))
2813            .wrap_in(I::PacketBuilder::new(
2814                reply_tuple.src_addr,
2815                reply_tuple.dst_addr,
2816                u8::MAX,
2817                IpProto::Udp.into(),
2818            ));
2819        let reply_packet_pre_nat = reply_packet.clone();
2820
2821        let verdict = perform_nat::<EgressHook, _, _, _, _>(
2822            &mut core_ctx,
2823            &mut bindings_ctx,
2824            NAT_ENABLED_FOR_TESTS,
2825            &conntrack,
2826            &mut conn,
2827            ConnectionDirection::Reply,
2828            &nat_routines,
2829            &mut reply_packet,
2830            <EgressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
2831        );
2832        assert_eq!(verdict, Verdict::Accept(()));
2833
2834        let reply_packet_expected = EmptyBuf
2835            .wrap_in(UdpPacketBuilder::new(
2836                I::ULTIMATE_DST,
2837                I::ULTIMATE_SRC,
2838                Some(NonZeroU16::new(22222).unwrap()),
2839                NonZeroU16::new(11111).unwrap(),
2840            ))
2841            .wrap_in(I::PacketBuilder::new(
2842                I::ULTIMATE_DST,
2843                I::ULTIMATE_SRC,
2844                u8::MAX,
2845                IpProto::Udp.into(),
2846            ));
2847        assert_eq!(reply_packet, reply_packet_expected);
2848
2849        // The packet sent from D -> S was invalid and triggered an ICMP error
2850        // either from either S or SR.
2851        let error_src_addr = match icmp_error_source {
2852            IcmpErrorSource::IntermediateRouter => I::ROUTER_SRC,
2853            IcmpErrorSource::EndHost => I::ULTIMATE_SRC,
2854        };
2855
2856        let mut error_packet = IE::make_serializer(
2857            error_src_addr,
2858            I::ULTIMATE_DST,
2859            reply_packet.clone().serialize_vec_outer().unwrap().unwrap_b().into_inner(),
2860        )
2861        .wrap_in(I::PacketBuilder::new(
2862            error_src_addr,
2863            I::ULTIMATE_DST,
2864            u8::MAX,
2865            IE::proto(),
2866        ));
2867
2868        let verdict = perform_nat::<IngressHook, _, _, _, _>(
2869            &mut core_ctx,
2870            &mut bindings_ctx,
2871            NAT_ENABLED_FOR_TESTS,
2872            &conntrack,
2873            &mut conn,
2874            direction,
2875            &nat_routines,
2876            &mut error_packet,
2877            <IngressHook as NatHookExt<I>>::interfaces(&FakeMatcherDeviceId::ethernet_interface()),
2878        );
2879        assert_eq!(verdict, Verdict::Accept(()).into());
2880
2881        let error_packet_expected = IE::make_serializer(
2882            // Unlike in the reply case, we shouldn't expect this address to be
2883            // rewritten since it's not one configured for NAT.
2884            error_src_addr,
2885            redirect_addr,
2886            reply_packet_pre_nat.serialize_vec_outer().unwrap().unwrap_b().into_inner(),
2887        )
2888        .wrap_in(I::PacketBuilder::new(
2889            error_src_addr,
2890            redirect_addr,
2891            u8::MAX,
2892            IE::proto(),
2893        ));
2894
2895        assert_eq!(error_packet, error_packet_expected);
2896    }
2897
2898    // This is an explanation of masquerade_icmp_error_in_reply_direction and
2899    // masquerade_icmp_error_in_original_direction. There is more background
2900    // information in the equivalent comment of the redirect ICMP error tests.
2901    //
2902    // The imaginary network used in these tests is set up as follows:
2903    //
2904    // S --- SR --- US --- DR --- D
2905    //
2906    // Where:
2907    //
2908    // - US: That's us :-)
2909    // - D:  The ultimate destination
2910    // - S:  The ultimate source
2911    // - DR: A router between us and the destination
2912    // - SR: A router between us and the source
2913    //
2914    // The IPs that matter are:
2915    //
2916    // - D: ULTIMATE_DST
2917    // - DR: ROUTER_DST
2918    // - S: ULTIMAKE_SOURCE
2919    // - SR: ROUTER_SRC
2920    // - US: ULTIMATE_SRC
2921    //
2922    // The network is configured such that any packets traveling from S through
2923    // US are rewritten to have the address of US as their source address.
2924
2925    #[test_matrix(
2926        [
2927            Icmpv4DestUnreachableError,
2928            Icmpv6DestUnreachableError,
2929        ],
2930        [
2931            IcmpErrorSource::IntermediateRouter,
2932            IcmpErrorSource::EndHost,
2933        ]
2934    )]
2935    fn masquerade_icmp_error_in_reply_direction<I: TestIpExt, IE: IcmpErrorMessage<I>>(
2936        _icmp_error: IE,
2937        icmp_error_source: IcmpErrorSource,
2938    ) {
2939        let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2940        let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2941        let assigned_addr = AddrSubnet::new(I::NETSTACK, I::SUBNET.prefix()).unwrap();
2942        let mut core_ctx =
2943            FakeNatCtx::new([(FakeMatcherDeviceId::ethernet_interface(), assigned_addr)]);
2944
2945        // Create a packet and get the corresponding connection from conntrack.
2946        let mut packet = EmptyBuf
2947            .wrap_in(UdpPacketBuilder::new(
2948                I::ULTIMATE_SRC,
2949                I::ULTIMATE_DST,
2950                Some(NonZeroU16::new(11111).unwrap()),
2951                NonZeroU16::new(22222).unwrap(),
2952            ))
2953            .wrap_in(I::PacketBuilder::new(
2954                I::ULTIMATE_SRC,
2955                I::ULTIMATE_DST,
2956                u8::MAX,
2957                IpProto::Udp.into(),
2958            ));
2959        let packet_pre_nat = packet.clone();
2960        let (mut conn, direction) = conntrack
2961            .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2962            .expect("packet should be valid")
2963            .expect("packet should be trackable");
2964        let original_tuple = conn.original_tuple().clone();
2965
2966        // Perform Masquerade NAT at EGRESS.
2967        let nat_routines = Hook {
2968            routines: vec![Routine {
2969                rules: vec![Rule::new(
2970                    PacketMatcher::default(),
2971                    Action::Masquerade { src_port: Some(LOCAL_PORT..=LOCAL_PORT) },
2972                )],
2973            }],
2974        };
2975        let verdict = perform_nat::<EgressHook, _, _, _, _>(
2976            &mut core_ctx,
2977            &mut bindings_ctx,
2978            NAT_ENABLED_FOR_TESTS,
2979            &conntrack,
2980            &mut conn,
2981            direction,
2982            &nat_routines,
2983            &mut packet,
2984            Interfaces { ingress: None, egress: Some(&FakeMatcherDeviceId::ethernet_interface()) },
2985        );
2986        assert_eq!(verdict, Verdict::Accept(()));
2987
2988        // The packet's source address should be rewritten, and SNAT should be
2989        // configured for the packet; the reply tuple's destination should be rewritten
2990        // to match the new source.
2991        let expected = EmptyBuf
2992            .wrap_in(UdpPacketBuilder::new(
2993                I::NETSTACK,
2994                I::ULTIMATE_DST,
2995                Some(LOCAL_PORT),
2996                packet.inner().outer().dst_port().unwrap(),
2997            ))
2998            .wrap_in(I::PacketBuilder::new(
2999                I::NETSTACK,
3000                I::ULTIMATE_DST,
3001                u8::MAX,
3002                IpProto::Udp.into(),
3003            ));
3004        assert_eq!(packet, expected);
3005        assert_matches!(
3006            conn.external_data().source.get().expect("SNAT should be configured"),
3007            &ShouldNat::Yes(Some(_))
3008        );
3009        assert_eq!(conn.external_data().destination.get(), None, "DNAT should not be configured");
3010        assert_eq!(conn.original_tuple(), &original_tuple);
3011        let reply_tuple = Tuple {
3012            dst_addr: I::NETSTACK,
3013            dst_port_or_id: LOCAL_PORT.get(),
3014            ..original_tuple.invert()
3015        };
3016        assert_eq!(conn.reply_tuple(), &reply_tuple);
3017
3018        // The packet sent from S -> D was invalid and triggered an ICMP error
3019        // either from D or DR.
3020        let error_src_addr = match icmp_error_source {
3021            IcmpErrorSource::IntermediateRouter => I::ROUTER_DST,
3022            IcmpErrorSource::EndHost => I::ULTIMATE_DST,
3023        };
3024
3025        let mut error_packet = IE::make_serializer(
3026            error_src_addr,
3027            I::NETSTACK,
3028            packet.clone().serialize_vec_outer().unwrap().unwrap_b().into_inner(),
3029        )
3030        .wrap_in(I::PacketBuilder::new(error_src_addr, I::NETSTACK, u8::MAX, IE::proto()));
3031
3032        let verdict = perform_nat::<IngressHook, _, _, _, _>(
3033            &mut core_ctx,
3034            &mut bindings_ctx,
3035            NAT_ENABLED_FOR_TESTS,
3036            &conntrack,
3037            &mut conn,
3038            ConnectionDirection::Reply,
3039            &nat_routines,
3040            &mut error_packet,
3041            Interfaces { ingress: Some(&FakeMatcherDeviceId::ethernet_interface()), egress: None },
3042        );
3043        assert_eq!(verdict, Verdict::Accept(()).into());
3044
3045        let error_packet_expected = IE::make_serializer(
3046            // We expect this address to be rewritten since `redirect_addr`
3047            // shouldn't end up in the packet destined to the other host.
3048            error_src_addr,
3049            I::ULTIMATE_SRC,
3050            packet_pre_nat.serialize_vec_outer().unwrap().unwrap_b().into_inner(),
3051        )
3052        .wrap_in(I::PacketBuilder::new(
3053            error_src_addr,
3054            I::ULTIMATE_SRC,
3055            u8::MAX,
3056            IE::proto(),
3057        ));
3058
3059        assert_eq!(error_packet, error_packet_expected);
3060    }
3061
3062    #[test_matrix(
3063        [
3064            Icmpv4DestUnreachableError,
3065            Icmpv6DestUnreachableError,
3066        ],
3067        [
3068            IcmpErrorSource::IntermediateRouter,
3069            IcmpErrorSource::EndHost,
3070        ]
3071    )]
3072    fn masquerade_icmp_error_in_original_direction<I: TestIpExt, IE: IcmpErrorMessage<I>>(
3073        _icmp_error: IE,
3074        icmp_error_source: IcmpErrorSource,
3075    ) {
3076        let mut bindings_ctx = FakeBindingsCtx::<I>::new();
3077        let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
3078        let assigned_addr = AddrSubnet::new(I::NETSTACK, I::SUBNET.prefix()).unwrap();
3079        let mut core_ctx =
3080            FakeNatCtx::new([(FakeMatcherDeviceId::ethernet_interface(), assigned_addr)]);
3081
3082        // Create a packet and get the corresponding connection from conntrack.
3083        let mut packet = EmptyBuf
3084            .wrap_in(UdpPacketBuilder::new(
3085                I::ULTIMATE_SRC,
3086                I::ULTIMATE_DST,
3087                Some(NonZeroU16::new(11111).unwrap()),
3088                NonZeroU16::new(22222).unwrap(),
3089            ))
3090            .wrap_in(I::PacketBuilder::new(
3091                I::ULTIMATE_SRC,
3092                I::ULTIMATE_DST,
3093                u8::MAX,
3094                IpProto::Udp.into(),
3095            ));
3096        let (mut conn, direction) = conntrack
3097            .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
3098            .expect("packet should be valid")
3099            .expect("packet should be trackable");
3100        let original_tuple = conn.original_tuple().clone();
3101
3102        // Perform Masquerade NAT at EGRESS.
3103        let nat_routines = Hook {
3104            routines: vec![Routine {
3105                rules: vec![Rule::new(
3106                    PacketMatcher::default(),
3107                    Action::Masquerade { src_port: Some(LOCAL_PORT..=LOCAL_PORT) },
3108                )],
3109            }],
3110        };
3111        let verdict = perform_nat::<EgressHook, _, _, _, _>(
3112            &mut core_ctx,
3113            &mut bindings_ctx,
3114            NAT_ENABLED_FOR_TESTS,
3115            &conntrack,
3116            &mut conn,
3117            direction,
3118            &nat_routines,
3119            &mut packet,
3120            Interfaces { ingress: None, egress: Some(&FakeMatcherDeviceId::ethernet_interface()) },
3121        );
3122        assert_eq!(verdict, Verdict::Accept(()));
3123
3124        // The packet's source address should be rewritten, and SNAT should be
3125        // configured for the packet; the reply tuple's destination should be rewritten
3126        // to match the new source.
3127        let expected = EmptyBuf
3128            .wrap_in(UdpPacketBuilder::new(
3129                I::NETSTACK,
3130                I::ULTIMATE_DST,
3131                Some(LOCAL_PORT),
3132                packet.inner().outer().dst_port().unwrap(),
3133            ))
3134            .wrap_in(I::PacketBuilder::new(
3135                I::NETSTACK,
3136                I::ULTIMATE_DST,
3137                u8::MAX,
3138                IpProto::Udp.into(),
3139            ));
3140        assert_eq!(packet, expected);
3141        assert_matches!(
3142            conn.external_data().source.get().expect("SNAT should be configured"),
3143            &ShouldNat::Yes(Some(_))
3144        );
3145        assert_eq!(conn.external_data().destination.get(), None, "DNAT should not be configured");
3146        assert_eq!(conn.original_tuple(), &original_tuple);
3147        let reply_tuple = Tuple {
3148            dst_addr: I::NETSTACK,
3149            dst_port_or_id: LOCAL_PORT.get(),
3150            ..original_tuple.invert()
3151        };
3152        assert_eq!(conn.reply_tuple(), &reply_tuple);
3153
3154        // When a reply to the original packet arrives at INGRESS, it should
3155        // have reverse SNAT applied, i.e. its destination should be rewritten
3156        // to match the original source of the connection.
3157        let mut reply_packet = EmptyBuf
3158            .wrap_in(UdpPacketBuilder::new(
3159                reply_tuple.src_addr,
3160                reply_tuple.dst_addr,
3161                Some(NonZeroU16::new(reply_tuple.src_port_or_id).unwrap()),
3162                NonZeroU16::new(reply_tuple.dst_port_or_id).unwrap(),
3163            ))
3164            .wrap_in(I::PacketBuilder::new(
3165                reply_tuple.src_addr,
3166                reply_tuple.dst_addr,
3167                u8::MAX,
3168                IpProto::Udp.into(),
3169            ));
3170        let reply_packet_pre_nat = reply_packet.clone();
3171
3172        let verdict = perform_nat::<IngressHook, _, _, _, _>(
3173            &mut core_ctx,
3174            &mut bindings_ctx,
3175            NAT_ENABLED_FOR_TESTS,
3176            &conntrack,
3177            &mut conn,
3178            ConnectionDirection::Reply,
3179            &nat_routines,
3180            &mut reply_packet,
3181            Interfaces { ingress: Some(&FakeMatcherDeviceId::ethernet_interface()), egress: None },
3182        );
3183        assert_eq!(verdict, Verdict::Accept(()).into());
3184
3185        let reply_packet_expected = EmptyBuf
3186            .wrap_in(UdpPacketBuilder::new(
3187                I::ULTIMATE_DST,
3188                I::ULTIMATE_SRC,
3189                Some(NonZeroU16::new(22222).unwrap()),
3190                NonZeroU16::new(11111).unwrap(),
3191            ))
3192            .wrap_in(I::PacketBuilder::new(
3193                I::ULTIMATE_DST,
3194                I::ULTIMATE_SRC,
3195                u8::MAX,
3196                IpProto::Udp.into(),
3197            ));
3198        assert_eq!(reply_packet, reply_packet_expected);
3199
3200        let error_src_addr = match icmp_error_source {
3201            IcmpErrorSource::IntermediateRouter => I::ROUTER_SRC,
3202            IcmpErrorSource::EndHost => I::ULTIMATE_SRC,
3203        };
3204
3205        let mut error_packet = IE::make_serializer(
3206            error_src_addr,
3207            I::ULTIMATE_DST,
3208            reply_packet.clone().serialize_vec_outer().unwrap().unwrap_b().into_inner(),
3209        )
3210        .wrap_in(I::PacketBuilder::new(
3211            error_src_addr,
3212            I::ULTIMATE_DST,
3213            u8::MAX,
3214            IE::proto(),
3215        ));
3216
3217        let verdict = perform_nat::<EgressHook, _, _, _, _>(
3218            &mut core_ctx,
3219            &mut bindings_ctx,
3220            NAT_ENABLED_FOR_TESTS,
3221            &conntrack,
3222            &mut conn,
3223            direction,
3224            &nat_routines,
3225            &mut error_packet,
3226            Interfaces { ingress: None, egress: Some(&FakeMatcherDeviceId::ethernet_interface()) },
3227        );
3228        assert_eq!(verdict, Verdict::Accept(()));
3229
3230        let error_packet_expected =
3231            I::PacketBuilder::new(I::NETSTACK, I::ULTIMATE_DST, u8::MAX, IE::proto()).wrap_body(
3232                IE::make_serializer(
3233                    // We expect this address to be rewritten since `redirect_addr`
3234                    // shouldn't end up in the packet destined to the other host.
3235                    I::NETSTACK,
3236                    I::ULTIMATE_DST,
3237                    reply_packet_pre_nat.serialize_vec_outer().unwrap().unwrap_b().into_inner(),
3238                ),
3239            );
3240
3241        assert_eq!(error_packet, error_packet_expected);
3242    }
3243}