Skip to content

Commit

Permalink
Change config format & some tests
Browse files Browse the repository at this point in the history
  • Loading branch information
containerscrew committed Dec 4, 2024
1 parent 910dbf0 commit 1b7018c
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 172 deletions.
1 change: 1 addition & 0 deletions docs/todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
- [2] Prometheus metrics for XDP program.
- [3] Implement in how much time a log_connection_event will be sended to Perf Ring Buffer.
- [3] Allow the user to change the config in the runtime.
- [4] Implement default values in nflux.toml (config.rs)
33 changes: 15 additions & 18 deletions nflux.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
[firewall]
# TODO: Add support for multiple interfaces
[nflux]
# Global configuration for nflux
interface_names = ["wlp2s0", "eth0"]

[logging]
log_level = "info" # trace, debug, info, warn, or error. Defaults to info if not set
log_type = "text" # text or json. Defaults to text if not set
# TODO
# default_action = "deny" # global default action if no specific rule matches
# log_file = "/var/log/firewall.log"

[ip_rules]
# Fine-tuned rules for IP-based filtering
Expand All @@ -13,19 +14,15 @@ log_type = "text" # text or json. Defaults to text if not set
# "192.168.0.170/24" = { priority = 2, action = "deny", ports = [22], protocol = "tcp", log = false, description = "Deny SSH from entire subnet" }
# "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" }
# [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" }
# "00:0a:95:9d:68:17" = { action = "deny" }

[mac_rules]
# Rules for MAC address filtering
"00:0a:95:9d:68:16" = { action = "allow" }
"00:0a:95:9d:68:17" = { action = "deny" }

[logging]
log_denied_packets = true
log_allowed_packets = false
log_format = "json"
log_file = "/var/log/firewall.log"
94 changes: 44 additions & 50 deletions nflux/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,39 @@
use anyhow::{Context, Result};
use serde::{Deserialize, Deserializer};
use serde::Deserialize;
use std::collections::HashMap;
use std::env;
use std::fs;

/// Enum for `action`
#[derive(Debug, Deserialize)]
#[serde(rename_all = "lowercase")] // Allow "deny" or "allow" in config
#[derive(Debug, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum Action {
Deny,
Allow,
}

/// Enum for `protocol`
#[derive(Debug, Deserialize)]
#[serde(rename_all = "lowercase")] // Allow "tcp" or "udp" in config
#[derive(Debug, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum Protocol {
Tcp,
Udp,
Icmp,
}

// General firewall configuration
#[derive(Debug, Deserialize)]
pub struct NfluxConfig {
pub interface_names: Vec<String>,
}

// Logging config
#[derive(Debug, Deserialize)]
pub struct LoggingConfig {
pub log_level: String,
pub log_type: String,
}

/// Generic rule for both IPv4 and IPv6
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
Expand All @@ -33,61 +46,42 @@ pub struct Rules {
pub description: String,
}

/// Configuration for ICMP rules
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub struct IcmpRules {
pub action: Action, // Allow or Deny
pub protocol: String, // Always "icmp"
}

/// Configuration for MAC-based rules
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub struct MacRule {
pub action: Action, // Allow or Deny
}

/// General firewall configuration
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub struct Firewall {
pub interface_names: Vec<String>, // List of interfaces
pub log_level: String, // Log level
pub log_type: String, // Log type
}

/// Logging configuration
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub struct LoggingConfig {
pub log_denied_packets: bool,
pub log_allowed_packets: bool,
pub log_format: String,
pub log_file: String,
}

/// Top-level configuration structure
// Top-level configuration structure
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub struct FirewallConfig {
pub firewall: Firewall,
pub ip_rules: HashMap<String, Rules>,
pub icmp_rules: HashMap<String, IcmpRules>,
pub mac_rules: HashMap<String, MacRule>,
pub struct Nflux {
pub nflux: NfluxConfig,
pub logging: LoggingConfig,
pub ip_rules: HashMap<String, Rules>,
}

impl FirewallConfig {
/// Load the configuration from a file, defaulting to `/etc/nflux/nflux.toml` if not specified
pub fn load() -> Result<Self> {
impl Nflux {
// Load the configuration from a file and return the `Nflux` struct
pub fn load_config() -> Result<Self> {
let config_file = env::var("NFLUX_CONFIG_FILE_PATH")
.unwrap_or_else(|_| "/etc/nflux/nflux.toml".to_string());

let config_content = fs::read_to_string(&config_file)
.with_context(|| format!("Failed to read configuration file: {}", config_file))?;

toml::from_str(&config_content)
.with_context(|| format!("Failed to parse configuration file: {}", config_file))
let config: Self = toml::from_str(&config_content)
.with_context(|| format!("Failed to parse configuration file: {}", config_file))?;

config.validate()?;

Ok(config)
}

// A separate validation function to ensure correctness
pub fn validate(&self) -> Result<()> {
for (ip, rule) in &self.ip_rules {
if rule.priority == 0 {
anyhow::bail!("Priority must be greater than 0");
}
if !rule.ports.iter().all(|&port| (1..=65535).contains(&port)) {
anyhow::bail!("Invalid port number in rule for IP: {}", ip);
}
}
Ok(())
}
}
3 changes: 2 additions & 1 deletion nflux/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ mod logger;
mod utils;

// Dependencies
pub use config::{FirewallConfig, IcmpRules, Protocol, Rules};
pub use config::{NfluxConfig, Rules, Nflux, Action, Protocol};
pub use core::set_mem_limit;
pub use utils::{is_root_user, wait_for_shutdown};

/// RXH version.
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
21 changes: 13 additions & 8 deletions nflux/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ mod core;
mod logger;
mod utils;

use crate::utils::{is_root_user, wait_for_shutdown};

use anyhow::Context;
use aya::maps::lpm_trie::Key;
use aya::maps::perf::{AsyncPerfEventArrayBuffer, PerfBufferError};
Expand All @@ -12,23 +12,24 @@ use aya::programs::{Xdp, XdpFlags};
use aya::util::online_cpus;
use aya::{include_bytes_aligned, Ebpf};
use bytes::BytesMut;
use config::{Action, FirewallConfig, Protocol, Rules};
use config::{Action, Nflux, Protocol, Rules};
use logger::setup_logger;
use nflux::set_mem_limit;
use nflux_common::{convert_protocol, ConnectionEvent, IpRule, LpmKeyIpv4, LpmKeyIpv6};
use utils::{is_root_user, wait_for_shutdown};
use core::set_mem_limit;
use std::collections::HashMap;
use std::net::{Ipv4Addr, Ipv6Addr};
use std::net::Ipv4Addr;
use std::ptr;
use tokio::task;
use tracing::{error, info, warn};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Load configuration file
let config = FirewallConfig::load().context("Failed to load firewall configuration")?;
let config = Nflux::load_config().context("Failed to load nflux configuration")?;

// Enable logging
setup_logger(&config.firewall.log_level, &config.firewall.log_type);
setup_logger(&config.logging.log_level, &config.logging.log_type);

// Ensure the program is run as root
if !is_root_user() {
Expand All @@ -50,7 +51,7 @@ async fn main() -> anyhow::Result<()> {
let program: &mut Xdp = bpf.program_mut("nflux").unwrap().try_into()?;
program.load()?;
program
.attach(&config.firewall.interface_names[0], XdpFlags::default())
.attach(&config.nflux.interface_names[0], XdpFlags::default())
.context(
"Failed to attach XDP program. Ensure the interface is physical and not virtual.",
)?;
Expand All @@ -59,7 +60,7 @@ async fn main() -> anyhow::Result<()> {
info!("nflux started successfully!");
info!(
"XDP program attached to interface: {:?}",
config.firewall.interface_names[0]
config.nflux.interface_names[0]
);

// Start processing events from the eBPF program
Expand Down Expand Up @@ -170,6 +171,10 @@ fn prepare_ip_rule(rule: &Rules) -> anyhow::Result<IpRule> {
Protocol::Tcp => 6,
Protocol::Udp => 17,
Protocol::Icmp => 1,
_ => {
warn!("Unsupported protocol: {:?}", rule.protocol);
return Err(anyhow::anyhow!("Unsupported protocol"));
}
},
priority: rule.priority,
})
Expand Down
Loading

0 comments on commit 1b7018c

Please sign in to comment.