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::ip::IpVersionMarker;
14use net_types::SpecifiedAddr;
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.into()),
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.into()),
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.into()),
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.into()),
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.gen_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 packet::{InnerPacketBuilder, Serializer};
1319    use packet_formats::ip::{IpPacketBuilder, IpProto};
1320    use packet_formats::udp::UdpPacketBuilder;
1321    use test_case::{test_case, test_matrix};
1322
1323    use super::*;
1324    use crate::conntrack::Tuple;
1325    use crate::context::testutil::{
1326        FakeBindingsCtx, FakeDeviceClass, FakeNatCtx, FakePrimaryAddressId, FakeWeakAddressId,
1327    };
1328    use crate::matchers::testutil::{ethernet_interface, FakeDeviceId};
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().clone()
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.into()
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.into()
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 = FakeNatCtx::new([(ethernet_interface(), assigned_addr)]);
1612        let packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1613        let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
1614
1615        let hook = Hook {
1616            routines: vec![
1617                Routine {
1618                    rules: vec![
1619                        // Masquerade all traffic.
1620                        Rule::new(PacketMatcher::default(), Action::Masquerade { src_port: None }),
1621                    ],
1622                },
1623                Routine {
1624                    rules: vec![
1625                        // Drop all traffic.
1626                        Rule::new(PacketMatcher::default(), Action::Drop),
1627                    ],
1628                },
1629            ],
1630        };
1631
1632        assert_matches!(
1633            configure_nat::<EgressHook, _, _, _, _>(
1634                &mut core_ctx,
1635                &mut bindings_ctx,
1636                &conntrack,
1637                &mut conn,
1638                &hook,
1639                &packet,
1640                Interfaces { ingress: None, egress: Some(&ethernet_interface()) },
1641            ),
1642            Verdict::Accept(NatConfigurationResult::Result(ShouldNat::Yes(Some(_))))
1643        );
1644    }
1645
1646    #[test]
1647    fn redirect_ingress_drops_packet_if_no_assigned_address() {
1648        let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
1649        let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1650        let mut core_ctx = FakeNatCtx::default();
1651        let packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1652        let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
1653
1654        let hook = Hook {
1655            routines: vec![Routine {
1656                rules: vec![Rule::new(
1657                    PacketMatcher::default(),
1658                    Action::Redirect { dst_port: None },
1659                )],
1660            }],
1661        };
1662
1663        assert_eq!(
1664            configure_nat::<IngressHook, _, _, _, _>(
1665                &mut core_ctx,
1666                &mut bindings_ctx,
1667                &conntrack,
1668                &mut conn,
1669                &hook,
1670                &packet,
1671                Interfaces { ingress: Some(&ethernet_interface()), egress: None },
1672            ),
1673            Verdict::Drop.into()
1674        );
1675    }
1676
1677    #[test]
1678    fn masquerade_egress_drops_packet_if_no_assigned_address() {
1679        let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
1680        let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1681        let mut core_ctx = FakeNatCtx::default();
1682        let packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1683        let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
1684
1685        let hook = Hook {
1686            routines: vec![Routine {
1687                rules: vec![Rule::new(
1688                    PacketMatcher::default(),
1689                    Action::Masquerade { src_port: None },
1690                )],
1691            }],
1692        };
1693
1694        assert_eq!(
1695            configure_nat::<EgressHook, _, _, _, _>(
1696                &mut core_ctx,
1697                &mut bindings_ctx,
1698                &conntrack,
1699                &mut conn,
1700                &hook,
1701                &packet,
1702                Interfaces { ingress: None, egress: Some(&ethernet_interface()) },
1703            ),
1704            Verdict::Drop.into()
1705        );
1706    }
1707
1708    trait NatHookExt<I: FilterIpExt>: NatHook<I, Verdict<()>: PartialEq> {
1709        fn interfaces<'a>(interface: &'a FakeDeviceId) -> Interfaces<'a, FakeDeviceId>;
1710    }
1711
1712    impl<I: FilterIpExt> NatHookExt<I> for IngressHook {
1713        fn interfaces<'a>(interface: &'a FakeDeviceId) -> Interfaces<'a, FakeDeviceId> {
1714            Interfaces { ingress: Some(interface), egress: None }
1715        }
1716    }
1717
1718    impl<I: FilterIpExt> NatHookExt<I> for LocalEgressHook {
1719        fn interfaces<'a>(interface: &'a FakeDeviceId) -> Interfaces<'a, FakeDeviceId> {
1720            Interfaces { ingress: None, egress: Some(interface) }
1721        }
1722    }
1723
1724    impl<I: FilterIpExt> NatHookExt<I> for EgressHook {
1725        fn interfaces<'a>(interface: &'a FakeDeviceId) -> Interfaces<'a, FakeDeviceId> {
1726            Interfaces { ingress: None, egress: Some(interface) }
1727        }
1728    }
1729
1730    const NAT_ENABLED_FOR_TESTS: bool = true;
1731
1732    #[ip_test(I)]
1733    fn nat_disabled_for_self_connected_flows<I: TestIpExt>() {
1734        let mut bindings_ctx = FakeBindingsCtx::<I>::new();
1735        let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1736        let mut core_ctx = FakeNatCtx::default();
1737
1738        let mut packet = FakeIpPacket::<I, _> {
1739            src_ip: I::SRC_IP,
1740            dst_ip: I::SRC_IP,
1741            body: FakeUdpPacket { src_port: 22222, dst_port: 22222 },
1742        };
1743        let (mut conn, direction) = conntrack
1744            .get_connection_for_packet_and_update(
1745                &bindings_ctx,
1746                packet.conntrack_packet().expect("packet should be valid"),
1747            )
1748            .expect("packet should be valid")
1749            .expect("packet should be trackable");
1750
1751        // Even with a Redirect NAT rule in LOCAL_EGRESS, and a Masquerade NAT rule in
1752        // EGRESS, DNAT and SNAT should both be disabled for the connection because it
1753        // is self-connected.
1754        let verdict = perform_nat::<LocalEgressHook, _, _, _, _>(
1755            &mut core_ctx,
1756            &mut bindings_ctx,
1757            NAT_ENABLED_FOR_TESTS,
1758            &conntrack,
1759            &mut conn,
1760            direction,
1761            &Hook {
1762                routines: vec![Routine {
1763                    rules: vec![Rule::new(
1764                        PacketMatcher::default(),
1765                        Action::Redirect { dst_port: None },
1766                    )],
1767                }],
1768            },
1769            &mut packet,
1770            <LocalEgressHook as NatHookExt<I>>::interfaces(&ethernet_interface()),
1771        );
1772        assert_eq!(verdict, Verdict::Accept(()).into());
1773
1774        let verdict = perform_nat::<EgressHook, _, _, _, _>(
1775            &mut core_ctx,
1776            &mut bindings_ctx,
1777            NAT_ENABLED_FOR_TESTS,
1778            &conntrack,
1779            &mut conn,
1780            direction,
1781            &Hook {
1782                routines: vec![Routine {
1783                    rules: vec![Rule::new(
1784                        PacketMatcher::default(),
1785                        Action::Masquerade { src_port: None },
1786                    )],
1787                }],
1788            },
1789            &mut packet,
1790            <EgressHook as NatHookExt<I>>::interfaces(&ethernet_interface()),
1791        );
1792        assert_eq!(verdict, Verdict::Accept(()).into());
1793
1794        assert_eq!(conn.external_data().destination.get(), Some(&ShouldNat::No));
1795        assert_eq!(conn.external_data().source.get(), Some(&ShouldNat::No));
1796    }
1797
1798    #[ip_test(I)]
1799    fn nat_disabled_if_not_configured_before_connection_finalized<I: TestIpExt>() {
1800        let mut bindings_ctx = FakeBindingsCtx::<I>::new();
1801        let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1802        let mut core_ctx = FakeNatCtx::default();
1803
1804        let mut packet = FakeIpPacket::<I, FakeUdpPacket>::arbitrary_value();
1805        let (mut conn, direction) = conntrack
1806            .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
1807            .expect("packet should be valid")
1808            .expect("packet should be trackable");
1809
1810        // Skip NAT so the connection is finalized without DNAT configured for it.
1811        let verdict = perform_nat::<LocalEgressHook, _, _, _, _>(
1812            &mut core_ctx,
1813            &mut bindings_ctx,
1814            false, /* nat_installed */
1815            &conntrack,
1816            &mut conn,
1817            direction,
1818            &Hook::default(),
1819            &mut packet,
1820            <LocalEgressHook as NatHookExt<I>>::interfaces(&ethernet_interface()),
1821        );
1822        assert_eq!(verdict, Verdict::Accept(()).into());
1823        assert_eq!(conn.external_data().destination.get(), None);
1824        assert_eq!(conn.external_data().source.get(), None);
1825
1826        // Skip NAT so the connection is finalized without SNAT configured for it.
1827        let verdict = perform_nat::<EgressHook, _, _, _, _>(
1828            &mut core_ctx,
1829            &mut bindings_ctx,
1830            false, /* nat_installed */
1831            &conntrack,
1832            &mut conn,
1833            direction,
1834            &Hook::default(),
1835            &mut packet,
1836            <EgressHook as NatHookExt<I>>::interfaces(&ethernet_interface()),
1837        );
1838        assert_eq!(verdict, Verdict::Accept(()).into());
1839        assert_eq!(conn.external_data().destination.get(), None);
1840        assert_eq!(conn.external_data().source.get(), None);
1841
1842        let (inserted, _weak) = conntrack
1843            .finalize_connection(&mut bindings_ctx, conn)
1844            .expect("connection should not conflict");
1845        assert!(inserted);
1846
1847        // Now, when a reply comes in to INGRESS, expect that SNAT will be configured as
1848        // `DoNotNat` given it has not already been configured for the connection.
1849        let mut reply = packet.reply();
1850        let (mut conn, direction) = conntrack
1851            .get_connection_for_packet_and_update(&bindings_ctx, reply.conntrack_packet().unwrap())
1852            .expect("packet should be valid")
1853            .expect("packet should be trackable");
1854        let verdict = perform_nat::<IngressHook, _, _, _, _>(
1855            &mut core_ctx,
1856            &mut bindings_ctx,
1857            NAT_ENABLED_FOR_TESTS,
1858            &conntrack,
1859            &mut conn,
1860            direction,
1861            &Hook::default(),
1862            &mut reply,
1863            <IngressHook as NatHookExt<I>>::interfaces(&ethernet_interface()),
1864        );
1865        assert_eq!(verdict, Verdict::Accept(()).into());
1866        assert_eq!(conn.external_data().destination.get(), None);
1867        assert_eq!(conn.external_data().source.get(), Some(&ShouldNat::No));
1868
1869        // And finally, on LOCAL_INGRESS, DNAT should also be configured as `DoNotNat`.
1870        let verdict = perform_nat::<LocalIngressHook, _, _, _, _>(
1871            &mut core_ctx,
1872            &mut bindings_ctx,
1873            NAT_ENABLED_FOR_TESTS,
1874            &conntrack,
1875            &mut conn,
1876            direction,
1877            &Hook::default(),
1878            &mut reply,
1879            <IngressHook as NatHookExt<I>>::interfaces(&ethernet_interface()),
1880        );
1881        assert_eq!(verdict, Verdict::Accept(()).into());
1882        assert_eq!(conn.external_data().destination.get(), Some(&ShouldNat::No));
1883        assert_eq!(conn.external_data().source.get(), Some(&ShouldNat::No));
1884    }
1885
1886    const LOCAL_PORT: NonZeroU16 = NonZeroU16::new(55555).unwrap();
1887
1888    #[ip_test(I)]
1889    #[test_case(
1890        PhantomData::<IngressHook>, PhantomData::<EgressHook>, None;
1891        "redirect INGRESS"
1892    )]
1893    #[test_case(
1894        PhantomData::<IngressHook>, PhantomData::<EgressHook>, Some(LOCAL_PORT);
1895        "redirect INGRESS to local port"
1896    )]
1897    #[test_case(
1898        PhantomData::<LocalEgressHook>, PhantomData::<EgressHook>, None;
1899        "redirect LOCAL_EGRESS"
1900    )]
1901    #[test_case(
1902        PhantomData::<LocalEgressHook>, PhantomData::<EgressHook>, Some(LOCAL_PORT);
1903        "redirect LOCAL_EGRESS to local port"
1904    )]
1905    fn redirect<I: TestIpExt, Original: NatHookExt<I>, Reply: NatHookExt<I>>(
1906        _original_nat_hook: PhantomData<Original>,
1907        _reply_nat_hook: PhantomData<Reply>,
1908        dst_port: Option<NonZeroU16>,
1909    ) {
1910        let mut bindings_ctx = FakeBindingsCtx::<I>::new();
1911        let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1912        let mut core_ctx = FakeNatCtx::new([(
1913            ethernet_interface(),
1914            AddrSubnet::new(I::DST_IP_2, I::SUBNET.prefix()).unwrap(),
1915        )]);
1916
1917        // Create a packet and get the corresponding connection from conntrack.
1918        let mut packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
1919        let pre_nat_packet = packet.clone();
1920        let (mut conn, direction) = conntrack
1921            .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
1922            .expect("packet should be valid")
1923            .expect("packet should be trackable");
1924        let original = conn.original_tuple().clone();
1925
1926        // Perform NAT at the first hook where we'd encounter this packet (either
1927        // INGRESS, if it's an incoming packet, or LOCAL_EGRESS, if it's an outgoing
1928        // packet).
1929        let nat_routines = Hook {
1930            routines: vec![Routine {
1931                rules: vec![Rule::new(
1932                    PacketMatcher::default(),
1933                    Action::Redirect { dst_port: dst_port.map(|port| port..=port) },
1934                )],
1935            }],
1936        };
1937        let verdict = perform_nat::<Original, _, _, _, _>(
1938            &mut core_ctx,
1939            &mut bindings_ctx,
1940            NAT_ENABLED_FOR_TESTS,
1941            &conntrack,
1942            &mut conn,
1943            direction,
1944            &nat_routines,
1945            &mut packet,
1946            Original::interfaces(&ethernet_interface()),
1947        );
1948        assert_eq!(verdict, Verdict::Accept(()).into());
1949
1950        // The packet's destination should be rewritten, and DNAT should be configured
1951        // for the packet; the reply tuple's source should be rewritten to match the new
1952        // destination.
1953        let (redirect_addr, cached_addr) = Original::redirect_addr(
1954            &mut core_ctx,
1955            &packet,
1956            Original::interfaces(&ethernet_interface()).ingress,
1957        )
1958        .expect("get redirect addr for NAT hook");
1959        let expected = FakeIpPacket::<_, FakeUdpPacket> {
1960            src_ip: packet.src_ip,
1961            dst_ip: redirect_addr,
1962            body: FakeUdpPacket {
1963                src_port: packet.body.src_port,
1964                dst_port: dst_port.map(NonZeroU16::get).unwrap_or(packet.body.dst_port),
1965            },
1966        };
1967        assert_eq!(packet, expected);
1968        assert_eq!(
1969            conn.external_data().destination.get().expect("DNAT should be configured"),
1970            &ShouldNat::Yes(cached_addr)
1971        );
1972        assert_eq!(conn.external_data().source.get(), None, "SNAT should not be configured");
1973        assert_eq!(conn.original_tuple(), &original);
1974        let mut reply = Tuple { src_addr: redirect_addr, ..original.invert() };
1975        if let Some(port) = dst_port {
1976            reply.src_port_or_id = port.get();
1977        }
1978        assert_eq!(conn.reply_tuple(), &reply);
1979
1980        // When a reply to the original packet arrives at the corresponding hook, it
1981        // should have reverse DNAT applied, i.e. its source should be rewritten to
1982        // match the original destination of the connection.
1983        let mut reply_packet = packet.reply();
1984        // Install a NAT routine that simply drops all packets. This should have no
1985        // effect, because only the first packet for a given connection traverses NAT
1986        // routines.
1987        let nat_routines = Hook {
1988            routines: vec![Routine {
1989                rules: vec![Rule::new(PacketMatcher::default(), Action::Drop)],
1990            }],
1991        };
1992        let verdict = perform_nat::<Reply, _, _, _, _>(
1993            &mut core_ctx,
1994            &mut bindings_ctx,
1995            NAT_ENABLED_FOR_TESTS,
1996            &conntrack,
1997            &mut conn,
1998            ConnectionDirection::Reply,
1999            &nat_routines,
2000            &mut reply_packet,
2001            Reply::interfaces(&ethernet_interface()),
2002        );
2003        assert_eq!(verdict, Verdict::Accept(()).into());
2004        assert_eq!(reply_packet, pre_nat_packet.reply());
2005    }
2006
2007    #[ip_test(I)]
2008    #[test_case(None; "masquerade")]
2009    #[test_case(Some(LOCAL_PORT); "masquerade to specified port")]
2010    fn masquerade<I: TestIpExt>(src_port: Option<NonZeroU16>) {
2011        let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2012        let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2013        let assigned_addr = AddrSubnet::new(I::SRC_IP_2, I::SUBNET.prefix()).unwrap();
2014        let mut core_ctx = FakeNatCtx::new([(ethernet_interface(), assigned_addr)]);
2015
2016        // Create a packet and get the corresponding connection from conntrack.
2017        let mut packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
2018        let pre_nat_packet = packet.clone();
2019        let (mut conn, direction) = conntrack
2020            .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2021            .expect("packet should be valid")
2022            .expect("packet should be trackable");
2023        let original = conn.original_tuple().clone();
2024
2025        // Perform Masquerade NAT at EGRESS.
2026        let nat_routines = Hook {
2027            routines: vec![Routine {
2028                rules: vec![Rule::new(
2029                    PacketMatcher::default(),
2030                    Action::Masquerade { src_port: src_port.map(|port| port..=port) },
2031                )],
2032            }],
2033        };
2034        let verdict = perform_nat::<EgressHook, _, _, _, _>(
2035            &mut core_ctx,
2036            &mut bindings_ctx,
2037            NAT_ENABLED_FOR_TESTS,
2038            &conntrack,
2039            &mut conn,
2040            direction,
2041            &nat_routines,
2042            &mut packet,
2043            Interfaces { ingress: None, egress: Some(&ethernet_interface()) },
2044        );
2045        assert_eq!(verdict, Verdict::Accept(()).into());
2046
2047        // The packet's source address should be rewritten, and SNAT should be
2048        // configured for the packet; the reply tuple's destination should be rewritten
2049        // to match the new source.
2050        let expected = FakeIpPacket::<_, FakeUdpPacket> {
2051            src_ip: I::SRC_IP_2,
2052            dst_ip: packet.dst_ip,
2053            body: FakeUdpPacket {
2054                src_port: src_port.map(NonZeroU16::get).unwrap_or(packet.body.src_port),
2055                dst_port: packet.body.dst_port,
2056            },
2057        };
2058        assert_eq!(packet, expected);
2059        assert_matches!(
2060            conn.external_data().source.get().expect("SNAT should be configured"),
2061            &ShouldNat::Yes(Some(_))
2062        );
2063        assert_eq!(conn.external_data().destination.get(), None, "DNAT should not be configured");
2064        assert_eq!(conn.original_tuple(), &original);
2065        let mut reply = Tuple { dst_addr: I::SRC_IP_2, ..original.invert() };
2066        if let Some(port) = src_port {
2067            reply.dst_port_or_id = port.get();
2068        }
2069        assert_eq!(conn.reply_tuple(), &reply);
2070
2071        // When a reply to the original packet arrives at INGRESS, it should have
2072        // reverse SNAT applied, i.e. its destination should be rewritten to match the
2073        // original source of the connection.
2074        let mut reply_packet = packet.reply();
2075        // Install a NAT routine that simply drops all packets. This should have no
2076        // effect, because only the first packet for a given connection traverses NAT
2077        // routines.
2078        let nat_routines = Hook {
2079            routines: vec![Routine {
2080                rules: vec![Rule::new(PacketMatcher::default(), Action::Drop)],
2081            }],
2082        };
2083        let verdict = perform_nat::<IngressHook, _, _, _, _>(
2084            &mut core_ctx,
2085            &mut bindings_ctx,
2086            NAT_ENABLED_FOR_TESTS,
2087            &conntrack,
2088            &mut conn,
2089            ConnectionDirection::Reply,
2090            &nat_routines,
2091            &mut reply_packet,
2092            Interfaces { ingress: Some(&ethernet_interface()), egress: None },
2093        );
2094        assert_eq!(verdict, Verdict::Accept(()).into());
2095        assert_eq!(reply_packet, pre_nat_packet.reply());
2096    }
2097
2098    #[ip_test(I)]
2099    #[test_case(22, 1..=511)]
2100    #[test_case(853, 1..=1023)]
2101    #[test_case(11111, 1024..=u16::MAX)]
2102    fn masquerade_reply_tuple_dst_port_rewritten_even_if_target_range_unspecified<I: TestIpExt>(
2103        src_port: u16,
2104        expected_range: RangeInclusive<u16>,
2105    ) {
2106        let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2107        let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2108        let assigned_addr = AddrSubnet::new(I::SRC_IP_2, I::SUBNET.prefix()).unwrap();
2109        let mut core_ctx = FakeNatCtx::new([(ethernet_interface(), assigned_addr)]);
2110        let packet = FakeIpPacket {
2111            body: FakeUdpPacket { src_port, ..ArbitraryValue::arbitrary_value() },
2112            ..ArbitraryValue::arbitrary_value()
2113        };
2114
2115        // First, insert a connection in conntrack with the same the source address the
2116        // packet will be masqueraded to, and the same source port, to cause a conflict.
2117        let reply = FakeIpPacket { src_ip: I::SRC_IP_2, ..packet.clone() };
2118        let (conn, _dir) = conntrack
2119            .get_connection_for_packet_and_update(&bindings_ctx, reply.conntrack_packet().unwrap())
2120            .expect("packet should be valid")
2121            .expect("packet should be trackable");
2122        assert_matches!(
2123            conntrack
2124                .finalize_connection(&mut bindings_ctx, conn)
2125                .expect("connection should not conflict"),
2126            (true, Some(_))
2127        );
2128
2129        // Now, configure Masquerade NAT for a new connection that conflicts with the
2130        // existing one, but do not specify a port range to which the source port should
2131        // be rewritten.
2132        let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
2133        let verdict = configure_masquerade_nat(
2134            &mut core_ctx,
2135            &mut bindings_ctx,
2136            &conntrack,
2137            &mut conn,
2138            &packet,
2139            &Interfaces { ingress: None, egress: Some(&ethernet_interface()) },
2140            /* src_port */ None,
2141        );
2142
2143        // The destination address of the reply tuple should have been rewritten to the
2144        // new source address, and the destination port should also have been rewritten
2145        // (to a "similar" value), even though a rewrite range was not specified,
2146        // because otherwise it would conflict with the existing connection in the
2147        // table.
2148        assert_matches!(
2149            verdict,
2150            Verdict::Accept(NatConfigurationResult::Result(ShouldNat::Yes(Some(_))))
2151        );
2152        let reply_tuple = conn.reply_tuple();
2153        assert_eq!(reply_tuple.dst_addr, I::SRC_IP_2);
2154        assert_ne!(reply_tuple.dst_port_or_id, src_port);
2155        assert!(expected_range.contains(&reply_tuple.dst_port_or_id));
2156    }
2157
2158    #[ip_test(I)]
2159    #[test_case(
2160        PhantomData::<IngressHook>, Action::Redirect { dst_port: None };
2161        "redirect in INGRESS"
2162    )]
2163    #[test_case(
2164        PhantomData::<EgressHook>, Action::Masquerade { src_port: None };
2165        "masquerade in EGRESS"
2166    )]
2167    fn assigned_addr_cached_and_validated<I: TestIpExt, N: NatHookExt<I>>(
2168        _nat_hook: PhantomData<N>,
2169        action: Action<I, FakeDeviceClass, ()>,
2170    ) {
2171        let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2172        let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2173        let assigned_addr = AddrSubnet::new(I::SRC_IP_2, I::SUBNET.prefix()).unwrap();
2174        let mut core_ctx = FakeNatCtx::new([(ethernet_interface(), assigned_addr)]);
2175
2176        // Create a packet and get the corresponding connection from conntrack.
2177        let mut packet = FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value();
2178        let (mut conn, direction) = conntrack
2179            .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2180            .expect("packet should be valid")
2181            .expect("packet should be trackable");
2182
2183        // Perform NAT.
2184        let nat_routines = Hook {
2185            routines: vec![Routine { rules: vec![Rule::new(PacketMatcher::default(), action)] }],
2186        };
2187        let verdict = perform_nat::<N, _, _, _, _>(
2188            &mut core_ctx,
2189            &mut bindings_ctx,
2190            NAT_ENABLED_FOR_TESTS,
2191            &conntrack,
2192            &mut conn,
2193            direction,
2194            &nat_routines,
2195            &mut packet,
2196            N::interfaces(&ethernet_interface()),
2197        );
2198        assert_eq!(verdict, Verdict::Accept(()).into());
2199
2200        // A weakly-held reference to the address assigned to the interface should be
2201        // cached in the NAT config.
2202        let (nat, _nat_type) = conn.relevant_config(N::NAT_TYPE, ConnectionDirection::Original);
2203        let nat = nat.get().unwrap_or_else(|| panic!("{:?} NAT should be configured", N::NAT_TYPE));
2204        let id = assert_matches!(nat, ShouldNat::Yes(Some(CachedAddr { id, _marker })) => id);
2205        let id = id
2206            .lock()
2207            .as_ref()
2208            .expect("address ID should be cached in NAT config")
2209            .upgrade()
2210            .expect("address ID should be valid");
2211        assert_eq!(*id, assigned_addr);
2212        drop(id);
2213
2214        // Remove the assigned address; subsequent packets in the original direction on
2215        // the same flow should check the validity of the cached address, see that it's
2216        // invalid, and be dropped.
2217        core_ctx.device_addrs.clear();
2218        let verdict = perform_nat::<N, _, _, _, _>(
2219            &mut core_ctx,
2220            &mut bindings_ctx,
2221            NAT_ENABLED_FOR_TESTS,
2222            &conntrack,
2223            &mut conn,
2224            ConnectionDirection::Original,
2225            &nat_routines,
2226            &mut FakeIpPacket::<_, FakeUdpPacket>::arbitrary_value(),
2227            N::interfaces(&ethernet_interface()),
2228        );
2229        assert_eq!(verdict, Verdict::Drop.into());
2230        let (nat, _nat_type) = conn.relevant_config(N::NAT_TYPE, ConnectionDirection::Original);
2231        let nat = nat.get().unwrap_or_else(|| panic!("{:?} NAT should be configured", N::NAT_TYPE));
2232        let id = assert_matches!(nat, ShouldNat::Yes(Some(CachedAddr { id, _marker })) => id);
2233        assert_eq!(*id.lock(), None, "cached weak address ID should be cleared");
2234
2235        // Reassign the address to the interface, and packet traversal should now
2236        // succeed, with NAT re-acquiring a handle to the address.
2237        assert_matches!(
2238            core_ctx
2239                .device_addrs
2240                .insert(ethernet_interface(), FakePrimaryAddressId(Arc::new(assigned_addr))),
2241            None
2242        );
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(&ethernet_interface()),
2253        );
2254        assert_eq!(verdict, Verdict::Accept(()).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        let id = id
2259            .lock()
2260            .as_ref()
2261            .expect("address ID should be cached in NAT config")
2262            .upgrade()
2263            .expect("address Id should be valid");
2264        assert_eq!(*id, assigned_addr);
2265    }
2266
2267    #[test_case(ReplyTuplePort::Source)]
2268    #[test_case(ReplyTuplePort::Destination)]
2269    fn rewrite_port_noop_if_in_range(which: ReplyTuplePort) {
2270        let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
2271        let table = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2272        let mut conn =
2273            ConnectionExclusive::with_reply_tuple(&bindings_ctx, which, LOCAL_PORT.get());
2274
2275        // If the port is already in the specified range, rewriting should succeed and
2276        // be a no-op.
2277        let pre_nat = conn.reply_tuple().clone();
2278        let result = rewrite_reply_tuple_port(
2279            &mut bindings_ctx,
2280            &table,
2281            &mut conn,
2282            which,
2283            LOCAL_PORT..=LOCAL_PORT,
2284            true, /* ensure_port_in_range */
2285            ConflictStrategy::RewritePort,
2286        );
2287        assert_eq!(
2288            result,
2289            Verdict::Accept(NatConfigurationResult::Result(
2290                ShouldNat::<_, FakeWeakAddressId<Ipv4>>::No
2291            ))
2292        );
2293        assert_eq!(conn.reply_tuple(), &pre_nat);
2294    }
2295
2296    #[test_case(ReplyTuplePort::Source)]
2297    #[test_case(ReplyTuplePort::Destination)]
2298    fn rewrite_port_noop_if_no_conflict(which: ReplyTuplePort) {
2299        let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
2300        let table = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2301        let mut conn =
2302            ConnectionExclusive::with_reply_tuple(&bindings_ctx, which, LOCAL_PORT.get());
2303
2304        // If there is no conflicting tuple in the table and we provide `false` for
2305        // `ensure_port_in_range` (as is done for implicit SNAT), then rewriting should
2306        // succeed and be a no-op, even if the port is not in the specified range,
2307        let pre_nat = conn.reply_tuple().clone();
2308        const NEW_PORT: NonZeroU16 = LOCAL_PORT.checked_add(1).unwrap();
2309        let result = rewrite_reply_tuple_port(
2310            &mut bindings_ctx,
2311            &table,
2312            &mut conn,
2313            which,
2314            NEW_PORT..=NEW_PORT,
2315            false, /* ensure_port_in_range */
2316            ConflictStrategy::RewritePort,
2317        );
2318        assert_eq!(
2319            result,
2320            Verdict::Accept(NatConfigurationResult::Result(
2321                ShouldNat::<_, FakeWeakAddressId<Ipv4>>::No
2322            ))
2323        );
2324        assert_eq!(conn.reply_tuple(), &pre_nat);
2325    }
2326
2327    #[test_case(ReplyTuplePort::Source)]
2328    #[test_case(ReplyTuplePort::Destination)]
2329    fn rewrite_port_succeeds_if_available_port_in_range(which: ReplyTuplePort) {
2330        let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
2331        let table = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2332        let mut conn =
2333            ConnectionExclusive::with_reply_tuple(&bindings_ctx, which, LOCAL_PORT.get());
2334
2335        // If the port is not in the specified range, but there is an available port,
2336        // rewriting should succeed.
2337        const NEW_PORT: NonZeroU16 = LOCAL_PORT.checked_add(1).unwrap();
2338        let result = rewrite_reply_tuple_port(
2339            &mut bindings_ctx,
2340            &table,
2341            &mut conn,
2342            which,
2343            NEW_PORT..=NEW_PORT,
2344            true, /* ensure_port_in_range */
2345            ConflictStrategy::RewritePort,
2346        );
2347        assert_eq!(
2348            result,
2349            Verdict::Accept(NatConfigurationResult::Result(
2350                ShouldNat::<_, FakeWeakAddressId<Ipv4>>::Yes(None)
2351            ))
2352        );
2353        assert_eq!(conn.reply_tuple(), &tuple_with_port(which, NEW_PORT.get()));
2354    }
2355
2356    #[test_case(ReplyTuplePort::Source)]
2357    #[test_case(ReplyTuplePort::Destination)]
2358    fn rewrite_port_fails_if_no_available_port_in_range(which: ReplyTuplePort) {
2359        let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
2360        let table: Table<_, NatConfig<_, FakeWeakAddressId<Ipv4>>, _> =
2361            Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2362
2363        // If there is no port available in the specified range that does not conflict
2364        // with a tuple already in the table, rewriting should fail and the packet
2365        // should be dropped.
2366        let packet = packet_with_port(which, LOCAL_PORT.get());
2367        let (conn, _dir) = table
2368            .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2369            .expect("packet should be valid")
2370            .expect("packet should be trackable");
2371        assert_matches!(
2372            table
2373                .finalize_connection(&mut bindings_ctx, conn)
2374                .expect("connection should not conflict"),
2375            (true, Some(_))
2376        );
2377
2378        let mut conn =
2379            ConnectionExclusive::with_reply_tuple(&bindings_ctx, which, LOCAL_PORT.get());
2380        let result = rewrite_reply_tuple_port(
2381            &mut bindings_ctx,
2382            &table,
2383            &mut conn,
2384            which,
2385            LOCAL_PORT..=LOCAL_PORT,
2386            true, /* ensure_port_in_range */
2387            ConflictStrategy::RewritePort,
2388        );
2389        assert_eq!(result, Verdict::Drop.into());
2390    }
2391
2392    #[test_case(ReplyTuplePort::Source)]
2393    #[test_case(ReplyTuplePort::Destination)]
2394    fn port_rewritten_to_ensure_unique_tuple_even_if_in_range(which: ReplyTuplePort) {
2395        let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
2396        let table = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2397
2398        // Fill the conntrack table with tuples such that there is only one tuple that
2399        // does not conflict with an existing one and which has a port in the specified
2400        // range.
2401        const MAX_PORT: NonZeroU16 = NonZeroU16::new(LOCAL_PORT.get() + 100).unwrap();
2402        for port in LOCAL_PORT.get()..=MAX_PORT.get() {
2403            let packet = packet_with_port(which, port);
2404            let (conn, _dir) = table
2405                .get_connection_for_packet_and_update(
2406                    &bindings_ctx,
2407                    packet.conntrack_packet().unwrap(),
2408                )
2409                .expect("packet should be valid")
2410                .expect("packet should be trackable");
2411            assert_matches!(
2412                table
2413                    .finalize_connection(&mut bindings_ctx, conn)
2414                    .expect("connection should not conflict"),
2415                (true, Some(_))
2416            );
2417        }
2418
2419        // If the port is in the specified range, but results in a non-unique tuple,
2420        // rewriting should succeed as long as some port exists in the range that
2421        // results in a unique tuple.
2422        let mut conn =
2423            ConnectionExclusive::with_reply_tuple(&bindings_ctx, which, LOCAL_PORT.get());
2424        const MIN_PORT: NonZeroU16 = NonZeroU16::new(LOCAL_PORT.get() - 1).unwrap();
2425        let result = rewrite_reply_tuple_port(
2426            &mut bindings_ctx,
2427            &table,
2428            &mut conn,
2429            which,
2430            MIN_PORT..=MAX_PORT,
2431            true, /* ensure_port_in_range */
2432            ConflictStrategy::RewritePort,
2433        );
2434        assert_eq!(
2435            result,
2436            Verdict::Accept(NatConfigurationResult::Result(
2437                ShouldNat::<_, FakeWeakAddressId<Ipv4>>::Yes(None)
2438            ))
2439        );
2440        assert_eq!(conn.reply_tuple(), &tuple_with_port(which, MIN_PORT.get()));
2441    }
2442
2443    #[test_case(ReplyTuplePort::Source)]
2444    #[test_case(ReplyTuplePort::Destination)]
2445    fn rewrite_port_skipped_if_existing_connection_can_be_adopted(which: ReplyTuplePort) {
2446        let mut bindings_ctx = FakeBindingsCtx::<Ipv4>::new();
2447        let table: Table<_, NatConfig<_, FakeWeakAddressId<Ipv4>>, _> =
2448            Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2449
2450        // If there is a conflicting connection already in the table, but the caller
2451        // specifies that existing connections can be adopted if they are identical to
2452        // the one we are NATing, and the one in the table is a match, the existing
2453        // connection should be adopted.
2454        let packet = packet_with_port(which, LOCAL_PORT.get());
2455        let (conn, _dir) = table
2456            .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2457            .expect("packet should be valid")
2458            .expect("packet should be trackable");
2459        let existing = assert_matches!(
2460            table
2461                .finalize_connection(&mut bindings_ctx, conn)
2462                .expect("connection should not conflict"),
2463            (true, Some(conn)) => conn
2464        );
2465
2466        let mut conn = ConnectionExclusive::from_packet(&bindings_ctx, &packet);
2467        let result = rewrite_reply_tuple_port(
2468            &mut bindings_ctx,
2469            &table,
2470            &mut conn,
2471            which,
2472            NonZeroU16::MIN..=NonZeroU16::MAX,
2473            false, /* ensure_port_in_range */
2474            ConflictStrategy::AdoptExisting,
2475        );
2476        let conn = assert_matches!(
2477            result,
2478            Verdict::Accept(NatConfigurationResult::AdoptExisting(Connection::Shared(conn))) => conn
2479        );
2480        assert!(Arc::ptr_eq(&existing, &conn));
2481    }
2482
2483    trait IcmpErrorTestIpExt: TestIpExt {
2484        const NETSTACK: Self::Addr = Self::DST_IP;
2485        const ULTIMATE_SRC: Self::Addr = Self::SRC_IP;
2486        const ULTIMATE_DST: Self::Addr = Self::DST_IP_3;
2487        const ROUTER_SRC: Self::Addr = Self::SRC_IP_2;
2488        const ROUTER_DST: Self::Addr = Self::DST_IP_2;
2489    }
2490
2491    impl<I> IcmpErrorTestIpExt for I where I: TestIpExt {}
2492
2493    enum IcmpErrorSource {
2494        IntermediateRouter,
2495        EndHost,
2496    }
2497
2498    // The next two test cases (redirect_icmp_error_in_reply_direction and
2499    // redirect_icmp_error_in_original_direction) require some explanation. The
2500    // tests are based on the `redirect` test above, but are validating that
2501    // ICMP error NAT works properly.
2502    //
2503    // The imaginary network used in these tests is set up as follows:
2504    //
2505    // D --- US --- SR --- S
2506    //
2507    // Where:
2508    //
2509    // - US: That's us :-)
2510    // - D:  The ultimate destination
2511    // - S:  The ultimate source
2512    // - SR: A router between us and the source
2513    //
2514    // The IPs that matter are:
2515    //
2516    // - D: ULTIMATE_DST
2517    // - US: NETSTACK
2518    // - S: ULTIMATE_SRC
2519    // - SR: ROUTER_SRC
2520    //
2521    // There is DNAT configured such that packets coming from S to D are
2522    // rewritten to instead go to US. Packets going from US -> S will have their
2523    // source rewritten to replace US with the address of D.
2524    //
2525    // Each test checks that an ICMP error traveling in either the original or
2526    // reply direction are rewritten appropriately.
2527
2528    #[test_case(Icmpv4DestUnreachableError)]
2529    #[test_case(Icmpv6DestUnreachableError)]
2530    fn redirect_icmp_error_in_reply_direction<I: IcmpErrorTestIpExt, IE: IcmpErrorMessage<I>>(
2531        _icmp_error: IE,
2532    ) {
2533        let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2534        let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2535        let mut core_ctx = FakeNatCtx::new([(
2536            ethernet_interface(),
2537            AddrSubnet::new(I::NETSTACK, I::SUBNET.prefix()).unwrap(),
2538        )]);
2539
2540        // Create a packet and get the corresponding connection from conntrack.
2541        let mut packet = []
2542            .into_serializer()
2543            .encapsulate(UdpPacketBuilder::new(
2544                I::ULTIMATE_SRC,
2545                I::ULTIMATE_DST,
2546                Some(NonZeroU16::new(11111).unwrap()),
2547                NonZeroU16::new(22222).unwrap(),
2548            ))
2549            .encapsulate(I::PacketBuilder::new(
2550                I::ULTIMATE_SRC,
2551                I::ULTIMATE_DST,
2552                u8::MAX,
2553                IpProto::Udp.into(),
2554            ));
2555        let packet_pre_nat = packet.clone();
2556        let (mut conn, _) = conntrack
2557            .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2558            .expect("packet should be valid")
2559            .expect("packet should be trackable");
2560        let original_tuple = conn.original_tuple().clone();
2561
2562        let nat_routines = Hook {
2563            routines: vec![Routine {
2564                rules: vec![Rule::new(
2565                    PacketMatcher::default(),
2566                    Action::Redirect { dst_port: Some(LOCAL_PORT..=LOCAL_PORT) },
2567                )],
2568            }],
2569        };
2570        let verdict = perform_nat::<IngressHook, _, _, _, _>(
2571            &mut core_ctx,
2572            &mut bindings_ctx,
2573            NAT_ENABLED_FOR_TESTS,
2574            &conntrack,
2575            &mut conn,
2576            ConnectionDirection::Original,
2577            &nat_routines,
2578            &mut packet,
2579            <IngressHook as NatHookExt<I>>::interfaces(&ethernet_interface()),
2580        );
2581        assert_eq!(verdict, Verdict::Accept(()).into());
2582
2583        // The packet's destination should be rewritten and DNAT should be
2584        // configured for the packet; the reply tuple's source should be
2585        // rewritten to match the new destination.
2586        let (redirect_addr, cached_addr) = IngressHook::redirect_addr(
2587            &mut core_ctx,
2588            &packet,
2589            <IngressHook as NatHookExt<I>>::interfaces(&ethernet_interface()).ingress,
2590        )
2591        .expect("get redirect addr for NAT hook");
2592
2593        // Since this is in the INGRESS hook, redirect_addr should be the
2594        // address of the receiving interface.
2595        assert_eq!(redirect_addr, I::NETSTACK);
2596
2597        // The destination address and port were updated.
2598        let expected = []
2599            .into_serializer()
2600            .encapsulate(UdpPacketBuilder::new(
2601                I::ULTIMATE_SRC,
2602                redirect_addr,
2603                packet.inner().outer().src_port(),
2604                LOCAL_PORT,
2605            ))
2606            .encapsulate(I::PacketBuilder::new(
2607                I::ULTIMATE_SRC,
2608                redirect_addr,
2609                u8::MAX,
2610                IpProto::Udp.into(),
2611            ));
2612        assert_eq!(packet, expected);
2613        assert_eq!(
2614            conn.external_data().destination.get().expect("DNAT should be configured"),
2615            &ShouldNat::Yes(cached_addr)
2616        );
2617        assert_eq!(conn.external_data().source.get(), None, "SNAT should not be configured");
2618        assert_eq!(conn.original_tuple(), &original_tuple);
2619
2620        let reply_tuple = Tuple {
2621            src_addr: redirect_addr,
2622            src_port_or_id: LOCAL_PORT.get(),
2623            ..original_tuple.invert()
2624        };
2625        assert_eq!(conn.reply_tuple(), &reply_tuple);
2626
2627        let mut error_packet = IE::make_serializer(
2628            redirect_addr,
2629            I::ULTIMATE_SRC,
2630            packet.clone().serialize_vec_outer().unwrap().unwrap_b().into_inner(),
2631        )
2632        .encapsulate(I::PacketBuilder::new(
2633            redirect_addr,
2634            I::ULTIMATE_SRC,
2635            u8::MAX,
2636            IE::proto(),
2637        ));
2638
2639        let verdict = perform_nat::<EgressHook, _, _, _, _>(
2640            &mut core_ctx,
2641            &mut bindings_ctx,
2642            NAT_ENABLED_FOR_TESTS,
2643            &conntrack,
2644            &mut conn,
2645            ConnectionDirection::Reply,
2646            &nat_routines,
2647            &mut error_packet,
2648            <EgressHook as NatHookExt<I>>::interfaces(&ethernet_interface()),
2649        );
2650        assert_eq!(verdict, Verdict::Accept(()).into());
2651
2652        let error_packet_expected = IE::make_serializer(
2653            // We expect this address to be rewritten since `redirect_addr`
2654            // shouldn't end up in the packet destined to the other host.
2655            I::ULTIMATE_DST,
2656            I::ULTIMATE_SRC,
2657            packet_pre_nat.clone().serialize_vec_outer().unwrap().unwrap_b().into_inner(),
2658        )
2659        .encapsulate(I::PacketBuilder::new(
2660            I::ULTIMATE_DST,
2661            I::ULTIMATE_SRC,
2662            u8::MAX,
2663            IE::proto(),
2664        ));
2665
2666        assert_eq!(error_packet, error_packet_expected);
2667    }
2668
2669    #[test_matrix(
2670        [
2671            Icmpv4DestUnreachableError,
2672            Icmpv6DestUnreachableError,
2673        ],
2674        [
2675            IcmpErrorSource::IntermediateRouter,
2676            IcmpErrorSource::EndHost,
2677        ]
2678    )]
2679    fn redirect_icmp_error_in_original_direction<I: IcmpErrorTestIpExt, IE: IcmpErrorMessage<I>>(
2680        _icmp_error: IE,
2681        icmp_error_source: IcmpErrorSource,
2682    ) {
2683        let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2684        let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2685        let mut core_ctx = FakeNatCtx::new([(
2686            ethernet_interface(),
2687            AddrSubnet::new(I::NETSTACK, I::SUBNET.prefix()).unwrap(),
2688        )]);
2689
2690        // Create a packet and get the corresponding connection from conntrack.
2691        let mut packet = []
2692            .into_serializer()
2693            .encapsulate(UdpPacketBuilder::new(
2694                I::ULTIMATE_SRC,
2695                I::ULTIMATE_DST,
2696                Some(NonZeroU16::new(11111).unwrap()),
2697                NonZeroU16::new(22222).unwrap(),
2698            ))
2699            .encapsulate(I::PacketBuilder::new(
2700                I::ULTIMATE_SRC,
2701                I::ULTIMATE_DST,
2702                u8::MAX,
2703                IpProto::Udp.into(),
2704            ));
2705        let (mut conn, direction) = conntrack
2706            .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2707            .expect("packet should be valid")
2708            .expect("packet should be trackable");
2709        let original_tuple = conn.original_tuple().clone();
2710
2711        // Perform NAT at the first hook where we'd encounter this packet (either
2712        // INGRESS, if it's an incoming packet, or LOCAL_EGRESS, if it's an outgoing
2713        // packet).
2714        let nat_routines = Hook {
2715            routines: vec![Routine {
2716                rules: vec![Rule::new(
2717                    PacketMatcher::default(),
2718                    Action::Redirect { dst_port: Some(LOCAL_PORT..=LOCAL_PORT) },
2719                )],
2720            }],
2721        };
2722        let verdict = perform_nat::<IngressHook, _, _, _, _>(
2723            &mut core_ctx,
2724            &mut bindings_ctx,
2725            NAT_ENABLED_FOR_TESTS,
2726            &conntrack,
2727            &mut conn,
2728            direction,
2729            &nat_routines,
2730            &mut packet,
2731            <IngressHook as NatHookExt<I>>::interfaces(&ethernet_interface()),
2732        );
2733        assert_eq!(verdict, Verdict::Accept(()).into());
2734
2735        // The packet's destination should be rewritten, and DNAT should be configured
2736        // for the packet; the reply tuple's source should be rewritten to match the new
2737        // destination.
2738        let (redirect_addr, cached_addr) = IngressHook::redirect_addr(
2739            &mut core_ctx,
2740            &packet,
2741            <IngressHook as NatHookExt<I>>::interfaces(&ethernet_interface()).ingress,
2742        )
2743        .expect("get redirect addr for NAT hook");
2744
2745        // Since this is in the INGRESS hook, redirect_addr should be the
2746        // address of the receiving interface.
2747        assert_eq!(redirect_addr, I::NETSTACK);
2748
2749        // The destination address and port were updated.
2750        let expected = []
2751            .into_serializer()
2752            .encapsulate(UdpPacketBuilder::new(
2753                I::ULTIMATE_SRC,
2754                redirect_addr,
2755                packet.inner().outer().src_port(),
2756                LOCAL_PORT,
2757            ))
2758            .encapsulate(I::PacketBuilder::new(
2759                I::ULTIMATE_SRC,
2760                redirect_addr,
2761                u8::MAX,
2762                IpProto::Udp.into(),
2763            ));
2764        assert_eq!(packet, expected);
2765        assert_eq!(
2766            conn.external_data().destination.get().expect("DNAT should be configured"),
2767            &ShouldNat::Yes(cached_addr)
2768        );
2769        assert_eq!(conn.external_data().source.get(), None, "SNAT should not be configured");
2770        assert_eq!(conn.original_tuple(), &original_tuple);
2771        let reply_tuple = Tuple {
2772            src_addr: redirect_addr,
2773            src_port_or_id: LOCAL_PORT.get(),
2774            ..original_tuple.invert()
2775        };
2776        assert_eq!(conn.reply_tuple(), &reply_tuple);
2777
2778        let mut reply_packet = []
2779            .into_serializer()
2780            .encapsulate(UdpPacketBuilder::new(
2781                reply_tuple.src_addr,
2782                reply_tuple.dst_addr,
2783                Some(NonZeroU16::new(reply_tuple.src_port_or_id).unwrap()),
2784                NonZeroU16::new(reply_tuple.dst_port_or_id).unwrap(),
2785            ))
2786            .encapsulate(I::PacketBuilder::new(
2787                reply_tuple.src_addr,
2788                reply_tuple.dst_addr,
2789                u8::MAX,
2790                IpProto::Udp.into(),
2791            ));
2792        let reply_packet_pre_nat = reply_packet.clone();
2793
2794        let verdict = perform_nat::<EgressHook, _, _, _, _>(
2795            &mut core_ctx,
2796            &mut bindings_ctx,
2797            NAT_ENABLED_FOR_TESTS,
2798            &conntrack,
2799            &mut conn,
2800            ConnectionDirection::Reply,
2801            &nat_routines,
2802            &mut reply_packet,
2803            <EgressHook as NatHookExt<I>>::interfaces(&ethernet_interface()),
2804        );
2805        assert_eq!(verdict, Verdict::Accept(()).into());
2806
2807        let reply_packet_expected = []
2808            .into_serializer()
2809            .encapsulate(UdpPacketBuilder::new(
2810                I::ULTIMATE_DST,
2811                I::ULTIMATE_SRC,
2812                Some(NonZeroU16::new(22222).unwrap()),
2813                NonZeroU16::new(11111).unwrap(),
2814            ))
2815            .encapsulate(I::PacketBuilder::new(
2816                I::ULTIMATE_DST,
2817                I::ULTIMATE_SRC,
2818                u8::MAX,
2819                IpProto::Udp.into(),
2820            ));
2821        assert_eq!(reply_packet, reply_packet_expected);
2822
2823        // The packet sent from D -> S was invalid and triggered an ICMP error
2824        // either from either S or SR.
2825        let error_src_addr = match icmp_error_source {
2826            IcmpErrorSource::IntermediateRouter => I::ROUTER_SRC,
2827            IcmpErrorSource::EndHost => I::ULTIMATE_SRC,
2828        };
2829
2830        let mut error_packet = IE::make_serializer(
2831            error_src_addr,
2832            I::ULTIMATE_DST,
2833            reply_packet.clone().serialize_vec_outer().unwrap().unwrap_b().into_inner(),
2834        )
2835        .encapsulate(I::PacketBuilder::new(
2836            error_src_addr,
2837            I::ULTIMATE_DST,
2838            u8::MAX,
2839            IE::proto(),
2840        ));
2841
2842        let verdict = perform_nat::<IngressHook, _, _, _, _>(
2843            &mut core_ctx,
2844            &mut bindings_ctx,
2845            NAT_ENABLED_FOR_TESTS,
2846            &conntrack,
2847            &mut conn,
2848            direction,
2849            &nat_routines,
2850            &mut error_packet,
2851            <IngressHook as NatHookExt<I>>::interfaces(&ethernet_interface()),
2852        );
2853        assert_eq!(verdict, Verdict::Accept(()).into());
2854
2855        let error_packet_expected = IE::make_serializer(
2856            // Unlike in the reply case, we shouldn't expect this address to be
2857            // rewritten since it's not one configured for NAT.
2858            error_src_addr,
2859            redirect_addr,
2860            reply_packet_pre_nat.clone().serialize_vec_outer().unwrap().unwrap_b().into_inner(),
2861        )
2862        .encapsulate(I::PacketBuilder::new(
2863            error_src_addr,
2864            redirect_addr,
2865            u8::MAX,
2866            IE::proto(),
2867        ));
2868
2869        assert_eq!(error_packet, error_packet_expected);
2870    }
2871
2872    // This is an explanation of masquerade_icmp_error_in_reply_direction and
2873    // masquerade_icmp_error_in_original_direction. There is more background
2874    // information in the equivalent comment of the redirect ICMP error tests.
2875    //
2876    // The imaginary network used in these tests is set up as follows:
2877    //
2878    // S --- SR --- US --- DR --- D
2879    //
2880    // Where:
2881    //
2882    // - US: That's us :-)
2883    // - D:  The ultimate destination
2884    // - S:  The ultimate source
2885    // - DR: A router between us and the destination
2886    // - SR: A router between us and the source
2887    //
2888    // The IPs that matter are:
2889    //
2890    // - D: ULTIMATE_DST
2891    // - DR: ROUTER_DST
2892    // - S: ULTIMAKE_SOURCE
2893    // - SR: ROUTER_SRC
2894    // - US: ULTIMATE_SRC
2895    //
2896    // The network is configured such that any packets traveling from S through
2897    // US are rewritten to have the address of US as their source address.
2898
2899    #[test_matrix(
2900        [
2901            Icmpv4DestUnreachableError,
2902            Icmpv6DestUnreachableError,
2903        ],
2904        [
2905            IcmpErrorSource::IntermediateRouter,
2906            IcmpErrorSource::EndHost,
2907        ]
2908    )]
2909    fn masquerade_icmp_error_in_reply_direction<I: TestIpExt, IE: IcmpErrorMessage<I>>(
2910        _icmp_error: IE,
2911        icmp_error_source: IcmpErrorSource,
2912    ) {
2913        let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2914        let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2915        let assigned_addr = AddrSubnet::new(I::NETSTACK, I::SUBNET.prefix()).unwrap();
2916        let mut core_ctx = FakeNatCtx::new([(ethernet_interface(), assigned_addr)]);
2917
2918        // Create a packet and get the corresponding connection from conntrack.
2919        let mut packet = []
2920            .into_serializer()
2921            .encapsulate(UdpPacketBuilder::new(
2922                I::ULTIMATE_SRC,
2923                I::ULTIMATE_DST,
2924                Some(NonZeroU16::new(11111).unwrap()),
2925                NonZeroU16::new(22222).unwrap(),
2926            ))
2927            .encapsulate(I::PacketBuilder::new(
2928                I::ULTIMATE_SRC,
2929                I::ULTIMATE_DST,
2930                u8::MAX,
2931                IpProto::Udp.into(),
2932            ));
2933        let packet_pre_nat = packet.clone();
2934        let (mut conn, direction) = conntrack
2935            .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
2936            .expect("packet should be valid")
2937            .expect("packet should be trackable");
2938        let original_tuple = conn.original_tuple().clone();
2939
2940        // Perform Masquerade NAT at EGRESS.
2941        let nat_routines = Hook {
2942            routines: vec![Routine {
2943                rules: vec![Rule::new(
2944                    PacketMatcher::default(),
2945                    Action::Masquerade { src_port: Some(LOCAL_PORT..=LOCAL_PORT) },
2946                )],
2947            }],
2948        };
2949        let verdict = perform_nat::<EgressHook, _, _, _, _>(
2950            &mut core_ctx,
2951            &mut bindings_ctx,
2952            NAT_ENABLED_FOR_TESTS,
2953            &conntrack,
2954            &mut conn,
2955            direction,
2956            &nat_routines,
2957            &mut packet,
2958            Interfaces { ingress: None, egress: Some(&ethernet_interface()) },
2959        );
2960        assert_eq!(verdict, Verdict::Accept(()).into());
2961
2962        // The packet's source address should be rewritten, and SNAT should be
2963        // configured for the packet; the reply tuple's destination should be rewritten
2964        // to match the new source.
2965        let expected = []
2966            .into_serializer()
2967            .encapsulate(UdpPacketBuilder::new(
2968                I::NETSTACK,
2969                I::ULTIMATE_DST,
2970                Some(LOCAL_PORT),
2971                packet.inner().outer().dst_port().unwrap(),
2972            ))
2973            .encapsulate(I::PacketBuilder::new(
2974                I::NETSTACK,
2975                I::ULTIMATE_DST,
2976                u8::MAX,
2977                IpProto::Udp.into(),
2978            ));
2979        assert_eq!(packet, expected);
2980        assert_matches!(
2981            conn.external_data().source.get().expect("SNAT should be configured"),
2982            &ShouldNat::Yes(Some(_))
2983        );
2984        assert_eq!(conn.external_data().destination.get(), None, "DNAT should not be configured");
2985        assert_eq!(conn.original_tuple(), &original_tuple);
2986        let reply_tuple = Tuple {
2987            dst_addr: I::NETSTACK,
2988            dst_port_or_id: LOCAL_PORT.get(),
2989            ..original_tuple.invert()
2990        };
2991        assert_eq!(conn.reply_tuple(), &reply_tuple);
2992
2993        // The packet sent from S -> D was invalid and triggered an ICMP error
2994        // either from D or DR.
2995        let error_src_addr = match icmp_error_source {
2996            IcmpErrorSource::IntermediateRouter => I::ROUTER_DST,
2997            IcmpErrorSource::EndHost => I::ULTIMATE_DST,
2998        };
2999
3000        let mut error_packet = IE::make_serializer(
3001            error_src_addr,
3002            I::NETSTACK,
3003            packet.clone().serialize_vec_outer().unwrap().unwrap_b().into_inner(),
3004        )
3005        .encapsulate(I::PacketBuilder::new(
3006            error_src_addr,
3007            I::NETSTACK,
3008            u8::MAX,
3009            IE::proto(),
3010        ));
3011
3012        let verdict = perform_nat::<IngressHook, _, _, _, _>(
3013            &mut core_ctx,
3014            &mut bindings_ctx,
3015            NAT_ENABLED_FOR_TESTS,
3016            &conntrack,
3017            &mut conn,
3018            ConnectionDirection::Reply,
3019            &nat_routines,
3020            &mut error_packet,
3021            Interfaces { ingress: Some(&ethernet_interface()), egress: None },
3022        );
3023        assert_eq!(verdict, Verdict::Accept(()).into());
3024
3025        let error_packet_expected = IE::make_serializer(
3026            // We expect this address to be rewritten since `redirect_addr`
3027            // shouldn't end up in the packet destined to the other host.
3028            error_src_addr,
3029            I::ULTIMATE_SRC,
3030            packet_pre_nat.clone().serialize_vec_outer().unwrap().unwrap_b().into_inner(),
3031        )
3032        .encapsulate(I::PacketBuilder::new(
3033            error_src_addr,
3034            I::ULTIMATE_SRC,
3035            u8::MAX,
3036            IE::proto(),
3037        ));
3038
3039        assert_eq!(error_packet, error_packet_expected);
3040    }
3041
3042    #[test_matrix(
3043        [
3044            Icmpv4DestUnreachableError,
3045            Icmpv6DestUnreachableError,
3046        ],
3047        [
3048            IcmpErrorSource::IntermediateRouter,
3049            IcmpErrorSource::EndHost,
3050        ]
3051    )]
3052    fn masquerade_icmp_error_in_original_direction<I: TestIpExt, IE: IcmpErrorMessage<I>>(
3053        _icmp_error: IE,
3054        icmp_error_source: IcmpErrorSource,
3055    ) {
3056        let mut bindings_ctx = FakeBindingsCtx::<I>::new();
3057        let conntrack = Table::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
3058        let assigned_addr = AddrSubnet::new(I::NETSTACK, I::SUBNET.prefix()).unwrap();
3059        let mut core_ctx = FakeNatCtx::new([(ethernet_interface(), assigned_addr)]);
3060
3061        // Create a packet and get the corresponding connection from conntrack.
3062        let mut packet = []
3063            .into_serializer()
3064            .encapsulate(UdpPacketBuilder::new(
3065                I::ULTIMATE_SRC,
3066                I::ULTIMATE_DST,
3067                Some(NonZeroU16::new(11111).unwrap()),
3068                NonZeroU16::new(22222).unwrap(),
3069            ))
3070            .encapsulate(I::PacketBuilder::new(
3071                I::ULTIMATE_SRC,
3072                I::ULTIMATE_DST,
3073                u8::MAX,
3074                IpProto::Udp.into(),
3075            ));
3076        let (mut conn, direction) = conntrack
3077            .get_connection_for_packet_and_update(&bindings_ctx, packet.conntrack_packet().unwrap())
3078            .expect("packet should be valid")
3079            .expect("packet should be trackable");
3080        let original_tuple = conn.original_tuple().clone();
3081
3082        // Perform Masquerade NAT at EGRESS.
3083        let nat_routines = Hook {
3084            routines: vec![Routine {
3085                rules: vec![Rule::new(
3086                    PacketMatcher::default(),
3087                    Action::Masquerade { src_port: Some(LOCAL_PORT..=LOCAL_PORT) },
3088                )],
3089            }],
3090        };
3091        let verdict = perform_nat::<EgressHook, _, _, _, _>(
3092            &mut core_ctx,
3093            &mut bindings_ctx,
3094            NAT_ENABLED_FOR_TESTS,
3095            &conntrack,
3096            &mut conn,
3097            direction,
3098            &nat_routines,
3099            &mut packet,
3100            Interfaces { ingress: None, egress: Some(&ethernet_interface()) },
3101        );
3102        assert_eq!(verdict, Verdict::Accept(()).into());
3103
3104        // The packet's source address should be rewritten, and SNAT should be
3105        // configured for the packet; the reply tuple's destination should be rewritten
3106        // to match the new source.
3107        let expected = []
3108            .into_serializer()
3109            .encapsulate(UdpPacketBuilder::new(
3110                I::NETSTACK,
3111                I::ULTIMATE_DST,
3112                Some(LOCAL_PORT),
3113                packet.inner().outer().dst_port().unwrap(),
3114            ))
3115            .encapsulate(I::PacketBuilder::new(
3116                I::NETSTACK,
3117                I::ULTIMATE_DST,
3118                u8::MAX,
3119                IpProto::Udp.into(),
3120            ));
3121        assert_eq!(packet, expected);
3122        assert_matches!(
3123            conn.external_data().source.get().expect("SNAT should be configured"),
3124            &ShouldNat::Yes(Some(_))
3125        );
3126        assert_eq!(conn.external_data().destination.get(), None, "DNAT should not be configured");
3127        assert_eq!(conn.original_tuple(), &original_tuple);
3128        let reply_tuple = Tuple {
3129            dst_addr: I::NETSTACK,
3130            dst_port_or_id: LOCAL_PORT.get(),
3131            ..original_tuple.invert()
3132        };
3133        assert_eq!(conn.reply_tuple(), &reply_tuple);
3134
3135        // When a reply to the original packet arrives at INGRESS, it should
3136        // have reverse SNAT applied, i.e. its destination should be rewritten
3137        // to match the original source of the connection.
3138        let mut reply_packet = []
3139            .into_serializer()
3140            .encapsulate(UdpPacketBuilder::new(
3141                reply_tuple.src_addr,
3142                reply_tuple.dst_addr,
3143                Some(NonZeroU16::new(reply_tuple.src_port_or_id).unwrap()),
3144                NonZeroU16::new(reply_tuple.dst_port_or_id).unwrap(),
3145            ))
3146            .encapsulate(I::PacketBuilder::new(
3147                reply_tuple.src_addr,
3148                reply_tuple.dst_addr,
3149                u8::MAX,
3150                IpProto::Udp.into(),
3151            ));
3152        let reply_packet_pre_nat = reply_packet.clone();
3153
3154        let verdict = perform_nat::<IngressHook, _, _, _, _>(
3155            &mut core_ctx,
3156            &mut bindings_ctx,
3157            NAT_ENABLED_FOR_TESTS,
3158            &conntrack,
3159            &mut conn,
3160            ConnectionDirection::Reply,
3161            &nat_routines,
3162            &mut reply_packet,
3163            Interfaces { ingress: Some(&ethernet_interface()), egress: None },
3164        );
3165        assert_eq!(verdict, Verdict::Accept(()).into());
3166
3167        let reply_packet_expected = []
3168            .into_serializer()
3169            .encapsulate(UdpPacketBuilder::new(
3170                I::ULTIMATE_DST,
3171                I::ULTIMATE_SRC,
3172                Some(NonZeroU16::new(22222).unwrap()),
3173                NonZeroU16::new(11111).unwrap(),
3174            ))
3175            .encapsulate(I::PacketBuilder::new(
3176                I::ULTIMATE_DST,
3177                I::ULTIMATE_SRC,
3178                u8::MAX,
3179                IpProto::Udp.into(),
3180            ));
3181        assert_eq!(reply_packet, reply_packet_expected);
3182
3183        let error_src_addr = match icmp_error_source {
3184            IcmpErrorSource::IntermediateRouter => I::ROUTER_SRC,
3185            IcmpErrorSource::EndHost => I::ULTIMATE_SRC,
3186        };
3187
3188        let mut error_packet = IE::make_serializer(
3189            error_src_addr,
3190            I::ULTIMATE_DST,
3191            reply_packet.clone().serialize_vec_outer().unwrap().unwrap_b().into_inner(),
3192        )
3193        .encapsulate(I::PacketBuilder::new(
3194            error_src_addr,
3195            I::ULTIMATE_DST,
3196            u8::MAX,
3197            IE::proto(),
3198        ));
3199
3200        let verdict = perform_nat::<EgressHook, _, _, _, _>(
3201            &mut core_ctx,
3202            &mut bindings_ctx,
3203            NAT_ENABLED_FOR_TESTS,
3204            &conntrack,
3205            &mut conn,
3206            direction,
3207            &nat_routines,
3208            &mut error_packet,
3209            Interfaces { ingress: None, egress: Some(&ethernet_interface()) },
3210        );
3211        assert_eq!(verdict, Verdict::Accept(()).into());
3212
3213        let error_packet_expected = IE::make_serializer(
3214            // We expect this address to be rewritten since `redirect_addr`
3215            // shouldn't end up in the packet destined to the other host.
3216            I::NETSTACK,
3217            I::ULTIMATE_DST,
3218            reply_packet_pre_nat.clone().serialize_vec_outer().unwrap().unwrap_b().into_inner(),
3219        )
3220        .encapsulate(I::PacketBuilder::new(
3221            I::NETSTACK,
3222            I::ULTIMATE_DST,
3223            u8::MAX,
3224            IE::proto(),
3225        ));
3226
3227        assert_eq!(error_packet, error_packet_expected);
3228    }
3229}