1use crate::{errors::*, FirewallBackend, ProcessContext};
13use failure::format_err;
14use slog::info;
15use std::{
16 collections::BTreeMap,
17 io::{BufWriter, Write},
18 process::{Command, Stdio},
19 str,
20};
21use strum::EnumDiscriminants;
22
23mod process;
24mod rule;
25pub mod types;
26
27const DFW_FORWARD_CHAIN: &str = "DFWRS_FORWARD";
28const DFW_INPUT_CHAIN: &str = "DFWRS_INPUT";
29const DFW_POSTROUTING_CHAIN: &str = "DFWRS_POSTROUTING";
30const DFW_PREROUTING_CHAIN: &str = "DFWRS_PREROUTING";
31
32const COMMAND_IPTABLES_RESTORE: &str = "iptables-restore";
33const COMMAND_IP6TABLES_RESTORE: &str = "ip6tables-restore";
34
35type Table = String;
36type Chain = String;
37type Policy = String;
38type Rule = String;
39
40#[derive(Debug)]
42pub struct Iptables;
43impl FirewallBackend for Iptables {
44 type Rule = IptablesRule;
45 type Defaults = types::Defaults;
46
47 fn apply(rules: Vec<Self::Rule>, ctx: &ProcessContext<Self>) -> Result<()> {
48 if ctx.dry_run {
49 info!(ctx.logger, "Performing dry-run, will not update any rules");
50 } else {
51 info!(
52 ctx.logger,
53 "Applying IPv4 rules (using {})", COMMAND_IPTABLES_RESTORE
54 );
55 Self::restore(IptablesRuleDiscriminants::V4, rules.clone())?;
56 info!(
57 ctx.logger,
58 "Applying IPv6 rules (using {})", COMMAND_IP6TABLES_RESTORE
59 );
60 Self::restore(IptablesRuleDiscriminants::V6, rules)?;
61 }
62 Ok(())
63 }
64}
65
66impl Iptables {
67 fn restore(
68 rule_discriminant: IptablesRuleDiscriminants,
69 rules: Vec<IptablesRule>,
70 ) -> Result<()> {
71 let command = match rule_discriminant {
72 IptablesRuleDiscriminants::V4 => COMMAND_IPTABLES_RESTORE,
73 IptablesRuleDiscriminants::V6 => COMMAND_IP6TABLES_RESTORE,
74 };
75 let mut process = Command::new(command)
76 .stdin(Stdio::piped())
77 .stdout(Stdio::piped())
78 .stderr(Stdio::piped())
79 .spawn()?;
80
81 match process.stdin.as_mut() {
83 Some(s) => Self::write_rules(rules, rule_discriminant, s)?,
84 None => return Err(format_err!("cannot get stdin of {}", command)),
85 }
86
87 let output = process.wait_with_output()?;
89 if output.status.success() {
90 Ok(())
91 } else {
92 Err(format_err!(
93 "{} failed: '{}'",
94 command,
95 str::from_utf8(&output.stderr).unwrap_or("").trim(),
96 ))
97 }
98 }
99
100 pub fn get_rules(
102 rules: Vec<IptablesRule>,
103 rule_discriminant: IptablesRuleDiscriminants,
104 ) -> Vec<String> {
105 let mut w = BufWriter::new(Vec::new());
107 Self::write_rules(rules, rule_discriminant, &mut w).unwrap();
109 let v = w.into_inner().unwrap();
111 let s = unsafe { str::from_utf8_unchecked(&v) };
114
115 s.trim().split('\n').map(|e| e.to_owned()).collect()
117 }
118
119 fn write_rules<W: Write>(
123 rules: Vec<IptablesRule>,
124 rule_discriminant: IptablesRuleDiscriminants,
125 w: &mut W,
126 ) -> Result<()> {
127 #[allow(clippy::type_complexity)]
128 let mut rule_map: BTreeMap<Table, BTreeMap<Chain, (Option<Policy>, Vec<Rule>)>> =
129 BTreeMap::new();
130 for rule in rules {
131 if rule_discriminant != (&rule).into() {
132 continue;
133 }
134 match rule.policy_or_rule() {
135 PolicyOrRule::Policy {
136 table,
137 chain,
138 policy,
139 } => {
140 rule_map
141 .entry(table.to_owned())
142 .or_default()
143 .entry(chain.to_owned())
144 .or_insert_with(|| (None, Vec::new()))
145 .0 = Some(policy.to_owned());
146 }
147 PolicyOrRule::Rule {
148 table,
149 chain,
150 value,
151 } => {
152 rule_map
153 .entry(table.to_owned())
154 .or_default()
155 .entry(chain.to_owned())
156 .or_insert_with(|| (None, Vec::new()))
157 .1
158 .push(value.to_owned());
159 }
160 }
161 }
162
163 for (table, chains) in rule_map.iter() {
164 writeln!(w, "*{}", table)?;
165 for (chain, (policy, _)) in chains.iter() {
166 if let Some(policy) = policy {
167 writeln!(w, ":{} {} [0:0]", chain, policy)?;
168 }
169 }
170 for (_, rules) in chains.values() {
171 for rule in rules {
172 writeln!(w, "{}", rule)?;
173 }
174 }
175 writeln!(w, "COMMIT")?;
176 }
177
178 Ok(())
179 }
180}
181
182#[derive(Debug, Clone, EnumDiscriminants)]
184pub enum IptablesRule {
185 V4(PolicyOrRule),
187 V6(PolicyOrRule),
189}
190
191impl IptablesRule {
192 fn policy_or_rule(&self) -> &PolicyOrRule {
193 match self {
194 Self::V4(policy_or_rule) => policy_or_rule,
195 Self::V6(policy_or_rule) => policy_or_rule,
196 }
197 }
198
199 pub(crate) fn from_discriminant(
200 discriminant: IptablesRuleDiscriminants,
201 policy_or_rule: PolicyOrRule,
202 ) -> Self {
203 match discriminant {
204 IptablesRuleDiscriminants::V4 => IptablesRule::V4(policy_or_rule),
205 IptablesRuleDiscriminants::V6 => IptablesRule::V6(policy_or_rule),
206 }
207 }
208}
209
210#[derive(Debug, Clone)]
212pub enum PolicyOrRule {
213 Policy {
215 table: String,
217 chain: String,
219 policy: String,
221 },
222 Rule {
224 table: String,
226 chain: String,
228 value: String,
230 },
231}