use std::collections::hash_map::Entry;
use std::collections::{HashMap, HashSet};
use std::num::NonZeroU64;
use fidl_fuchsia_net_filter_ext::{
self as fnet_filter_ext, Action, Change, CommitError, Domain, InstalledIpRoutine,
InstalledNatRoutine, InterfaceMatcher, IpHook, Matchers, Namespace, NamespaceId, NatHook,
PushChangesError, Resource, ResourceId, Routine, RoutineId, RoutineType, Rule, RuleId,
};
use fuchsia_async::DurationExt as _;
use {
fidl_fuchsia_net_filter as fnet_filter,
fidl_fuchsia_net_filter_deprecated as fnet_filter_deprecated,
fidl_fuchsia_net_masquerade as fnet_masquerade,
};
use anyhow::{bail, Context as _};
use tracing::{error, info, warn};
use crate::{exit_with_fidl_error, FilterConfig, InterfaceId, InterfaceType};
#[derive(Debug)]
pub(crate) enum FilterError {
Push(PushChangesError),
Commit(CommitError),
}
pub(crate) enum FilterControl {
Deprecated(fnet_filter_deprecated::FilterProxy),
Current(FilterState),
}
impl FilterControl {
pub(super) async fn new(
deprecated_proxy: Option<fnet_filter_deprecated::FilterProxy>,
current_proxy: Option<fnet_filter::ControlProxy>,
) -> Result<Self, anyhow::Error> {
if let Some(proxy) = deprecated_proxy {
if probe_for_presence(&proxy).await {
return Ok(FilterControl::Deprecated(proxy));
}
}
if let Some(proxy) = current_proxy {
let controller_id = fnet_filter_ext::ControllerId(String::from("netcfg"));
let filter = FilterControl::Current(FilterState {
controller: fnet_filter_ext::Controller::new(&proxy, &controller_id)
.await
.context("could not create controller from filter proxy")?,
uninstalled_ip_routines: filter_routines(false ),
installed_ip_routines: filter_routines(true ),
current_installed_rule_index: 0,
masquerade: MasqueradeState {
routine_id: masquerade_routine(),
next_rule_index: 0,
},
});
return Ok(filter);
}
Err(anyhow::anyhow!("no filtering proxy available!"))
}
pub(super) async fn update_filters(
&mut self,
config: FilterConfig,
) -> Result<(), anyhow::Error> {
match self {
FilterControl::Deprecated(proxy) => update_filters_deprecated(proxy, config).await,
FilterControl::Current(state) => state.update_filters_current(config).await,
}
}
}
struct MasqueradeState {
routine_id: RoutineId,
next_rule_index: u32,
}
pub(super) struct FilterState {
controller: fnet_filter_ext::Controller,
uninstalled_ip_routines: netfilter::parser::FilterRoutines,
installed_ip_routines: netfilter::parser::FilterRoutines,
masquerade: MasqueradeState,
current_installed_rule_index: u32,
}
impl FilterState {
async fn update_filters_current(&mut self, config: FilterConfig) -> Result<(), anyhow::Error> {
let FilterState {
controller,
uninstalled_ip_routines,
installed_ip_routines,
current_installed_rule_index: _,
masquerade,
} = self;
let changes = generate_initial_filter_changes(
uninstalled_ip_routines,
installed_ip_routines,
&masquerade.routine_id,
config,
)?;
controller
.push_changes(changes)
.await
.context("failed to push changes to filter controller")?;
controller.commit().await.context("failed to commit changes to filter controller")?;
info!("initial filter configuration has been committed successfully");
Ok(())
}
}
fn filter_routines(installed: bool) -> netfilter::parser::FilterRoutines {
let suffix = if !installed { "_uninstalled" } else { "" };
netfilter::parser::FilterRoutines {
local_ingress: Some(RoutineId {
namespace: namespace_id(),
name: format!("local_ingress{suffix}"),
}),
local_egress: Some(RoutineId {
namespace: namespace_id(),
name: format!("local_egress{suffix}"),
}),
}
}
fn masquerade_routine() -> RoutineId {
RoutineId { namespace: namespace_id(), name: format!("egress_masquerade") }
}
fn namespace_id() -> NamespaceId {
NamespaceId(String::from("netcfg"))
}
pub(super) async fn probe_for_presence(filter: &fnet_filter_deprecated::FilterProxy) -> bool {
match filter.check_presence().await {
Ok(()) => true,
Err(fidl::Error::ClientChannelClosed { status: _, protocol_name: _ }) => false,
Err(e) => panic!("unexpected error while probing: {e}"),
}
}
fn generate_initial_filter_changes(
uninstalled_ip_routines: &netfilter::parser::FilterRoutines,
installed_ip_routines: &netfilter::parser::FilterRoutines,
masquerade_routine: &RoutineId,
config: FilterConfig,
) -> Result<Vec<Change>, anyhow::Error> {
let namespace = Change::Create(Resource::Namespace(Namespace {
id: NamespaceId(String::from("netcfg")),
domain: Domain::AllIp,
}));
let mut changes = vec![namespace];
let netfilter::parser::FilterRoutines { local_ingress, local_egress } = uninstalled_ip_routines;
let uninstalled_local_ingress =
local_ingress.clone().map(|id| Routine { id, routine_type: RoutineType::Ip(None) });
let uninstalled_local_egress =
local_egress.clone().map(|id| Routine { id, routine_type: RoutineType::Ip(None) });
fn installed_routine_from_id(id: RoutineId, hook: IpHook) -> Routine {
Routine {
id: id,
routine_type: RoutineType::Ip(Some(InstalledIpRoutine { hook, priority: 0i32 })),
}
}
let netfilter::parser::FilterRoutines { local_ingress, local_egress } = installed_ip_routines;
let local_ingress =
local_ingress.clone().map(|id| installed_routine_from_id(id, IpHook::LocalIngress));
let local_egress =
local_egress.clone().map(|id| installed_routine_from_id(id, IpHook::LocalEgress));
let masquerade = Routine {
id: masquerade_routine.clone(),
routine_type: RoutineType::Nat(Some(InstalledNatRoutine {
hook: NatHook::Egress,
priority: 0i32,
})),
};
let routine_changes = [
uninstalled_local_ingress,
local_ingress,
uninstalled_local_egress,
local_egress,
Some(masquerade),
]
.into_iter()
.filter_map(|routine| routine)
.map(|routine| Change::Create(Resource::Routine(routine)));
changes.extend(routine_changes);
let FilterConfig { rules, nat_rules: _, rdr_rules: _ } = config;
if !rules.is_empty() {
let rules =
netfilter::parser::parse_str_to_rules(&rules.join(""), &uninstalled_ip_routines)
.context("error parsing filter rules")?;
let rule_changes = rules.into_iter().map(|rule| Change::Create(Resource::Rule(rule)));
changes.extend(rule_changes);
}
Ok(changes)
}
fn generate_updated_filter_rules(
uninstalled_ip_routines: &netfilter::parser::FilterRoutines,
installed_ip_routines: &netfilter::parser::FilterRoutines,
interface_id: InterfaceId,
current_installed_rule_index: u32,
) -> Vec<Rule> {
let netfilter::parser::FilterRoutines {
local_ingress: uninstalled_local_ingress,
local_egress: uninstalled_local_egress,
} = uninstalled_ip_routines;
let netfilter::parser::FilterRoutines { local_ingress, local_egress } = installed_ip_routines;
let local_ingress_rule = local_ingress.clone().map(|routine_id| {
create_interface_matching_jump_rule(
routine_id,
current_installed_rule_index,
interface_id,
IpHook::LocalIngress,
&uninstalled_local_ingress
.as_ref()
.expect("there should be a corresponding uninstalled routine for local ingress")
.name,
)
});
let local_egress_rule = local_egress.clone().map(|routine_id| {
create_interface_matching_jump_rule(
routine_id,
current_installed_rule_index,
interface_id,
IpHook::LocalEgress,
&uninstalled_local_egress
.as_ref()
.expect("there should be a corresponding uninstalled routine for local egress")
.name,
)
});
let rules: Vec<_> =
vec![local_ingress_rule, local_egress_rule].into_iter().filter_map(|rule| rule).collect();
rules
}
fn create_interface_matching_jump_rule(
routine_id: RoutineId,
index: u32,
interface_id: InterfaceId,
hook: IpHook,
target_routine_name: &str,
) -> Rule {
let (in_interface, out_interface) = match hook {
IpHook::LocalIngress | IpHook::Ingress => {
(Some(InterfaceMatcher::Id(interface_id.into())), None)
}
IpHook::LocalEgress | IpHook::Egress => {
(None, Some(InterfaceMatcher::Id(interface_id.into())))
}
IpHook::Forwarding => (
Some(InterfaceMatcher::Id(interface_id.into())),
Some(InterfaceMatcher::Id(interface_id.into())),
),
};
Rule {
id: RuleId { routine: routine_id, index },
matchers: Matchers { in_interface, out_interface, ..Default::default() },
action: Action::Jump(target_routine_name.to_string()),
}
}
pub(crate) const FILTER_CAS_RETRY_MAX: i32 = 3;
pub(crate) const FILTER_CAS_RETRY_INTERVAL_MILLIS: i64 = 500;
macro_rules! cas_filter_rules {
($filter:expr, $get_rules:ident, $update_rules:ident, $rules:expr, $error_type:ident) => {
for retry in 0..FILTER_CAS_RETRY_MAX {
let (_rules, generation) =
$filter.$get_rules().await.unwrap_or_else(|err| exit_with_fidl_error(err));
match $filter
.$update_rules(&$rules, generation)
.await
.unwrap_or_else(|err| exit_with_fidl_error(err))
{
Ok(()) => {
break;
}
Err(fnet_filter_deprecated::$error_type::GenerationMismatch)
if retry < FILTER_CAS_RETRY_MAX - 1 =>
{
fuchsia_async::Timer::new(
zx::MonotonicDuration::from_millis(FILTER_CAS_RETRY_INTERVAL_MILLIS)
.after_now(),
)
.await;
}
Err(e) => {
bail!("{} failed: {:?}", stringify!($update_rules), e);
}
}
}
};
}
macro_rules! no_update_filter_rules {
($filter:expr, $get_rules:ident, $update_rules:ident, $rules:expr, $error_type:ident) => {
let (_rules, generation) =
$filter.$get_rules().await.unwrap_or_else(|err| exit_with_fidl_error(err));
match $filter
.$update_rules(&$rules, generation)
.await
.unwrap_or_else(|err| exit_with_fidl_error(err))
{
Ok(()) => {}
Err(fnet_filter_deprecated::$error_type::NotSupported) => {
error!("{} not supported", stringify!($update_rules));
}
}
};
}
async fn update_filters_deprecated(
filter: &mut fnet_filter_deprecated::FilterProxy,
config: FilterConfig,
) -> Result<(), anyhow::Error> {
let FilterConfig { rules, nat_rules, rdr_rules } = config;
if !rules.is_empty() {
let rules = netfilter::parser_deprecated::parse_str_to_rules(&rules.join(""))
.context("error parsing filter rules")?;
cas_filter_rules!(filter, get_rules, update_rules, rules, FilterUpdateRulesError);
}
if !nat_rules.is_empty() {
let nat_rules = netfilter::parser_deprecated::parse_str_to_nat_rules(&nat_rules.join(""))
.context("error parsing NAT rules")?;
cas_filter_rules!(
filter,
get_nat_rules,
update_nat_rules,
nat_rules,
FilterUpdateNatRulesError
);
}
if !rdr_rules.is_empty() {
let rdr_rules = netfilter::parser_deprecated::parse_str_to_rdr_rules(&rdr_rules.join(""))
.context("error parsing RDR rules")?;
no_update_filter_rules!(
filter,
get_rdr_rules,
update_rdr_rules,
rdr_rules,
FilterUpdateRdrRulesError
);
}
Ok(())
}
#[derive(Debug)]
struct MasqueradeCounter(NonZeroU64);
impl MasqueradeCounter {
fn new() -> Self {
Self(NonZeroU64::new(1).unwrap())
}
fn increment(&mut self) {
*self = Self(self.0.checked_add(1).expect("integer_overflow on u64"));
}
fn decrement(&self) -> Option<Self> {
NonZeroU64::new(self.0.get() - 1).map(Self)
}
}
#[derive(Debug, Default)]
pub(super) struct FilterEnabledState {
interface_types: HashSet<InterfaceType>,
masquerade_enabled_interface_ids: HashMap<InterfaceId, MasqueradeCounter>,
currently_enabled_interfaces: HashMap<InterfaceId, Vec<RuleId>>,
}
impl FilterEnabledState {
pub(super) fn new(interface_types: HashSet<InterfaceType>) -> Self {
Self { interface_types, ..Default::default() }
}
pub(super) async fn maybe_update(
&mut self,
interface_type: Option<InterfaceType>,
interface_id: InterfaceId,
filter: &mut FilterControl,
) -> Result<(), anyhow::Error> {
match filter {
FilterControl::Deprecated(proxy) => self
.maybe_update_deprecated(interface_type, interface_id, proxy)
.await
.map_err(|e| anyhow::anyhow!("{e:?}")),
FilterControl::Current(filter_state) => self
.maybe_update_current(interface_type, interface_id, filter_state)
.await
.map_err(|e| anyhow::anyhow!("{e:?}")),
}
}
pub(super) async fn maybe_update_deprecated<
Filter: fnet_filter_deprecated::FilterProxyInterface,
>(
&mut self,
interface_type: Option<InterfaceType>,
interface_id: InterfaceId,
filter: &Filter,
) -> Result<(), fnet_filter_deprecated::EnableDisableInterfaceError> {
let should_be_enabled = self.should_enable(interface_type, interface_id);
let is_enabled = self.currently_enabled_interfaces.entry(interface_id);
match (should_be_enabled, is_enabled) {
(true, Entry::Vacant(entry)) => {
if let Err(e) = filter
.enable_interface(interface_id.get())
.await
.unwrap_or_else(|err| exit_with_fidl_error(err))
{
warn!("failed to enable interface {interface_id}: {e:?}");
return Err(e);
}
let _ = entry.insert(vec![]);
}
(false, Entry::Occupied(entry)) => {
if let Err(e) = filter
.disable_interface(interface_id.get())
.await
.unwrap_or_else(|err| exit_with_fidl_error(err))
{
warn!("failed to disable interface {interface_id}: {e:?}");
return Err(e);
}
let _ = entry.remove();
}
(true, Entry::Occupied(_)) | (false, Entry::Vacant(_)) => {
}
}
Ok(())
}
pub(super) async fn maybe_update_current(
&mut self,
interface_type: Option<InterfaceType>,
interface_id: InterfaceId,
filter: &mut FilterState,
) -> Result<(), FilterError> {
let should_be_enabled = self.should_enable(interface_type, interface_id);
let is_enabled = self.currently_enabled_interfaces.entry(interface_id);
match (should_be_enabled, is_enabled) {
(true, Entry::Vacant(entry)) => {
let FilterState {
controller,
uninstalled_ip_routines,
installed_ip_routines,
current_installed_rule_index,
masquerade: _,
} = filter;
let rules = generate_updated_filter_rules(
uninstalled_ip_routines,
installed_ip_routines,
interface_id,
*current_installed_rule_index,
);
if !rules.is_empty() {
let rule_changes = rules
.clone()
.into_iter()
.map(|rule| Change::Create(Resource::Rule(rule)))
.collect();
controller.push_changes(rule_changes).await.map_err(FilterError::Push)?;
controller.commit().await.map_err(FilterError::Commit)?;
info!(
"new filter rules for iface with id {interface_id:?} \
have been committed successfully"
);
*current_installed_rule_index = current_installed_rule_index.wrapping_add(1);
}
let rule_ids: Vec<_> = rules.into_iter().map(|rule| rule.id).collect();
let _ = entry.insert(rule_ids);
}
(false, Entry::Occupied(entry)) => {
let FilterState { controller, .. } = filter;
let rule_changes: Vec<_> = entry
.remove()
.into_iter()
.map(|rule_id| Change::Remove(ResourceId::Rule(rule_id)))
.collect();
if !rule_changes.is_empty() {
controller.push_changes(rule_changes).await.map_err(FilterError::Push)?;
controller.commit().await.map_err(FilterError::Commit)?;
info!(
"removal of filter rules for iface with id {interface_id:?} \
have been committed successfully"
);
}
}
(true, Entry::Occupied(_)) | (false, Entry::Vacant(_)) => {
}
}
Ok(())
}
fn should_enable(
&self,
interface_type: Option<InterfaceType>,
interface_id: InterfaceId,
) -> bool {
interface_type
.as_ref()
.map(|ty| match ty {
InterfaceType::WlanClient | InterfaceType::Ethernet => {
self.interface_types.contains(ty)
}
InterfaceType::WlanAp => {
self.interface_types.contains(ty)
| self.interface_types.contains(&InterfaceType::WlanClient)
}
})
.unwrap_or(false)
|| self.masquerade_enabled_interface_ids.contains_key(&interface_id)
}
pub(crate) fn increment_masquerade_count_on_interface(&mut self, interface_id: InterfaceId) {
match self.masquerade_enabled_interface_ids.entry(interface_id) {
Entry::Vacant(entry) => {
let _new_count = entry.insert(MasqueradeCounter::new());
}
Entry::Occupied(mut entry) => entry.get_mut().increment(),
}
}
pub(crate) fn decrement_masquerade_count_on_interface(&mut self, interface_id: InterfaceId) {
match self.masquerade_enabled_interface_ids.entry(interface_id) {
Entry::Vacant(_) => panic!(
"asked to decrement the masquerade count for a non-configured interface: {}",
interface_id
),
Entry::Occupied(mut entry) => match entry.get().decrement() {
None => {
let _old_count = entry.remove();
}
Some(count) => {
let _old_count = entry.insert(count);
}
},
}
}
}
async fn update_nat_rules_deprecated(
filter: &mut fnet_filter_deprecated::FilterProxy,
update_fn: impl Fn(&mut Vec<fnet_filter_deprecated::Nat>) -> Result<(), fnet_masquerade::Error>,
) -> Result<(), fnet_masquerade::Error> {
for _ in 0..FILTER_CAS_RETRY_MAX {
let (mut rules, generation) =
filter.get_nat_rules().await.expect("call to GetNatRules failed");
update_fn(&mut rules)?;
match filter
.update_nat_rules(&rules, generation)
.await
.expect("call to UpdateNatRules failed")
{
Ok(()) => return Ok(()),
Err(fnet_filter_deprecated::FilterUpdateNatRulesError::GenerationMismatch) => {
fuchsia_async::Timer::new(
zx::MonotonicDuration::from_millis(
crate::filter::FILTER_CAS_RETRY_INTERVAL_MILLIS,
)
.after_now(),
)
.await;
}
Err(fnet_filter_deprecated::FilterUpdateNatRulesError::BadRule) => {
panic!("Generated Nat rule was invalid. This should never happen: {rules:?}");
}
}
}
error!("Failed to update Nat rules");
Err(fnet_masquerade::Error::RetryExceeded)
}
pub(crate) async fn add_masquerade_rule_deprecated(
filter: &mut fnet_filter_deprecated::FilterProxy,
rule: fnet_filter_deprecated::Nat,
) -> Result<(), fnet_masquerade::Error> {
update_nat_rules_deprecated(filter, |rules| {
if rules.iter().any(|existing_rule| existing_rule == &rule) {
Err(fnet_masquerade::Error::AlreadyExists)
} else {
rules.push(rule.clone());
Ok(())
}
})
.await
}
pub(crate) async fn remove_masquerade_rule_deprecated(
filter: &mut fnet_filter_deprecated::FilterProxy,
rule: fnet_filter_deprecated::Nat,
) -> Result<(), fnet_masquerade::Error> {
update_nat_rules_deprecated(filter, |rules| {
rules.retain(|existing_rule| existing_rule != &rule);
Ok(())
})
.await
}
pub(crate) async fn add_masquerade_rule_current(
filter: &mut FilterState,
matchers: Matchers,
) -> Result<RuleId, FilterError> {
let MasqueradeState { routine_id, next_rule_index } = &mut filter.masquerade;
let rule_id = RuleId { routine: routine_id.clone(), index: *next_rule_index };
let rule_changes = vec![Change::Create(Resource::Rule(Rule {
id: rule_id.clone(),
matchers: matchers,
action: Action::Masquerade { src_port: None },
}))];
filter.controller.push_changes(rule_changes).await.map_err(FilterError::Push)?;
filter.controller.commit().await.map_err(FilterError::Commit)?;
*next_rule_index += 1;
Ok(rule_id)
}
pub(crate) async fn remove_masquerade_rule_current(
filter: &mut FilterState,
rule: &RuleId,
) -> Result<(), FilterError> {
let rule_changes = vec![Change::Remove(ResourceId::Rule(rule.clone()))];
filter.controller.push_changes(rule_changes).await.map_err(FilterError::Push)?;
filter.controller.commit().await.map_err(FilterError::Commit)
}
#[cfg(test)]
mod tests {
use const_unwrap::const_unwrap_option;
use test_case::test_case;
use crate::interface::DeviceInfoRef;
use crate::DeviceClass;
use super::*;
const INTERFACE_ID: InterfaceId = const_unwrap_option(InterfaceId::new(1));
const LOCAL_INGRESS: &str = "local_ingress";
const UNINSTALLED_LOCAL_INGRESS: &str = "local_ingress_uninstalled";
const LOCAL_EGRESS: &str = "local_egress";
const UNINSTALLED_LOCAL_EGRESS: &str = "local_egress_uninstalled";
const MASQUERADE: &str = "egress_masquerade";
fn get_foundational_changes() -> Vec<Change> {
let mut changes = vec![Change::Create(Resource::Namespace(Namespace {
id: namespace_id(),
domain: Domain::AllIp,
}))];
let local_ingress = (LOCAL_INGRESS, UNINSTALLED_LOCAL_INGRESS, IpHook::LocalIngress);
let local_egress = (LOCAL_EGRESS, UNINSTALLED_LOCAL_EGRESS, IpHook::LocalEgress);
let routine_changes = vec![local_ingress, local_egress]
.into_iter()
.map(|(installed_name, uninstalled_name, hook)| {
vec![
Routine {
id: RoutineId {
namespace: namespace_id(),
name: String::from(uninstalled_name),
},
routine_type: RoutineType::Ip(None),
},
Routine {
id: RoutineId {
namespace: namespace_id(),
name: String::from(installed_name),
},
routine_type: RoutineType::Ip(Some(InstalledIpRoutine {
hook,
priority: 0i32,
})),
},
]
})
.flatten()
.chain([Routine {
id: RoutineId { namespace: namespace_id(), name: String::from(MASQUERADE) },
routine_type: RoutineType::Nat(Some(InstalledNatRoutine {
hook: NatHook::Egress,
priority: 0i32,
})),
}])
.map(|routine| Change::Create(Resource::Routine(routine)));
changes.extend(routine_changes);
changes
}
fn create_rule(routine: RoutineId, index: u32, action: Action) -> Rule {
Rule { id: RuleId { routine, index }, matchers: Matchers::default(), action }
}
fn create_routine_id(name: &str) -> RoutineId {
RoutineId { namespace: namespace_id(), name: String::from(name) }
}
fn create_filter_routines(
namespace: NamespaceId,
local_ingress: &str,
local_egress: &str,
) -> netfilter::parser::FilterRoutines {
netfilter::parser::FilterRoutines {
local_ingress: Some(RoutineId {
namespace: namespace.clone(),
name: local_ingress.to_owned(),
}),
local_egress: Some(RoutineId { namespace, name: local_egress.to_owned() }),
}
}
#[test_case(vec![], vec![]; "no_rules")]
#[test_case(
vec!["pass in;"],
vec![create_rule(
create_routine_id(UNINSTALLED_LOCAL_INGRESS),
0,
Action::Accept,
)]; "ingress_accept")]
#[test_case(
vec!["drop out;"],
vec![create_rule(
create_routine_id(UNINSTALLED_LOCAL_EGRESS),
0,
Action::Drop,
)]; "egress_drop")]
#[test_case(
vec!["pass in; drop out;"],
vec![create_rule(
create_routine_id(UNINSTALLED_LOCAL_INGRESS),
0,
Action::Accept),
create_rule(
create_routine_id(UNINSTALLED_LOCAL_EGRESS),
1,
Action::Drop,
)]; "ingress_accept_egress_drop")]
fn test_initial_filter_changes(rules_input: Vec<&str>, expected_rules: Vec<Rule>) {
let namespace = namespace_id();
let installed_filter_routines =
create_filter_routines(namespace.clone(), LOCAL_INGRESS, LOCAL_EGRESS);
let uninstalled_filter_routines =
create_filter_routines(namespace, UNINSTALLED_LOCAL_INGRESS, UNINSTALLED_LOCAL_EGRESS);
let changes = generate_initial_filter_changes(
&uninstalled_filter_routines,
&installed_filter_routines,
&masquerade_routine(),
FilterConfig {
rules: rules_input.into_iter().map(|rule| rule.to_owned()).collect(),
nat_rules: vec![],
rdr_rules: vec![],
},
)
.expect("rules should be formatted correctly");
let mut expected_changes = get_foundational_changes();
let expected_rule_changes =
expected_rules.into_iter().map(|rule| Change::Create(Resource::Rule(rule)));
expected_changes.extend(expected_rule_changes);
assert_eq!(changes, expected_changes);
}
#[test]
fn test_generate_updated_filter_rules() {
let namespace = namespace_id();
let installed_filter_routines =
create_filter_routines(namespace.clone(), LOCAL_INGRESS, LOCAL_EGRESS);
let uninstalled_filter_routines =
create_filter_routines(namespace, UNINSTALLED_LOCAL_INGRESS, UNINSTALLED_LOCAL_EGRESS);
let rules = generate_updated_filter_rules(
&uninstalled_filter_routines,
&installed_filter_routines,
INTERFACE_ID,
0,
);
let local_ingress = (
installed_filter_routines.local_ingress.unwrap(),
uninstalled_filter_routines.local_ingress.unwrap().name,
IpHook::LocalIngress,
);
let local_egress = (
installed_filter_routines.local_egress.unwrap(),
uninstalled_filter_routines.local_egress.unwrap().name,
IpHook::LocalEgress,
);
let expected_rules: Vec<_> = vec![local_ingress, local_egress]
.into_iter()
.map(|(installed_routine, uninstalled_routine_name, hook)| {
create_interface_matching_jump_rule(
installed_routine,
0,
INTERFACE_ID,
hook,
&uninstalled_routine_name,
)
})
.collect();
assert_eq!(rules, expected_rules);
}
#[test]
fn test_should_enable_filter() {
let types_empty: HashSet<InterfaceType> = [].iter().cloned().collect();
let types_ethernet: HashSet<InterfaceType> =
[InterfaceType::Ethernet].iter().cloned().collect();
let types_wlan: HashSet<InterfaceType> =
[InterfaceType::WlanClient].iter().cloned().collect();
let types_ap: HashSet<InterfaceType> = [InterfaceType::WlanAp].iter().cloned().collect();
let id = const_unwrap_option(InterfaceId::new(10));
let make_info = |device_class| DeviceInfoRef {
device_class,
mac: &fidl_fuchsia_net_ext::MacAddress { octets: [0x1, 0x1, 0x1, 0x1, 0x1, 0x1] },
topological_path: "",
};
let wlan_info = make_info(DeviceClass::WlanClient);
let wlan_ap_info = make_info(DeviceClass::WlanAp);
let ethernet_info = make_info(DeviceClass::Ethernet);
let mut fes = FilterEnabledState::new(types_empty.clone());
assert_eq!(fes.should_enable(Some(wlan_info.interface_type()), id), false);
assert_eq!(fes.should_enable(Some(wlan_ap_info.interface_type()), id), false);
assert_eq!(fes.should_enable(Some(ethernet_info.interface_type()), id), false);
fes.increment_masquerade_count_on_interface(id);
assert_eq!(fes.should_enable(Some(ethernet_info.interface_type()), id), true);
let mut fes = FilterEnabledState::new(types_ethernet);
assert_eq!(fes.should_enable(Some(wlan_info.interface_type()), id), false);
assert_eq!(fes.should_enable(Some(wlan_ap_info.interface_type()), id), false);
assert_eq!(fes.should_enable(Some(ethernet_info.interface_type()), id), true);
fes.increment_masquerade_count_on_interface(id);
assert_eq!(fes.should_enable(Some(wlan_info.interface_type()), id), true);
let mut fes = FilterEnabledState::new(types_wlan);
assert_eq!(fes.should_enable(Some(wlan_info.interface_type()), id), true);
assert_eq!(fes.should_enable(Some(wlan_ap_info.interface_type()), id), true);
assert_eq!(fes.should_enable(Some(ethernet_info.interface_type()), id), false);
fes.increment_masquerade_count_on_interface(id);
assert_eq!(fes.should_enable(Some(ethernet_info.interface_type()), id), true);
let mut fes = FilterEnabledState::new(types_ap);
assert_eq!(fes.should_enable(Some(wlan_info.interface_type()), id), false);
assert_eq!(fes.should_enable(Some(wlan_ap_info.interface_type()), id), true);
assert_eq!(fes.should_enable(Some(ethernet_info.interface_type()), id), false);
fes.increment_masquerade_count_on_interface(id);
assert_eq!(fes.should_enable(Some(wlan_info.interface_type()), id), true);
assert_eq!(fes.should_enable(Some(ethernet_info.interface_type()), id), true);
let mut fes = FilterEnabledState::new(types_empty);
for _ in 0..3 {
fes.increment_masquerade_count_on_interface(id);
}
for expect_enabled in [true, true, false] {
fes.decrement_masquerade_count_on_interface(id);
assert_eq!(fes.should_enable(Some(wlan_info.interface_type()), id), expect_enabled);
assert_eq!(fes.should_enable(Some(wlan_ap_info.interface_type()), id), expect_enabled);
assert_eq!(fes.should_enable(Some(ethernet_info.interface_type()), id), expect_enabled);
}
}
}