From eed7af23d87e4b4e933882ffb8231b7ca41af75b Mon Sep 17 00:00:00 2001 From: Paul Lecuq Date: Sat, 28 Oct 2023 16:04:25 +0200 Subject: [PATCH] feat: ipv6 + misc fix * ipv6 support * misc fixes on functions * added macro for ipevent --- src/config.rs | 30 +++++++++- src/fw.rs | 139 ++++++++++++++++++++++++++++++------------- src/ip.rs | 83 +++++++++++--------------- src/ipblc.rs | 27 +++------ src/regexps/ipv6.txt | 2 +- src/webservice.rs | 2 + 6 files changed, 171 insertions(+), 112 deletions(-) diff --git a/src/config.rs b/src/config.rs index cf6fccd..1a76101 100644 --- a/src/config.rs +++ b/src/config.rs @@ -443,6 +443,26 @@ impl Config { Ok(()) } + pub async fn _get_last(server: &String) -> Result, ReqError> { + let resp = httpclient() + .get(format!("{server}/ips/last")) + .query(&[("interval", "3 hours")]) + .send() + .await; + + let req = match resp { + Ok(re) => re, + Err(err) => return Err(err), + }; + + let data: Vec = match req.json::>().await { + Ok(res) => res, + Err(err) => return Err(err), + }; + + Ok(data) + } + pub fn build_trustnets(&self) -> Vec { let mut trustnets: Vec = vec![]; for trustnet in &self.trustnets { @@ -462,6 +482,7 @@ impl Config { mode: String::from("ws"), hostname: gethostname(true), ipdata: IpData { + t: 4, ip: "".to_string(), src: "".to_string(), date: "".to_string(), @@ -551,6 +572,7 @@ mod test { mode: String::from("ws"), hostname: String::from("localhost"), ipdata: IpData { + t: 4, ip: "1.1.1.1".to_string(), hostname: "test1".to_string(), date: now.to_rfc3339().to_string(), @@ -566,6 +588,7 @@ mod test { mode: String::from("ws"), hostname: String::from("localhost"), ipdata: IpData { + t: 4, ip: "1.1.1.2".to_string(), hostname: "test2".to_string(), date: now.to_rfc3339().to_string(), @@ -580,6 +603,7 @@ mod test { mode: String::from("ws"), hostname: String::from("localhost"), ipdata: IpData { + t: 4, ip: "1.1.1.3".to_string(), hostname: "testgood".to_string(), date: now.to_rfc3339().to_string(), @@ -593,6 +617,7 @@ mod test { mode: String::from("ws"), hostname: String::from("localhost"), ipdata: IpData { + t: 4, ip: "1.1.1.4".to_string(), hostname: "testgood".to_string(), date: now.to_rfc3339().to_string(), @@ -606,6 +631,7 @@ mod test { mode: String::from("ws"), hostname: String::from("localhost"), ipdata: IpData { + t: 4, ip: "1.1.1.4".to_string(), hostname: "testgood".to_string(), date: now.to_rfc3339().to_string(), @@ -614,10 +640,10 @@ mod test { }) .await; - let mut ip1 = ctx.blocklist.get_mut(&"1.1.1.1".to_string()).unwrap(); + let ip1 = ctx.blocklist.get_mut(&"1.1.1.1".to_string()).unwrap(); ip1.starttime = DateTime::from(now) - Duration::minutes(61); - let mut ip2 = ctx.blocklist.get_mut(&"1.1.1.2".to_string()).unwrap(); + let ip2 = ctx.blocklist.get_mut(&"1.1.1.2".to_string()).unwrap(); ip2.starttime = DateTime::from(now) - Duration::minutes(62); ctx } diff --git a/src/fw.rs b/src/fw.rs index b08f219..647f3a6 100644 --- a/src/fw.rs +++ b/src/fw.rs @@ -2,13 +2,40 @@ use crate::ip::IpData; use crate::ipblc::PKG_NAME; use nftnl::{nft_expr, Batch, Chain, FinalizedBatch, ProtoFamily, Rule, Table}; -use std::{ffi::CString, io::Error, net::Ipv4Addr}; +use std::{ + ffi::CString, + io::Error, + net::{Ipv4Addr, Ipv6Addr}, +}; -pub fn fwinit() -> (Batch, Table) { +pub fn fwinit(t: isize) -> (Batch, Table) { + let table_name: String; + let table: Table; + match t { + 4 => { + table_name = format!("{PKG_NAME}{t}"); + table = Table::new( + &CString::new(format!("{table_name}")).unwrap(), + ProtoFamily::Ipv4, + ); + } + 6 => { + table_name = format!("{PKG_NAME}{t}"); + table = Table::new( + &CString::new(format!("{table_name}")).unwrap(), + ProtoFamily::Ipv6, + ); + } + _ => { + table_name = format!("{PKG_NAME}4"); + table = Table::new( + &CString::new(format!("{table_name}")).unwrap(), + ProtoFamily::Ipv4, + ); + } + } let mut batch = Batch::new(); - let table = Table::new(&CString::new(PKG_NAME).unwrap(), ProtoFamily::Ipv4); - batch.add(&table, nftnl::MsgType::Add); batch.add(&table, nftnl::MsgType::Del); @@ -21,45 +48,83 @@ pub fn fwblock( ret: &mut Vec, fwlen: &mut usize, ) -> std::result::Result<(), Error> { - // convert chain - let ips_add = convert(ips_add); - let (mut batch, table) = fwinit(); + let (mut batch4, table4) = fwinit(4); + let (mut batch6, table6) = fwinit(6); - // build chain - let mut chain = Chain::new(&CString::new(PKG_NAME).unwrap(), &table); - chain.set_hook(nftnl::Hook::In, 1); - chain.set_policy(nftnl::Policy::Accept); + // build chain for ipv4 + let mut chain4 = Chain::new(&CString::new(PKG_NAME).unwrap(), &table4); + chain4.set_hook(nftnl::Hook::In, 1); + chain4.set_policy(nftnl::Policy::Accept); // add chain - batch.add(&chain, nftnl::MsgType::Add); + batch4.add(&chain4, nftnl::MsgType::Add); - let rule = Rule::new(&chain); - batch.add(&rule, nftnl::MsgType::Del); + batch4.add(&Rule::new(&chain4), nftnl::MsgType::Del); - let mut rule = Rule::new(&chain); - rule.add_expr(&nft_expr!(ct state)); - rule.add_expr(&nft_expr!(bitwise mask 4u32, xor 0u32)); - rule.add_expr(&nft_expr!(cmp != 0u32)); - rule.add_expr(&nft_expr!(counter)); - rule.add_expr(&nft_expr!(verdict accept)); - batch.add(&rule, nftnl::MsgType::Add); + let mut rule4 = Rule::new(&chain4); + rule4.add_expr(&nft_expr!(ct state)); + rule4.add_expr(&nft_expr!(bitwise mask 4u32, xor 0u32)); + rule4.add_expr(&nft_expr!(cmp != 0u32)); + rule4.add_expr(&nft_expr!(counter)); + rule4.add_expr(&nft_expr!(verdict accept)); + batch4.add(&rule4, nftnl::MsgType::Add); + + // build chain for ipv6 + let mut chain6 = Chain::new(&CString::new(PKG_NAME).unwrap(), &table6); + chain6.set_hook(nftnl::Hook::In, 1); + chain6.set_policy(nftnl::Policy::Accept); + + // add chain + batch6.add(&chain6, nftnl::MsgType::Add); + + batch6.add(&Rule::new(&chain6), nftnl::MsgType::Del); + + let mut rule6 = Rule::new(&chain6); + rule6.add_expr(&nft_expr!(ct state)); + rule6.add_expr(&nft_expr!(bitwise mask 4u32, xor 0u32)); + rule6.add_expr(&nft_expr!(cmp != 0u32)); + rule6.add_expr(&nft_expr!(counter)); + rule6.add_expr(&nft_expr!(verdict accept)); + batch6.add(&rule6, nftnl::MsgType::Add); // build and add rules - for ip in ips_add.clone() { - let mut rule = Rule::new(&chain); - rule.add_expr(&nft_expr!(payload ipv4 saddr)); - rule.add_expr(&nft_expr!(cmp == ip)); - rule.add_expr(&nft_expr!(ct state)); - rule.add_expr(&nft_expr!(bitwise mask 10u32, xor 0u32)); - rule.add_expr(&nft_expr!(cmp != 0u32)); - rule.add_expr(&nft_expr!(counter)); - rule.add_expr(&nft_expr!(verdict drop)); - batch.add(&rule, nftnl::MsgType::Add); + for ipdata in ips_add.clone() { + match ipdata.t { + 4 => { + let ip = ipdata.ip.parse::().unwrap(); + let mut rule = Rule::new(&chain4); + rule.add_expr(&nft_expr!(payload ipv4 saddr)); + rule.add_expr(&nft_expr!(cmp == ip)); + rule.add_expr(&nft_expr!(ct state)); + rule.add_expr(&nft_expr!(bitwise mask 10u32, xor 0u32)); + rule.add_expr(&nft_expr!(cmp != 0u32)); + rule.add_expr(&nft_expr!(counter)); + rule.add_expr(&nft_expr!(verdict drop)); + batch4.add(&rule, nftnl::MsgType::Add); + } + 6 => { + let ip = ipdata.ip.parse::().unwrap(); + let mut rule = Rule::new(&chain6); + rule.add_expr(&nft_expr!(payload ipv6 saddr)); + rule.add_expr(&nft_expr!(cmp == ip)); + rule.add_expr(&nft_expr!(ct state)); + rule.add_expr(&nft_expr!(bitwise mask 10u32, xor 0u32)); + rule.add_expr(&nft_expr!(cmp != 0u32)); + rule.add_expr(&nft_expr!(counter)); + rule.add_expr(&nft_expr!(verdict drop)); + batch6.add(&rule, nftnl::MsgType::Add); + } + _ => { + todo!() + } + } } // validate and send batch - let finalized_batch = batch.finalize(); - send_and_process(&finalized_batch)?; + for b in [batch4, batch6] { + let bf = b.finalize(); + send_and_process(&bf).unwrap(); + } if fwlen != &mut ips_add.len() { ret.push(format!("{length} ip in firewall", length = ips_add.len())); } @@ -94,11 +159,3 @@ fn socket_recv<'a>( Ok(None) } } - -fn convert(input: &Vec) -> Vec { - let mut output: Vec = vec![]; - for val in input { - output.push(val.ip.parse::().unwrap()); - } - output -} diff --git a/src/ip.rs b/src/ip.rs index 0a4ea2d..5ced4d3 100644 --- a/src/ip.rs +++ b/src/ip.rs @@ -1,4 +1,3 @@ -use crate::config::httpclient; use crate::utils::gethostname; use chrono::offset::LocalResult; @@ -6,7 +5,6 @@ use chrono::prelude::*; use ipnet::IpNet; use lazy_static::lazy_static; use regex::Regex; -use reqwest::Error as ReqError; use serde::{Deserialize, Serialize}; use std::cmp::Ordering; use std::fmt::{Display, Formatter}; @@ -27,12 +25,16 @@ pub struct IpEvent { pub ipdata: IpData, } -#[derive(Clone, Debug, Serialize, Deserialize, Eq)] -pub struct IpData { - pub ip: String, - pub src: String, - pub date: String, - pub hostname: String, +#[macro_export] +macro_rules! ipevent { + ($msgtype:expr,$mode:expr,$hostname:expr,$ipdata:expr) => { + IpEvent { + msgtype: String::from($msgtype), + mode: String::from($mode), + hostname: $hostname, + ipdata: $ipdata, + } + }; } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -43,6 +45,15 @@ pub struct BlockIpData { pub starttime: DateTime, } +#[derive(Clone, Debug, Serialize, Deserialize, Eq)] +pub struct IpData { + pub t: isize, + pub ip: String, + pub src: String, + pub date: String, + pub hostname: String, +} + impl PartialEq for IpData { fn eq(&self, other: &IpData) -> bool { self.ip.as_bytes() == other.ip.as_bytes() && self.src == other.src @@ -91,15 +102,18 @@ pub fn filter( if let Ok(l) = line { if regex.is_match(l.as_str()) { let s_ipaddr: String; + let t: isize; match R_IPV4.captures(l.as_str()) { Some(sv4) => { s_ipaddr = sv4.get(0).unwrap().as_str().to_string(); + t = 4; } None => { match R_IPV6.captures(l.as_str()) { Some(sv6) => { s_ipaddr = sv6.get(0).unwrap().as_str().to_string(); + t = 6; } None => { continue; @@ -132,6 +146,7 @@ pub fn filter( if !is_trusted(&ipaddr, &trustnets) { iplist.push(IpData { ip: s_ipaddr, + t: t, src: src.to_owned(), date: s_date.to_rfc3339().to_owned(), hostname: hostname.to_owned(), @@ -145,30 +160,22 @@ pub fn filter( } fn parse_date(input: regex::Captures) -> DateTime { - let mut ymd: Vec = vec![]; - let mut hms: Vec = vec![]; + let mut ymd: Vec = vec![]; + let mut hms: Vec = vec![]; - let (daterange, hourrange) = (2..5, 5..8); - - for i in daterange { - ymd.push(input.get(i).unwrap().as_str().parse::().unwrap()); + for capture in 2..5 { + ymd.push(input.get(capture).unwrap().as_str().parse::().unwrap()); } - for i in hourrange { - hms.push(input.get(i).unwrap().as_str().parse::().unwrap()); + for capture in 5..8 { + hms.push(input.get(capture).unwrap().as_str().parse::().unwrap()); } - let date: DateTime = match Local.with_ymd_and_hms( - ymd[0] as i32, - ymd[1] as u32, - ymd[2] as u32, - hms[0] as u32, - hms[1] as u32, - hms[2] as u32, - ) { - LocalResult::Single(s) => s, - LocalResult::Ambiguous(a, _b) => a, - LocalResult::None => Local::now(), - }; + let date: DateTime = + match Local.with_ymd_and_hms(ymd[0] as i32, ymd[1], ymd[2], hms[0], hms[1], hms[2]) { + LocalResult::Single(s) => s, + LocalResult::Ambiguous(a, _b) => a, + LocalResult::None => Local::now(), + }; date } @@ -180,23 +187,3 @@ fn is_trusted(ip: &IpAddr, trustnets: &Vec) -> bool { } false } - -pub async fn _get_last(server: &String) -> Result, ReqError> { - let resp = httpclient() - .get(format!("{server}/ips/last")) - .query(&[("interval", "3 hours")]) - .send() - .await; - - let req = match resp { - Ok(re) => re, - Err(err) => return Err(err), - }; - - let data: Vec = match req.json::>().await { - Ok(res) => res, - Err(err) => return Err(err), - }; - - Ok(data) -} diff --git a/src/ipblc.rs b/src/ipblc.rs index b31d6d8..e9ca0d9 100644 --- a/src/ipblc.rs +++ b/src/ipblc.rs @@ -1,6 +1,7 @@ use crate::config::{Context, GIT_VERSION}; use crate::fw::{fwblock, fwinit}; use crate::ip::{filter, IpData, IpEvent}; +use crate::ipevent; use crate::monitoring::apiserver; use crate::utils::{gethostname, read_lines, sleep_s}; use crate::webservice::send_to_ipbl_api; @@ -29,7 +30,8 @@ pub async fn run() { let mut last_cfg_reload: DateTime = Local::now().trunc_subsecs(0); println!("Launching {}, version {}", PKG_NAME, pkgversion); - fwinit(); + fwinit(4); + fwinit(6); let ctxapi = Arc::clone(&ctxarc); apiserver(&ctxapi).await.unwrap(); @@ -73,12 +75,7 @@ pub async fn run() { if received_ip.msgtype == "bootstrap".to_string() { for ip_to_send in toblock { - let ipe = IpEvent{ - msgtype: String::from("init"), - mode: String::from("ws"), - hostname: gethostname(true), - ipdata: ip_to_send, - }; + let ipe = ipevent!("init","ws",gethostname(true),ip_to_send); if !send_to_ipbl_websocket(&mut wssocketrr, &ipe).await { wssocketrr = websocketreqrep(&ctxwsrr).await; break; @@ -98,12 +95,7 @@ pub async fn run() { if let Some(ipevent) = filtered_ipevent { if received_ip.msgtype != "init" { println!("sending {} to api and ws", ipevent.ipdata.ip); - let ipe = IpEvent{ - msgtype: String::from("add"), - mode: String::from("ws"), - hostname: gethostname(true), - ipdata: ipevent.ipdata, - }; + let ipe = ipevent!("add","ws",gethostname(true),ipevent.ipdata); send_to_ipbl_api(&server.clone(), &ipe).await; let status = send_to_ipbl_websocket(&mut wssocketrr, &ipe).await; if !status { @@ -258,14 +250,9 @@ async fn compare_files_changes( } } for ip in iplist { - let ipevent = IpEvent { - msgtype: String::from("add"), - hostname: gethostname(true), - mode: String::from("file"), - ipdata: ip, - }; + let ipe = ipevent!("add", "file", gethostname(true), ip); let ipetx = ipeventtx.write().await; - ipetx.send(ipevent).await.unwrap(); + ipetx.send(ipe).await.unwrap(); } } None => {} diff --git a/src/regexps/ipv6.txt b/src/regexps/ipv6.txt index d69593c..a5c5291 100644 --- a/src/regexps/ipv6.txt +++ b/src/regexps/ipv6.txt @@ -1 +1 @@ -((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$)) \ No newline at end of file +(((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*)|(((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?) \ No newline at end of file diff --git a/src/webservice.rs b/src/webservice.rs index 48891f8..a4d74f6 100644 --- a/src/webservice.rs +++ b/src/webservice.rs @@ -31,6 +31,7 @@ async fn push_ip(client: &Client, server: &str, ip: &IpData) -> Result<(), ReqEr let mut data: Vec = vec![]; data.push(IpData { + t: ip.t, ip: ip.ip.to_string(), src: ip.src.to_string(), date: ip.date.to_string(), @@ -56,6 +57,7 @@ async fn _push_ip_bulk( for ip in ips { data.push(IpData { + t: ip.t, ip: ip.ip.to_string(), src: ip.src.to_string(), date: ip.date.to_string(),