extern crate regex;
extern crate nix;
pub mod error;
use std::process::{Command, Output};
use regex::Regex;
use error::{IPTResult, IPTError};
use std::fs::File;
use std::os::unix::io::AsRawFd;
use nix::fcntl::{flock, FlockArg};
use std::vec::Vec;
use std::ffi::OsStr;
const BUILTIN_CHAINS_FILTER: &'static [&'static str] = &["INPUT", "FORWARD", "OUTPUT"];
const BUILTIN_CHAINS_MANGLE: &'static [&'static str] =
&["PREROUTING", "OUTPUT", "INPUT", "FORWARD", "POSTROUTING"];
const BUILTIN_CHAINS_NAT: &'static [&'static str] = &["PREROUTING", "POSTROUTING", "OUTPUT"];
const BUILTIN_CHAINS_RAW: &'static [&'static str] = &["PREROUTING", "OUTPUT"];
const BUILTIN_CHAINS_SECURITY: &'static [&'static str] = &["INPUT", "OUTPUT", "FORWARD"];
fn get_builtin_chains(table: &str) -> IPTResult<&[&str]> {
match table {
"filter" => Ok(BUILTIN_CHAINS_FILTER),
"mangle" => Ok(BUILTIN_CHAINS_MANGLE),
"nat" => Ok(BUILTIN_CHAINS_NAT),
"raw" => Ok(BUILTIN_CHAINS_RAW),
"security" => Ok(BUILTIN_CHAINS_SECURITY),
_ => Err(IPTError::Other("given table is not supported by iptables")),
}
}
pub struct IPTables {
pub cmd: &'static str,
pub has_check: bool,
pub has_wait: bool,
}
#[cfg(not(target_os = "linux"))]
pub fn new(is_ipv6: bool) -> IPTResult<IPTables> {
Err(IPTError::Other("iptables only works on Linux"))
}
#[cfg(target_os = "linux")]
pub fn new(is_ipv6: bool) -> IPTResult<IPTables> {
let cmd = if is_ipv6 {
"ip6tables"
} else {
"iptables"
};
let version_output = Command::new(cmd).arg("--version").output()?;
let re = Regex::new(r"v(\d+)\.(\d+)\.(\d+)")?;
let version_string = String::from_utf8_lossy(&version_output.stdout).into_owned();
let versions = re.captures(&version_string).ok_or("invalid version number")?;
let v_major = versions.get(1).ok_or("unable to get major version number")?.as_str().parse::<i32>()?;
let v_minor = versions.get(2).ok_or("unable to get minor version number")?.as_str().parse::<i32>()?;
let v_patch = versions.get(3).ok_or("unable to get patch version number")?.as_str().parse::<i32>()?;
Ok(IPTables {
cmd: cmd,
has_check: (v_major > 1) || (v_major == 1 && v_minor > 4) || (v_major == 1 && v_minor == 4 && v_patch > 10),
has_wait: (v_major > 1) || (v_major == 1 && v_minor > 4) || (v_major == 1 && v_minor == 4 && v_patch > 19),
})
}
impl IPTables {
pub fn get_policy(&self, table: &str, chain: &str) -> IPTResult<String> {
let builtin_chains = get_builtin_chains(table)?;
if !builtin_chains.iter().as_slice().contains(&chain) {
return Err(IPTError::Other("given chain is not a default chain in the given table, can't get policy"));
}
let output = String::from_utf8_lossy(&self.run(&["-t", table, "-L", chain])?.stdout)
.into_owned();
for item in output.trim().split("\n") {
let fields = item.split(" ").collect::<Vec<&str>>();
if fields.len() > 1 && fields[0] == "Chain" && fields[1] == chain {
return Ok(fields[3].replace(")", ""));
}
}
Err(IPTError::Other("could not find the default policy for table and chain"))
}
pub fn set_policy(&self, table: &str, chain: &str, policy: &str) -> IPTResult<bool> {
let builtin_chains = get_builtin_chains(table)?;
if !builtin_chains.iter().as_slice().contains(&chain) {
return Err(IPTError::Other("given chain is not a default chain in the given table, can't set policy"));
}
match self.run(&["-t", table, "-P", chain, policy]) {
Ok(output) => Ok(output.status.success()),
Err(err) => Err(err),
}
}
pub fn execute(&self, table: &str, command: &str) -> IPTResult<Output> {
self.run(&[&["-t", table], command.split(" ").collect::<Vec<&str>>().as_slice()].concat())
}
#[cfg(target_os = "linux")]
pub fn exists(&self, table: &str, chain: &str, rule: &str) -> IPTResult<bool> {
if !self.has_check {
return self.exists_old_version(table, chain, rule);
}
match self.run(&[&["-t", table, "-C", chain], rule.split(" ").collect::<Vec<&str>>().as_slice()].concat()) {
Ok(output) => Ok(output.status.success()),
Err(err) => Err(err),
}
}
#[cfg(target_os = "linux")]
pub fn chain_exists(&self, table: &str, chain: &str) -> IPTResult<bool> {
match self.run(&["-t", table, "-L", chain]) {
Ok(output) => Ok(output.status.success()),
Err(err) => Err(err),
}
}
pub fn insert(&self, table: &str, chain: &str, rule: &str, position: i32) -> IPTResult<bool> {
match self.run(&[&["-t", table, "-I", chain, &position.to_string()], rule.split(" ").collect::<Vec<&str>>().as_slice()].concat()) {
Ok(output) => Ok(output.status.success()),
Err(err) => Err(err),
}
}
pub fn insert_unique(&self, table: &str, chain: &str, rule: &str, position: i32) -> IPTResult<bool> {
if self.exists(table, chain, rule)? {
return Err(IPTError::Other("the rule exists in the table/chain"))
}
self.insert(table, chain, rule, position)
}
pub fn replace(&self, table: &str, chain: &str, rule: &str, position: i32) -> IPTResult<bool> {
match self.run(&[&["-t", table, "-R", chain, &position.to_string()], rule.split(" ").collect::<Vec<&str>>().as_slice()].concat()) {
Ok(output) => Ok(output.status.success()),
Err(err) => Err(err),
}
}
pub fn append(&self, table: &str, chain: &str, rule: &str) -> IPTResult<bool> {
match self.run(&[&["-t", table, "-A", chain], rule.split(" ").collect::<Vec<&str>>().as_slice()].concat()) {
Ok(output) => Ok(output.status.success()),
Err(err) => Err(err),
}
}
pub fn append_unique(&self, table: &str, chain: &str, rule: &str) -> IPTResult<bool> {
if self.exists(table, chain, rule)? {
return Err(IPTError::Other("the rule exists in the table/chain"))
}
self.append(table, chain, rule)
}
pub fn append_replace(&self, table: &str, chain: &str, rule: &str) -> IPTResult<bool> {
if self.exists(table, chain, rule)? {
self.delete(table, chain, rule)?;
}
self.append(table, chain, rule)
}
pub fn delete(&self, table: &str, chain: &str, rule: &str) -> IPTResult<bool> {
match self.run(&[&["-t", table, "-D", chain], rule.split(" ").collect::<Vec<&str>>().as_slice()].concat()) {
Ok(output) => Ok(output.status.success()),
Err(err) => Err(err),
}
}
pub fn delete_all(&self, table: &str, chain: &str, rule: &str) -> IPTResult<bool> {
while self.exists(table, chain, rule)? {
self.delete(table, chain, rule)?;
}
Ok(true)
}
pub fn list(&self, table: &str, chain: &str) -> IPTResult<Vec<String>> {
self.get_list(&["-t", table, "-S", chain])
}
pub fn list_table(&self, table: &str) -> IPTResult<Vec<String>> {
self.get_list(&["-t", table, "-S"])
}
pub fn list_chains(&self, table: &str) -> IPTResult<Vec<String>> {
let mut list = Vec::new();
let output = String::from_utf8_lossy(&self.run(&["-t", table, "-S"])?.stdout).into_owned();
for item in output.trim().split("\n") {
let fields = item.split(" ").collect::<Vec<&str>>();
if fields.len() > 1 && (fields[0] == "-P" || fields[0] == "-N") {
list.push(fields[1].to_string());
}
}
Ok(list)
}
pub fn new_chain(&self, table: &str, chain: &str) -> IPTResult<bool> {
match self.run(&["-t", table, "-N", chain]) {
Ok(output) => Ok(output.status.success()),
Err(err) => Err(err),
}
}
pub fn flush_chain(&self, table: &str, chain: &str) -> IPTResult<bool> {
match self.run(&["-t", table, "-F", chain]) {
Ok(output) => Ok(output.status.success()),
Err(err) => Err(err),
}
}
pub fn rename_chain(&self, table: &str, old_chain: &str, new_chain: &str) -> IPTResult<bool> {
match self.run(&["-t", table, "-E", old_chain, new_chain]) {
Ok(output) => Ok(output.status.success()),
Err(err) => Err(err),
}
}
pub fn delete_chain(&self, table: &str, chain: &str) -> IPTResult<bool> {
match self.run(&["-t", table, "-X", chain]) {
Ok(output) => Ok(output.status.success()),
Err(err) => Err(err),
}
}
pub fn flush_table(&self, table: &str) -> IPTResult<bool> {
match self.run(&["-t", table, "-F"]) {
Ok(output) => Ok(output.status.success()),
Err(err) => Err(err),
}
}
fn exists_old_version(&self, table: &str, chain: &str, rule: &str) -> IPTResult<bool> {
match self.run(&["-t", table, "-S"]) {
Ok(output) => Ok(String::from_utf8_lossy(&output.stdout).into_owned().contains(&format!("-A {} {}", chain, rule))),
Err(err) => Err(err),
}
}
fn get_list<S: AsRef<OsStr>>(&self, args: &[S]) -> IPTResult<Vec<String>> {
let mut list = Vec::new();
let output = String::from_utf8_lossy(&self.run(args)?.stdout).into_owned();
for item in output.trim().split("\n") {
list.push(item.to_string())
}
Ok(list)
}
fn run<S: AsRef<OsStr>>(&self, args: &[S]) -> IPTResult<Output> {
let mut file_lock = None;
let mut output_cmd = Command::new(self.cmd);
let output;
if self.has_wait {
output = output_cmd.args(args).arg("--wait").output()?;
} else {
file_lock = Some(File::create("/var/run/xtables_old.lock")?);
let mut need_retry = true;
while need_retry {
match flock(file_lock.as_ref().unwrap().as_raw_fd(), FlockArg::LockExclusiveNonblock) {
Ok(_) => need_retry = false,
Err(e) => if e.errno() == nix::errno::EAGAIN {
need_retry = true;
} else {
return Err(IPTError::Nix(e));
},
}
}
output = output_cmd.args(args).output()?;
}
if !self.has_wait {
match file_lock {
Some(f) => drop(f),
None => (),
};
}
Ok(output)
}
}