Skip to main content

netstack3_filter/logic/
nat.rs

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