riptables/
lib.rs

1use std::ffi::OsStr;
2use std::fs::File;
3use std::os::unix::io::AsRawFd;
4use std::process::Command;
5
6use nix::fcntl::{flock, FlockArg};
7
8use error::{RIPTError, RIPTResult};
9use rule::{Archive, RIPTRule};
10
11mod iptparser;
12pub mod error;
13pub mod rule;
14
15
16// List of built-in chains taken from: man 8 iptables
17const BUILTIN_CHAINS_FILTER: &'static [&'static str] = &["INPUT", "FORWARD", "OUTPUT"];
18const BUILTIN_CHAINS_MANGLE: &'static [&'static str] = &["PREROUTING", "OUTPUT", "INPUT", "FORWARD", "POSTROUTING"];
19const BUILTIN_CHAINS_NAT: &'static [&'static str] = &["PREROUTING", "POSTROUTING", "OUTPUT"];
20const BUILTIN_CHAINS_RAW: &'static [&'static str] = &["PREROUTING", "OUTPUT"];
21const BUILTIN_CHAINS_SECURITY: &'static [&'static str] = &["INPUT", "OUTPUT", "FORWARD"];
22
23
24pub struct RIPTables {
25  /// The utility command which must be 'iptables' or 'ip6tables'.
26  pub cmd: &'static str,
27
28  /// Indicates if iptables has -C (--check) option
29  pub has_check: bool,
30
31  /// Indicates if iptables has -w (--wait) option
32  pub has_wait: bool,
33}
34
35//#[cfg(not(target_os = "linux"))]
36//pub fn new(ipv6: bool) -> RIPTResult<RIPTables> {
37//  Err(RIPTError::Other("iptables only works on Linux"))
38//}
39
40#[cfg(target_os = "linux")]
41pub fn new(ipv6: bool) -> RIPTResult<RIPTables> {
42  let cmd = if ipv6 { "ip6tables" } else { "iptables" };
43  let version_output = Command::new(cmd).arg("--version").output()?;
44  let version_string = String::from_utf8_lossy(&version_output.stdout).into_owned();
45  let (v_major, v_minor, v_patch) = iptparser::iptables_version(version_string)?;
46
47  Ok(RIPTables {
48    cmd,
49    has_check: (v_major > 1) || (v_major == 1 && v_minor > 4) || (v_major == 1 && v_minor == 4 && v_patch > 10),
50    has_wait: (v_major > 1) || (v_major == 1 && v_minor > 4) || (v_major == 1 && v_minor == 4 && v_patch > 19),
51  })
52}
53
54impl RIPTables {
55  /// Execute iptables command
56  ///
57  /// # Example
58  ///
59  /// ```rust
60  /// let iptables = riptables::new(false).unwrap();
61  /// iptables.execute(|iptables| iptables.args(&["-t", "nat", "-A", "TESTNAT", "-j", "ACCEPT"])).is_ok();
62  /// ```
63  pub fn execute<T>(&self, caller: T) -> RIPTResult<(i32, String)> where T: Fn(&mut Command) -> &mut Command {
64    IptablesCaller::new(self.cmd, caller).call(self.has_wait)
65  }
66
67  /// Get the default policy for a table/chain.
68  ///
69  /// # Example
70  ///
71  /// ```rust
72  /// let iptables = riptables::new(false).unwrap();
73  /// iptables.get_policy("filter", "INPUT").is_ok();
74  /// ```
75  pub fn get_policy<S>(&self, table: S, chain: S) -> RIPTResult<Option<String>> where S: AsRef<OsStr> + Clone {
76    let bchs = self::builtin_chains(table.clone())?;
77    if !bchs.iter().as_slice().contains(&&self::to_string(chain.clone())[..]) {
78      return Err(RIPTError::Other("given chain is not a default chain in the given table, can't get policy"));
79    }
80
81    let (code, output) = self.execute(|iptables| iptables.arg("-t").arg(table.clone()).arg("-S").arg(chain.clone()))?;
82    if code != 0 {
83      return Err(RIPTError::Stderr(output));
84    }
85    let rules = iptparser::parse_rules(self::to_string(table.clone()), output)?;
86    Ok(rules.into_iter()
87      .find(|item| item.archive == Archive::Policy && item.chain == self::to_string(chain.clone()))
88      .map(|item| item.jump))
89  }
90
91  /// Set the default policy for a table/chain.
92  ///
93  /// # Example
94  ///
95  /// ```rust
96  /// let iptables = riptables::new(false).unwrap();
97  /// iptables.set_policy("mangle", "FORWARD", "DROP").unwrap();
98  /// ```
99  pub fn set_policy<S>(&self, table: S, chain: S, policy: S) -> RIPTResult<bool> where S: AsRef<OsStr> + Clone {
100    let bchs = self::builtin_chains(table.clone())?;
101    if !bchs.iter().as_slice().contains(&&self::to_string(chain.clone())[..]) {
102      return Err(RIPTError::Other("given chain is not a default chain in the given table, can't get policy"));
103    }
104    let (code, _output) = self.execute(|iptables| iptables.arg("-t").arg(table.clone()).arg("-P").arg(chain.clone()).arg(policy.clone()))?;
105    Ok(code == 0)
106  }
107
108  /// Inserts `rule` in the `position` to the table/chain.
109  /// Returns `true` if the rule is inserted.
110  ///
111  /// # Example
112  ///
113  /// ```rust
114  /// let iptables = riptables::new(false).unwrap();
115  /// iptables.insert("nat", "TESTNAT", "-j ACCEPT", 1).unwrap();
116  /// ```
117  pub fn insert<S>(&self, table: S, chain: S, rule: S, position: i32) -> RIPTResult<bool> where S: AsRef<OsStr> + Clone {
118    let rule_vec = iptparser::split_quoted(rule);
119    let pstr = position.to_string();
120    let args = &[
121      &[
122        "-t",
123        table.as_ref().to_str().unwrap(),
124        "-I",
125        chain.as_ref().to_str().unwrap(),
126        &pstr
127      ],
128      rule_vec.iter().map(|item| &item[..]).collect::<Vec<&str>>().as_slice()
129    ].concat();
130    let (code, _output) = self.execute(|iptables| iptables.args(args))?;
131    Ok(code == 0)
132  }
133
134
135  /// Inserts `rule` in the `position` to the table/chain if it does not exist.
136  /// Returns `true` if the rule is inserted.
137  ///
138  /// # Example
139  ///
140  /// ```rust
141  /// let iptables = riptables::new(false).unwrap();
142  /// iptables.insert_unique("nat", "TESTNAT", "-j ACCEPT", 1).unwrap();
143  /// ```
144  pub fn insert_unique<S>(&self, table: S, chain: S, rule: S, position: i32) -> RIPTResult<bool> where S: AsRef<OsStr> + Clone {
145    if self.exists(table.clone(), chain.clone(), rule.clone())? {
146      return Ok(true);
147    }
148    self.insert(table.clone(), chain.clone(), rule.clone(), position)
149  }
150
151  /// Replaces `rule` in the `position` to the table/chain.
152  /// Returns `true` if the rule is replaced.
153  ///
154  /// # Example
155  ///
156  /// ```rust
157  /// let iptables = riptables::new(false).unwrap();
158  /// iptables.replace("nat", "TESTNAT", "-j ACCEPT", 1).unwrap();
159  /// ```
160  pub fn replace<S>(&self, table: S, chain: S, rule: S, position: i32) -> RIPTResult<bool> where S: AsRef<OsStr> + Clone {
161    let rule_vec = iptparser::split_quoted(rule);
162    let pstr = position.to_string();
163    let args = &[
164      &[
165        "-t",
166        table.as_ref().to_str().unwrap(),
167        "-R",
168        chain.as_ref().to_str().unwrap(),
169        &pstr
170      ],
171      rule_vec.iter().map(|item| &item[..]).collect::<Vec<&str>>().as_slice()
172    ].concat();
173    let (code, _output) = self.execute(|iptables| iptables.args(args))?;
174    Ok(code == 0)
175  }
176
177
178  /// Appends `rule` to the table/chain.
179  /// Returns `true` if the rule is appended.
180  ///
181  /// # Example
182  ///
183  /// ```rust
184  /// let iptables = riptables::new(false).unwrap();
185  /// iptables.append("nat", "TESTNAT", "-m comment --comment \"double-quoted comment\" -j ACCEPT").unwrap();
186  /// ```
187  pub fn append<S>(&self, table: S, chain: S, rule: S) -> RIPTResult<bool> where S: AsRef<OsStr> + Clone {
188    let rule_vec = iptparser::split_quoted(rule);
189    let args = &[
190      &[
191        "-t",
192        table.as_ref().to_str().unwrap(),
193        "-A",
194        chain.as_ref().to_str().unwrap(),
195      ],
196      rule_vec.iter().map(|item| &item[..]).collect::<Vec<&str>>().as_slice()
197    ].concat();
198    let (code, _output) = self.execute(|iptables| iptables.args(args))?;
199    Ok(code == 0)
200  }
201
202  /// Appends `rule` to the table/chain if it does not exist.
203  /// Returns `true` if the rule is appended.
204  ///
205  /// # Example
206  ///
207  /// ```rust
208  /// let iptables = riptables::new(false).unwrap();
209  /// iptables.append_unique("nat", "TESTNAT", "-m comment --comment \"double-quoted comment\" -j ACCEPT").unwrap();
210  /// ```
211  pub fn append_unique<S>(&self, table: S, chain: S, rule: S) -> RIPTResult<bool> where S: AsRef<OsStr> + Clone {
212    if self.exists(table.clone(), chain.clone(), rule.clone())? {
213      return Ok(true);
214    }
215    self.append(table.clone(), chain.clone(), rule.clone())
216  }
217
218  /// Appends or replaces `rule` to the table/chain if it does not exist.
219  /// Returns `true` if the rule is appended or replaced.
220  ///
221  /// # Example
222  ///
223  /// ```rust
224  /// let iptables = riptables::new(false).unwrap();
225  /// iptables.append_replace("nat", "TESTNAT", "-m comment --comment \"double-quoted comment\" -j ACCEPT").unwrap();
226  /// ```
227  pub fn append_replace<S>(&self, table: S, chain: S, rule: S) -> RIPTResult<bool> where S: AsRef<OsStr> + Clone {
228    if self.exists(table.clone(), chain.clone(), rule.clone())? {
229      if !self.delete(table.clone(), chain.clone(), rule.clone())? {
230        return Ok(false);
231      }
232    }
233    self.append(table, chain, rule)
234  }
235
236  /// Deletes `rule` from the table/chain.
237  /// Returns `true` if the rule is deleted.
238  ///
239  /// # Example
240  ///
241  /// ```rust
242  /// let iptables = riptables::new(false).unwrap();
243  /// iptables.delete("nat", "TESTNAT", "-j ACCEPT").unwrap();
244  /// ```
245  pub fn delete<S>(&self, table: S, chain: S, rule: S) -> RIPTResult<bool> where S: AsRef<OsStr> + Clone {
246    let rule_vec = iptparser::split_quoted(rule);
247    let args = &[
248      &[
249        "-t",
250        table.as_ref().to_str().unwrap(),
251        "-D",
252        chain.as_ref().to_str().unwrap()
253      ],
254      rule_vec.iter().map(|item| &item[..]).collect::<Vec<&str>>().as_slice()
255    ].concat();
256    let (code, _output) = self.execute(|iptables| iptables.args(args))?;
257    Ok(code == 0)
258  }
259
260  /// Deletes all repetition of the `rule` from the table/chain.
261  /// Returns `true` if the rules are deleted.
262  /// ```rust
263  /// let iptables = riptables::new(false).unwrap();
264  /// iptables.delete_all("nat", "TESTNAT", "-j ACCEPT").unwrap();
265  /// ```
266  pub fn delete_all<S>(&self, table: S, chain: S, rule: S) -> RIPTResult<bool> where S: AsRef<OsStr> + Clone {
267    while self.exists(table.clone(), chain.clone(), rule.clone())? {
268      self.delete(table.clone(), chain.clone(), rule.clone())?;
269    }
270    Ok(true)
271  }
272
273  /// Lists rules in the table/chain.
274  ///
275  /// # Example
276  ///
277  /// ```rust
278  /// use riptables::rule::{Archive, RIPTRule};
279  ///
280  /// let iptables = riptables::new(false).unwrap();
281  ///
282  /// let table = "nat";
283  /// let name = "TESTNAT";
284  /// iptables.new_chain(table, name).unwrap();
285  /// iptables.insert(table, name, "-j ACCEPT", 1).unwrap();
286  /// let rules: Vec<RIPTRule> = iptables.list("nat").unwrap();
287  /// iptables.delete(table, name, "-j ACCEPT").unwrap();
288  /// iptables.delete_chain(table, name).unwrap();
289  ///
290  /// println!("{}", rules.len());
291  ///
292  /// for rule in rules {
293  ///   println!("{:?}", rule);
294  ///   println!("{:?}", rule.table);
295  ///   println!("{:?}", rule.chain);
296  ///   println!("{:?}", rule.origin);
297  /// }
298  /// ```
299  pub fn list<S>(&self, table: S) -> RIPTResult<Vec<RIPTRule>> where S: AsRef<OsStr> + Clone {
300    let (code, output) = self.execute(|iptables| iptables.arg("-t").arg(table.clone()).arg("-S"))?;
301//    let sodt = "-P OUTPUT  ACCEPT".to_string();
302    if code != 0 {
303      return Err(RIPTError::Stderr(output));
304    }
305    Ok(iptparser::parse_rules(self::to_string(table), output)?)
306  }
307
308
309  /// Lists the name of each chain in the table.
310  ///
311  /// # Example
312  ///
313  /// ```rust
314  /// let iptables = riptables::new(false).unwrap();
315  /// let names = iptables.chain_names("nat");
316  /// ```
317  pub fn chain_names<S>(&self, table: S) -> RIPTResult<Vec<String>> where S: AsRef<OsStr> + Clone {
318    Ok(self.list(table)?.iter()
319      .filter(|item| item.archive == Archive::Policy || item.archive == Archive::NewChain)
320      .map(|item| item.chain.clone())
321      .collect::<Vec<String>>())
322  }
323
324  /// Lists rules in the table/chain.
325  ///
326  /// # Example
327  ///
328  /// ```rust
329  /// use riptables::rule::RIPTRule;
330  ///
331  /// let iptables = riptables::new(false).unwrap();
332  /// let rules: Vec<RIPTRule> = iptables.list_chains("nat", "INPUT").unwrap();
333  /// ```
334  pub fn list_chains<S>(&self, table: S, chain: S) -> RIPTResult<Vec<RIPTRule>> where S: AsRef<OsStr> + Clone {
335    let (code, output) = self.execute(|iptables| iptables.arg("-t").arg(table.clone()).arg("-S").arg(chain.clone()))?;
336    if code != 0 {
337      return Err(RIPTError::Stderr(output));
338    }
339    Ok(iptparser::parse_rules(self::to_string(table), output)?)
340  }
341
342  /// Creates a new user-defined chain.
343  /// Returns `true` if the chain is created.
344  ///
345  /// # Example
346  ///
347  /// ```rust
348  /// let iptables = riptables::new(false).unwrap();
349  /// iptables.new_chain("nat", "TESTNAT");
350  /// ```
351  pub fn new_chain<S>(&self, table: S, chain: S) -> RIPTResult<bool> where S: AsRef<OsStr> + Clone {
352    let (code, _output) = self.execute(|iptables| iptables.arg("-t").arg(table.clone()).arg("-N").arg(chain.clone()))?;
353    Ok(code == 0)
354  }
355
356  /// Deletes a user-defined chain in the table.
357  /// Returns `true` if the chain is deleted.
358  ///
359  /// # Example
360  ///
361  /// ```rust
362  /// let iptables = riptables::new(false).unwrap();
363  /// iptables.delete_chain("nat", "TESTNAT");
364  /// ```
365  pub fn delete_chain<S>(&self, table: S, chain: S) -> RIPTResult<bool> where S: AsRef<OsStr> + Clone {
366    let (code, _output) = self.execute(|iptables| iptables.arg("-t").arg(table.clone()).arg("-X").arg(chain.clone()))?;
367    Ok(code == 0)
368  }
369
370  /// Renames a chain in the table.
371  /// Returns `true` if the chain is renamed.
372  ///
373  /// # Example
374  ///
375  /// ```rust
376  /// let iptables = riptables::new(false).unwrap();
377  /// iptables.rename_chain("nat", "TESTNAT", "OTHERNAME");
378  /// ```
379  pub fn rename_chain<S>(&self, table: S, old_chain: S, new_chain: S) -> RIPTResult<bool> where S: AsRef<OsStr> + Clone {
380    let (code, _output) = self.execute(|iptables| iptables.arg("-t").arg(table.clone()).arg("-E").arg(old_chain.clone()).arg(new_chain.clone()))?;
381    Ok(code == 0)
382  }
383
384  /// Flushes (deletes all rules) a chain.
385  /// Returns `true` if the chain is flushed.
386  ///
387  /// # Example
388  ///
389  /// ```rust
390  /// let iptables = riptables::new(false).unwrap();
391  /// iptables.flush_chain("nat", "TESTNAT");
392  /// ```
393  pub fn flush_chain<S>(&self, table: S, chain: S) -> RIPTResult<bool> where S: AsRef<OsStr> + Clone {
394    let (code, _output) = self.execute(|iptables| iptables.arg("-t").arg(table.clone()).arg("-F").arg(chain.clone()))?;
395    Ok(code == 0)
396  }
397
398  /// Checks for the existence of the `chain` in the table.
399  /// Returns true if the chain exists.
400  ///
401  /// # Example
402  ///
403  /// ```rust
404  /// let iptables = riptables::new(false).unwrap();
405  /// iptables.exists_chain("nat", "TESTNAT");
406  /// ```
407  pub fn exists_chain<S>(&self, table: S, chain: S) -> RIPTResult<bool> where S: AsRef<OsStr> + Clone {
408    let (code, _output) = self.execute(|iptables| iptables.arg("-t").arg(table.clone()).arg("-L").arg(chain.clone()))?;
409    Ok(code == 0)
410  }
411
412  /// Flushes all chains in a table.
413  /// Returns `true` if the chains are flushed.
414  ///
415  /// # Example
416  ///
417  /// ```rust
418  /// let iptables = riptables::new(false).unwrap();
419  /// iptables.flush_table("nat");
420  /// ```
421  pub fn flush_table<S>(&self, table: S) -> RIPTResult<bool> where S: AsRef<OsStr> + Clone {
422    let (code, _output) = self.execute(|iptables| iptables.arg("-t").arg(table.clone()).arg("-F"))?;
423    Ok(code == 0)
424  }
425
426  /// Lists rules in the table.
427  ///
428  /// # Example
429  ///
430  /// ```rust
431  /// use riptables::rule::RIPTRule;
432  ///
433  /// let iptables = riptables::new(false).unwrap();
434  /// let rule: Vec<RIPTRule> = iptables.list_tables("nat").unwrap();
435  /// ```
436  pub fn list_tables<S>(&self, table: S) -> RIPTResult<Vec<RIPTRule>> where S: AsRef<OsStr> + Clone {
437    let (code, output) = self.execute(|iptables| iptables.arg("-t").arg(table.clone()).arg("-S"))?;
438    if code != 0 {
439      return Err(RIPTError::Stderr(output));
440    }
441    Ok(iptparser::parse_rules(self::to_string(table.clone()), output)?)
442  }
443
444  /// Checks for the existence of the `rule` in the table/chain.
445  /// Returns true if the rule exists.
446  /// 
447  /// # Example
448  /// 
449  /// ```rust
450  /// let iptables = riptables::new(false).unwrap();
451  /// iptables.exists("nat", "TESTNAT", "-j ACCEPT").unwrap();
452  /// ```
453  pub fn exists<S>(&self, table: S, chain: S, rule: S) -> RIPTResult<bool> where S: AsRef<OsStr> + Clone {
454    if !self.has_check {
455      return self.exists_old_version(table, chain, rule);
456    }
457
458    let rule_vec = iptparser::split_quoted(rule);
459    let args = &[
460      &[
461        "-t",
462        table.as_ref().to_str().unwrap(),
463        "-C",
464        chain.as_ref().to_str().unwrap()
465      ],
466      rule_vec.iter().map(|item| &item[..]).collect::<Vec<&str>>().as_slice()
467    ].concat();
468    let (code, _output) = self.execute(|iptables| iptables.args(args))?;
469    Ok(code == 0)
470  }
471
472  fn exists_old_version<S>(&self, table: S, chain: S, rule: S) -> RIPTResult<bool> where S: AsRef<OsStr> + Clone {
473    let (code, output) = self.execute(|iptables| iptables.arg("-t").arg(table.clone()).arg("-S"))?;
474    if code != 0 {
475      return Ok(false);
476    }
477    let exists = output.contains(&format!("-A {} {}", chain.as_ref().to_str().unwrap(), rule.as_ref().to_str().unwrap()));
478    Ok(exists)
479  }
480}
481
482struct IptablesCaller<T> where T: Fn(&mut Command) -> &mut Command {
483  command: Command,
484  fill: T,
485}
486
487impl<T> IptablesCaller<T> where T: Fn(&mut Command) -> &mut Command {
488  fn new<S: AsRef<OsStr>>(program: S, fill: T) -> IptablesCaller<T> {
489    IptablesCaller {
490      command: Command::new(program),
491      fill,
492    }
493  }
494
495  fn call(&mut self, has_wait: bool) -> RIPTResult<(i32, String)> {
496    let command = (self.fill)(&mut self.command);
497
498    let mut file_lock = None;
499
500    if has_wait {
501      command.arg("--wait");
502    } else {
503      file_lock = Some(File::create("/var/run/xtables_old.lock")?);
504
505      let mut need_retry = true;
506      while need_retry {
507        match flock(file_lock.as_ref().unwrap().as_raw_fd(), FlockArg::LockExclusiveNonblock) {
508          Ok(_) => need_retry = false,
509          Err(e) => if e.errno() == nix::errno::EAGAIN {
510            // FIXME: may cause infinite loop
511            need_retry = true;
512          } else {
513            return Err(RIPTError::Nix(e));
514          },
515        }
516      }
517    }
518
519//    println!("{:?}", command);
520    let output = command.output()?;
521    if !has_wait {
522      if let Some(f) = file_lock {
523        drop(f);
524      }
525    }
526
527    let status = output.status.code();
528
529    match status {
530      Some(0) => {
531        let stdout = String::from_utf8_lossy(&output.stdout).into_owned();
532        Ok((0, stdout))
533      }
534      Some(code) => {
535        let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
536        Ok((code, stderr))
537      }
538      None => Err(RIPTError::Other("None output code"))
539    }
540  }
541}
542
543fn builtin_chains<S>(table: S) -> RIPTResult<&'static [&'static str]> where S: AsRef<OsStr> + Clone {
544  match &self::to_string(table)[..] {
545    "filter" => Ok(BUILTIN_CHAINS_FILTER),
546    "mangle" => Ok(BUILTIN_CHAINS_MANGLE),
547    "nat" => Ok(BUILTIN_CHAINS_NAT),
548    "raw" => Ok(BUILTIN_CHAINS_RAW),
549    "security" => Ok(BUILTIN_CHAINS_SECURITY),
550    _ => Err(RIPTError::Other("given table is not supported by iptables")),
551  }
552}
553
554fn to_string<S>(text: S) -> String where S: AsRef<OsStr> {
555  text.as_ref().to_str().unwrap().to_string()
556}