use crate::{errors::*, FirewallBackend, ProcessContext};
use failure::format_err;
use slog::info;
use std::{
collections::BTreeMap,
io::{BufWriter, Write},
process::{Command, Stdio},
str,
};
use strum::EnumDiscriminants;
mod process;
mod rule;
pub mod types;
const DFW_FORWARD_CHAIN: &str = "DFWRS_FORWARD";
const DFW_INPUT_CHAIN: &str = "DFWRS_INPUT";
const DFW_POSTROUTING_CHAIN: &str = "DFWRS_POSTROUTING";
const DFW_PREROUTING_CHAIN: &str = "DFWRS_PREROUTING";
const COMMAND_IPTABLES_RESTORE: &str = "iptables-restore";
const COMMAND_IP6TABLES_RESTORE: &str = "ip6tables-restore";
type Table = String;
type Chain = String;
type Policy = String;
type Rule = String;
#[derive(Debug)]
pub struct Iptables;
impl FirewallBackend for Iptables {
type Rule = IptablesRule;
type Defaults = types::Defaults;
fn apply(rules: Vec<Self::Rule>, ctx: &ProcessContext<Self>) -> Result<()> {
if ctx.dry_run {
info!(ctx.logger, "Performing dry-run, will not update any rules");
} else {
info!(
ctx.logger,
"Applying IPv4 rules (using {})", COMMAND_IPTABLES_RESTORE
);
Self::restore(IptablesRuleDiscriminants::V4, rules.clone())?;
info!(
ctx.logger,
"Applying IPv6 rules (using {})", COMMAND_IP6TABLES_RESTORE
);
Self::restore(IptablesRuleDiscriminants::V6, rules)?;
}
Ok(())
}
}
impl Iptables {
fn restore(
rule_discriminant: IptablesRuleDiscriminants,
rules: Vec<IptablesRule>,
) -> Result<()> {
let command = match rule_discriminant {
IptablesRuleDiscriminants::V4 => COMMAND_IPTABLES_RESTORE,
IptablesRuleDiscriminants::V6 => COMMAND_IP6TABLES_RESTORE,
};
let mut process = Command::new(command)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
match process.stdin.as_mut() {
Some(s) => Self::write_rules(rules, rule_discriminant, s)?,
None => return Err(format_err!("cannot get stdin of {}", command)),
}
let output = process.wait_with_output()?;
if output.status.success() {
Ok(())
} else {
Err(format_err!(
"{} failed: '{}'",
command,
str::from_utf8(&output.stderr).unwrap_or("").trim(),
))
}
}
pub fn get_rules(
rules: Vec<IptablesRule>,
rule_discriminant: IptablesRuleDiscriminants,
) -> Vec<String> {
let mut w = BufWriter::new(Vec::new());
Self::write_rules(rules, rule_discriminant, &mut w).unwrap();
let v = w.into_inner().unwrap();
let s = unsafe { str::from_utf8_unchecked(&v) };
s.trim().split('\n').map(|e| e.to_owned()).collect()
}
fn write_rules<W: Write>(
rules: Vec<IptablesRule>,
rule_discriminant: IptablesRuleDiscriminants,
w: &mut W,
) -> Result<()> {
#[allow(clippy::type_complexity)]
let mut rule_map: BTreeMap<Table, BTreeMap<Chain, (Option<Policy>, Vec<Rule>)>> =
BTreeMap::new();
for rule in rules {
if rule_discriminant != (&rule).into() {
continue;
}
match rule.policy_or_rule() {
PolicyOrRule::Policy {
table,
chain,
policy,
} => {
rule_map
.entry(table.to_owned())
.or_insert_with(BTreeMap::new)
.entry(chain.to_owned())
.or_insert_with(|| (None, Vec::new()))
.0 = Some(policy.to_owned());
}
PolicyOrRule::Rule {
table,
chain,
value,
} => {
rule_map
.entry(table.to_owned())
.or_insert_with(BTreeMap::new)
.entry(chain.to_owned())
.or_insert_with(|| (None, Vec::new()))
.1
.push(value.to_owned());
}
}
}
for (table, chains) in rule_map.iter() {
writeln!(w, "*{}", table)?;
for (chain, (policy, _)) in chains.iter() {
if let Some(policy) = policy {
writeln!(w, ":{} {} [0:0]", chain, policy)?;
}
}
for (_, (_, rules)) in chains.iter() {
for rule in rules {
writeln!(w, "{}", rule)?;
}
}
writeln!(w, "COMMIT")?;
}
Ok(())
}
}
#[derive(Debug, Clone, EnumDiscriminants)]
pub enum IptablesRule {
V4(PolicyOrRule),
V6(PolicyOrRule),
}
impl IptablesRule {
fn policy_or_rule(&self) -> &PolicyOrRule {
match self {
Self::V4(policy_or_rule) => policy_or_rule,
Self::V6(policy_or_rule) => policy_or_rule,
}
}
pub(crate) fn from_discriminant(
discriminant: IptablesRuleDiscriminants,
policy_or_rule: PolicyOrRule,
) -> Self {
match discriminant {
IptablesRuleDiscriminants::V4 => IptablesRule::V4(policy_or_rule),
IptablesRuleDiscriminants::V6 => IptablesRule::V6(policy_or_rule),
}
}
}
#[derive(Debug, Clone)]
pub enum PolicyOrRule {
Policy {
table: String,
chain: String,
policy: String,
},
Rule {
table: String,
chain: String,
value: String,
},
}