From e91bbcdd5a8a180112cf0f8b0048c804d6450808 Mon Sep 17 00:00:00 2001 From: containerscrew Date: Thu, 5 Dec 2024 14:21:53 +0100 Subject: [PATCH] Permit tcp syn-ack packets & config testing --- nflux-ebpf/src/main.rs | 38 ++++++++++++++++++++++++++++++----- nflux.toml | 4 +--- nflux/src/config.rs | 45 ++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 77 insertions(+), 10 deletions(-) diff --git a/nflux-ebpf/src/main.rs b/nflux-ebpf/src/main.rs index 3924a49..815eaa7 100644 --- a/nflux-ebpf/src/main.rs +++ b/nflux-ebpf/src/main.rs @@ -11,9 +11,10 @@ use aya_ebpf::{ programs::XdpContext, }; use core::mem; +use network_types::ip::IpProto; use network_types::{ eth::{EthHdr, EtherType}, - ip::{IpProto, Ipv4Hdr}, + ip::Ipv4Hdr, tcp::TcpHdr, udp::UdpHdr, }; @@ -89,11 +90,36 @@ fn start_nflux(ctx: XdpContext) -> Result { let tcphdr: *const TcpHdr = unsafe { ptr_at(&ctx, EthHdr::LEN + Ipv4Hdr::LEN)? }; let dst_port = u16::from_be(unsafe { (*tcphdr).dest }); - - if rule.ports.contains(&dst_port) && rule.action == 1 { - log_new_connection(ctx, source_ip, dst_port, 6); + let syn = unsafe { (*tcphdr).syn() }; + let ack = unsafe { (*tcphdr).ack() }; + + if syn == 1 && ack == 0 { + // SYN packet: Apply rules for incoming or outgoing connections + if rule.ports.contains(&dst_port) { + if rule.action == 1 { + log_new_connection(ctx, source_ip, dst_port, 6); + return Ok(xdp_action::XDP_PASS); + } else { + log_new_connection(ctx, source_ip, dst_port, 6); + return Ok(xdp_action::XDP_DROP); + } + } + } else if syn == 1 && ack == 1 { + // SYN-ACK packets: Allow for outgoing connection responses + return Ok(xdp_action::XDP_PASS); + } else if ack == 1 { + // ACK packets: Allow for established connections return Ok(xdp_action::XDP_PASS); } + + // For other TCP packets, apply rules + if rule.ports.contains(&dst_port) { + if rule.action == 1 { + log_new_connection(ctx, source_ip, dst_port, 6); + return Ok(xdp_action::XDP_PASS); + } + } + log_new_connection(ctx, source_ip, dst_port, 6); return Ok(xdp_action::XDP_DROP); } IpProto::Udp => { @@ -105,7 +131,9 @@ fn start_nflux(ctx: XdpContext) -> Result { log_new_connection(ctx, source_ip, dst_port, 17); return Ok(xdp_action::XDP_PASS); } - return Ok(xdp_action::XDP_DROP); + // By the moment, allow every UDP packet + // Necessary to allow DNS UDP packets (internet browsing, for example) + return Ok(xdp_action::XDP_PASS); } IpProto::Icmp => { if rule.action == 1 { diff --git a/nflux.toml b/nflux.toml index 56d4b3b..06b00bb 100644 --- a/nflux.toml +++ b/nflux.toml @@ -10,9 +10,7 @@ log_type = "text" # text or json. Defaults to text if not set [ip_rules] # The /32 CIDR block is used to represent a single IP address rather than a range "192.168.0.174/32" = { priority = 1, action = "allow", ports = [22], protocol = "tcp", log = false, description = "Allow SSH for specific IP" } -"192.168.0.0/24" = { priority = 2, action = "deny", ports = [22], protocol = "tcp", log = false, description = "Deny SSH for entire subnet" } - - +"192.168.0.0/24" = { priority = 2, action = "deny", ports = [8081], protocol = "tcp", log = false, description = "Deny SSH for entire subnet" } # todo: ipv6 support # "2001:0db8:85a3:0000:0000:8a2e:0370:7334" = { action = "deny", ports = [80], protocol = "tcp" } diff --git a/nflux/src/config.rs b/nflux/src/config.rs index 94615b6..f6c9f4f 100644 --- a/nflux/src/config.rs +++ b/nflux/src/config.rs @@ -1,6 +1,6 @@ use anyhow::{Context, Result}; use serde::Deserialize; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::env; use std::fs; @@ -74,14 +74,29 @@ impl Nflux { // A separate validation function to ensure correctness pub fn validate(&self) -> Result<()> { + let mut priorities: HashSet = HashSet::new(); + for (ip, rule) in &self.ip_rules { + // Ensure priority is greater than 0 if rule.priority == 0 { - anyhow::bail!("Priority must be greater than 0"); + anyhow::bail!("Priority must be greater than 0 for rule: {}", ip); } + + // Ensure port numbers are within the valid range if !rule.ports.iter().all(|&port| (1..=65535).contains(&port)) { anyhow::bail!("Invalid port number in rule for IP: {}", ip); } + + // Check for duplicate priorities + if !priorities.insert(rule.priority) { + anyhow::bail!( + "Duplicate priority found: {} in rule for IP: {}", + rule.priority, + ip + ); + } } + Ok(()) } } @@ -148,6 +163,32 @@ mod tests { .contains("Failed to read configuration file")); } + #[test] + fn test_duplicate_priority() { + let config_content = r#" + [nflux] + interface_names = ["eth0", "wlan0"] + + [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" } + "192.168.0.4" = { priority = 1, action = "allow", ports = [80], protocol = "tcp", log = true, description = "Nginx rule" } + "#; + + let _temp_dir = setup_temp_config(config_content); + + let config = Nflux::load_config(); + + // Check that the configuration loading fails due to duplicate priorities + assert!( + config.is_err(), + "Expected duplicate priorities to cause an error" + ); + } + // #[test] // fn test_load_invalid_config_format() { // let invalid_config_content = "invalid: [toml";