use rustables::{Batch, FinalizedBatch, Chain, Hook, Match, MatchError, Policy, Rule, Protocol, ProtoFamily, Table, MsgType, expr::LogGroup};
use ipnetwork::IpNetwork;
use std::ffi::{CString, NulError};
use std::str::Utf8Error;
use std::rc::Rc;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Unable to open netlink socket to netfilter")]
NetlinkOpenError(#[source] std::io::Error),
#[error("Firewall is already started")]
AlreadyDone,
#[error("Error converting from a C String")]
NulError(#[from] NulError),
#[error("Error creating match")]
MatchError(#[from] MatchError),
#[error("Error converting to utf-8 string")]
Utf8Error(#[from] Utf8Error),
#[error("Error applying batch")]
BatchError(#[from] std::io::Error),
}
const TABLE_NAME: &str = "main-table";
fn main() -> Result<(), Error> {
let mut fw = Firewall::new()?;
fw.start()?;
Ok(())
}
pub struct Firewall {
table: Rc
}
impl Firewall {
pub fn new() -> Result {
let table = Rc::new(Table::new(
&CString::new(TABLE_NAME)?,
ProtoFamily::Inet
));
Ok(Firewall { table })
}
/// Attempt to use the batch from the struct holding the table.
pub fn allow_port(&mut self, port: &str, protocol: &Protocol, chain: Rc, batch: &mut Batch) -> Result<(), Error> {
let rule = Rule::new(chain).dport(port, protocol)?.accept().add_to_batch(batch);
batch.add(&rule, MsgType::Add);
Ok(())
}
/// Apply the current realm's batch.
pub fn start(&mut self) -> Result<(), Error> {
let mut batch = Batch::new();
batch.add(&self.table, MsgType::Add);
let local_net = IpNetwork::new([192, 168, 1, 0].into(), 24).unwrap();
let mut inbound = Chain::new(&CString::new("in")?, Rc::clone(&self.table));
inbound.set_hook(Hook::In, 0);
inbound.set_policy(Policy::Drop);
let inbound = Rc::new(inbound);
batch.add(&inbound, MsgType::Add);
let mut outbound = Chain::new(&CString::new("out")?, Rc::clone(&self.table));
outbound.set_hook(Hook::Out, 0);
outbound.set_policy(Policy::Accept);
batch.add(&outbound, MsgType::Add);
let mut forward = Chain::new(&CString::new("forward")?, Rc::clone(&self.table));
forward.set_hook(Hook::Forward, 0);
forward.set_policy(Policy::Accept);
batch.add(&forward, MsgType::Add);
Rule::new(Rc::clone(&inbound))
.established()
.accept()
.add_to_batch(&mut batch);
Rule::new(Rc::clone(&inbound))
.iface("lo")?
.accept()
.add_to_batch(&mut batch);
self.allow_port("22", &Protocol::TCP, Rc::clone(&inbound), &mut batch)?;
Rule::new(Rc::clone(&inbound))
.dport("80", &Protocol::TCP)?
.snetwork(local_net)
.accept()
.add_to_batch(&mut batch);
Rule::new(Rc::clone(&inbound))
.icmp()
.accept()
.add_to_batch(&mut batch);
Rule::new(Rc::clone(&inbound))
.igmp()
.drop()
.add_to_batch(&mut batch);
//use nftnl::expr::LogPrefix;
//let prefix = "REALM=".to_string() + &self.realm_def.name;
Rule::new(Rc::clone(&inbound))
.log(Some(LogGroup(1)), None)
//.log( Some(LogGroup(1)), Some(LogPrefix::new(&prefix)
// .expect("Could not convert log prefix string to CString")))
.add_to_batch(&mut batch);
let finalized_batch = batch.finalize().unwrap();
apply_nftnl_batch(finalized_batch)?;
println!("ruleset applied");
Ok(())
}
/// If there are any rulesets applied, remove them.
pub fn stop(&mut self) -> Result<(), Error> {
let table = Table::new(&CString::new(TABLE_NAME)?, ProtoFamily::Inet);
let mut batch = Batch::new();
batch.add(&table, MsgType::Add);
batch.add(&table, MsgType::Del);
Ok(())
}
}
fn apply_nftnl_batch(mut nftnl_finalized_batch: FinalizedBatch)
-> Result<(), std::io::Error> {
let socket = mnl::Socket::new(mnl::Bus::Netfilter)?;
socket.send_all(&mut nftnl_finalized_batch)?;
// Parse results from the socket :
let portid = socket.portid();
let mut buffer = vec![0; rustables::nft_nlmsg_maxsize() as usize];
// Unclear variable :
let seq = 0;
loop {
let length = socket.recv(&mut buffer[..])?;
if length == 0 {
eprintln!("batch socket returned 0");
break;
}
match mnl::cb_run(&buffer[..length], seq, portid)? {
mnl::CbResult::Stop => {
break;
}
mnl::CbResult::Ok => (),
}
}
Ok(())
}