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
16const 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 pub cmd: &'static str,
27
28 pub has_check: bool,
30
31 pub has_wait: bool,
33}
34
35#[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 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 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 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 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 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 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 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 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 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 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 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 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"))?;
301if code != 0 {
303 return Err(RIPTError::Stderr(output));
304 }
305 Ok(iptparser::parse_rules(self::to_string(table), output)?)
306 }
307
308
309 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 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 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 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 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 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 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 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 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 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 need_retry = true;
512 } else {
513 return Err(RIPTError::Nix(e));
514 },
515 }
516 }
517 }
518
519let 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}