mirror of
https://github.com/DaLaw2/NetGuardia.git
synced 2026-08-24 14:10:28 +09:00
Use tail call to split the feature into multiple functions
This commit is contained in:
parent
4e6a561321
commit
06d73da8f5
5
Cargo.lock
generated
5
Cargo.lock
generated
@ -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",
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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() }
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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 }
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user