1pub mod error;
18
19use error::IptablesError;
20use lazy_static::lazy_static;
21use regex::{Match, Regex};
22use std::convert::From;
23use std::error::Error;
24use std::ffi::OsStr;
25use std::process::{Command, Output};
26use std::vec::Vec;
27
28const BUILTIN_CHAINS_FILTER: &[&str] = &["INPUT", "FORWARD", "OUTPUT"];
30const BUILTIN_CHAINS_MANGLE: &[&str] = &["PREROUTING", "OUTPUT", "INPUT", "FORWARD", "POSTROUTING"];
31const BUILTIN_CHAINS_NAT: &[&str] = &["PREROUTING", "POSTROUTING", "OUTPUT"];
32const BUILTIN_CHAINS_RAW: &[&str] = &["PREROUTING", "OUTPUT"];
33const BUILTIN_CHAINS_SECURITY: &[&str] = &["INPUT", "OUTPUT", "FORWARD"];
34
35lazy_static! {
36 static ref RE_SPLIT: Regex = Regex::new(r#"["'].+?["']|[^ ]+"#).unwrap();
37}
38
39trait SplitQuoted {
40 fn split_quoted(&self) -> Vec<&str>;
41}
42
43impl SplitQuoted for str {
44 fn split_quoted(&self) -> Vec<&str> {
45 RE_SPLIT
46 .find_iter(self)
48 .map(|m| Match::as_str(&m))
50 .map(|s| s.trim_matches(|c| c == '"' || c == '\''))
52 .collect::<Vec<_>>()
54 }
55}
56
57fn error_from_str(msg: &str) -> Box<dyn Error> {
58 msg.into()
59}
60
61fn output_to_result(output: Output) -> Result<(), Box<dyn Error>> {
62 if !output.status.success() {
63 return Err(Box::new(IptablesError::from(output)));
64 }
65 Ok(())
66}
67
68fn get_builtin_chains(table: &str) -> Result<&[&str], Box<dyn Error>> {
69 match table {
70 "filter" => Ok(BUILTIN_CHAINS_FILTER),
71 "mangle" => Ok(BUILTIN_CHAINS_MANGLE),
72 "nat" => Ok(BUILTIN_CHAINS_NAT),
73 "raw" => Ok(BUILTIN_CHAINS_RAW),
74 "security" => Ok(BUILTIN_CHAINS_SECURITY),
75 _ => Err(error_from_str("given table is not supported by iptables")),
76 }
77}
78
79pub struct IPTables {
82 pub cmd: &'static str,
84
85 pub has_check: bool,
87
88 pub has_wait: bool,
90
91 pub is_numeric: bool,
93}
94
95#[cfg(not(target_os = "linux"))]
97pub fn new(is_ipv6: bool) -> Result<IPTables, Box<dyn Error>> {
98 Err(error_from_str("iptables only works on Linux"))
99}
100
101#[cfg(target_os = "linux")]
103pub fn new(is_ipv6: bool) -> Result<IPTables, Box<dyn Error>> {
104 let cmd = if is_ipv6 { "ip6tables" } else { "iptables" };
105
106 let version_output = Command::new(cmd).arg("--version").output()?;
107 let re = Regex::new(r"v(\d+)\.(\d+)\.(\d+)")?;
108 let version_string = String::from_utf8_lossy(version_output.stdout.as_slice());
109 let versions = re
110 .captures(&version_string)
111 .ok_or("invalid version number")?;
112 let v_major = versions
113 .get(1)
114 .ok_or("unable to get major version number")?
115 .as_str()
116 .parse::<i32>()?;
117 let v_minor = versions
118 .get(2)
119 .ok_or("unable to get minor version number")?
120 .as_str()
121 .parse::<i32>()?;
122 let v_patch = versions
123 .get(3)
124 .ok_or("unable to get patch version number")?
125 .as_str()
126 .parse::<i32>()?;
127
128 Ok(IPTables {
129 cmd,
130 has_check: (v_major > 1)
131 || (v_major == 1 && v_minor > 4)
132 || (v_major == 1 && v_minor == 4 && v_patch > 10),
133 has_wait: (v_major > 1)
134 || (v_major == 1 && v_minor > 4)
135 || (v_major == 1 && v_minor == 4 && v_patch > 19),
136 is_numeric: false,
137 })
138}
139
140impl IPTables {
141 pub fn get_policy(&self, table: &str, chain: &str) -> Result<String, Box<dyn Error>> {
143 let builtin_chains = get_builtin_chains(table)?;
144 if !builtin_chains.iter().as_slice().contains(&chain) {
145 return Err(error_from_str(
146 "given chain is not a default chain in the given table, can't get policy",
147 ));
148 }
149
150 let stdout = match self.is_numeric {
151 false => self.run(&["-t", table, "-L", chain])?.stdout,
152 true => self.run(&["-t", table, "-L", chain, "-n"])?.stdout,
153 };
154 let output = String::from_utf8_lossy(stdout.as_slice());
155 for item in output.trim().split('\n') {
156 let fields = item.split(' ').collect::<Vec<&str>>();
157 if fields.len() > 1 && fields[0] == "Chain" && fields[1] == chain {
158 return Ok(fields[3].replace(")", ""));
159 }
160 }
161 Err(error_from_str(
162 "could not find the default policy for table and chain",
163 ))
164 }
165
166 pub fn set_policy(&self, table: &str, chain: &str, policy: &str) -> Result<(), Box<dyn Error>> {
168 let builtin_chains = get_builtin_chains(table)?;
169 if !builtin_chains.iter().as_slice().contains(&chain) {
170 return Err(error_from_str(
171 "given chain is not a default chain in the given table, can't set policy",
172 ));
173 }
174
175 self.run(&["-t", table, "-P", chain, policy])
176 .and_then(output_to_result)
177 }
178
179 pub fn execute(&self, table: &str, command: &str) -> Result<Output, Box<dyn Error>> {
182 self.run(&[&["-t", table], command.split_quoted().as_slice()].concat())
183 }
184
185 #[cfg(target_os = "linux")]
188 pub fn exists(&self, table: &str, chain: &str, rule: &str) -> Result<bool, Box<dyn Error>> {
189 if !self.has_check {
190 return self.exists_old_version(table, chain, rule);
191 }
192
193 self.run(&[&["-t", table, "-C", chain], rule.split_quoted().as_slice()].concat())
194 .map(|output| output.status.success())
195 }
196
197 #[cfg(target_os = "linux")]
200 pub fn chain_exists(&self, table: &str, chain: &str) -> Result<bool, Box<dyn Error>> {
201 match self.is_numeric {
202 false => self
203 .run(&["-t", table, "-L", chain])
204 .map(|output| output.status.success()),
205 true => self
206 .run(&["-t", table, "-L", chain, "-n"])
207 .map(|output| output.status.success()),
208 }
209 }
210
211 fn exists_old_version(
212 &self,
213 table: &str,
214 chain: &str,
215 rule: &str,
216 ) -> Result<bool, Box<dyn Error>> {
217 match self.is_numeric {
218 false => self.run(&["-t", table, "-S"]).map(|output| {
219 String::from_utf8_lossy(&output.stdout).contains(&format!("-A {} {}", chain, rule))
220 }),
221 true => self.run(&["-t", table, "-S", "-n"]).map(|output| {
222 String::from_utf8_lossy(&output.stdout).contains(&format!("-A {} {}", chain, rule))
223 }),
224 }
225 }
226
227 pub fn insert(
229 &self,
230 table: &str,
231 chain: &str,
232 rule: &str,
233 position: i32,
234 ) -> Result<(), Box<dyn Error>> {
235 self.run(
236 &[
237 &["-t", table, "-I", chain, &position.to_string()],
238 rule.split_quoted().as_slice(),
239 ]
240 .concat(),
241 )
242 .and_then(output_to_result)
243 }
244
245 pub fn insert_unique(
247 &self,
248 table: &str,
249 chain: &str,
250 rule: &str,
251 position: i32,
252 ) -> Result<(), Box<dyn Error>> {
253 if self.exists(table, chain, rule)? {
254 return Err(error_from_str("the rule exists in the table/chain"));
255 }
256
257 self.insert(table, chain, rule, position)
258 }
259
260 pub fn replace(
262 &self,
263 table: &str,
264 chain: &str,
265 rule: &str,
266 position: i32,
267 ) -> Result<(), Box<dyn Error>> {
268 self.run(
269 &[
270 &["-t", table, "-R", chain, &position.to_string()],
271 rule.split_quoted().as_slice(),
272 ]
273 .concat(),
274 )
275 .and_then(output_to_result)
276 }
277
278 pub fn append(&self, table: &str, chain: &str, rule: &str) -> Result<(), Box<dyn Error>> {
280 self.run(&[&["-t", table, "-A", chain], rule.split_quoted().as_slice()].concat())
281 .and_then(output_to_result)
282 }
283
284 pub fn append_unique(
286 &self,
287 table: &str,
288 chain: &str,
289 rule: &str,
290 ) -> Result<(), Box<dyn Error>> {
291 if self.exists(table, chain, rule)? {
292 return Err(error_from_str("the rule exists in the table/chain"));
293 }
294
295 self.append(table, chain, rule)
296 }
297
298 pub fn append_replace(
300 &self,
301 table: &str,
302 chain: &str,
303 rule: &str,
304 ) -> Result<(), Box<dyn Error>> {
305 if self.exists(table, chain, rule)? {
306 self.delete(table, chain, rule)?;
307 }
308
309 self.append(table, chain, rule)
310 }
311
312 pub fn delete(&self, table: &str, chain: &str, rule: &str) -> Result<(), Box<dyn Error>> {
314 self.run(&[&["-t", table, "-D", chain], rule.split_quoted().as_slice()].concat())
315 .and_then(output_to_result)
316 }
317
318 pub fn delete_all(&self, table: &str, chain: &str, rule: &str) -> Result<(), Box<dyn Error>> {
320 while self.exists(table, chain, rule)? {
321 self.delete(table, chain, rule)?;
322 }
323
324 Ok(())
325 }
326
327 pub fn list(&self, table: &str, chain: &str) -> Result<Vec<String>, Box<dyn Error>> {
329 match self.is_numeric {
330 false => self.get_list(&["-t", table, "-S", chain]),
331 true => self.get_list(&["-t", table, "-S", chain, "-n"]),
332 }
333 }
334
335 pub fn list_table(&self, table: &str) -> Result<Vec<String>, Box<dyn Error>> {
337 match self.is_numeric {
338 false => self.get_list(&["-t", table, "-S"]),
339 true => self.get_list(&["-t", table, "-S", "-n"]),
340 }
341 }
342
343 pub fn list_chains(&self, table: &str) -> Result<Vec<String>, Box<dyn Error>> {
345 let mut list = Vec::new();
346 let stdout = self.run(&["-t", table, "-S"])?.stdout;
347 let output = String::from_utf8_lossy(stdout.as_slice());
348 for item in output.trim().split('\n') {
349 let fields = item.split(' ').collect::<Vec<&str>>();
350 if fields.len() > 1 && (fields[0] == "-P" || fields[0] == "-N") {
351 list.push(fields[1].to_string());
352 }
353 }
354 Ok(list)
355 }
356
357 pub fn new_chain(&self, table: &str, chain: &str) -> Result<(), Box<dyn Error>> {
359 self.run(&["-t", table, "-N", chain])
360 .and_then(output_to_result)
361 }
362
363 pub fn flush_chain(&self, table: &str, chain: &str) -> Result<(), Box<dyn Error>> {
365 self.run(&["-t", table, "-F", chain])
366 .and_then(output_to_result)
367 }
368
369 pub fn rename_chain(
371 &self,
372 table: &str,
373 old_chain: &str,
374 new_chain: &str,
375 ) -> Result<(), Box<dyn Error>> {
376 self.run(&["-t", table, "-E", old_chain, new_chain])
377 .and_then(output_to_result)
378 }
379
380 pub fn delete_chain(&self, table: &str, chain: &str) -> Result<(), Box<dyn Error>> {
382 self.run(&["-t", table, "-X", chain])
383 .and_then(output_to_result)
384 }
385
386 pub fn flush_table(&self, table: &str) -> Result<(), Box<dyn Error>> {
388 self.run(&["-t", table, "-F"]).and_then(output_to_result)
389 }
390
391 fn get_list<S: AsRef<OsStr>>(&self, args: &[S]) -> Result<Vec<String>, Box<dyn Error>> {
392 let stdout = self.run(args)?.stdout;
393 Ok(String::from_utf8_lossy(stdout.as_slice())
394 .trim()
395 .split('\n')
396 .map(String::from)
397 .collect())
398 }
399
400 pub fn set_numeric(&mut self, numeric: bool) {
403 self.is_numeric = numeric;
404 }
405
406 fn run<S: AsRef<OsStr>>(&self, args: &[S]) -> Result<Output, Box<dyn Error>> {
407 let mut output_cmd = Command::new(self.cmd);
408 let output;
409
410 if self.has_wait {
411 output = output_cmd.args(args).arg("--wait").output()?;
412 } else {
413 output = output_cmd.args(args).output()?;
414 }
415
416 Ok(output)
417 }
418}