From 14c39c3afe1a80d0087c71712dbeb3cb1835ab48 Mon Sep 17 00:00:00 2001 From: containerscrew Date: Thu, 5 Dec 2024 15:09:00 +0100 Subject: [PATCH] Implement icmp enable/disable --- .pre-commit-config.yaml | 10 +++++----- nflux-ebpf/src/main.rs | 16 +++++++++++---- nflux.toml | 9 +++------ nflux/src/config.rs | 42 +++++++++++++++++++-------------------- nflux/src/ebpf_mapping.rs | 21 ++++++++++++++++++++ nflux/src/main.rs | 10 ++++++---- 6 files changed, 68 insertions(+), 40 deletions(-) create mode 100644 nflux/src/ebpf_mapping.rs diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 808d974..2ce0012 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,11 +18,11 @@ repos: hooks: - id: mtoc args: [ "-e", ".target/" ] - - repo: https://github.com/doublify/pre-commit-rust - rev: v1.0 - hooks: - - id: fmt - args: ['--verbose', '--'] +# - repo: https://github.com/doublify/pre-commit-rust +# rev: v1.0 +# hooks: +# - id: fmt +# args: ['--verbose', '--'] # - id: cargo-check # - id: clippy # args: ["--", "-D", "warnings", "-D", "unused-imports"] diff --git a/nflux-ebpf/src/main.rs b/nflux-ebpf/src/main.rs index 815eaa7..83c86fa 100644 --- a/nflux-ebpf/src/main.rs +++ b/nflux-ebpf/src/main.rs @@ -3,7 +3,7 @@ #![allow(nonstandard_style, dead_code)] use aya_ebpf::maps::lpm_trie::Key; -use aya_ebpf::maps::LpmTrie; +use aya_ebpf::maps::{Array, LpmTrie}; use aya_ebpf::{ bindings::xdp_action, macros::{map, xdp}, @@ -35,6 +35,9 @@ static IPV6_RULES: LpmTrie = LpmTrie::with_max_entries(1024, #[map] static CONNECTION_EVENTS: PerfEventArray = PerfEventArray::new(0); +#[map] +static ICMP_RULE: Array = Array::with_max_entries(1, 0); + #[xdp] pub fn nflux(ctx: XdpContext) -> u32 { match start_nflux(ctx) { @@ -136,10 +139,15 @@ fn start_nflux(ctx: XdpContext) -> Result { return Ok(xdp_action::XDP_PASS); } IpProto::Icmp => { - if rule.action == 1 { - log_new_connection(ctx, source_ip, 0, 1); - return Ok(xdp_action::XDP_PASS); + // Read from EBPF map + if let Some(&icmp_ping) = ICMP_RULE.get(0) { + if icmp_ping == 1 { + // Allow ICMP packets if enabled + log_new_connection(ctx, source_ip, 0, 1); + return Ok(xdp_action::XDP_PASS); + } } + // Block ICMP packets by default return Ok(xdp_action::XDP_DROP); } _ => return Ok(xdp_action::XDP_DROP), diff --git a/nflux.toml b/nflux.toml index 06b00bb..7e2513b 100644 --- a/nflux.toml +++ b/nflux.toml @@ -1,6 +1,9 @@ [nflux] # Global configuration for nflux interface_names = ["wlp2s0", "eth0"] +# Control ICMP ping packets for each IP I think is not necessary by the moment +# Lets decide if ICMP packets will be allowed or denied globally +icmp_ping = "false" [logging] log_level = "info" # trace, debug, info, warn, or error. Defaults to info if not set @@ -15,12 +18,6 @@ log_type = "text" # text or json. Defaults to text if not set # todo: ipv6 support # "2001:0db8:85a3:0000:0000:8a2e:0370:7334" = { action = "deny", ports = [80], protocol = "tcp" } -# [icmp_rules] -# # Rules for ICMP traffic -# "192.168.0.1/24" = { action = "deny", protocol = "icmp" } -# "192.168.0.88/24" = { action = "allow", protocol = "icmp" } -# "192.168.0.22/24" = { action = "deny", protocol = "icmp" } - # [mac_rules] # # Rules for MAC address filtering # "00:0a:95:9d:68:16" = { action = "allow" } diff --git a/nflux/src/config.rs b/nflux/src/config.rs index f6c9f4f..fc61e36 100644 --- a/nflux/src/config.rs +++ b/nflux/src/config.rs @@ -12,19 +12,27 @@ pub enum Action { Allow, } -/// Enum for `protocol` +// Enum for `protocol` #[derive(Debug, Deserialize, PartialEq)] #[serde(rename_all = "lowercase")] pub enum Protocol { Tcp, Udp, - Icmp, +} + +// Enum for `is enabled` +#[derive(Debug, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum IsEnabled { + True, + False, } // General firewall configuration #[derive(Debug, Deserialize)] pub struct NfluxConfig { pub interface_names: Vec, + pub icmp_ping: IsEnabled, } // Logging config @@ -34,10 +42,10 @@ pub struct LoggingConfig { pub log_type: String, } -/// Generic rule for both IPv4 and IPv6 +// Generic rule for both IPv4 and IPv6 #[derive(Debug, Deserialize)] #[allow(dead_code)] -pub struct Rules { +pub struct IpRules { pub priority: u32, pub action: Action, pub ports: Vec, @@ -52,7 +60,7 @@ pub struct Rules { pub struct Nflux { pub nflux: NfluxConfig, pub logging: LoggingConfig, - pub ip_rules: HashMap, + pub ip_rules: HashMap, } impl Nflux { @@ -122,11 +130,13 @@ mod tests { let config_content = r#" [nflux] interface_names = ["eth0", "wlan0"] + icmp_ping = "true" [logging] log_level = "debug" log_type = "json" + [ip_rules] "192.168.0.1" = { priority = 1, action = "allow", ports = [22], protocol = "tcp", log = true, description = "SSH rule" } "#; @@ -137,6 +147,7 @@ mod tests { // Assertions assert_eq!(config.nflux.interface_names, vec!["eth0", "wlan0"]); + assert_eq!(config.nflux.icmp_ping, IsEnabled::True); assert_eq!(config.logging.log_level, "debug"); assert_eq!(config.logging.log_type, "json"); @@ -168,11 +179,16 @@ mod tests { let config_content = r#" [nflux] interface_names = ["eth0", "wlan0"] + icmp_ping = "true" [logging] log_level = "debug" log_type = "json" + [icmp_rules] + # Rules for ICMP traffic + "192.168.0.0/24" = { action = "deny", protocol = "icmp" } + [ip_rules] "192.168.0.1" = { priority = 1, action = "allow", ports = [22], protocol = "tcp", log = true, description = "SSH rule" } "192.168.0.4" = { priority = 1, action = "allow", ports = [80], protocol = "tcp", log = true, description = "Nginx rule" } @@ -188,20 +204,4 @@ mod tests { "Expected duplicate priorities to cause an error" ); } - - // #[test] - // fn test_load_invalid_config_format() { - // let invalid_config_content = "invalid: [toml"; - - // setup_temp_config(invalid_config_content); - - // let result = Nflux::load_config(); - - // // Assert that loading fails due to parse error - // assert!(result.is_err()); - // assert!(result - // .unwrap_err() - // .to_string() - // .contains("Failed to parse configuration file")); - // } } diff --git a/nflux/src/ebpf_mapping.rs b/nflux/src/ebpf_mapping.rs new file mode 100644 index 0000000..a962159 --- /dev/null +++ b/nflux/src/ebpf_mapping.rs @@ -0,0 +1,21 @@ +use anyhow::Context; +use aya::Ebpf; +use aya::maps::{Array}; +use crate::config::IsEnabled; + +pub fn populate_icmp_rule(bpf: &mut Ebpf, icmp_ping: IsEnabled) -> anyhow::Result<()> { + let mut settings_map = Array::<_, u32>::try_from( + bpf.map_mut("ICMP_RULE").context("Failed to find GLOBAL_SETTINGS map")?, + )?; + + let value = match icmp_ping { + IsEnabled::True => 1, + IsEnabled::False => 0, + }; + + settings_map + .set(0, value, 0) + .context("Failed to set ICMP_MAP")?; + + Ok(()) +} diff --git a/nflux/src/main.rs b/nflux/src/main.rs index 082b0af..d78fc92 100644 --- a/nflux/src/main.rs +++ b/nflux/src/main.rs @@ -2,6 +2,7 @@ mod config; mod core; mod logger; mod utils; +mod ebpf_mapping; use anyhow::Context; use aya::maps::lpm_trie::Key; @@ -11,7 +12,7 @@ use aya::programs::{Xdp, XdpFlags}; use aya::util::online_cpus; use aya::{include_bytes_aligned, Ebpf}; use bytes::BytesMut; -use config::{Action, Nflux, Protocol, Rules}; +use config::{Action, Nflux, Protocol, IpRules}; use core::set_mem_limit; use logger::setup_logger; use nflux_common::{convert_protocol, ConnectionEvent, IpRule, LpmKeyIpv4}; @@ -21,6 +22,7 @@ use std::ptr; use tokio::task; use tracing::{error, info}; use utils::{is_root_user, wait_for_shutdown}; +use crate::ebpf_mapping::populate_icmp_rule; #[tokio::main] async fn main() -> anyhow::Result<()> { @@ -44,6 +46,7 @@ async fn main() -> anyhow::Result<()> { // Populate eBPF maps with configuration data populate_ipv4_rules(&mut bpf, &config.ip_rules)?; + populate_icmp_rule(&mut bpf, config.nflux.icmp_ping)?; // populate_ipv6_rules(&mut bpf, &config.ip_rules)?; // Attach XDP program @@ -120,7 +123,7 @@ fn parse_connection_event(buf: &BytesMut) -> anyhow::Result { } } -fn populate_ipv4_rules(bpf: &mut Ebpf, ip_rules: &HashMap) -> anyhow::Result<()> { +fn populate_ipv4_rules(bpf: &mut Ebpf, ip_rules: &HashMap) -> anyhow::Result<()> { let mut ipv4_map: LpmTrie<&mut MapData, LpmKeyIpv4, IpRule> = LpmTrie::try_from( bpf.map_mut("IPV4_RULES") .context("Failed to find IPV4_RULES map")?, @@ -150,7 +153,7 @@ fn populate_ipv4_rules(bpf: &mut Ebpf, ip_rules: &HashMap) -> any Ok(()) } -fn prepare_ip_rule(rule: &Rules) -> anyhow::Result { +fn prepare_ip_rule(rule: &IpRules) -> anyhow::Result { let mut ports = [0u16; 16]; for (i, &port) in rule.ports.iter().enumerate().take(16) { ports[i] = port as u16; @@ -165,7 +168,6 @@ fn prepare_ip_rule(rule: &Rules) -> anyhow::Result { protocol: match rule.protocol { Protocol::Tcp => 6, Protocol::Udp => 17, - Protocol::Icmp => 1, }, priority: rule.priority, })