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