diff --git a/nflux-common/src/lib.rs b/nflux-common/src/lib.rs index a9bd5d5..ad23a0f 100644 --- a/nflux-common/src/lib.rs +++ b/nflux-common/src/lib.rs @@ -8,8 +8,10 @@ pub const MAX_ALLOWED_IPV4: usize = 1024; #[repr(C)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct EgressConfig { + pub log_only_new_connections: u8, // 0 = no, 1 = yes pub log_udp_connections: u8, // 0 = no, 1 = yes pub log_tcp_connections: u8, // 0 = no, 1 = yes + pub log_private_connections: u8, // 0 = no, 1 = yes } #[repr(C)] diff --git a/nflux-ebpf/src/egress.rs b/nflux-ebpf/src/egress.rs index a85f908..4ee30ef 100644 --- a/nflux-ebpf/src/egress.rs +++ b/nflux-ebpf/src/egress.rs @@ -12,7 +12,6 @@ use nflux_common::EgressEvent; use crate::maps::{ACTIVE_CONNECTIONS, EGRESS_CONFIG, EGRESS_EVENT}; - #[inline] fn ptr_at(ctx: &TcContext, offset: usize) -> Result<*const T, ()> { let start = ctx.data(); @@ -26,6 +25,21 @@ fn ptr_at(ctx: &TcContext, offset: usize) -> Result<*const T, ()> { Ok((start + offset) as *const T) } +#[inline] +unsafe fn log_connection(ctx: &TcContext, destination: u32, src_port: u16, dst_port: u16, protocol: u8, pid: u64) { + // Check if this destination is already active + if ACTIVE_CONNECTIONS.get(&destination).is_none() { + // Log only new connections + let event = EgressEvent { dst_ip: destination, src_port, dst_port, protocol, pid }; + EGRESS_EVENT.output(ctx, &event, 0); + + // Mark connection as active + if ACTIVE_CONNECTIONS.insert(&destination, &1, 0).is_err() { + return; + } + } +} + pub fn try_tc_egress(ctx: TcContext) -> Result { let ethhdr: EthHdr = ctx.load(0).map_err(|_| ())?; @@ -52,16 +66,31 @@ pub fn try_tc_egress(ctx: TcContext) -> Result { let pid_tgid = bpf_get_current_pid_tgid(); let pid = pid_tgid >> 32; - // Check if this destination is already active - if ACTIVE_CONNECTIONS.get(&destination).is_none() { - // Log only new connections - let event = EgressEvent { dst_ip: destination, src_port, dst_port, protocol, pid }; - EGRESS_EVENT.output(&ctx, &event, 0); - - // Mark connection as active - if ACTIVE_CONNECTIONS.insert(&destination, &1, 0).is_err() { - return Err(()); - } + // If log_tcp_connections is enabled, log the connection + if egress_config.log_tcp_connections == 1 { + log_connection(&ctx, destination, src_port, dst_port, protocol, pid); + // If log_only_new_connections is enabled, only log new connections + // match egress_config.log_only_new_connections { + // 0 => { + // // Check if this destination is already active + // if ACTIVE_CONNECTIONS.get(&destination).is_none() { + // // Log only new connections + // let event = EgressEvent { dst_ip: destination, src_port, dst_port, protocol, pid }; + // EGRESS_EVENT.output(&ctx, &event, 0); + + // // Mark connection as active + // if ACTIVE_CONNECTIONS.insert(&destination, &1, 0).is_err() { + // return Err(()); + // } + // } + // } + // 1 => { + // // Log all connections + // let event = EgressEvent { dst_ip: destination, src_port, dst_port, protocol, pid }; + // EGRESS_EVENT.output(&ctx, &event, 0); + // } + // _ => {} + // } } return Ok(TC_ACT_PIPE) } @@ -73,8 +102,31 @@ pub fn try_tc_egress(ctx: TcContext) -> Result { let pid_tgid = bpf_get_current_pid_tgid(); let pid = pid_tgid >> 32; - let event = EgressEvent { dst_ip: destination, src_port, dst_port, protocol, pid}; - EGRESS_EVENT.output(&ctx, &event, 0); + // If log_tcp_connections is enabled, log the connection + if egress_config.log_udp_connections == 1 { + // If log_only_new_connections is enabled, only log new connections + match egress_config.log_only_new_connections { + 0 => { + // Check if this destination is already active + if ACTIVE_CONNECTIONS.get(&destination).is_none() { + // Log only new connections + let event = EgressEvent { dst_ip: destination, src_port, dst_port, protocol, pid }; + EGRESS_EVENT.output(&ctx, &event, 0); + + // Mark connection as active + if ACTIVE_CONNECTIONS.insert(&destination, &1, 0).is_err() { + return Err(()); + } + } + } + 1 => { + // Log all connections + let event = EgressEvent { dst_ip: destination, src_port, dst_port, protocol, pid }; + EGRESS_EVENT.output(&ctx, &event, 0); + } + _ => {} + } + } return Ok(TC_ACT_PIPE) } diff --git a/nflux.toml b/nflux.toml index 941ab49..04cee26 100644 --- a/nflux.toml +++ b/nflux.toml @@ -15,10 +15,15 @@ icmp_ping = "false" # Allow or deny ICMP ping requests "192.168.0.76/32" = { priority = 2, action = "deny", ports = [8080], protocol = "tcp", description = "Deny a specific IP address" } [egress] -# By the moment, working with physical interfaces (not virtual, like VPNs) enabled = "true" -interfaces = ["wlp2s0"] -log_udp_connections = "false" # Decide if udp packets should be logged +physical_interfaces = ["enp0s20f0u4"] # Physical interfaces, your LAN interface +virtual_interfaces = ["protonvpn"] # If using VPN, add the virtual interface here + +[egress.logging] +# For example, if you perform 1000 requests to google.com, just log 1 (or every you execute) +# This affects to tcp and udp connections +log_only_new_connections = "true" +log_udp_connections = "true" # Decide if udp packets should be logged log_tcp_connections = "true" # Decide if tcp packets should be logged log_private_connections = "true" # Log private connections (10.X, 172.X, 192.X...) not only external ips diff --git a/nflux/src/config.rs b/nflux/src/config.rs index d7b6b08..3f2f236 100644 --- a/nflux/src/config.rs +++ b/nflux/src/config.rs @@ -37,15 +37,21 @@ pub struct Firewall { } #[derive(Debug, Deserialize)] -pub struct Egress { - pub enabled: IsEnabled, - pub interfaces: Vec, +pub struct EgressLogging { + pub log_only_new_connections: IsEnabled, pub log_udp_connections: IsEnabled, pub log_tcp_connections: IsEnabled, - #[allow(dead_code)] pub log_private_connections: IsEnabled, } +#[derive(Debug, Deserialize)] +pub struct Egress { + pub enabled: IsEnabled, + pub physical_interfaces: Vec, + pub virtual_interfaces: Vec, + pub logging: EgressLogging, +} + // Generic rule for both IPv4 and IPv6 #[derive(Debug, Deserialize)] pub struct FirewallRules { diff --git a/nflux/src/egress.rs b/nflux/src/egress.rs index a35aa58..04dcada 100644 --- a/nflux/src/egress.rs +++ b/nflux/src/egress.rs @@ -1,15 +1,47 @@ use std::net::Ipv4Addr; use std::ptr; +use anyhow::Context; use aya::Ebpf; -use aya::maps::MapData; +use aya::maps::{Array, MapData}; use aya::maps::perf::{AsyncPerfEventArrayBuffer, PerfBufferError}; use aya::programs::{tc, SchedClassifier, TcAttachType}; use bytes::BytesMut; use tracing::{error, info, warn}; -use nflux_common::{convert_protocol, EgressEvent}; -use crate::config::IsEnabled; +use nflux_common::{convert_protocol, EgressConfig, EgressEvent}; +use crate::config::{Egress, IsEnabled}; use crate::utils::{is_private_ip, lookup_address}; +pub fn populate_egress_config(bpf: &mut Ebpf, config: Egress) -> anyhow::Result<()> { + let mut egress_config = Array::<_, EgressConfig>::try_from( + bpf.map_mut("EGRESS_CONFIG").context("Failed to find EGRESS_CONFIG map")?, + )?; + + let config = EgressConfig { + log_only_new_connections: match config.logging.log_only_new_connections { + IsEnabled::True => 1, + IsEnabled::False => 0, + }, + log_udp_connections: match config.logging.log_udp_connections { + IsEnabled::True => 1, + IsEnabled::False => 0, + }, + log_tcp_connections: match config.logging.log_tcp_connections { + IsEnabled::True => 1, + IsEnabled::False => 0, + }, + log_private_connections: match config.logging.log_private_connections { + IsEnabled::True => 1, + IsEnabled::False => 0, + }, + }; + + egress_config + .set(0, config, 0) + .context("Failed to set ICMP_MAP")?; + + Ok(()) +} + pub fn attach_tc_egress_program(bpf: &mut Ebpf, interface_names: &[String]) -> anyhow::Result<()>{ // Retrieve the eBPF program let program = match bpf.program_mut("tc_egress") { diff --git a/nflux/src/main.rs b/nflux/src/main.rs index d863070..cffbebb 100644 --- a/nflux/src/main.rs +++ b/nflux/src/main.rs @@ -5,15 +5,15 @@ mod firewall; mod egress; use anyhow::Context; -use aya::maps::{Array, AsyncPerfEventArray}; +use aya::maps::AsyncPerfEventArray; use aya::util::online_cpus; use aya::{include_bytes_aligned, Ebpf}; use aya_log::EbpfLogger; use config::{IsEnabled, Nflux}; +use egress::populate_egress_config; use firewall::{attach_xdp_program, process_firewall_events}; use logger::setup_logger; -use nflux_common::EgressConfig; use tokio::task; use tracing::{error, info, warn}; use utils::{is_root_user, print_firewall_rules, set_mem_limit, wait_for_shutdown}; @@ -60,26 +60,8 @@ async fn main() -> anyhow::Result<()> { // Attach TC program (monitor egress connections) match config.egress.enabled { IsEnabled::True => { - attach_tc_egress_program(&mut bpf, &config.egress.interfaces)?; - let mut egress_config = Array::<_, EgressConfig>::try_from( - bpf.map_mut("EGRESS_CONFIG").context("Failed to find EGRESS_CONFIG map")?, - )?; - - let config = EgressConfig { - log_udp_connections: match config.egress.log_udp_connections { - IsEnabled::True => 1, - IsEnabled::False => 0, - }, - log_tcp_connections: match config.egress.log_tcp_connections { - IsEnabled::True => 1, - IsEnabled::False => 0, - }, - }; - - egress_config - .set(0, config, 0) - .context("Failed to set ICMP_MAP")?; - + attach_tc_egress_program(&mut bpf, &config.egress.physical_interfaces)?; + populate_egress_config(&mut bpf, config.egress)?; info!("TC egress started successfully!") } IsEnabled::False => {