From 1a9199011d6f879ec07051db2164713e17e81b2f Mon Sep 17 00:00:00 2001 From: darkgallium Date: Fri, 22 May 2020 01:12:53 +0200 Subject: first basic support for adding nft rules --- src/fw.rs | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 src/fw.rs (limited to 'src') diff --git a/src/fw.rs b/src/fw.rs new file mode 100644 index 0000000..aabc420 --- /dev/null +++ b/src/fw.rs @@ -0,0 +1,101 @@ +use nftnl::{nft_expr, Batch, Chain, FinalizedBatch, ProtoFamily, Rule, Table}; +use std::{ + ffi::{self, CString}, + io, +}; +use mnl; +use libc; + +const TABLE_NAME: &str = "diplonat"; +const OUT_CHAIN_NAME: &str = "out"; +const IN_CHAIN_NAME: &str = "in"; + +#[derive(Debug)] +struct Error(String); + +impl From for Error { + fn from(error: io::Error) -> Self { + Error(error.to_string()) + } +} + +impl From for Error { + fn from(error: ffi::NulError) -> Self { + Error(error.to_string()) + } +} + +fn send_and_process(batch: &FinalizedBatch) -> Result<(), Error> { + let socket = mnl::Socket::new(mnl::Bus::Netfilter)?; + socket.send_all(batch)?; + + let portid = socket.portid(); + let mut buffer = vec![0; nftnl::nft_nlmsg_maxsize() as usize]; + + while let Some(message) = socket_recv(&socket, &mut buffer[..])? { + match mnl::cb_run(message, 2, portid)? { + mnl::CbResult::Stop => { + break; + } + mnl::CbResult::Ok => (), + } + } + Ok(()) +} + +fn socket_recv<'a>(socket: &mnl::Socket, buf: &'a mut [u8]) -> Result, Error> { + let ret = socket.recv(buf)?; + if ret > 0 { + Ok(Some(&buf[..ret])) + } else { + Ok(None) + } +} + +fn add_port_allowed(port: u16) -> Result<(), Error> { + let mut batch = Batch::new(); + + // TODO: at the moment, I haven't found a way to properly separate setup part (create table and + // chains) and the add rule part because the add rule part needs a reference on the chains. + // apparently creating a table and chains that already exist seems to do nothing so it works + // doing the following. To be done properly though + + let table = Table::new(&CString::new(TABLE_NAME).unwrap(), ProtoFamily::Inet); + + batch.add(&table, nftnl::MsgType::Add); + + let mut out_chain = Chain::new(&CString::new(OUT_CHAIN_NAME).unwrap(), &table); + let mut in_chain = Chain::new(&CString::new(IN_CHAIN_NAME).unwrap(), &table); + + out_chain.set_hook(nftnl::Hook::Out, 0); + in_chain.set_hook(nftnl::Hook::In, 0); + + out_chain.set_policy(nftnl::Policy::Accept); + in_chain.set_policy(nftnl::Policy::Drop); + + batch.add(&out_chain, nftnl::MsgType::Add); + batch.add(&in_chain, nftnl::MsgType::Add); + + let mut _rule = Rule::new(&in_chain); + + _rule.add_expr(&nft_expr!(meta nfproto)); + _rule.add_expr(&nft_expr!(cmp == libc::NFPROTO_IPV4 as u8)); + + _rule.add_expr(&nft_expr!(meta l4proto)); + _rule.add_expr(&nft_expr!(cmp == libc::IPPROTO_TCP as u8)); + + _rule.add_expr(&nftnl::expr::Payload::Transport( + nftnl::expr::TransportHeaderField::Tcp(nftnl::expr::TcpHeaderField::Dport), + )); + _rule.add_expr(&nft_expr!(cmp == port.to_be())); + + _rule.add_expr(&nft_expr!(verdict accept)); + + batch.add(&_rule, nftnl::MsgType::Add); + + let finalized_batch = batch.finalize(); + send_and_process(&finalized_batch)?; + + Ok(()) + +} -- cgit v1.2.3 From 2dbf9da005f2b86da9c6861b8f30725033b9b5f2 Mon Sep 17 00:00:00 2001 From: darkgallium Date: Fri, 22 May 2020 16:02:31 +0200 Subject: changing backend to use the cleaner ipt bindings updating dependencies minor fixes --- src/fw.rs | 131 ++++++++++++++++++++++-------------------------------------- src/main.rs | 7 +++- 2 files changed, 53 insertions(+), 85 deletions(-) (limited to 'src') diff --git a/src/fw.rs b/src/fw.rs index aabc420..07282af 100644 --- a/src/fw.rs +++ b/src/fw.rs @@ -1,101 +1,64 @@ -use nftnl::{nft_expr, Batch, Chain, FinalizedBatch, ProtoFamily, Rule, Table}; -use std::{ - ffi::{self, CString}, - io, -}; -use mnl; -use libc; +use iptables; +use regex::Regex; +use std::collections::HashSet; + +#[derive(PartialEq,Eq,Debug,Hash)] +pub struct Port { + proto: String, + number: u16, +} -const TABLE_NAME: &str = "diplonat"; -const OUT_CHAIN_NAME: &str = "out"; -const IN_CHAIN_NAME: &str = "in"; +pub fn setup(ipt: &iptables::IPTables) { + ipt.new_chain("filter", "DIPLONAT").unwrap(); + ipt.insert("filter", "INPUT", "-j DIPLONAT", 1).unwrap(); +} -#[derive(Debug)] -struct Error(String); +pub fn open_ports(ipt: &iptables::IPTables, ports: Vec) { -impl From for Error { - fn from(error: io::Error) -> Self { - Error(error.to_string()) + for p in ports { + ipt.append("filter", "DIPLONAT", &format!("-p {} --dport {} -j ACCEPT", p.proto, p.number)).unwrap(); } } -impl From for Error { - fn from(error: ffi::NulError) -> Self { - Error(error.to_string()) - } -} +pub fn get_opened_ports(ipt: &iptables::IPTables) -> HashSet { + let mut opened_ports: HashSet = HashSet::new(); -fn send_and_process(batch: &FinalizedBatch) -> Result<(), Error> { - let socket = mnl::Socket::new(mnl::Bus::Netfilter)?; - socket.send_all(batch)?; + let list = ipt.list("filter", "DIPLONAT").unwrap(); + let re = Regex::new(r"\-A.*? \-p (\w+).*\-\-dport (\d+).*?\-j ACCEPT").unwrap(); + for i in list { + let caps = re.captures(&i); + match caps { + Some(c) => { + let raw_proto = c.get(1).unwrap(); + let raw_port = c.get(2).unwrap(); - let portid = socket.portid(); - let mut buffer = vec![0; nftnl::nft_nlmsg_maxsize() as usize]; + let proto = String::from(raw_proto.as_str()); + let number = String::from(raw_port.as_str()).parse::().unwrap(); - while let Some(message) = socket_recv(&socket, &mut buffer[..])? { - match mnl::cb_run(message, 2, portid)? { - mnl::CbResult::Stop => { - break; - } - mnl::CbResult::Ok => (), + opened_ports.insert( Port { proto, number } ); + }, + _ => {} } } - Ok(()) -} -fn socket_recv<'a>(socket: &mnl::Socket, buf: &'a mut [u8]) -> Result, Error> { - let ret = socket.recv(buf)?; - if ret > 0 { - Ok(Some(&buf[..ret])) - } else { - Ok(None) - } + opened_ports } -fn add_port_allowed(port: u16) -> Result<(), Error> { - let mut batch = Batch::new(); - - // TODO: at the moment, I haven't found a way to properly separate setup part (create table and - // chains) and the add rule part because the add rule part needs a reference on the chains. - // apparently creating a table and chains that already exist seems to do nothing so it works - // doing the following. To be done properly though - - let table = Table::new(&CString::new(TABLE_NAME).unwrap(), ProtoFamily::Inet); - - batch.add(&table, nftnl::MsgType::Add); - - let mut out_chain = Chain::new(&CString::new(OUT_CHAIN_NAME).unwrap(), &table); - let mut in_chain = Chain::new(&CString::new(IN_CHAIN_NAME).unwrap(), &table); - - out_chain.set_hook(nftnl::Hook::Out, 0); - in_chain.set_hook(nftnl::Hook::In, 0); - - out_chain.set_policy(nftnl::Policy::Accept); - in_chain.set_policy(nftnl::Policy::Drop); - - batch.add(&out_chain, nftnl::MsgType::Add); - batch.add(&in_chain, nftnl::MsgType::Add); - - let mut _rule = Rule::new(&in_chain); - - _rule.add_expr(&nft_expr!(meta nfproto)); - _rule.add_expr(&nft_expr!(cmp == libc::NFPROTO_IPV4 as u8)); - - _rule.add_expr(&nft_expr!(meta l4proto)); - _rule.add_expr(&nft_expr!(cmp == libc::IPPROTO_TCP as u8)); - - _rule.add_expr(&nftnl::expr::Payload::Transport( - nftnl::expr::TransportHeaderField::Tcp(nftnl::expr::TcpHeaderField::Dport), - )); - _rule.add_expr(&nft_expr!(cmp == port.to_be())); - - _rule.add_expr(&nft_expr!(verdict accept)); +pub fn cleanup(ipt: &iptables::IPTables) { + ipt.flush_chain("filter", "DIPLONAT").unwrap(); + ipt.delete("filter", "INPUT", "-j DIPLONAT").unwrap(); + ipt.delete_chain("filter", "DIPLONAT").unwrap(); +} - batch.add(&_rule, nftnl::MsgType::Add); +/* +fn main() { + let ipt = iptables::new(false).unwrap(); + setup(&ipt); - let finalized_batch = batch.finalize(); - send_and_process(&finalized_batch)?; - - Ok(()) - + let mut test: HashSet = HashSet::new(); + test.insert(Port { proto: String::from("tcp"), number: 443 }); + let a = get_opened_ports(&ipt); + let l = test.difference(&a).collect::>(); + println!("{:?}", l); } +*/ diff --git a/src/main.rs b/src/main.rs index a35916a..bf8248d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,9 @@ mod consul; mod consul_actor; mod igd_actor; mod diplonat; +mod fw; +use iptables; use log::*; use diplonat::Diplonat; @@ -12,7 +14,10 @@ use diplonat::Diplonat; async fn main() { pretty_env_logger::init(); info!("Starting Diplonat"); - + + let ipt = iptables::new(false).unwrap(); + fw::setup(&ipt).expect("iptables setup failed"); + let mut diplo = Diplonat::new().await.expect("Setup failed"); diplo.listen().await.expect("A runtime error occured"); } -- cgit v1.2.3 From a2d25820985b04f15f3c0f38cabfd7130124d943 Mon Sep 17 00:00:00 2001 From: darkgallium Date: Fri, 22 May 2020 18:51:46 +0200 Subject: add better error handling --- src/fw.rs | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/fw.rs b/src/fw.rs index 07282af..7650b3a 100644 --- a/src/fw.rs +++ b/src/fw.rs @@ -1,6 +1,8 @@ use iptables; use regex::Regex; use std::collections::HashSet; +use std::io; + #[derive(PartialEq,Eq,Debug,Hash)] pub struct Port { @@ -8,22 +10,35 @@ pub struct Port { number: u16, } -pub fn setup(ipt: &iptables::IPTables) { - ipt.new_chain("filter", "DIPLONAT").unwrap(); - ipt.insert("filter", "INPUT", "-j DIPLONAT", 1).unwrap(); +#[derive(Debug)] +pub struct FirewallError(String); + +impl From for FirewallError { + fn from(error: iptables::error::IPTError) -> Self { + FirewallError(error.to_string()) + } + } -pub fn open_ports(ipt: &iptables::IPTables, ports: Vec) { +pub fn setup(ipt: &iptables::IPTables) -> Result<(), FirewallError> { + ipt.new_chain("filter", "DIPLONAT")?; + ipt.insert("filter", "INPUT", "-j DIPLONAT", 1)?; + Ok(()) +} + +pub fn open_ports(ipt: &iptables::IPTables, ports: Vec) -> Result<(), FirewallError> { for p in ports { - ipt.append("filter", "DIPLONAT", &format!("-p {} --dport {} -j ACCEPT", p.proto, p.number)).unwrap(); + ipt.append("filter", "DIPLONAT", &format!("-p {} --dport {} -j ACCEPT", p.proto, p.number))?; } + + Ok(()) } -pub fn get_opened_ports(ipt: &iptables::IPTables) -> HashSet { +pub fn get_opened_ports(ipt: &iptables::IPTables) -> Result, FirewallError> { let mut opened_ports: HashSet = HashSet::new(); - let list = ipt.list("filter", "DIPLONAT").unwrap(); + let list = ipt.list("filter", "DIPLONAT")?; let re = Regex::new(r"\-A.*? \-p (\w+).*\-\-dport (\d+).*?\-j ACCEPT").unwrap(); for i in list { let caps = re.captures(&i); @@ -41,13 +56,14 @@ pub fn get_opened_ports(ipt: &iptables::IPTables) -> HashSet { } } - opened_ports + Ok(opened_ports) } -pub fn cleanup(ipt: &iptables::IPTables) { - ipt.flush_chain("filter", "DIPLONAT").unwrap(); - ipt.delete("filter", "INPUT", "-j DIPLONAT").unwrap(); - ipt.delete_chain("filter", "DIPLONAT").unwrap(); +pub fn cleanup(ipt: &iptables::IPTables) -> Result<(), FirewallError> { + ipt.flush_chain("filter", "DIPLONAT")?; + ipt.delete("filter", "INPUT", "-j DIPLONAT")?; + ipt.delete_chain("filter", "DIPLONAT")?; + Ok(()) } /* -- cgit v1.2.3 From d2ae084fc1be2671c2a301e689c8632576922785 Mon Sep 17 00:00:00 2001 From: darkgallium Date: Sun, 24 May 2020 20:40:49 +0200 Subject: add actor for firewall & massive refactor --- src/consul_actor.rs | 13 +++++---- src/diplonat.rs | 15 ++++++++-- src/fw.rs | 37 +++++++++++++++---------- src/fw_actor.rs | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 4 +-- src/messages.rs | 12 ++++---- 6 files changed, 129 insertions(+), 32 deletions(-) create mode 100644 src/fw_actor.rs (limited to 'src') diff --git a/src/consul_actor.rs b/src/consul_actor.rs index 1cbb1b8..ba5d704 100644 --- a/src/consul_actor.rs +++ b/src/consul_actor.rs @@ -8,11 +8,12 @@ use serde::{Serialize, Deserialize}; use serde_lexpr::{from_str,error}; use crate::messages; use crate::consul; +use std::collections::HashSet; #[derive(Serialize, Deserialize, Debug)] pub enum DiplonatParameter { - tcp_port(Vec), - udp_port(Vec) + tcp_port(HashSet), + udp_port(HashSet) } #[derive(Serialize, Deserialize, Debug)] @@ -53,8 +54,8 @@ fn to_parameters(catalog: &consul::CatalogNode) -> Vec { fn to_open_ports(params: &Vec) -> messages::PublicExposedPorts { let mut op = messages::PublicExposedPorts { - tcp_ports: Vec::new(), - udp_ports: Vec::new() + tcp_ports: HashSet::new(), + udp_ports: HashSet::new() }; for conf in params { @@ -73,8 +74,8 @@ fn to_open_ports(params: &Vec) -> messages::PublicExposedPorts { impl ConsulActor { pub fn new(url: &str, node: &str) -> Self { let (tx, rx) = watch::channel(messages::PublicExposedPorts{ - tcp_ports: Vec::new(), - udp_ports: Vec::new() + tcp_ports: HashSet::new(), + udp_ports: HashSet::new() }); return Self { diff --git a/src/diplonat.rs b/src/diplonat.rs index 1be00f7..7b7bbb8 100644 --- a/src/diplonat.rs +++ b/src/diplonat.rs @@ -4,10 +4,12 @@ use tokio::try_join; use crate::consul_actor::ConsulActor; use crate::igd_actor::IgdActor; use crate::environment::Environment; +use crate::fw_actor::FirewallActor; pub struct Diplonat { consul: ConsulActor, - igd: IgdActor + igd: IgdActor, + firewall: FirewallActor } impl Diplonat { @@ -21,9 +23,15 @@ impl Diplonat { &ca.rx_open_ports ).await?; + let fw = FirewallActor::new( + env.refresh_time, + &ca.rx_open_ports + ).await?; + let ctx = Self { consul: ca, - igd: ia + igd: ia, + firewall: fw }; return Ok(ctx); @@ -32,7 +40,8 @@ impl Diplonat { pub async fn listen(&mut self) -> Result<()> { try_join!( self.consul.listen(), - self.igd.listen() + self.igd.listen(), + self.firewall.listen() )?; return Ok(()); diff --git a/src/fw.rs b/src/fw.rs index 7650b3a..955425a 100644 --- a/src/fw.rs +++ b/src/fw.rs @@ -2,13 +2,7 @@ use iptables; use regex::Regex; use std::collections::HashSet; use std::io; - - -#[derive(PartialEq,Eq,Debug,Hash)] -pub struct Port { - proto: String, - number: u16, -} +use crate::messages; #[derive(Debug)] pub struct FirewallError(String); @@ -17,26 +11,34 @@ impl From for FirewallError { fn from(error: iptables::error::IPTError) -> Self { FirewallError(error.to_string()) } - } pub fn setup(ipt: &iptables::IPTables) -> Result<(), FirewallError> { + ipt.new_chain("filter", "DIPLONAT")?; ipt.insert("filter", "INPUT", "-j DIPLONAT", 1)?; + Ok(()) } -pub fn open_ports(ipt: &iptables::IPTables, ports: Vec) -> Result<(), FirewallError> { +pub fn open_ports(ipt: &iptables::IPTables, ports: messages::PublicExposedPorts) -> Result<(), FirewallError> { + + for p in ports.tcp_ports { + ipt.append("filter", "DIPLONAT", &format!("-p tcp --dport {} -j ACCEPT", p))?; + } - for p in ports { - ipt.append("filter", "DIPLONAT", &format!("-p {} --dport {} -j ACCEPT", p.proto, p.number))?; + for p in ports.udp_ports { + ipt.append("filter", "DIPLONAT", &format!("-p udp --dport {} -j ACCEPT", p))?; } Ok(()) } -pub fn get_opened_ports(ipt: &iptables::IPTables) -> Result, FirewallError> { - let mut opened_ports: HashSet = HashSet::new(); +pub fn get_opened_ports(ipt: &iptables::IPTables) -> Result { + let mut ports = messages::PublicExposedPorts { + tcp_ports: HashSet::new(), + udp_ports: HashSet::new() + }; let list = ipt.list("filter", "DIPLONAT")?; let re = Regex::new(r"\-A.*? \-p (\w+).*\-\-dport (\d+).*?\-j ACCEPT").unwrap(); @@ -50,13 +52,18 @@ pub fn get_opened_ports(ipt: &iptables::IPTables) -> Result, Firew let proto = String::from(raw_proto.as_str()); let number = String::from(raw_port.as_str()).parse::().unwrap(); - opened_ports.insert( Port { proto, number } ); + if proto == "tcp" { + ports.tcp_ports.insert(number); + } else { + ports.udp_ports.insert(number); + } + }, _ => {} } } - Ok(opened_ports) + Ok(ports) } pub fn cleanup(ipt: &iptables::IPTables) -> Result<(), FirewallError> { diff --git a/src/fw_actor.rs b/src/fw_actor.rs new file mode 100644 index 0000000..9bc6610 --- /dev/null +++ b/src/fw_actor.rs @@ -0,0 +1,80 @@ +use igd::aio::*; +use igd::PortMappingProtocol; +use std::net::SocketAddrV4; +use log::*; +use anyhow::{Result, Context}; +use tokio::{ + select, + sync::watch, + time::{ + self, + Duration +}}; + +use iptables; +use crate::messages; +use crate::fw; +use std::collections::HashSet; + +pub struct FirewallActor { + ipt: iptables::IPTables, + rx_ports: watch::Receiver, + last_ports: messages::PublicExposedPorts, + refresh: Duration +} + +impl FirewallActor { + pub async fn new(_refresh: Duration, rxp: &watch::Receiver) -> Result { + + + let ctx = Self { + ipt: iptables::new(false).unwrap(), + rx_ports: rxp.clone(), + last_ports: messages::PublicExposedPorts::new(), + refresh: _refresh, + }; + + fw::setup(&ctx.ipt).expect("iptables setup failed"); + + return Ok(ctx); + } + + pub async fn listen(&mut self) -> Result<()> { + let mut interval = time::interval(self.refresh); + loop { + // 1. Wait for an event + let new_ports = select! { + Some(ports) = self.rx_ports.recv() => Some(ports), + _ = interval.tick() => None, + else => return Ok(()) // Sender dropped, terminate loop. + }; + + // 2. Update last ports if needed + if let Some(p) = new_ports { self.last_ports = p; } + + // 3. Update firewall rules + match self.do_fw_update().await { + Ok(()) => debug!("Successfully updated firewall rules"), + Err(e) => error!("An error occured while updating firewall rules. {}", e), + } + } + } + + pub async fn do_fw_update(&self) -> Result<()> { + + let curr_opened_ports = fw::get_opened_ports(&self.ipt).unwrap(); + + let diff_tcp = self.last_ports.tcp_ports.difference(&curr_opened_ports.tcp_ports).copied().collect::>(); + let diff_udp = self.last_ports.udp_ports.difference(&curr_opened_ports.udp_ports).copied().collect::>(); + + let ports_to_open = messages::PublicExposedPorts { + tcp_ports: diff_tcp, + udp_ports: diff_udp + }; + + fw::open_ports(&self.ipt, ports_to_open).unwrap(); + + return Ok(()); + } + +} diff --git a/src/main.rs b/src/main.rs index bf8248d..e845017 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ mod consul_actor; mod igd_actor; mod diplonat; mod fw; +mod fw_actor; use iptables; use log::*; @@ -15,9 +16,6 @@ async fn main() { pretty_env_logger::init(); info!("Starting Diplonat"); - let ipt = iptables::new(false).unwrap(); - fw::setup(&ipt).expect("iptables setup failed"); - let mut diplo = Diplonat::new().await.expect("Setup failed"); diplo.listen().await.expect("A runtime error occured"); } diff --git a/src/messages.rs b/src/messages.rs index 31ed48f..09a7c14 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -1,14 +1,16 @@ -#[derive(Debug, Clone)] +use std::collections::HashSet; + +#[derive(Debug, Clone, PartialEq, Eq)] pub struct PublicExposedPorts { - pub tcp_ports: Vec, - pub udp_ports: Vec + pub tcp_ports: HashSet, + pub udp_ports: HashSet } impl PublicExposedPorts { pub fn new() -> Self { return Self { - tcp_ports: Vec::new(), - udp_ports: Vec::new() + tcp_ports: HashSet::new(), + udp_ports: HashSet::new() } } } -- cgit v1.2.3 From 6fe86469ee9b74a8cf628ff21513a8f298a6b4b6 Mon Sep 17 00:00:00 2001 From: darkgallium Date: Sun, 28 Jun 2020 17:01:12 +0200 Subject: update README & Dockerfile --- src/fw.rs | 12 ------------ src/fw_actor.rs | 1 + 2 files changed, 1 insertion(+), 12 deletions(-) (limited to 'src') diff --git a/src/fw.rs b/src/fw.rs index 955425a..42ce73a 100644 --- a/src/fw.rs +++ b/src/fw.rs @@ -73,15 +73,3 @@ pub fn cleanup(ipt: &iptables::IPTables) -> Result<(), FirewallError> { Ok(()) } -/* -fn main() { - let ipt = iptables::new(false).unwrap(); - setup(&ipt); - - let mut test: HashSet = HashSet::new(); - test.insert(Port { proto: String::from("tcp"), number: 443 }); - let a = get_opened_ports(&ipt); - let l = test.difference(&a).collect::>(); - println!("{:?}", l); -} -*/ diff --git a/src/fw_actor.rs b/src/fw_actor.rs index 9bc6610..0ef08eb 100644 --- a/src/fw_actor.rs +++ b/src/fw_actor.rs @@ -78,3 +78,4 @@ impl FirewallActor { } } + -- cgit v1.2.3 From a59ed3812151410c125f62f60b00aad673fd4c66 Mon Sep 17 00:00:00 2001 From: darkgallium Date: Sun, 28 Jun 2020 18:22:23 +0200 Subject: ensure chain jump is added only once --- src/diplonat.rs | 1 - src/fw.rs | 11 ++++++----- src/fw_actor.rs | 12 +++--------- src/main.rs | 1 - 4 files changed, 9 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/diplonat.rs b/src/diplonat.rs index 7b7bbb8..798b779 100644 --- a/src/diplonat.rs +++ b/src/diplonat.rs @@ -1,5 +1,4 @@ use anyhow::Result; -use log::*; use tokio::try_join; use crate::consul_actor::ConsulActor; use crate::igd_actor::IgdActor; diff --git a/src/fw.rs b/src/fw.rs index 42ce73a..8ee3e6b 100644 --- a/src/fw.rs +++ b/src/fw.rs @@ -1,7 +1,6 @@ use iptables; use regex::Regex; use std::collections::HashSet; -use std::io; use crate::messages; #[derive(Debug)] @@ -14,15 +13,17 @@ impl From for FirewallError { } pub fn setup(ipt: &iptables::IPTables) -> Result<(), FirewallError> { - - ipt.new_chain("filter", "DIPLONAT")?; - ipt.insert("filter", "INPUT", "-j DIPLONAT", 1)?; + + if !ipt.chain_exists("filter", "DIPLONAT")? { + ipt.new_chain("filter", "DIPLONAT")?; + } + + ipt.insert_unique("filter", "INPUT", "-j DIPLONAT", 1)?; Ok(()) } pub fn open_ports(ipt: &iptables::IPTables, ports: messages::PublicExposedPorts) -> Result<(), FirewallError> { - for p in ports.tcp_ports { ipt.append("filter", "DIPLONAT", &format!("-p tcp --dport {} -j ACCEPT", p))?; } diff --git a/src/fw_actor.rs b/src/fw_actor.rs index 0ef08eb..523bdaa 100644 --- a/src/fw_actor.rs +++ b/src/fw_actor.rs @@ -1,8 +1,4 @@ -use igd::aio::*; -use igd::PortMappingProtocol; -use std::net::SocketAddrV4; -use log::*; -use anyhow::{Result, Context}; +use anyhow::Result; use tokio::{ select, sync::watch, @@ -10,6 +6,7 @@ use tokio::{ self, Duration }}; +use log::*; use iptables; use crate::messages; @@ -17,7 +14,7 @@ use crate::fw; use std::collections::HashSet; pub struct FirewallActor { - ipt: iptables::IPTables, + pub ipt: iptables::IPTables, rx_ports: watch::Receiver, last_ports: messages::PublicExposedPorts, refresh: Duration @@ -25,8 +22,6 @@ pub struct FirewallActor { impl FirewallActor { pub async fn new(_refresh: Duration, rxp: &watch::Receiver) -> Result { - - let ctx = Self { ipt: iptables::new(false).unwrap(), rx_ports: rxp.clone(), @@ -61,7 +56,6 @@ impl FirewallActor { } pub async fn do_fw_update(&self) -> Result<()> { - let curr_opened_ports = fw::get_opened_ports(&self.ipt).unwrap(); let diff_tcp = self.last_ports.tcp_ports.difference(&curr_opened_ports.tcp_ports).copied().collect::>(); diff --git a/src/main.rs b/src/main.rs index e845017..ca36c26 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,6 @@ mod diplonat; mod fw; mod fw_actor; -use iptables; use log::*; use diplonat::Diplonat; -- cgit v1.2.3 From 4f4b6b048d53f3c4c9cc2437ba6bc6a9e70cb8c7 Mon Sep 17 00:00:00 2001 From: darkgallium Date: Sat, 4 Jul 2020 16:59:41 +0200 Subject: corrections after review --- src/fw.rs | 67 +++++++++++++++++++++++++++++++-------------------------- src/fw_actor.rs | 8 +++---- 2 files changed, 40 insertions(+), 35 deletions(-) (limited to 'src') diff --git a/src/fw.rs b/src/fw.rs index 8ee3e6b..bc4d119 100644 --- a/src/fw.rs +++ b/src/fw.rs @@ -2,61 +2,58 @@ use iptables; use regex::Regex; use std::collections::HashSet; use crate::messages; +use anyhow::{Result,Context}; +use log::*; -#[derive(Debug)] -pub struct FirewallError(String); +pub fn setup(ipt: &iptables::IPTables) -> Result<()> { -impl From for FirewallError { - fn from(error: iptables::error::IPTError) -> Self { - FirewallError(error.to_string()) - } -} - -pub fn setup(ipt: &iptables::IPTables) -> Result<(), FirewallError> { - - if !ipt.chain_exists("filter", "DIPLONAT")? { - ipt.new_chain("filter", "DIPLONAT")?; - } - - ipt.insert_unique("filter", "INPUT", "-j DIPLONAT", 1)?; + // ensure we start from a clean state without any rule already set + cleanup(ipt)?; + + ipt.new_chain("filter", "DIPLONAT").context("Failed to create new chain")?; + ipt.insert_unique("filter", "INPUT", "-j DIPLONAT", 1).context("Failed to insert jump rule")?; Ok(()) } -pub fn open_ports(ipt: &iptables::IPTables, ports: messages::PublicExposedPorts) -> Result<(), FirewallError> { +pub fn open_ports(ipt: &iptables::IPTables, ports: messages::PublicExposedPorts) -> Result<()> { for p in ports.tcp_ports { - ipt.append("filter", "DIPLONAT", &format!("-p tcp --dport {} -j ACCEPT", p))?; + ipt.append("filter", "DIPLONAT", &format!("-p tcp --dport {} -j ACCEPT", p)).context("Failed to insert port rule")?; } for p in ports.udp_ports { - ipt.append("filter", "DIPLONAT", &format!("-p udp --dport {} -j ACCEPT", p))?; + ipt.append("filter", "DIPLONAT", &format!("-p udp --dport {} -j ACCEPT", p)).context("Failed to insert port rule")?; } Ok(()) } -pub fn get_opened_ports(ipt: &iptables::IPTables) -> Result { +pub fn get_opened_ports(ipt: &iptables::IPTables) -> Result { let mut ports = messages::PublicExposedPorts { tcp_ports: HashSet::new(), udp_ports: HashSet::new() }; let list = ipt.list("filter", "DIPLONAT")?; - let re = Regex::new(r"\-A.*? \-p (\w+).*\-\-dport (\d+).*?\-j ACCEPT").unwrap(); + let re = Regex::new(r"\-A.*? \-p (\w+).*\-\-dport (\d+).*?\-j ACCEPT").context("Regex matching open ports encountered an unexpected rule")?; for i in list { let caps = re.captures(&i); match caps { Some(c) => { - let raw_proto = c.get(1).unwrap(); - let raw_port = c.get(2).unwrap(); + + if let (Some(raw_proto), Some(raw_port)) = (c.get(1), c.get(2)) { + + let proto = String::from(raw_proto.as_str()); + let number = String::from(raw_port.as_str()).parse::()?; - let proto = String::from(raw_proto.as_str()); - let number = String::from(raw_port.as_str()).parse::().unwrap(); + if proto == "tcp" { + ports.tcp_ports.insert(number); + } else { + ports.udp_ports.insert(number); + } - if proto == "tcp" { - ports.tcp_ports.insert(number); } else { - ports.udp_ports.insert(number); + error!("Unexpected rule found in DIPLONAT chain") } }, @@ -67,10 +64,18 @@ pub fn get_opened_ports(ipt: &iptables::IPTables) -> Result Result<(), FirewallError> { - ipt.flush_chain("filter", "DIPLONAT")?; - ipt.delete("filter", "INPUT", "-j DIPLONAT")?; - ipt.delete_chain("filter", "DIPLONAT")?; +pub fn cleanup(ipt: &iptables::IPTables) -> Result<()> { + + if ipt.chain_exists("filter", "DIPLONAT")? { + ipt.flush_chain("filter", "DIPLONAT").context("Failed to flush the DIPLONAT chain")?; + + if ipt.exists("filter", "INPUT", "-j DIPLONAT")? { + ipt.delete("filter", "INPUT", "-j DIPLONAT").context("Failed to delete jump rule")?; + } + + ipt.delete_chain("filter", "DIPLONAT").context("Failed to delete chain")?; + } + Ok(()) } diff --git a/src/fw_actor.rs b/src/fw_actor.rs index 523bdaa..b5e4c7e 100644 --- a/src/fw_actor.rs +++ b/src/fw_actor.rs @@ -23,13 +23,13 @@ pub struct FirewallActor { impl FirewallActor { pub async fn new(_refresh: Duration, rxp: &watch::Receiver) -> Result { let ctx = Self { - ipt: iptables::new(false).unwrap(), + ipt: iptables::new(false)?, rx_ports: rxp.clone(), last_ports: messages::PublicExposedPorts::new(), refresh: _refresh, }; - fw::setup(&ctx.ipt).expect("iptables setup failed"); + fw::setup(&ctx.ipt)?; return Ok(ctx); } @@ -56,7 +56,7 @@ impl FirewallActor { } pub async fn do_fw_update(&self) -> Result<()> { - let curr_opened_ports = fw::get_opened_ports(&self.ipt).unwrap(); + let curr_opened_ports = fw::get_opened_ports(&self.ipt)?; let diff_tcp = self.last_ports.tcp_ports.difference(&curr_opened_ports.tcp_ports).copied().collect::>(); let diff_udp = self.last_ports.udp_ports.difference(&curr_opened_ports.udp_ports).copied().collect::>(); @@ -66,7 +66,7 @@ impl FirewallActor { udp_ports: diff_udp }; - fw::open_ports(&self.ipt, ports_to_open).unwrap(); + fw::open_ports(&self.ipt, ports_to_open)?; return Ok(()); } -- cgit v1.2.3