Skip to content

Commit

Permalink
Wip
Browse files Browse the repository at this point in the history
  • Loading branch information
containerscrew committed Jan 12, 2025
1 parent 5d8a1fa commit c98e64e
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 45 deletions.
2 changes: 2 additions & 0 deletions nflux-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
78 changes: 65 additions & 13 deletions nflux-ebpf/src/egress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ use nflux_common::EgressEvent;

use crate::maps::{ACTIVE_CONNECTIONS, EGRESS_CONFIG, EGRESS_EVENT};


#[inline]
fn ptr_at<T>(ctx: &TcContext, offset: usize) -> Result<*const T, ()> {
let start = ctx.data();
Expand All @@ -26,6 +25,21 @@ fn ptr_at<T>(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<i32, ()> {
let ethhdr: EthHdr = ctx.load(0).map_err(|_| ())?;

Expand All @@ -52,16 +66,31 @@ pub fn try_tc_egress(ctx: TcContext) -> Result<i32, ()> {
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)
}
Expand All @@ -73,8 +102,31 @@ pub fn try_tc_egress(ctx: TcContext) -> Result<i32, ()> {
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)
}
Expand Down
11 changes: 8 additions & 3 deletions nflux.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
14 changes: 10 additions & 4 deletions nflux/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,21 @@ pub struct Firewall {
}

#[derive(Debug, Deserialize)]
pub struct Egress {
pub enabled: IsEnabled,
pub interfaces: Vec<String>,
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<String>,
pub virtual_interfaces: Vec<String>,
pub logging: EgressLogging,
}

// Generic rule for both IPv4 and IPv6
#[derive(Debug, Deserialize)]
pub struct FirewallRules {
Expand Down
38 changes: 35 additions & 3 deletions nflux/src/egress.rs
Original file line number Diff line number Diff line change
@@ -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") {
Expand Down
26 changes: 4 additions & 22 deletions nflux/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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 => {
Expand Down

0 comments on commit c98e64e

Please sign in to comment.