Use tail call to split the feature into multiple functions

This commit is contained in:
DaLaw2 2024-12-11 14:11:34 +08:00
parent 4e6a561321
commit 06d73da8f5
8 changed files with 269 additions and 43 deletions

5
Cargo.lock generated
View File

@ -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",

View File

@ -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,

View File

@ -58,6 +58,9 @@ fn ipv4_http_service_violation(
match protocol {
IpProto::Tcp => unsafe {
let offset = size_of::<EthHdr>() + size_of::<Ipv4Hdr>();
if start + offset + size_of::<TcpHdr>() > 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::<EthHdr>() + size_of::<Ipv6Hdr>();
if start + offset + size_of::<TcpHdr>() > 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;

View File

@ -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<Event> = 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<u32, ()> {
unsafe fn parsing(ctx: XdpContext) -> Result<u32, ()> {
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<u32, ()> {
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<u32, ()> {
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<u32, ()> {
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<u32, ()> {
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
unsafe { core::hint::unreachable_unchecked() }
}

View File

@ -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<bool, ()> {
let eth_size = size_of::<EthHdr>();
if start + eth_size > end {
return Ok(false);
}
Ok(true)
}
#[inline(always)]
pub fn validate_ipv4_bounds(start: usize, end: usize) -> Result<bool, ()> {
let eth_size = size_of::<EthHdr>();
let ipv4_size = size_of::<Ipv4Hdr>();
if start + eth_size + ipv4_size > end {
return Ok(false);
}
Ok(true)
}
#[inline(always)]
pub fn validate_ipv6_bounds(start: usize, end: usize) -> Result<bool, ()> {
let eth_size = size_of::<EthHdr>();
let ipv6_size = size_of::<Ipv6Hdr>();
if start + eth_size + ipv6_size > end {
return Ok(false);
}
Ok(true)
}

View File

@ -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<EtherType, ()> {
pub fn parse_packet(start: usize, end: usize) -> Result<Event, ()> {
if start + size_of::<EthHdr>() > 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<IPv4Event, ()> {
pub fn parse_ipv4_packet(start: usize, end: usize) -> Result<Event, ()> {
let mut offset = size_of::<EthHdr>();
if start + offset + size_of::<Ipv4Hdr>() > end {
return Err(());
@ -36,10 +38,11 @@ pub fn parse_ipv4_packet(start: usize, end: usize) -> Result<IPv4Event, ()> {
_ => 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<IPv4Event, ()> {
}
#[inline(always)]
pub fn parse_ipv6_packet(start: usize, end: usize) -> Result<IPv6Event, ()> {
pub fn parse_ipv6_packet(start: usize, end: usize) -> Result<Event, ()> {
let mut offset = size_of::<EthHdr>();
if start + offset + size_of::<Ipv6Hdr>() > end {
return Err(());
@ -66,7 +69,8 @@ pub fn parse_ipv6_packet(start: usize, end: usize) -> Result<IPv6Event, ()> {
_ => return Err(()),
};
Ok(IPv6Event {
Ok(Event {
eth_type: EtherType::Ipv6,
protocol,
source_ip,
destination_ip,

View File

@ -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 }

View File

@ -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<RwLock<System>> = OnceLock::new();
pub struct System {
pub ebpf: Ebpf,
pub boot_time: u64,
program_array: ProgramArray<MapData>,
}
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<MapData>,
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;