From 06d73da8f5886ba01f0dffa1a98ea12416bed934 Mon Sep 17 00:00:00 2001 From: DaLaw2 Date: Wed, 11 Dec 2024 14:11:34 +0800 Subject: [PATCH] Use tail call to split the feature into multiple functions --- Cargo.lock | 5 +- net-guardia-common/src/model/event.rs | 66 +++++++++++++ net-guardia-ebpf/src/action/service.rs | 6 ++ net-guardia-ebpf/src/main.rs | 124 ++++++++++++++++++++----- net-guardia-ebpf/src/utils/mod.rs | 32 +++++++ net-guardia-ebpf/src/utils/parsing.rs | 26 +++--- net-guardia/Cargo.toml | 4 +- net-guardia/src/core/system.rs | 49 +++++++++- 8 files changed, 269 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5563b42..d0ca0c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1208,6 +1208,7 @@ dependencies = [ "aya-log", "cargo_metadata", "lazy_static", + "libc", "mime_guess", "net-guardia-common", "rust-embed", @@ -1786,9 +1787,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.32.1" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c33cd241af0f2e9e3b5c32163b873b29956890b5342e6745b917ce9d490f4af" +checksum = "948512566b1895f93b1592c7574baeb2de842f224f2aab158799ecadb8ebbb46" dependencies = [ "core-foundation-sys", "libc", diff --git a/net-guardia-common/src/model/event.rs b/net-guardia-common/src/model/event.rs index 833f4be..7542825 100644 --- a/net-guardia-common/src/model/event.rs +++ b/net-guardia-common/src/model/event.rs @@ -1,6 +1,72 @@ +use network_types::eth::EtherType; use crate::model::ip_address::{EbpfAddrPortV4, EbpfAddrPortV6}; use network_types::ip::IpProto; +pub struct Event { + pub eth_type: EtherType, + pub protocol: IpProto, + pub source_ip: u128, + pub destination_ip: u128, + pub source_port: u16, + pub destination_port: u16, + pub len: u32, + pub timestamp: u64 +} + +impl Event { + #[inline(always)] + pub fn to_ipv4_event(&self) -> IPv4Event { + IPv4Event { + protocol: self.protocol, + source_ip: self.source_ip as u32, + destination_ip: self.destination_ip as u32, + source_port: self.source_port, + destination_port: self.destination_port, + len: self.len, + timestamp: self.timestamp, + } + } + + #[inline(always)] + pub fn to_ipv6_event(&self) -> IPv6Event { + IPv6Event { + protocol: self.protocol, + source_ip: self.source_ip, + destination_ip: self.destination_ip, + source_port: self.source_port, + destination_port: self.destination_port, + len: self.len, + timestamp: self.timestamp, + } + } + + #[inline(always)] + pub fn into_ipv4_event(self) -> IPv4Event { + IPv4Event { + protocol: self.protocol, + source_ip: self.source_ip as u32, + destination_ip: self.destination_ip as u32, + source_port: self.source_port, + destination_port: self.destination_port, + len: self.len, + timestamp: self.timestamp, + } + } + + #[inline(always)] + pub fn into_ipv6_event(self) -> IPv6Event { + IPv6Event { + protocol: self.protocol, + source_ip: self.source_ip, + destination_ip: self.destination_ip, + source_port: self.source_port, + destination_port: self.destination_port, + len: self.len, + timestamp: self.timestamp, + } + } +} + pub struct IPv4Event { pub protocol: IpProto, pub source_ip: u32, diff --git a/net-guardia-ebpf/src/action/service.rs b/net-guardia-ebpf/src/action/service.rs index 18ccab1..4df9dfe 100644 --- a/net-guardia-ebpf/src/action/service.rs +++ b/net-guardia-ebpf/src/action/service.rs @@ -58,6 +58,9 @@ fn ipv4_http_service_violation( match protocol { IpProto::Tcp => unsafe { let offset = size_of::() + size_of::(); + if start + offset + size_of::() > end { + return false + } let tcp_header = &*((start + offset) as *const TcpHdr); if tcp_header.syn() != 0 || tcp_header.rst() != 0 || tcp_header.fin() != 0 { return false; @@ -87,6 +90,9 @@ fn ipv6_http_service_violation( match protocol { IpProto::Tcp => unsafe { let offset = size_of::() + size_of::(); + if start + offset + size_of::() > end { + return false; + } let tcp_header = &*((start + offset) as *const TcpHdr); if tcp_header.syn() != 0 || tcp_header.rst() != 0 || tcp_header.fin() != 0 { return false; diff --git a/net-guardia-ebpf/src/main.rs b/net-guardia-ebpf/src/main.rs index d403f97..bbe1321 100644 --- a/net-guardia-ebpf/src/main.rs +++ b/net-guardia-ebpf/src/main.rs @@ -1,53 +1,131 @@ #![no_std] #![no_main] - mod action; mod utils; -use crate::utils::parsing; -use action::{blocking, monitor}; -use aya_ebpf::{bindings::xdp_action, macros::xdp, programs::XdpContext}; -#[allow(unused_imports)] -use aya_log_ebpf::info; -use network_types::eth::EtherType; use crate::action::{defence, service}; +use crate::utils::{parsing, validate_ipv4_bounds, validate_ipv6_bounds, validate_packet_bounds}; +use action::{blocking, monitor}; +use aya_ebpf::helpers::bpf_tail_call; +use aya_ebpf::macros::{map, xdp}; +use aya_ebpf::maps::{Array, PerCpuArray, ProgramArray}; +use aya_ebpf::{bindings::xdp_action, programs::XdpContext}; +use aya_log_ebpf::{error, info}; +use net_guardia_common::model::event::Event; +use network_types::eth::EtherType; + +#[map] +static PROGRAM_ARRAY: ProgramArray = ProgramArray::with_max_entries(4, 0); +#[map] +static PARSED_PACKET: PerCpuArray = PerCpuArray::with_max_entries(1, 0); #[xdp] pub fn net_guardia(ctx: XdpContext) -> u32 { - match try_net_guardia(ctx) { + match unsafe { parsing(ctx) } { Ok(ret) => ret, Err(_) => xdp_action::XDP_PASS, } } -fn try_net_guardia(ctx: XdpContext) -> Result { +unsafe fn parsing(ctx: XdpContext) -> Result { let start = ctx.data(); let end = ctx.data_end(); - match parsing::parse_ether_type(start, end)? { + let event = parsing::parse_packet(start, end)?; + let ptr = PARSED_PACKET.get_ptr_mut(0).ok_or(())?; + let parsed_packet = ptr.as_mut().ok_or(())?; + *parsed_packet = event; + if PROGRAM_ARRAY.tail_call(&ctx, 0).is_err() { + error!(&ctx, "Tail call failed"); + } + Err(()) +} + +#[xdp] +pub fn blocking(ctx: XdpContext) -> u32 { + match unsafe { try_blocking(ctx) } { + Ok(ret) => ret, + Err(_) => xdp_action::XDP_PASS, + } +} + +unsafe fn try_blocking(ctx: XdpContext) -> Result { + let start = ctx.data(); + let end = ctx.data_end(); + let ptr = PARSED_PACKET.get_ptr(0).ok_or(())?; + let parsed_packet = ptr.read_unaligned(); + match parsed_packet.eth_type { EtherType::Ipv4 => { - let event = parsing::parse_ipv4_packet(start, end)?; + let event = parsed_packet.into_ipv4_event(); if blocking::ipv4_should_block(&event) { return Ok(xdp_action::XDP_DROP); } - if service::ipv4_service_rule_violation(start, end, &event) { - return Ok(xdp_action::XDP_DROP); - } - // if defence::ipv4_is_attack(&event) { - // return Ok(xdp_action::XDP_DROP); - // } - monitor::ipv4_update_stats(&event); } EtherType::Ipv6 => { - let event = parsing::parse_ipv6_packet(start, end)?; + let event = parsed_packet.into_ipv6_event(); if blocking::ipv6_should_block(&event) { return Ok(xdp_action::XDP_DROP); } + } + _ => Err(())?, + } + if PROGRAM_ARRAY.tail_call(&ctx, 1).is_err() { + error!(&ctx, "Tail call failed"); + } + Err(()) +} + +#[xdp] +pub fn service(ctx: XdpContext) -> u32 { + match unsafe { try_service(ctx) } { + Ok(ret) => ret, + Err(_) => xdp_action::XDP_PASS, + } +} + +unsafe fn try_service(ctx: XdpContext) -> Result { + let start = ctx.data(); + let end = ctx.data_end(); + let ptr = PARSED_PACKET.get_ptr(0).ok_or(())?; + let parsed_packet = ptr.read_unaligned(); + match parsed_packet.eth_type { + EtherType::Ipv4 => { + let event = parsed_packet.into_ipv4_event(); + if service::ipv4_service_rule_violation(start, end, &event) { + return Ok(xdp_action::XDP_DROP); + } + } + EtherType::Ipv6 => { + let event = parsed_packet.into_ipv6_event(); if service::ipv6_service_rule_violation(start, end, &event) { return Ok(xdp_action::XDP_DROP); } - // if defence::ipv6_is_attack(&event) { - // return Ok(xdp_action::XDP_DROP); - // } + } + _ => Err(())?, + } + if PROGRAM_ARRAY.tail_call(&ctx, 2).is_err() { + error!(&ctx, "Fail call monitor function"); + } + Err(()) +} + +#[xdp] +pub fn monitor(ctx: XdpContext) -> u32 { + match unsafe { try_monitor(ctx) } { + Ok(ret) => ret, + Err(_) => xdp_action::XDP_PASS, + } +} + +unsafe fn try_monitor(_: XdpContext) -> Result { + let ptr = PARSED_PACKET.get_ptr(0).ok_or(())?; + let parsed_packet = ptr.read_unaligned(); + match parsed_packet.eth_type { + EtherType::Ipv4 => { + let event = parsed_packet.into_ipv4_event(); + monitor::ipv4_update_stats(&event); + } + EtherType::Ipv6 => { + let event = parsed_packet.into_ipv6_event(); monitor::ipv6_update_stats(&event); } _ => Err(())?, @@ -57,5 +135,5 @@ fn try_net_guardia(ctx: XdpContext) -> Result { #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { - loop {} + unsafe { core::hint::unreachable_unchecked() } } diff --git a/net-guardia-ebpf/src/utils/mod.rs b/net-guardia-ebpf/src/utils/mod.rs index cc52abf..f60084d 100644 --- a/net-guardia-ebpf/src/utils/mod.rs +++ b/net-guardia-ebpf/src/utils/mod.rs @@ -1,2 +1,34 @@ +use network_types::eth::EthHdr; +use network_types::ip::{Ipv4Hdr, Ipv6Hdr}; + pub mod change_destination; pub mod parsing; + +#[inline(always)] +pub fn validate_packet_bounds(start: usize, end: usize) -> Result { + let eth_size = size_of::(); + if start + eth_size > end { + return Ok(false); + } + Ok(true) +} + +#[inline(always)] +pub fn validate_ipv4_bounds(start: usize, end: usize) -> Result { + let eth_size = size_of::(); + let ipv4_size = size_of::(); + if start + eth_size + ipv4_size > end { + return Ok(false); + } + Ok(true) +} + +#[inline(always)] +pub fn validate_ipv6_bounds(start: usize, end: usize) -> Result { + let eth_size = size_of::(); + let ipv6_size = size_of::(); + if start + eth_size + ipv6_size > end { + return Ok(false); + } + Ok(true) +} diff --git a/net-guardia-ebpf/src/utils/parsing.rs b/net-guardia-ebpf/src/utils/parsing.rs index e22fc6b..e7069ee 100644 --- a/net-guardia-ebpf/src/utils/parsing.rs +++ b/net-guardia-ebpf/src/utils/parsing.rs @@ -1,5 +1,5 @@ use aya_ebpf::helpers::bpf_ktime_get_ns; -use net_guardia_common::model::event::{IPv4Event, IPv6Event}; +use net_guardia_common::model::event::Event; use network_types::{ eth::{EthHdr, EtherType}, ip::{IpProto, Ipv4Hdr, Ipv6Hdr}, @@ -7,18 +7,20 @@ use network_types::{ udp::UdpHdr, }; -#[inline(always)] -pub fn parse_ether_type(start: usize, end: usize) -> Result { +pub fn parse_packet(start: usize, end: usize) -> Result { if start + size_of::() > end { return Err(()); } let eth = unsafe { &*(start as *const EthHdr) }; - - Ok(eth.ether_type) + match eth.ether_type { + EtherType::Ipv4 => parse_ipv4_packet(start, end), + EtherType::Ipv6 => parse_ipv6_packet(start, end), + _ => Err(()) + } } #[inline(always)] -pub fn parse_ipv4_packet(start: usize, end: usize) -> Result { +pub fn parse_ipv4_packet(start: usize, end: usize) -> Result { let mut offset = size_of::(); if start + offset + size_of::() > end { return Err(()); @@ -36,10 +38,11 @@ pub fn parse_ipv4_packet(start: usize, end: usize) -> Result { _ => return Err(()), }; - Ok(IPv4Event { + Ok(Event { + eth_type: EtherType::Ipv4, protocol, - source_ip, - destination_ip, + source_ip: source_ip as u128, + destination_ip: destination_ip as u128, source_port, destination_port, len: (end - start) as u32, @@ -48,7 +51,7 @@ pub fn parse_ipv4_packet(start: usize, end: usize) -> Result { } #[inline(always)] -pub fn parse_ipv6_packet(start: usize, end: usize) -> Result { +pub fn parse_ipv6_packet(start: usize, end: usize) -> Result { let mut offset = size_of::(); if start + offset + size_of::() > end { return Err(()); @@ -66,7 +69,8 @@ pub fn parse_ipv6_packet(start: usize, end: usize) -> Result { _ => return Err(()), }; - Ok(IPv6Event { + Ok(Event { + eth_type: EtherType::Ipv6, protocol, source_ip, destination_ip, diff --git a/net-guardia/Cargo.toml b/net-guardia/Cargo.toml index 5d966b0..b88827a 100644 --- a/net-guardia/Cargo.toml +++ b/net-guardia/Cargo.toml @@ -10,7 +10,7 @@ anyhow = { workspace = true, default-features = true } aya = { workspace = true } aya-log = { workspace = true } tokio = { workspace = true, features = ["macros", "rt", "rt-multi-thread", "net", "signal", "sync", "time", "fs"] } - +libc = { workspace = true } serde = { version = "1.0.215", features = ["derive"] } toml = "0.8.19" tracing = "0.1.41" @@ -25,7 +25,7 @@ mime_guess = "2.0.5" actix-web-actors = "4.3.0" serde_json = "1.0.133" lazy_static = "1.5.0" -sysinfo = "0.32.1" +sysinfo = "0.33.0" [build-dependencies] cargo_metadata = { workspace = true } diff --git a/net-guardia/src/core/system.rs b/net-guardia/src/core/system.rs index 72f2d82..106ccf5 100644 --- a/net-guardia/src/core/system.rs +++ b/net-guardia/src/core/system.rs @@ -1,4 +1,5 @@ use crate::core::config_manager::ConfigManager; +use crate::core::control::Control; use crate::core::monitor::Monitor; use crate::utils::log_entry::ebpf::EbpfEntry; use crate::utils::log_entry::system::SystemEntry; @@ -7,19 +8,22 @@ use crate::web::api::{control, default, misc, monitor}; use actix_web::web::route; use actix_web::{App, HttpServer}; use anyhow::Context; +use aya::maps::{Array, MapData, ProgramArray}; use aya::programs::{Xdp, XdpFlags}; use aya::Ebpf; use std::sync::OnceLock; -use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; -use tracing::{error, info, warn}; -use crate::core::control::Control; +use std::time::Duration; use sysinfo::System as SystemInfo; +use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; +use tokio::time::sleep; +use tracing::{debug, error, info, warn}; static SYSTEM: OnceLock> = OnceLock::new(); pub struct System { pub ebpf: Ebpf, pub boot_time: u64, + program_array: ProgramArray, } impl System { @@ -37,6 +41,8 @@ impl System { async fn ebpf_initialize() -> anyhow::Result<()> { let config = ConfigManager::now().await; let interface = config.ingress_ifindex; + let boot_time = SystemInfo::boot_time() * 1_000_000_000; + Self::set_memory_limit()?; let mut ebpf = Ebpf::load(aya::include_bytes_aligned!(concat!( env!("OUT_DIR"), "/net-guardia" @@ -45,17 +51,50 @@ impl System { error!("{}", e); warn!("{}", EbpfEntry::LoggerInitializeFailed); } + let mut program_array = ProgramArray::try_from(ebpf.take_map("PROGRAM_ARRAY").unwrap())?; + Self::load_program(&mut ebpf, &mut program_array, "blocking", 0)?; + Self::load_program(&mut ebpf, &mut program_array, "service", 1)?; + Self::load_program(&mut ebpf, &mut program_array, "monitor", 2)?; let program: &mut Xdp = ebpf.program_mut("net_guardia").unwrap().try_into()?; program.load()?; program .attach(&interface, XdpFlags::default()) .context(EbpfEntry::AttachProgramFailed)?; - let boot_time = SystemInfo::boot_time() * 1_000_000_000; - SYSTEM.get_or_init(|| RwLock::new(System { ebpf, boot_time })); + let system = System { + ebpf, + boot_time, + program_array, + }; + SYSTEM.get_or_init(|| RwLock::new(system)); info!("{}", EbpfEntry::AttachProgramSuccess); Ok(()) } + fn set_memory_limit() -> anyhow::Result<()> { + let rlim = libc::rlimit { + rlim_cur: libc::RLIM_INFINITY, + rlim_max: libc::RLIM_INFINITY, + }; + let ret = unsafe { libc::setrlimit(libc::RLIMIT_MEMLOCK, &rlim) }; + if ret != 0 { + info!("Failed to remove limit on locked memory, ret is: {}", ret); + } + Ok(()) + } + + fn load_program( + ebpf: &mut Ebpf, + program_array: &mut ProgramArray, + function_name: &str, + index: u32, + ) -> anyhow::Result<()> { + let program: &mut Xdp = ebpf.program_mut(function_name).unwrap().try_into()?; + program.load()?; + let fd = program.fd()?; + program_array.set(index, fd, 0)?; + Ok(()) + } + pub async fn run() -> anyhow::Result<()> { info!("{}", SystemEntry::Online); Monitor::run().await;