netcfg/
filter.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
5use std::collections::hash_map::Entry;
6use std::collections::{HashMap, HashSet};
7use std::num::NonZeroU64;
8
9use fidl_fuchsia_net_filter_ext::{
10    self as fnet_filter_ext, Action, Change, CommitError, Domain, InstalledIpRoutine,
11    InstalledNatRoutine, IpHook, Matchers, Namespace, NamespaceId, NatHook, PushChangesError,
12    Resource, ResourceId, Routine, RoutineId, RoutineType, Rule, RuleId,
13};
14use fuchsia_async::DurationExt as _;
15use {
16    fidl_fuchsia_net_filter as fnet_filter,
17    fidl_fuchsia_net_filter_deprecated as fnet_filter_deprecated,
18    fidl_fuchsia_net_masquerade as fnet_masquerade,
19    fidl_fuchsia_net_matchers_ext as fnet_matchers_ext,
20};
21
22use anyhow::{bail, Context as _};
23use log::{error, info, warn};
24
25use crate::{exit_with_fidl_error, FilterConfig, InterfaceId, InterfaceType};
26
27/// An error observed on the `fuchsia.net.filter` API.
28#[derive(Debug)]
29pub(crate) enum FilterError {
30    Push(PushChangesError),
31    Commit(CommitError),
32}
33
34// A container to dispatch filtering functions depending on the
35// filtering API present.
36#[allow(clippy::large_enum_variant)] // TODO(https://fxbug.dev/401087529)
37pub(crate) enum FilterControl {
38    Deprecated(fnet_filter_deprecated::FilterProxy),
39    Current(FilterState),
40}
41
42impl FilterControl {
43    // Determine whether to use the fuchsia.net.filter.deprecated API or the
44    // fuchsia.net.filter API. When the deprecated API is present and active,
45    // we should use it.
46    pub(super) async fn new(
47        deprecated_proxy: Option<fnet_filter_deprecated::FilterProxy>,
48        current_proxy: Option<fnet_filter::ControlProxy>,
49    ) -> Result<Self, anyhow::Error> {
50        if let Some(proxy) = deprecated_proxy {
51            if probe_for_presence(&proxy).await {
52                return Ok(FilterControl::Deprecated(proxy));
53            }
54        }
55
56        if let Some(proxy) = current_proxy {
57            let controller_id = fnet_filter_ext::ControllerId(String::from("netcfg"));
58            let filter = FilterControl::Current(FilterState {
59                controller: fnet_filter_ext::Controller::new(&proxy, &controller_id)
60                    .await
61                    .context("could not create controller from filter proxy")?,
62                uninstalled_ip_routines: filter_routines(false /* installed */),
63                installed_ip_routines: filter_routines(true /* installed */),
64                current_installed_rule_index: 0,
65                masquerade: MasqueradeState {
66                    routine_id: masquerade_routine(),
67                    next_rule_index: 0,
68                },
69            });
70            return Ok(filter);
71        }
72
73        Err(anyhow::anyhow!("no filtering proxy available!"))
74    }
75
76    /// Updates the initial network filter configuration using either
77    /// fuchsia.net.filter.deprecated or fuchsia.net.filter.
78    pub(super) async fn update_filters(
79        &mut self,
80        config: FilterConfig,
81    ) -> Result<(), anyhow::Error> {
82        match self {
83            FilterControl::Deprecated(proxy) => update_filters_deprecated(proxy, config).await,
84            FilterControl::Current(state) => state.update_filters_current(config).await,
85        }
86    }
87}
88
89// Filtering state for Masquerade NAT on the current `fuchsia.net.filter` API.
90struct MasqueradeState {
91    // The routine that holds all masquerade rules.
92    routine_id: RoutineId,
93    // The index to use for the next masquerade rule.
94    //
95    // Note: By using a simple counter, we don't re-use indices that were once
96    // used but are now available. The upside to this approach is that all
97    // filtering config has a stable order: older filtering config will always
98    // have a lower index (and therefore a higher priority) than newer filtering
99    // config. On the other hand, we do run the risk of overflowing the index
100    // if Netcfg were to add/remove u32::MAX filtering rules. That should only
101    // happen under pathological circumstances, and thus is a non-concern.
102    next_rule_index: u32,
103}
104
105// Filtering state on the current `fuchsia.net.filter` API.
106pub(super) struct FilterState {
107    controller: fnet_filter_ext::Controller,
108    uninstalled_ip_routines: netfilter::parser::FilterRoutines,
109    installed_ip_routines: netfilter::parser::FilterRoutines,
110    masquerade: MasqueradeState,
111    current_installed_rule_index: u32,
112    // TODO(https://fxbug.dev/331469354): Add NAT routines when this
113    // functionality has been added to fuchsia.net.filter.
114}
115
116impl FilterState {
117    // Commit the initial filter state using fuchsia.net.filter.
118    async fn update_filters_current(&mut self, config: FilterConfig) -> Result<(), anyhow::Error> {
119        let FilterState {
120            controller,
121            uninstalled_ip_routines,
122            installed_ip_routines,
123            current_installed_rule_index: _,
124            masquerade,
125        } = self;
126        let changes = generate_initial_filter_changes(
127            uninstalled_ip_routines,
128            installed_ip_routines,
129            &masquerade.routine_id,
130            config,
131        )?;
132
133        controller
134            .push_changes(changes)
135            .await
136            .context("failed to push changes to filter controller")?;
137
138        controller.commit().await.context("failed to commit changes to filter controller")?;
139        info!("initial filter configuration has been committed successfully");
140        Ok(())
141    }
142}
143
144// Netcfg's `FilterRoutines` to maintain the same namespace
145// and routine for each of the filter `Rule`s across installed
146// and uninstalled routines.
147fn filter_routines(installed: bool) -> netfilter::parser::FilterRoutines {
148    let suffix = if !installed { "_uninstalled" } else { "" };
149    netfilter::parser::FilterRoutines {
150        local_ingress: Some(RoutineId {
151            namespace: namespace_id(),
152            name: format!("local_ingress{suffix}"),
153        }),
154        local_egress: Some(RoutineId {
155            namespace: namespace_id(),
156            name: format!("local_egress{suffix}"),
157        }),
158    }
159}
160
161// Netcfg's masquerade NAT `RoutineId`.
162//
163// Masquerade NAT rules are always installed at the EGRESS hook.
164fn masquerade_routine() -> RoutineId {
165    RoutineId { namespace: namespace_id(), name: format!("egress_masquerade") }
166}
167
168fn namespace_id() -> NamespaceId {
169    NamespaceId(String::from("netcfg"))
170}
171
172pub(super) async fn probe_for_presence(filter: &fnet_filter_deprecated::FilterProxy) -> bool {
173    match filter.check_presence().await {
174        Ok(()) => true,
175        Err(fidl::Error::ClientChannelClosed { .. }) => false,
176        Err(e) => panic!("unexpected error while probing: {e}"),
177    }
178}
179
180// Create a set of `fnet_filter_ext::Change`s that, when used with
181// `fnet_filter_ext::Controller`, will establish the initial filtering
182// state for the `netcfg` namespace.
183fn generate_initial_filter_changes(
184    uninstalled_ip_routines: &netfilter::parser::FilterRoutines,
185    installed_ip_routines: &netfilter::parser::FilterRoutines,
186    masquerade_routine: &RoutineId,
187    config: FilterConfig,
188) -> Result<Vec<Change>, anyhow::Error> {
189    let namespace = Change::Create(Resource::Namespace(Namespace {
190        id: NamespaceId(String::from("netcfg")),
191        domain: Domain::AllIp,
192    }));
193    let mut changes = vec![namespace];
194
195    // Create uninstalled `Routine`s that the installed `Routine`s can use to
196    // `Jump` to `Rule`s. There must be a separate uninstalled `Routine` for
197    // each `IpHook` so that there are not issues with a `Rule` containing
198    // a matcher that is not allowed in the installed `Routine`'s hook.
199    // E.g., A `Rule` in an installed ingress hook `Routine` that `Jump`s to a
200    // `Routine` with a `Rule` that specifies an out_interface matcher is not
201    // permitted.
202    let netfilter::parser::FilterRoutines { local_ingress, local_egress } = uninstalled_ip_routines;
203    let uninstalled_local_ingress =
204        local_ingress.clone().map(|id| Routine { id, routine_type: RoutineType::Ip(None) });
205    let uninstalled_local_egress =
206        local_egress.clone().map(|id| Routine { id, routine_type: RoutineType::Ip(None) });
207
208    // Push installed routines so that netcfg can install `Jump` rules
209    // at interface installation time that are rooted in these routines.
210    fn installed_routine_from_id(id: RoutineId, hook: IpHook) -> Routine {
211        Routine {
212            id: id,
213            routine_type: RoutineType::Ip(Some(InstalledIpRoutine { hook, priority: 0i32 })),
214        }
215    }
216    let netfilter::parser::FilterRoutines { local_ingress, local_egress } = installed_ip_routines;
217    let local_ingress =
218        local_ingress.clone().map(|id| installed_routine_from_id(id, IpHook::LocalIngress));
219    let local_egress =
220        local_egress.clone().map(|id| installed_routine_from_id(id, IpHook::LocalEgress));
221
222    let masquerade = Routine {
223        id: masquerade_routine.clone(),
224        routine_type: RoutineType::Nat(Some(InstalledNatRoutine {
225            hook: NatHook::Egress,
226            priority: 0i32,
227        })),
228    };
229
230    let routine_changes = [
231        uninstalled_local_ingress,
232        local_ingress,
233        uninstalled_local_egress,
234        local_egress,
235        Some(masquerade),
236    ]
237    .into_iter()
238    .filter_map(|routine| routine)
239    .map(|routine| Change::Create(Resource::Routine(routine)));
240    changes.extend(routine_changes);
241
242    // TODO(https://fxbug.dev/331469354): Handle NAT and NAT RDR rules when supported
243    // by netfilter and filtering library
244    let FilterConfig { rules, nat_rules: _, rdr_rules: _ } = config;
245    if !rules.is_empty() {
246        // Only insert the rules from the config into the uninstalled routine.
247        // The rules inserted in the installed routines will be intended for
248        // redirection to the uninstalled routines.
249        let rules =
250            netfilter::parser::parse_str_to_rules(&rules.join(""), &uninstalled_ip_routines)
251                .context("error parsing filter rules")?;
252        let rule_changes = rules.into_iter().map(|rule| Change::Create(Resource::Rule(rule)));
253        changes.extend(rule_changes);
254    }
255
256    Ok(changes)
257}
258
259// Create a list of `fnet_filter_ext::Rule`s that, when used with
260// `fnet_filter_ext::Controller`, will `Jump` on each available
261// `IpHook` to the corresponding uninstalled routine for
262// that `IpHook`.
263fn generate_updated_filter_rules(
264    uninstalled_ip_routines: &netfilter::parser::FilterRoutines,
265    installed_ip_routines: &netfilter::parser::FilterRoutines,
266    interface_id: InterfaceId,
267    current_installed_rule_index: u32,
268) -> Vec<Rule> {
269    let netfilter::parser::FilterRoutines {
270        local_ingress: uninstalled_local_ingress,
271        local_egress: uninstalled_local_egress,
272    } = uninstalled_ip_routines;
273    let netfilter::parser::FilterRoutines { local_ingress, local_egress } = installed_ip_routines;
274
275    // Use the same rule index for all rules created for the
276    // interface. It is assumed that all `Rule`s across the
277    // `FilterRoutines` will inserted in tandem.
278    let local_ingress_rule = local_ingress.clone().map(|routine_id| {
279        create_interface_matching_jump_rule(
280            routine_id,
281            current_installed_rule_index,
282            interface_id,
283            IpHook::LocalIngress,
284            &uninstalled_local_ingress
285                .as_ref()
286                .expect("there should be a corresponding uninstalled routine for local ingress")
287                .name,
288        )
289    });
290    let local_egress_rule = local_egress.clone().map(|routine_id| {
291        create_interface_matching_jump_rule(
292            routine_id,
293            current_installed_rule_index,
294            interface_id,
295            IpHook::LocalEgress,
296            &uninstalled_local_egress
297                .as_ref()
298                .expect("there should be a corresponding uninstalled routine for local egress")
299                .name,
300        )
301    });
302
303    let rules: Vec<_> =
304        vec![local_ingress_rule, local_egress_rule].into_iter().filter_map(|rule| rule).collect();
305
306    rules
307}
308
309fn create_interface_matching_jump_rule(
310    routine_id: RoutineId,
311    index: u32,
312    interface_id: InterfaceId,
313    hook: IpHook,
314    target_routine_name: &str,
315) -> Rule {
316    // Some matchers cannot be used on all `IpHook`s.
317    let (in_interface, out_interface) = match hook {
318        IpHook::LocalIngress | IpHook::Ingress => {
319            (Some(fnet_matchers_ext::Interface::Id(interface_id.into())), None)
320        }
321        IpHook::LocalEgress | IpHook::Egress => {
322            (None, Some(fnet_matchers_ext::Interface::Id(interface_id.into())))
323        }
324        IpHook::Forwarding => (
325            Some(fnet_matchers_ext::Interface::Id(interface_id.into())),
326            Some(fnet_matchers_ext::Interface::Id(interface_id.into())),
327        ),
328    };
329
330    // Full path qualification is preferred where types can get mistaken
331    // between filtering libraries.
332    Rule {
333        id: RuleId { routine: routine_id, index },
334        matchers: Matchers { in_interface, out_interface, ..Default::default() },
335        action: Action::Jump(target_routine_name.to_string()),
336    }
337}
338
339// We use Compare-And-Swap (CAS) protocol to update filter rules. $get_rules returns the current
340// generation number. $update_rules will send it with new rules to make sure we are updating the
341// intended generation. If the generation number doesn't match, $update_rules will return a
342// GenerationMismatch error, then we have to restart from $get_rules.
343
344pub(crate) const FILTER_CAS_RETRY_MAX: i32 = 3;
345pub(crate) const FILTER_CAS_RETRY_INTERVAL_MILLIS: i64 = 500;
346
347macro_rules! cas_filter_rules {
348    ($filter:expr, $get_rules:ident, $update_rules:ident, $rules:expr, $error_type:ident) => {
349        for retry in 0..FILTER_CAS_RETRY_MAX {
350            let (_rules, generation) =
351                $filter.$get_rules().await.unwrap_or_else(|err| exit_with_fidl_error(err));
352
353            match $filter
354                .$update_rules(&$rules, generation)
355                .await
356                .unwrap_or_else(|err| exit_with_fidl_error(err))
357            {
358                Ok(()) => {
359                    break;
360                }
361                Err(fnet_filter_deprecated::$error_type::GenerationMismatch)
362                    if retry < FILTER_CAS_RETRY_MAX - 1 =>
363                {
364                    fuchsia_async::Timer::new(
365                        zx::MonotonicDuration::from_millis(FILTER_CAS_RETRY_INTERVAL_MILLIS)
366                            .after_now(),
367                    )
368                    .await;
369                }
370                Err(e) => {
371                    bail!("{} failed: {:?}", stringify!($update_rules), e);
372                }
373            }
374        }
375    };
376}
377
378// This is a placeholder macro while some update operations are not supported.
379macro_rules! no_update_filter_rules {
380    ($filter:expr, $get_rules:ident, $update_rules:ident, $rules:expr, $error_type:ident) => {
381        let (_rules, generation) =
382            $filter.$get_rules().await.unwrap_or_else(|err| exit_with_fidl_error(err));
383
384        match $filter
385            .$update_rules(&$rules, generation)
386            .await
387            .unwrap_or_else(|err| exit_with_fidl_error(err))
388        {
389            Ok(()) => {}
390            Err(fnet_filter_deprecated::$error_type::NotSupported) => {
391                error!("{} not supported", stringify!($update_rules));
392            }
393        }
394    };
395}
396
397async fn update_filters_deprecated(
398    filter: &mut fnet_filter_deprecated::FilterProxy,
399    config: FilterConfig,
400) -> Result<(), anyhow::Error> {
401    let FilterConfig { rules, nat_rules, rdr_rules } = config;
402
403    if !rules.is_empty() {
404        let rules = netfilter::parser_deprecated::parse_str_to_rules(&rules.join(""))
405            .context("error parsing filter rules")?;
406        cas_filter_rules!(filter, get_rules, update_rules, rules, FilterUpdateRulesError);
407    }
408
409    if !nat_rules.is_empty() {
410        let nat_rules = netfilter::parser_deprecated::parse_str_to_nat_rules(&nat_rules.join(""))
411            .context("error parsing NAT rules")?;
412        cas_filter_rules!(
413            filter,
414            get_nat_rules,
415            update_nat_rules,
416            nat_rules,
417            FilterUpdateNatRulesError
418        );
419    }
420
421    if !rdr_rules.is_empty() {
422        let rdr_rules = netfilter::parser_deprecated::parse_str_to_rdr_rules(&rdr_rules.join(""))
423            .context("error parsing RDR rules")?;
424        // TODO(https://fxbug.dev/42147284): Change this to cas_filter_rules once
425        // update is supported.
426        no_update_filter_rules!(
427            filter,
428            get_rdr_rules,
429            update_rdr_rules,
430            rdr_rules,
431            FilterUpdateRdrRulesError
432        );
433    }
434
435    Ok(())
436}
437
438#[derive(Debug)]
439struct MasqueradeCounter(NonZeroU64);
440
441impl MasqueradeCounter {
442    fn new() -> Self {
443        Self(NonZeroU64::new(1).unwrap())
444    }
445
446    fn increment(&mut self) {
447        *self = Self(self.0.checked_add(1).expect("integer_overflow on u64"));
448    }
449
450    fn decrement(&self) -> Option<Self> {
451        NonZeroU64::new(self.0.get() - 1).map(Self)
452    }
453}
454
455#[derive(Debug, Default)]
456pub(super) struct FilterEnabledState {
457    interface_types: HashSet<InterfaceType>,
458    // A map of interface ID to the number of active masquerade configurations
459    // applied on that interface.
460    masquerade_enabled_interface_ids: HashMap<InterfaceId, MasqueradeCounter>,
461    // Indexed by interface id and stores `RuleId`s inserted for that interface.
462    // All rules for an interface should be removed upon interface removal.
463    // Vec will always be empty when using filter.deprecated.
464    //
465    // Note: Masquerade rules are not held here. Filtering on an interface can
466    // only be disabled when there are no masquerade configurations remaining
467    // (i.e. absence of an `InterfaceId` from `masquerade_enabled_interface_ids`
468    // is proof that there are no installed Masquerade Rules on the interface).
469    currently_enabled_interfaces: HashMap<InterfaceId, Vec<RuleId>>,
470}
471
472impl FilterEnabledState {
473    pub(super) fn new(interface_types: HashSet<InterfaceType>) -> Self {
474        Self { interface_types, ..Default::default() }
475    }
476
477    /// Updates the filter state for the provided `interface_id` using either
478    /// fuchsia.net.filter.deprecated or fuchsia.net.filter.
479    ///
480    /// `interface_type`: The type of the given interface. If the type cannot be
481    /// determined, this will be None, and `FilterEnabledState::interface_types`
482    /// will be ignored.
483    pub(super) async fn maybe_update(
484        &mut self,
485        interface_type: Option<InterfaceType>,
486        interface_id: InterfaceId,
487        filter: &mut FilterControl,
488    ) -> Result<(), anyhow::Error> {
489        match filter {
490            FilterControl::Deprecated(proxy) => self
491                .maybe_update_deprecated(interface_type, interface_id, proxy)
492                .await
493                .map_err(|e| anyhow::anyhow!("{e:?}")),
494            FilterControl::Current(filter_state) => self
495                .maybe_update_current(interface_type, interface_id, filter_state)
496                .await
497                .map_err(|e| anyhow::anyhow!("{e:?}")),
498        }
499    }
500
501    pub(super) async fn maybe_update_deprecated<
502        Filter: fnet_filter_deprecated::FilterProxyInterface,
503    >(
504        &mut self,
505        interface_type: Option<InterfaceType>,
506        interface_id: InterfaceId,
507        filter: &Filter,
508    ) -> Result<(), fnet_filter_deprecated::EnableDisableInterfaceError> {
509        let should_be_enabled = self.should_enable(interface_type, interface_id);
510        let is_enabled = self.currently_enabled_interfaces.entry(interface_id);
511
512        match (should_be_enabled, is_enabled) {
513            (true, Entry::Vacant(entry)) => {
514                if let Err(e) = filter
515                    .enable_interface(interface_id.get())
516                    .await
517                    .unwrap_or_else(|err| exit_with_fidl_error(err))
518                {
519                    warn!("failed to enable interface {interface_id}: {e:?}");
520                    return Err(e);
521                }
522                let _ = entry.insert(vec![]);
523            }
524            (false, Entry::Occupied(entry)) => {
525                if let Err(e) = filter
526                    .disable_interface(interface_id.get())
527                    .await
528                    .unwrap_or_else(|err| exit_with_fidl_error(err))
529                {
530                    warn!("failed to disable interface {interface_id}: {e:?}");
531                    return Err(e);
532                }
533                let _ = entry.remove();
534            }
535            (true, Entry::Occupied(_)) | (false, Entry::Vacant(_)) => {
536                // Do nothing. The interface's current state aligns with
537                // whether it is present in the map.
538            }
539        }
540        Ok(())
541    }
542
543    pub(super) async fn maybe_update_current(
544        &mut self,
545        interface_type: Option<InterfaceType>,
546        interface_id: InterfaceId,
547        filter: &mut FilterState,
548    ) -> Result<(), FilterError> {
549        let should_be_enabled = self.should_enable(interface_type, interface_id);
550        let is_enabled = self.currently_enabled_interfaces.entry(interface_id);
551
552        match (should_be_enabled, is_enabled) {
553            (true, Entry::Vacant(entry)) => {
554                let FilterState {
555                    controller,
556                    uninstalled_ip_routines,
557                    installed_ip_routines,
558                    current_installed_rule_index,
559                    masquerade: _,
560                } = filter;
561                let rules = generate_updated_filter_rules(
562                    uninstalled_ip_routines,
563                    installed_ip_routines,
564                    interface_id,
565                    *current_installed_rule_index,
566                );
567
568                if !rules.is_empty() {
569                    let rule_changes = rules
570                        .clone()
571                        .into_iter()
572                        .map(|rule| Change::Create(Resource::Rule(rule)))
573                        .collect();
574                    controller.push_changes(rule_changes).await.map_err(FilterError::Push)?;
575                    controller.commit().await.map_err(FilterError::Commit)?;
576                    info!(
577                        "new filter rules for iface with id {interface_id:?} \
578                                have been committed successfully"
579                    );
580                    // Increment the current rule index only on success since
581                    // `commit` will either apply changes in entirety, or none
582                    // at all.
583                    *current_installed_rule_index = current_installed_rule_index.wrapping_add(1);
584                }
585
586                // Get the `RuleId`s from the inserted `Rule`s so that they can be
587                // removed if the interface is disabled.
588                let rule_ids: Vec<_> = rules.into_iter().map(|rule| rule.id).collect();
589                let _ = entry.insert(rule_ids);
590            }
591            (false, Entry::Occupied(entry)) => {
592                let FilterState { controller, .. } = filter;
593                let rule_changes: Vec<_> = entry
594                    .remove()
595                    .into_iter()
596                    .map(|rule_id| Change::Remove(ResourceId::Rule(rule_id)))
597                    .collect();
598
599                if !rule_changes.is_empty() {
600                    controller.push_changes(rule_changes).await.map_err(FilterError::Push)?;
601                    controller.commit().await.map_err(FilterError::Commit)?;
602                    info!(
603                        "removal of filter rules for iface with id {interface_id:?} \
604                                have been committed successfully"
605                    );
606                }
607            }
608            (true, Entry::Occupied(_)) | (false, Entry::Vacant(_)) => {
609                // Do nothing. The interface's current state aligns with
610                // whether it is present in the map.
611            }
612        }
613
614        Ok(())
615    }
616
617    /// Determines whether a given `interface_id` should be enabled.
618    ///
619    /// `interface_type`: The type of the given interface. If the type cannot be
620    /// determined, this will be None, and `FilterEnabledState::interface_types`
621    /// will be ignored.
622    fn should_enable(
623        &self,
624        interface_type: Option<InterfaceType>,
625        interface_id: InterfaceId,
626    ) -> bool {
627        interface_type
628            .as_ref()
629            .map(|ty| match ty {
630                InterfaceType::WlanClient | InterfaceType::Ethernet | InterfaceType::Blackhole => {
631                    self.interface_types.contains(ty)
632                }
633                // An AP device can be filtered by specifying AP or WLAN.
634                InterfaceType::WlanAp => {
635                    self.interface_types.contains(ty)
636                        | self.interface_types.contains(&InterfaceType::WlanClient)
637                }
638            })
639            .unwrap_or(false)
640            || self.masquerade_enabled_interface_ids.contains_key(&interface_id)
641    }
642
643    pub(crate) fn increment_masquerade_count_on_interface(&mut self, interface_id: InterfaceId) {
644        match self.masquerade_enabled_interface_ids.entry(interface_id) {
645            Entry::Vacant(entry) => {
646                let _new_count = entry.insert(MasqueradeCounter::new());
647            }
648            Entry::Occupied(mut entry) => entry.get_mut().increment(),
649        }
650    }
651
652    pub(crate) fn decrement_masquerade_count_on_interface(&mut self, interface_id: InterfaceId) {
653        match self.masquerade_enabled_interface_ids.entry(interface_id) {
654            Entry::Vacant(_) => panic!(
655                "asked to decrement the masquerade count for a non-configured interface: {}",
656                interface_id
657            ),
658            Entry::Occupied(mut entry) => match entry.get().decrement() {
659                // Subtraction made the count 0; remove it.
660                None => {
661                    let _old_count = entry.remove();
662                }
663                Some(count) => {
664                    let _old_count = entry.insert(count);
665                }
666            },
667        }
668    }
669}
670
671/// Repeatedly attempts to update the NAT rules using `fuchsia.net.filter.deprecated`.
672///
673/// The update will be attempted up to `FILTER_CAS_RETRY_MAX` times.
674async fn update_nat_rules_deprecated(
675    filter: &mut fnet_filter_deprecated::FilterProxy,
676    update_fn: impl Fn(&mut Vec<fnet_filter_deprecated::Nat>) -> Result<(), fnet_masquerade::Error>,
677) -> Result<(), fnet_masquerade::Error> {
678    for _ in 0..FILTER_CAS_RETRY_MAX {
679        let (mut rules, generation) =
680            filter.get_nat_rules().await.expect("call to GetNatRules failed");
681        update_fn(&mut rules)?;
682
683        match filter
684            .update_nat_rules(&rules, generation)
685            .await
686            .expect("call to UpdateNatRules failed")
687        {
688            Ok(()) => return Ok(()),
689            Err(fnet_filter_deprecated::FilterUpdateNatRulesError::GenerationMismatch) => {
690                // We need to try again.
691                fuchsia_async::Timer::new(
692                    zx::MonotonicDuration::from_millis(
693                        crate::filter::FILTER_CAS_RETRY_INTERVAL_MILLIS,
694                    )
695                    .after_now(),
696                )
697                .await;
698            }
699            Err(fnet_filter_deprecated::FilterUpdateNatRulesError::BadRule) => {
700                // This can sometimes be triggered if the NIC is deleted before
701                // the call to `update_nat_rules`.
702                error!(
703                    "Generated Nat rule was invalid. Perhaps the requested \
704                     NIC has been removed {rules:?}"
705                );
706                // There is no point in retrying in this case.
707                return Err(fnet_masquerade::Error::BadRule);
708            }
709        }
710    }
711
712    error!("Failed to update Nat rules");
713    Err(fnet_masquerade::Error::RetryExceeded)
714}
715
716// Attempts to add a new masquerade NAT rule using `fuchsia.net.filter.deprecated`.
717pub(crate) async fn add_masquerade_rule_deprecated(
718    filter: &mut fnet_filter_deprecated::FilterProxy,
719    rule: fnet_filter_deprecated::Nat,
720) -> Result<(), fnet_masquerade::Error> {
721    update_nat_rules_deprecated(filter, |rules| {
722        if rules.iter().any(|existing_rule| existing_rule == &rule) {
723            Err(fnet_masquerade::Error::AlreadyExists)
724        } else {
725            rules.push(rule.clone());
726            Ok(())
727        }
728    })
729    .await
730}
731
732// Attempts to remove an existing masquerade NAT rule using `fuchsia.net.filter.deprecated`.
733pub(crate) async fn remove_masquerade_rule_deprecated(
734    filter: &mut fnet_filter_deprecated::FilterProxy,
735    rule: fnet_filter_deprecated::Nat,
736) -> Result<(), fnet_masquerade::Error> {
737    update_nat_rules_deprecated(filter, |rules| {
738        rules.retain(|existing_rule| existing_rule != &rule);
739        Ok(())
740    })
741    .await
742}
743
744// Attempts to add a new masquerade NAT rule using `fuchsia.net.filter`.
745pub(crate) async fn add_masquerade_rule_current(
746    filter: &mut FilterState,
747    matchers: Matchers,
748) -> Result<RuleId, FilterError> {
749    let MasqueradeState { routine_id, next_rule_index } = &mut filter.masquerade;
750    let rule_id = RuleId { routine: routine_id.clone(), index: *next_rule_index };
751    let rule_changes = vec![Change::Create(Resource::Rule(Rule {
752        id: rule_id.clone(),
753        matchers: matchers,
754        action: Action::Masquerade { src_port: None },
755    }))];
756    filter.controller.push_changes(rule_changes).await.map_err(FilterError::Push)?;
757    filter.controller.commit().await.map_err(FilterError::Commit)?;
758    *next_rule_index += 1;
759    Ok(rule_id)
760}
761
762// Attempts to remove an existing masquerade NAT rule using `fuchsia.net.filter`.
763pub(crate) async fn remove_masquerade_rule_current(
764    filter: &mut FilterState,
765    rule: &RuleId,
766) -> Result<(), FilterError> {
767    let rule_changes = vec![Change::Remove(ResourceId::Rule(rule.clone()))];
768    filter.controller.push_changes(rule_changes).await.map_err(FilterError::Push)?;
769    filter.controller.commit().await.map_err(FilterError::Commit)
770}
771
772#[cfg(test)]
773mod tests {
774    use test_case::test_case;
775
776    use crate::interface::DeviceInfoRef;
777    use crate::DeviceClass;
778
779    use super::*;
780
781    const INTERFACE_ID: InterfaceId = InterfaceId::new(1).unwrap();
782    const LOCAL_INGRESS: &str = "local_ingress";
783    const UNINSTALLED_LOCAL_INGRESS: &str = "local_ingress_uninstalled";
784    const LOCAL_EGRESS: &str = "local_egress";
785    const UNINSTALLED_LOCAL_EGRESS: &str = "local_egress_uninstalled";
786    const MASQUERADE: &str = "egress_masquerade";
787
788    fn get_foundational_changes() -> Vec<Change> {
789        let mut changes = vec![Change::Create(Resource::Namespace(Namespace {
790            id: namespace_id(),
791            domain: Domain::AllIp,
792        }))];
793
794        let local_ingress = (LOCAL_INGRESS, UNINSTALLED_LOCAL_INGRESS, IpHook::LocalIngress);
795        let local_egress = (LOCAL_EGRESS, UNINSTALLED_LOCAL_EGRESS, IpHook::LocalEgress);
796
797        let routine_changes = vec![local_ingress, local_egress]
798            .into_iter()
799            .map(|(installed_name, uninstalled_name, hook)| {
800                vec![
801                    Routine {
802                        id: RoutineId {
803                            namespace: namespace_id(),
804                            name: String::from(uninstalled_name),
805                        },
806                        routine_type: RoutineType::Ip(None),
807                    },
808                    Routine {
809                        id: RoutineId {
810                            namespace: namespace_id(),
811                            name: String::from(installed_name),
812                        },
813                        routine_type: RoutineType::Ip(Some(InstalledIpRoutine {
814                            hook,
815                            priority: 0i32,
816                        })),
817                    },
818                ]
819            })
820            .flatten()
821            .chain([Routine {
822                id: RoutineId { namespace: namespace_id(), name: String::from(MASQUERADE) },
823                routine_type: RoutineType::Nat(Some(InstalledNatRoutine {
824                    hook: NatHook::Egress,
825                    priority: 0i32,
826                })),
827            }])
828            .map(|routine| Change::Create(Resource::Routine(routine)));
829        changes.extend(routine_changes);
830
831        changes
832    }
833
834    fn create_rule(routine: RoutineId, index: u32, action: Action) -> Rule {
835        Rule { id: RuleId { routine, index }, matchers: Matchers::default(), action }
836    }
837
838    fn create_routine_id(name: &str) -> RoutineId {
839        RoutineId { namespace: namespace_id(), name: String::from(name) }
840    }
841
842    fn create_filter_routines(
843        namespace: NamespaceId,
844        local_ingress: &str,
845        local_egress: &str,
846    ) -> netfilter::parser::FilterRoutines {
847        netfilter::parser::FilterRoutines {
848            local_ingress: Some(RoutineId {
849                namespace: namespace.clone(),
850                name: local_ingress.to_owned(),
851            }),
852            local_egress: Some(RoutineId { namespace, name: local_egress.to_owned() }),
853        }
854    }
855
856    // This test only checks for `Ok` cases. The only possible failures for the function under
857    // test are related to Rule parsing, which the netfilter library already tests.
858    #[test_case(vec![], vec![]; "no_rules")]
859    #[test_case(
860        vec!["pass in;"],
861        vec![create_rule(
862                create_routine_id(UNINSTALLED_LOCAL_INGRESS),
863                0,
864                Action::Accept,
865            )]; "ingress_accept")]
866    #[test_case(
867        vec!["drop out;"],
868        vec![create_rule(
869                create_routine_id(UNINSTALLED_LOCAL_EGRESS),
870                0,
871                Action::Drop,
872            )]; "egress_drop")]
873    #[test_case(
874        vec!["pass in; drop out;"],
875        vec![create_rule(
876                create_routine_id(UNINSTALLED_LOCAL_INGRESS),
877                0,
878                Action::Accept),
879            create_rule(
880                create_routine_id(UNINSTALLED_LOCAL_EGRESS),
881                1,
882                Action::Drop,
883            )]; "ingress_accept_egress_drop")]
884    fn test_initial_filter_changes(rules_input: Vec<&str>, expected_rules: Vec<Rule>) {
885        let namespace = namespace_id();
886        let installed_filter_routines =
887            create_filter_routines(namespace.clone(), LOCAL_INGRESS, LOCAL_EGRESS);
888        let uninstalled_filter_routines =
889            create_filter_routines(namespace, UNINSTALLED_LOCAL_INGRESS, UNINSTALLED_LOCAL_EGRESS);
890
891        let changes = generate_initial_filter_changes(
892            &uninstalled_filter_routines,
893            &installed_filter_routines,
894            &masquerade_routine(),
895            FilterConfig {
896                rules: rules_input.into_iter().map(|rule| rule.to_owned()).collect(),
897                nat_rules: vec![],
898                rdr_rules: vec![],
899            },
900        )
901        .expect("rules should be formatted correctly");
902
903        let mut expected_changes = get_foundational_changes();
904        let expected_rule_changes =
905            expected_rules.into_iter().map(|rule| Change::Create(Resource::Rule(rule)));
906        expected_changes.extend(expected_rule_changes);
907
908        assert_eq!(changes, expected_changes);
909    }
910
911    #[test]
912    fn test_generate_updated_filter_rules() {
913        let namespace = namespace_id();
914        let installed_filter_routines =
915            create_filter_routines(namespace.clone(), LOCAL_INGRESS, LOCAL_EGRESS);
916        let uninstalled_filter_routines =
917            create_filter_routines(namespace, UNINSTALLED_LOCAL_INGRESS, UNINSTALLED_LOCAL_EGRESS);
918
919        let rules = generate_updated_filter_rules(
920            &uninstalled_filter_routines,
921            &installed_filter_routines,
922            INTERFACE_ID,
923            0,
924        );
925
926        let local_ingress = (
927            installed_filter_routines.local_ingress.unwrap(),
928            uninstalled_filter_routines.local_ingress.unwrap().name,
929            IpHook::LocalIngress,
930        );
931        let local_egress = (
932            installed_filter_routines.local_egress.unwrap(),
933            uninstalled_filter_routines.local_egress.unwrap().name,
934            IpHook::LocalEgress,
935        );
936        let expected_rules: Vec<_> = vec![local_ingress, local_egress]
937            .into_iter()
938            .map(|(installed_routine, uninstalled_routine_name, hook)| {
939                create_interface_matching_jump_rule(
940                    installed_routine,
941                    0,
942                    INTERFACE_ID,
943                    hook,
944                    &uninstalled_routine_name,
945                )
946            })
947            .collect();
948
949        assert_eq!(rules, expected_rules);
950    }
951
952    #[test]
953    fn test_should_enable_filter() {
954        let types_empty: HashSet<InterfaceType> = [].iter().cloned().collect();
955        let types_ethernet: HashSet<InterfaceType> =
956            [InterfaceType::Ethernet].iter().cloned().collect();
957        let types_wlan: HashSet<InterfaceType> =
958            [InterfaceType::WlanClient].iter().cloned().collect();
959        let types_ap: HashSet<InterfaceType> = [InterfaceType::WlanAp].iter().cloned().collect();
960
961        let id = InterfaceId::new(10).unwrap();
962
963        let make_info = |device_class| DeviceInfoRef {
964            device_class,
965            mac: &fidl_fuchsia_net_ext::MacAddress { octets: [0x1, 0x1, 0x1, 0x1, 0x1, 0x1] },
966            topological_path: "",
967        };
968
969        let wlan_info = make_info(DeviceClass::WlanClient);
970        let wlan_ap_info = make_info(DeviceClass::WlanAp);
971        let ethernet_info = make_info(DeviceClass::Ethernet);
972
973        let mut fes = FilterEnabledState::new(types_empty.clone());
974        assert_eq!(fes.should_enable(Some(wlan_info.interface_type()), id), false);
975        assert_eq!(fes.should_enable(Some(wlan_ap_info.interface_type()), id), false);
976        assert_eq!(fes.should_enable(Some(ethernet_info.interface_type()), id), false);
977
978        fes.increment_masquerade_count_on_interface(id);
979        assert_eq!(fes.should_enable(Some(ethernet_info.interface_type()), id), true);
980
981        let mut fes = FilterEnabledState::new(types_ethernet);
982        assert_eq!(fes.should_enable(Some(wlan_info.interface_type()), id), false);
983        assert_eq!(fes.should_enable(Some(wlan_ap_info.interface_type()), id), false);
984        assert_eq!(fes.should_enable(Some(ethernet_info.interface_type()), id), true);
985
986        fes.increment_masquerade_count_on_interface(id);
987        assert_eq!(fes.should_enable(Some(wlan_info.interface_type()), id), true);
988
989        let mut fes = FilterEnabledState::new(types_wlan);
990        assert_eq!(fes.should_enable(Some(wlan_info.interface_type()), id), true);
991        assert_eq!(fes.should_enable(Some(wlan_ap_info.interface_type()), id), true);
992        assert_eq!(fes.should_enable(Some(ethernet_info.interface_type()), id), false);
993
994        fes.increment_masquerade_count_on_interface(id);
995        assert_eq!(fes.should_enable(Some(ethernet_info.interface_type()), id), true);
996
997        let mut fes = FilterEnabledState::new(types_ap);
998        assert_eq!(fes.should_enable(Some(wlan_info.interface_type()), id), false);
999        assert_eq!(fes.should_enable(Some(wlan_ap_info.interface_type()), id), true);
1000        assert_eq!(fes.should_enable(Some(ethernet_info.interface_type()), id), false);
1001
1002        fes.increment_masquerade_count_on_interface(id);
1003        assert_eq!(fes.should_enable(Some(wlan_info.interface_type()), id), true);
1004        assert_eq!(fes.should_enable(Some(ethernet_info.interface_type()), id), true);
1005
1006        // Verify that the count can be decremented while keeping filtering enabled.
1007        let mut fes = FilterEnabledState::new(types_empty);
1008        for _ in 0..3 {
1009            fes.increment_masquerade_count_on_interface(id);
1010        }
1011        for expect_enabled in [true, true, false] {
1012            fes.decrement_masquerade_count_on_interface(id);
1013            assert_eq!(fes.should_enable(Some(wlan_info.interface_type()), id), expect_enabled);
1014            assert_eq!(fes.should_enable(Some(wlan_ap_info.interface_type()), id), expect_enabled);
1015            assert_eq!(fes.should_enable(Some(ethernet_info.interface_type()), id), expect_enabled);
1016        }
1017    }
1018}