wip: Add AF_XDP support (#8)

* wip: remove singleton pattern

* wip: Fix transmission func not working

* wip: Fix transmission func not working

* fix: Fix ebpf run on bpflib 0.5 error

* fix: Build frontend error and websocket not work

* misc: Use AF_XDP replace RingBuffer

* feat: Complete XskManager and Xsk

* fix: Ingress Ebpf Attach Failed
This commit is contained in:
DaLaw2 2025-10-13 13:16:44 +08:00 committed by GitHub
parent 24af672c41
commit 97d511d17a
49 changed files with 1643 additions and 1846 deletions

132
Cargo.lock generated
View File

@ -11,7 +11,7 @@ dependencies = [
"actix-macros",
"actix-rt",
"actix_derive",
"bitflags",
"bitflags 2.9.4",
"bytes",
"crossbeam-channel",
"futures-core",
@ -33,7 +33,7 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a"
dependencies = [
"bitflags",
"bitflags 2.9.4",
"bytes",
"futures-core",
"futures-sink",
@ -70,7 +70,7 @@ dependencies = [
"actix-service",
"actix-utils",
"base64",
"bitflags",
"bitflags 2.9.4",
"brotli",
"bytes",
"bytestring",
@ -316,7 +316,7 @@ checksum = "d18bc4e506fbb85ab7392ed993a7db4d1a452c71b75a246af4a80ab8c9d2dd50"
dependencies = [
"assert_matches",
"aya-obj",
"bitflags",
"bitflags 2.9.4",
"bytes",
"libc",
"log",
@ -458,9 +458,15 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bitflags"
version = "2.8.0"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
[[package]]
name = "block-buffer"
@ -627,6 +633,19 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-epoch",
"crossbeam-queue",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.14"
@ -636,6 +655,34 @@ dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-queue"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
@ -1088,7 +1135,7 @@ version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b"
dependencies = [
"bitflags",
"bitflags 2.9.4",
"cfg-if",
"libc",
]
@ -1120,6 +1167,16 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libbpf-sys"
version = "0.6.2+v0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c2c15bbeae2b87e3a63feea85a579272ed082f0f4d8f0e7968cc9a17e1b4d69"
dependencies = [
"cc",
"pkg-config",
]
[[package]]
name = "libc"
version = "0.2.174"
@ -1157,19 +1214,18 @@ checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487"
[[package]]
name = "lock_api"
version = "0.4.12"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.25"
version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]]
name = "macros"
@ -1253,11 +1309,13 @@ dependencies = [
"aya-log",
"cargo_metadata",
"common",
"crossbeam",
"dotenvy",
"futures-util",
"libc",
"macros",
"mime_guess",
"parking_lot",
"rust-embed",
"serde",
"serde_json",
@ -1268,6 +1326,7 @@ dependencies = [
"tracing",
"tracing-appender",
"tracing-subscriber",
"xsk-rs",
]
[[package]]
@ -1338,7 +1397,7 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166"
dependencies = [
"bitflags",
"bitflags 2.9.4",
]
[[package]]
@ -1380,9 +1439,9 @@ dependencies = [
[[package]]
name = "parking_lot"
version = "0.12.3"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
dependencies = [
"lock_api",
"parking_lot_core",
@ -1390,15 +1449,15 @@ dependencies = [
[[package]]
name = "parking_lot_core"
version = "0.9.10"
version = "0.9.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets",
"windows-link 0.2.0",
]
[[package]]
@ -1528,7 +1587,7 @@ version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
dependencies = [
"bitflags",
"bitflags 2.9.4",
]
[[package]]
@ -1612,7 +1671,7 @@ version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
dependencies = [
"bitflags",
"bitflags 2.9.4",
"errno",
"libc",
"linux-raw-sys",
@ -2284,7 +2343,7 @@ dependencies = [
"windows-collections",
"windows-core",
"windows-future",
"windows-link",
"windows-link 0.1.3",
"windows-numerics",
]
@ -2305,7 +2364,7 @@ checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link",
"windows-link 0.1.3",
"windows-result",
"windows-strings",
]
@ -2317,7 +2376,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
dependencies = [
"windows-core",
"windows-link",
"windows-link 0.1.3",
"windows-threading",
]
@ -2349,6 +2408,12 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]]
name = "windows-link"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
[[package]]
name = "windows-numerics"
version = "0.2.0"
@ -2356,7 +2421,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
dependencies = [
"windows-core",
"windows-link",
"windows-link 0.1.3",
]
[[package]]
@ -2365,7 +2430,7 @@ version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
dependencies = [
"windows-link",
"windows-link 0.1.3",
]
[[package]]
@ -2374,7 +2439,7 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
dependencies = [
"windows-link",
"windows-link 0.1.3",
]
[[package]]
@ -2417,7 +2482,7 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
dependencies = [
"windows-link",
"windows-link 0.1.3",
]
[[package]]
@ -2501,6 +2566,19 @@ version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
[[package]]
name = "xsk-rs"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb9ed3e0b3a5cdd470cf7bc74000ec4888881cb275a558aaead48c32fd801dee"
dependencies = [
"bitflags 1.3.2",
"cfg-if",
"libbpf-sys",
"libc",
"log",
]
[[package]]
name = "yoke"
version = "0.7.5"

View File

@ -8,11 +8,11 @@ aya = { version = "0.13.1", default-features = false }
aya-ebpf = { version = "0.1.1", default-features = false }
aya-log = { version = "0.2.1", default-features = false }
aya-log-ebpf = { version = "0.1.1", default-features = false }
cargo_metadata = { version = "0.22.0", default-features = false }
libc = { version = "0.2.159", default-features = false }
serde = { version = "1.0.215", features = ["derive"] }
network-types = "0.1.0"
serde = { version = "1.0.215", features = ["derive"] }
xsk-rs = { version = "0.4.1", default-features = false }
[profile.dev]
panic = "abort"

View File

@ -1,3 +1,4 @@
pub mod offset;
pub mod other;
pub mod program_array;
pub mod setting;

View File

@ -0,0 +1,10 @@
pub mod ingress {
pub const ACCESS_CONTROL: u32 = 0;
pub const SERVICE: u32 = 1;
pub const STATISTICS: u32 = 2;
pub const TRANSMISSION: u32 = 3;
}
pub mod egress {
pub const STATISTICS: u32 = 0;
}

View File

@ -3,12 +3,14 @@ use network_types::ip::IpProto;
use crate::model::ip_address::{AddrPortV4, AddrPortV6};
#[repr(C, align(8))]
#[derive(Clone)]
pub enum Event {
IPv4(IPv4Event),
IPv6(IPv6Event),
}
#[repr(C, align(8))]
#[derive(Clone)]
pub struct IPv4Event {
pub protocol: IpProto,
pub source_ip: u32,
@ -22,22 +24,17 @@ pub struct IPv4Event {
impl IPv4Event {
#[inline(always)]
pub fn source_addr(&self) -> AddrPortV4 {
AddrPortV4 {
ip: self.source_ip,
port: self.source_port,
}
AddrPortV4::new(self.source_ip, self.source_port)
}
#[inline(always)]
pub fn destination_addr(&self) -> AddrPortV4 {
AddrPortV4 {
ip: self.destination_ip,
port: self.destination_port,
}
AddrPortV4::new(self.destination_ip, self.destination_port)
}
}
#[repr(C, align(8))]
#[derive(Clone)]
pub struct IPv6Event {
pub protocol: IpProto,
pub source_ip: u128,
@ -51,17 +48,11 @@ pub struct IPv6Event {
impl IPv6Event {
#[inline(always)]
pub fn source_addr(&self) -> AddrPortV6 {
AddrPortV6 {
ip: self.source_ip,
port: self.source_port,
}
AddrPortV6::new(self.source_ip, self.source_port)
}
#[inline(always)]
pub fn destination_addr(&self) -> AddrPortV6 {
AddrPortV6 {
ip: self.destination_ip,
port: self.destination_port,
}
AddrPortV6::new(self.destination_ip, self.destination_port)
}
}

View File

@ -5,32 +5,72 @@ pub type IPv4 = u32;
pub type IPv6 = u128;
pub type Port = u16;
#[repr(C, align(8))]
#[derive(Copy, Clone)]
pub struct AddrPortV4 {
pub ip: IPv4,
pub port: Port,
}
#[repr(transparent)]
#[derive(Debug, Copy, Clone)]
pub struct AddrPortV4([u8; 8]);
impl AddrPortV4 {
pub fn new(ip: IPv4, port: Port) -> Self {
AddrPortV4 { ip, port }
#[inline(always)]
pub fn new(ip: u32, port: u16) -> Self {
let mut key = [0u8; 8];
key[0..4].copy_from_slice(&ip.to_ne_bytes());
key[4..6].copy_from_slice(&port.to_ne_bytes());
Self(key)
}
#[inline(always)]
pub fn as_bytes(&self) -> &[u8; 8] {
&self.0
}
#[inline(always)]
pub fn ip(&self) -> IPv4 {
let mut ip_bytes = [0u8; 4];
ip_bytes.copy_from_slice(&self.0[0..4]);
u32::from_ne_bytes(ip_bytes)
}
#[inline(always)]
pub fn port(&self) -> Port {
let mut port_bytes = [0u8; 2];
port_bytes.copy_from_slice(&self.0[4..6]);
u16::from_ne_bytes(port_bytes)
}
}
#[cfg(feature = "user")]
unsafe impl Pod for AddrPortV4 {}
#[repr(C, align(8))]
#[derive(Copy, Clone)]
pub struct AddrPortV6 {
pub ip: IPv6,
pub port: Port,
}
#[repr(transparent)]
#[derive(Debug, Copy, Clone)]
pub struct AddrPortV6([u8; 32]);
impl AddrPortV6 {
pub fn new(ip: IPv6, port: Port) -> Self {
AddrPortV6 { ip, port }
#[inline(always)]
pub fn new(ip: u128, port: u16) -> Self {
let mut key = [0u8; 32];
key[0..16].copy_from_slice(&ip.to_ne_bytes());
key[16..18].copy_from_slice(&port.to_ne_bytes());
Self(key)
}
#[inline(always)]
pub fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
#[inline(always)]
pub fn ip(&self) -> IPv6 {
let mut ip_bytes = [0u8; 16];
ip_bytes.copy_from_slice(&self.0[0..16]);
u128::from_ne_bytes(ip_bytes)
}
#[inline(always)]
pub fn port(&self) -> Port {
let mut port_bytes = [0u8; 2];
port_bytes.copy_from_slice(&self.0[16..18]);
u16::from_ne_bytes(port_bytes)
}
}

View File

@ -1,14 +1,5 @@
use crate::define::other::STANDARD_MTU;
use crate::model::event::Event;
#[repr(C, align(8))]
pub struct Packet {
pub event: Event,
pub raw_data: [u8; STANDARD_MTU],
}
impl Packet {
pub fn new(event: Event, raw_data: [u8; STANDARD_MTU]) -> Self {
Self { event, raw_data }
}
}
#[repr(transparent)]
pub struct Packet(pub [u8; size_of::<Event>() + STANDARD_MTU]);

View File

@ -1,7 +1,12 @@
[Config]
ingress_ifindex = "enp2s0f0" # nic name
egress_ifindex = "enp2s0f1" # nic name
management_ifindex = "enp4s0" # nic name
alert_path = "/tmp/alert"
http_server_bind_port = 8080 # port
refresh_interval = 1 # seconds
ingress_ifname = "enp4s0f0" # Ingress NIC Name
egress_ifname = "enp4s0f1" # Egress NIC Name
combined_queue_count = 8 # NIC Combined Queue Count (ethtool -l <NIC>)
fill_queue_size = 2048 # Umem Used (Should not modify)
comp_queue_size = 2048 # Umem Used (Should not modify)
tx_queue_size = 2048 # Umem Used (Should not modify)
rx_queue_size = 2048 # Umem Used (Should not modify)
frame_size = 2048 # Umem Used (Should not modify)
frame_count = 2048 # Umem Used (Should not modify)
http_server_bind_port = 8080 # Http Server Listen Port
refresh_interval = 5 # Statistics Refresh Time

View File

@ -7,9 +7,10 @@ use aya_ebpf::bindings::xdp_action;
use aya_ebpf::macros::{map, xdp};
use aya_ebpf::maps::{PerCpuArray, ProgramArray};
use aya_ebpf::programs::XdpContext;
use aya_log_ebpf::error;
#[allow(unused_imports)]
use aya_log_ebpf::info;
use common::define::program_array::egress::*;
use common::{ebpf::parsing, model::event::Event};
use network_types::eth::EtherType;
#[map]
static PROGRAM_ARRAY: ProgramArray = ProgramArray::with_max_entries(8, 0);
@ -18,23 +19,29 @@ static PARSED_PACKET: PerCpuArray<Event> = PerCpuArray::with_max_entries(1, 0);
#[xdp]
pub fn net_guardia(ctx: XdpContext) -> u32 {
unsafe { packet_intake(ctx).unwrap_or(xdp_action::XDP_PASS) }
unsafe {
let _ = packet_intake(ctx);
xdp_action::XDP_PASS
}
}
unsafe fn packet_intake(ctx: XdpContext) -> Result<u32, ()> {
let start = ctx.data();
let end = ctx.data_end();
let ptr = PARSED_PACKET.get_ptr_mut(0).ok_or(())?;
parsing::parse_packet(start, end, ptr)?;
if unsafe { PROGRAM_ARRAY.tail_call(&ctx, 0).is_err() } {
error!(&ctx, "Tail call failed");
unsafe {
let start = ctx.data();
let end = ctx.data_end();
let ptr = PARSED_PACKET.get_ptr_mut(0).ok_or(())?;
parsing::parse_packet(start, end, ptr)?;
let _ = PROGRAM_ARRAY.tail_call(&ctx, STATISTICS);
Err(())
}
Ok(xdp_action::XDP_PASS)
}
#[xdp]
pub fn statistics(ctx: XdpContext) -> u32 {
unsafe { try_statistics(ctx).unwrap_or(xdp_action::XDP_PASS) }
unsafe {
let _ = try_statistics(ctx);
xdp_action::XDP_PASS
}
}
unsafe fn try_statistics(_: XdpContext) -> Result<u32, ()> {

View File

@ -1,4 +1,3 @@
pub mod access_control;
pub mod service;
pub mod statistics;
pub mod transmission;

View File

@ -137,9 +137,9 @@ fn ipv4_ssh_service_violation(source: &AddrPortV4, destination: &AddrPortV4) ->
unsafe {
if IPV4_SSH_SERVICE.get(destination).is_some() {
if SSH_WHITE_LIST_ENABLE.get(0).is_some() {
IPV4_SSH_WHITE_LIST.get(&source.ip).is_none()
IPV4_SSH_WHITE_LIST.get(&source.ip()).is_none()
} else {
IPV4_SSH_BLACK_LIST.get(&source.ip).is_some()
IPV4_SSH_BLACK_LIST.get(&source.ip()).is_some()
}
} else {
false
@ -152,9 +152,9 @@ fn ipv6_ssh_service_violation(source_ip: &AddrPortV6, destination: &AddrPortV6)
unsafe {
if IPV6_SSH_SERVICE.get(destination).is_some() {
if SSH_WHITE_LIST_ENABLE.get(0).is_some() {
IPV6_SSH_WHITE_LIST.get(&source_ip.ip).is_none()
IPV6_SSH_WHITE_LIST.get(&source_ip.ip()).is_none()
} else {
IPV6_SSH_BLACK_LIST.get(&source_ip.ip).is_some()
IPV6_SSH_BLACK_LIST.get(&source_ip.ip()).is_some()
}
} else {
false

View File

@ -1,29 +0,0 @@
use aya_ebpf::{macros::map, maps::RingBuf};
use common::define::other::STANDARD_MTU;
use common::define::setting::MAX_BUFFERED_PACKETS;
use common::model::event::Event;
use common::model::packet::Packet;
#[map]
static PACKET_RING: RingBuf = RingBuf::with_byte_size((MAX_BUFFERED_PACKETS * STANDARD_MTU) as u32, 0);
pub fn transmission(start: usize, end: usize, event: *mut Event) -> Result<(), ()> {
if start + STANDARD_MTU > end {
return Err(());
}
if let Some(mut entry) = PACKET_RING.reserve::<Packet>(0) {
unsafe {
let packet_ptr = entry.as_mut_ptr();
core::ptr::copy_nonoverlapping(
event as *const u8,
&mut (*packet_ptr).event as *mut Event as *mut u8,
size_of::<Event>(),
);
core::ptr::copy_nonoverlapping(start as *const u8, (*packet_ptr).raw_data.as_mut_ptr(), STANDARD_MTU);
}
entry.submit(0);
Ok(())
} else {
Err(())
}
}

View File

@ -4,49 +4,67 @@ mod action;
use aya_ebpf::bindings::xdp_action;
use aya_ebpf::macros::{map, xdp};
use aya_ebpf::maps::{PerCpuArray, ProgramArray};
use aya_ebpf::maps::{PerCpuArray, ProgramArray, XskMap};
use aya_ebpf::programs::XdpContext;
use aya_log_ebpf::error;
#[allow(unused_imports)]
use aya_log_ebpf::info;
use common::define::program_array::ingress::*;
use common::ebpf::parsing;
use common::model::event::Event;
use crate::action::{access_control, service, statistics, transmission};
use crate::action::{access_control, service, statistics};
#[map]
static PROGRAM_ARRAY: ProgramArray = ProgramArray::with_max_entries(8, 0);
#[map]
static PARSED_PACKET: PerCpuArray<Event> = PerCpuArray::with_max_entries(1, 0);
#[map]
static XSKS_MAP: XskMap = XskMap::with_max_entries(64, 0);
#[xdp]
pub fn net_guardia(ctx: XdpContext) -> u32 {
unsafe { packet_intake(ctx).unwrap_or(xdp_action::XDP_PASS) }
unsafe {
let _ = packet_intake(&ctx);
let _ = PROGRAM_ARRAY.tail_call(&ctx, TRANSMISSION);
xdp_action::XDP_PASS
}
}
unsafe fn packet_intake(ctx: XdpContext) -> Result<u32, ()> {
let start = ctx.data();
let end = ctx.data_end();
let ptr = PARSED_PACKET.get_ptr_mut(0).ok_or(())?;
parsing::parse_packet(start, end, ptr)?;
transmission::transmission(start, end, ptr)?;
if unsafe { PROGRAM_ARRAY.tail_call(&ctx, 0).is_err() } {
error!(&ctx, "Tail call failed");
#[inline(always)]
unsafe fn packet_intake(ctx: &XdpContext) -> Result<u32, ()> {
unsafe {
let start = ctx.data();
let end = ctx.data_end();
let ptr = PARSED_PACKET.get_ptr_mut(0).ok_or(())?;
parsing::parse_packet(start, end, ptr)?;
let _ = PROGRAM_ARRAY.tail_call(ctx, ACCESS_CONTROL);
Err(())
}
Ok(xdp_action::XDP_PASS)
}
#[xdp]
pub fn access_control(ctx: XdpContext) -> u32 {
unsafe { try_access_control(ctx).unwrap_or(xdp_action::XDP_PASS) }
unsafe {
match try_access_control(&ctx) {
Ok(action) => action,
Err(_) => {
let _ = PROGRAM_ARRAY.tail_call(&ctx, TRANSMISSION);
xdp_action::XDP_PASS
}
}
}
}
unsafe fn try_access_control(ctx: XdpContext) -> Result<u32, ()> {
#[inline(always)]
unsafe fn try_access_control(ctx: &XdpContext) -> Result<u32, ()> {
unsafe {
let ptr = PARSED_PACKET.get_ptr(0).ok_or(())?;
let parsed_packet = &*ptr;
match parsed_packet {
Event::IPv4(event) => {
if access_control::ipv4_is_whitelisted(event) {
return Ok(xdp_action::XDP_PASS);
let _ = PROGRAM_ARRAY.tail_call(ctx, STATISTICS);
return Err(());
}
if access_control::ipv4_is_blacklisted(event) {
return Ok(xdp_action::XDP_DROP);
@ -54,26 +72,34 @@ unsafe fn try_access_control(ctx: XdpContext) -> Result<u32, ()> {
}
Event::IPv6(event) => {
if access_control::ipv6_is_whitelisted(event) {
return Ok(xdp_action::XDP_PASS);
let _ = PROGRAM_ARRAY.tail_call(ctx, STATISTICS);
return Err(());
}
if access_control::ipv6_is_blacklisted(event) {
return Ok(xdp_action::XDP_DROP);
}
}
}
if PROGRAM_ARRAY.tail_call(&ctx, 1).is_err() {
error!(&ctx, "Tail call failed");
}
let _ = PROGRAM_ARRAY.tail_call(ctx, SERVICE);
Err(())
}
}
#[xdp]
pub fn service(ctx: XdpContext) -> u32 {
unsafe { try_service(ctx).unwrap_or(xdp_action::XDP_PASS) }
unsafe {
match try_service(&ctx) {
Ok(action) => action,
Err(_) => {
let _ = PROGRAM_ARRAY.tail_call(&ctx, TRANSMISSION);
xdp_action::XDP_PASS
}
}
}
}
unsafe fn try_service(ctx: XdpContext) -> Result<u32, ()> {
#[inline(always)]
unsafe fn try_service(ctx: &XdpContext) -> Result<u32, ()> {
unsafe {
let start = ctx.data();
let end = ctx.data_end();
@ -91,19 +117,21 @@ unsafe fn try_service(ctx: XdpContext) -> Result<u32, ()> {
}
}
}
if PROGRAM_ARRAY.tail_call(&ctx, 2).is_err() {
error!(&ctx, "Tail call failed");
}
let _ = PROGRAM_ARRAY.tail_call(ctx, STATISTICS);
Err(())
}
}
#[xdp]
pub fn statistics(ctx: XdpContext) -> u32 {
unsafe { try_statistics(ctx).unwrap_or(xdp_action::XDP_PASS) }
unsafe {
let _ = try_statistics(&ctx);
xdp_action::XDP_PASS
}
}
unsafe fn try_statistics(_: XdpContext) -> Result<u32, ()> {
#[inline(always)]
unsafe fn try_statistics(ctx: &XdpContext) -> Result<u32, ()> {
unsafe {
let ptr = PARSED_PACKET.get_ptr(0).ok_or(())?;
let parsed_packet = &*ptr;
@ -115,7 +143,17 @@ unsafe fn try_statistics(_: XdpContext) -> Result<u32, ()> {
statistics::ipv6_update_stats(&event);
}
}
Ok(xdp_action::XDP_PASS)
let _ = PROGRAM_ARRAY.tail_call(ctx, TRANSMISSION);
Err(())
}
}
#[xdp]
pub fn transmission(ctx: XdpContext) -> u32 {
let queue_id = unsafe { (*ctx.ctx).rx_queue_index };
match XSKS_MAP.redirect(queue_id, 0) {
Ok(action) => action,
Err(_) => xdp_action::XDP_PASS,
}
}

View File

@ -10,10 +10,14 @@ macros = { path = "../macros" }
actix = "0.13.5"
actix-cors = "0.7.1"
actix-web = "4.11.0"
actix-ws = "0.3.0"
aya = { workspace = true }
aya-log = { workspace = true }
crossbeam = "0.8.4"
futures-util = "0.3.30"
libc = { workspace = true }
mime_guess = "2.0.5"
parking_lot = "0.12.5"
rust-embed = "8.7.2"
serde = { workspace = true }
serde_json = "1.0.143"
@ -24,9 +28,7 @@ toml = "0.9.5"
tracing = "0.1.41"
tracing-appender = "0.2.3"
tracing-subscriber = { version = "0.3.20", features = ["env-filter"] }
actix-ws = "0.3.0"
futures-util = "0.3.30"
xsk-rs = { workspace = true }
[build-dependencies]
cargo_metadata = { workspace = true }

View File

@ -259,9 +259,17 @@ fn build_frontend() {
panic!("FRONTEND_DIR environment variable is required but not set");
};
let Some(node_bin_dir) = env::var_os("NODE_PATH") else {
panic!("NODE_BIN_DIR environment variable is required but not set");
};
let project_root = env::var("CARGO_MANIFEST_DIR").unwrap();
let static_dir = PathBuf::from(project_root).join("static").join("web");
let frontend_dir = PathBuf::from(frontend_dir);
let node_bin_dir = PathBuf::from(node_bin_dir);
let npm_path = node_bin_dir.join("npm");
let npx_path = node_bin_dir.join("npx");
if !frontend_dir.exists() {
panic!("Frontend directory {:?} does not exist", frontend_dir);
@ -292,14 +300,22 @@ fn build_frontend() {
);
let out_dir = frontend_dir.join("out");
let need_build = needs_frontend_rebuild(&frontend_dir, &out_dir, &static_dir);
if !need_build {
return;
}
let mut cmd = Command::new("npm");
cmd.arg("install").current_dir(&frontend_dir);
let current_path = env::var("PATH").unwrap_or_default();
let new_path = if current_path.is_empty() {
node_bin_dir.to_string_lossy().to_string()
} else {
format!("{}:{}", node_bin_dir.to_string_lossy(), current_path)
};
let mut cmd = Command::new(&npm_path);
cmd.arg("install")
.current_dir(&frontend_dir)
.env("PATH", &new_path);
let status = cmd
.status()
@ -308,8 +324,10 @@ fn build_frontend() {
panic!("npm install failed with exit code: {:?}", status.code());
}
let mut cmd = Command::new("npx");
cmd.args(["next", "build"]).current_dir(&frontend_dir);
let mut cmd = Command::new(&npx_path);
cmd.args(["next", "build"])
.current_dir(&frontend_dir)
.env("PATH", &new_path);
let status = cmd
.status()

View File

@ -1,60 +0,0 @@
use std::fs;
use std::sync::OnceLock;
use std::sync::RwLock as SyncRwLock;
use macros::log;
use tokio::sync::RwLock as AsyncRwLock;
use crate::model::config::{Config, ConfigTable};
use crate::model::error::system::SystemError;
use crate::model::error::Error;
use crate::model::log::system::SystemLog;
static SYNC_CONFIG: OnceLock<SyncRwLock<Config>> = OnceLock::new();
static ASYNC_CONFIG: OnceLock<AsyncRwLock<Config>> = OnceLock::new();
pub struct AppConfig;
impl AppConfig {
pub async fn initialization() -> Result<(), Error> {
log!(SystemLog::Initializing);
let config = Self::load_config()?;
SYNC_CONFIG.get_or_init(|| SyncRwLock::new(config.clone()));
ASYNC_CONFIG.get_or_init(move || AsyncRwLock::new(config));
log!(SystemLog::InitializeComplete);
Ok(())
}
fn load_config() -> Result<Config, Error> {
let toml_string = fs::read_to_string("./config.toml").map_err(SystemError::ConfigNotFound)?;
let config_table = toml::from_str::<ConfigTable>(&toml_string).map_err(|_| SystemError::InvalidConfig)?;
let config = config_table.config;
if !Self::validate(&config) {
Err(SystemError::InvalidConfig)?
} else {
Ok(config)
}
}
pub fn now_blocking() -> Config {
// Initialization has been ensured
let once_lock = SYNC_CONFIG.get().unwrap();
// There is no lock acquired multiple times, so this is safe
once_lock.read().unwrap().clone()
}
pub async fn now() -> Config {
// Initialization has been ensured
let once_lock = ASYNC_CONFIG.get().unwrap();
// There is no lock acquired multiple times, so this is safe
once_lock.read().await.clone()
}
fn validate(config: &Config) -> bool {
Self::validate_second(config.refresh_interval)
}
fn validate_second(second: u64) -> bool {
second <= 3600
}
}

View File

@ -1,222 +0,0 @@
use std::collections::HashMap as StdHashMap;
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6};
use std::sync::OnceLock;
use aya::maps::{HashMap as AyaHashMap, MapData};
use aya::Pod;
use common::define::setting::MAX_RULES_PORT;
use common::model::ip_address::{IPv4, IPv6, Port};
use macros::log;
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use crate::core::system::System;
use crate::model::direction::FlowDirection;
use crate::model::error::ebpf::EbpfError;
use crate::model::error::Error;
use crate::model::ip_address::IntoNative;
use crate::model::list_type::ListType;
use crate::model::log::system::SystemLog;
use crate::utils::ip_address::convert_ports_to_vec;
static ACCESS_CONTROL: OnceLock<RwLock<AccessControl>> = OnceLock::new();
pub struct AccessControl {
ipv4_maps: StdHashMap<(FlowDirection, ListType), AccessMap<IPv4>>,
ipv6_maps: StdHashMap<(FlowDirection, ListType), AccessMap<IPv6>>,
}
impl AccessControl {
const MAP_CONFIGS: [((FlowDirection, ListType), (&'static str, &'static str)); 4] = [
(
(FlowDirection::Source, ListType::White),
("IPV4_SRC_WHITELIST", "IPV6_SRC_WHITELIST"),
),
(
(FlowDirection::Source, ListType::Black),
("IPV4_SRC_BLACKLIST", "IPV6_SRC_BLACKLIST"),
),
(
(FlowDirection::Destination, ListType::White),
("IPV4_DST_WHITELIST", "IPV6_DST_WHITELIST"),
),
(
(FlowDirection::Destination, ListType::Black),
("IPV4_DST_BLACKLIST", "IPV6_DST_BLACKLIST"),
),
];
pub async fn initialize() -> Result<(), Error> {
log!(SystemLog::Initializing);
let mut system = System::instance_mut().await;
let ebpf = &mut system.ingress_ebpf;
let mut ipv4_maps = StdHashMap::new();
let mut ipv6_maps = StdHashMap::new();
for (key, (ipv4_name, ipv6_name)) in Self::MAP_CONFIGS {
let ipv4_map = ebpf.take_map(ipv4_name).ok_or(EbpfError::MapNotFound)?;
let ipv6_map = ebpf.take_map(ipv6_name).ok_or(EbpfError::MapNotFound)?;
ipv4_maps.insert(
key,
AccessMap {
map: AyaHashMap::try_from(ipv4_map).map_err(EbpfError::MapOperationError)?,
},
);
ipv6_maps.insert(
key,
AccessMap {
map: AyaHashMap::try_from(ipv6_map).map_err(EbpfError::MapOperationError)?,
},
);
}
ACCESS_CONTROL.get_or_init(|| RwLock::new(AccessControl { ipv4_maps, ipv6_maps }));
log!(SystemLog::InitializeComplete);
Ok(())
}
#[inline(always)]
pub async fn instance() -> RwLockReadGuard<'static, AccessControl> {
let once_lock = ACCESS_CONTROL.get().unwrap();
once_lock.read().await
}
#[inline(always)]
pub async fn instance_mut() -> RwLockWriteGuard<'static, AccessControl> {
let once_lock = ACCESS_CONTROL.get().unwrap();
once_lock.write().await
}
pub async fn get_ipv4_list(direction: FlowDirection, list_type: ListType) -> StdHashMap<Ipv4Addr, Vec<Port>> {
let access_list = AccessControl::instance().await;
access_list
.ipv4_maps
.get(&(direction, list_type))
.map(|map| map.get_list())
.unwrap()
}
pub async fn get_ipv6_list(direction: FlowDirection, list_type: ListType) -> StdHashMap<Ipv6Addr, Vec<Port>> {
let access_list = AccessControl::instance().await;
access_list
.ipv6_maps
.get(&(direction, list_type))
.map(|map| map.get_list())
.unwrap()
}
pub async fn add_ipv4_list(
direction: FlowDirection,
list_type: ListType,
address: SocketAddrV4,
) -> Result<(), Error> {
let ip: u32 = (*address.ip()).into();
let port = address.port();
let mut access_list = AccessControl::instance_mut().await;
let map = access_list.ipv4_maps.get_mut(&(direction, list_type)).unwrap();
map.add(ip, port)
}
pub async fn add_ipv6_list(
direction: FlowDirection,
list_type: ListType,
address: SocketAddrV6,
) -> Result<(), Error> {
let ip: u128 = (*address.ip()).into();
let port = address.port();
let mut access_list = AccessControl::instance_mut().await;
let map = access_list.ipv6_maps.get_mut(&(direction, list_type)).unwrap();
map.add(ip, port)
}
pub async fn remove_ipv4_list(
direction: FlowDirection,
list_type: ListType,
address: SocketAddrV4,
) -> Result<(), Error> {
let ip: u32 = (*address.ip()).into();
let port = address.port();
let mut access_list = AccessControl::instance_mut().await;
let map = access_list.ipv4_maps.get_mut(&(direction, list_type)).unwrap();
map.remove(ip, port)
}
pub async fn remove_ipv6_list(
direction: FlowDirection,
list_type: ListType,
address: SocketAddrV6,
) -> Result<(), Error> {
let ip: u128 = (*address.ip()).into();
let port = address.port();
let mut access_list = AccessControl::instance_mut().await;
let map = access_list.ipv6_maps.get_mut(&(direction, list_type)).unwrap();
map.remove(ip, port)
}
}
struct AccessMap<T> {
map: AyaHashMap<MapData, T, [Port; MAX_RULES_PORT]>,
}
impl<T: IntoNative + Pod> AccessMap<T> {
fn get_list(&self) -> StdHashMap<T::Native, Vec<Port>> {
self.map
.iter()
.filter_map(Result::ok)
.map(|(key, value)| (key.into_native(), convert_ports_to_vec(value)))
.collect()
}
fn add(&mut self, ip: T, port: Port) -> Result<(), Error> {
let mut new_ports = [0_u16; MAX_RULES_PORT];
if port == 0 {
new_ports[0] = 0;
} else if let Ok(ports) = self.map.get(&ip, 0) {
if ports[0] == 0 {
return Ok(());
}
let mut index = None;
for (i, &value) in ports.iter().enumerate() {
if value == port {
return Ok(());
}
if index.is_none() && value == 0 {
index = Some(i);
}
}
if index.is_none() {
Err(EbpfError::RuleReachLimit)?;
}
new_ports.copy_from_slice(&ports);
new_ports[index.unwrap()] = port;
} else {
new_ports[0] = port;
}
self.map
.insert(ip, new_ports, 0)
.map_err(EbpfError::MapOperationError)?;
Ok(())
}
fn remove(&mut self, ip: T, port: Port) -> Result<(), Error> {
if let Ok(mut ports) = self.map.get(&ip, 0) {
if port == 0 {
self.map.remove(&ip).map_err(EbpfError::MapOperationError)?;
return Ok(());
}
if let Some(index) = ports.iter().position(|&x| x == port) {
for i in index..(MAX_RULES_PORT - 1) {
ports[i] = ports[i + 1];
}
ports[MAX_RULES_PORT - 1] = 0;
if ports[0] == 0 {
self.map.remove(&ip).map_err(EbpfError::MapOperationError)?;
} else {
self.map.insert(ip, ports, 0).map_err(EbpfError::MapOperationError)?;
}
}
Ok(())
} else {
Err(EbpfError::IpDoesNotExist)?
}
}
}

View File

@ -1,15 +0,0 @@
use crate::core::control::access_control::AccessControl;
use crate::core::control::service::Service;
use crate::model::error::Error;
pub mod access_control;
pub mod service;
pub struct Control;
impl Control {
pub async fn initialize() -> Result<(), Error> {
AccessControl::initialize().await?;
Service::initialize().await
}
}

View File

@ -1,422 +0,0 @@
use std::collections::HashMap as StdHashMap;
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6};
use std::sync::OnceLock;
use aya::maps::{Array as AyaArray, HashMap as AyaHashMap, MapData};
use common::model::http_method::{HttpMethod, HttpMethodBitmap};
use common::model::ip_address::{AddrPortV4, AddrPortV6, IPv4, IPv6};
use common::model::placeholder::PlaceHolder;
use macros::log;
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use crate::core::system::System;
use crate::model::error::ebpf::EbpfError;
use crate::model::error::Error;
use crate::model::ip_address::IntoNative;
use crate::model::log::system::SystemLog;
static SERVICE: OnceLock<RwLock<Service>> = OnceLock::new();
pub struct Service {
ipv4_http_service: AyaHashMap<MapData, AddrPortV4, HttpMethodBitmap>,
ipv6_http_service: AyaHashMap<MapData, AddrPortV6, HttpMethodBitmap>,
ssh_white_list_enable: AyaArray<MapData, PlaceHolder>,
ipv4_ssh_service: AyaHashMap<MapData, AddrPortV4, PlaceHolder>,
ipv6_ssh_service: AyaHashMap<MapData, AddrPortV6, PlaceHolder>,
ipv4_ssh_white_list: AyaHashMap<MapData, IPv4, PlaceHolder>,
ipv6_ssh_white_list: AyaHashMap<MapData, IPv6, PlaceHolder>,
ipv4_ssh_black_list: AyaHashMap<MapData, IPv4, PlaceHolder>,
ipv6_ssh_black_list: AyaHashMap<MapData, IPv6, PlaceHolder>,
}
impl Service {
pub async fn initialize() -> Result<(), Error> {
log!(SystemLog::Initializing);
let mut system = System::instance_mut().await;
let ebpf = &mut system.ingress_ebpf;
let mut service = Service {
ipv4_http_service: AyaHashMap::try_from(ebpf.take_map("IPV4_HTTP_SERVICE").ok_or(EbpfError::MapNotFound)?)
.map_err(EbpfError::MapOperationError)?,
ipv6_http_service: AyaHashMap::try_from(ebpf.take_map("IPV6_HTTP_SERVICE").ok_or(EbpfError::MapNotFound)?)
.map_err(EbpfError::MapOperationError)?,
ssh_white_list_enable: AyaArray::try_from(
ebpf.take_map("SSH_WHITE_LIST_ENABLE").ok_or(EbpfError::MapNotFound)?,
)
.map_err(EbpfError::MapOperationError)?,
ipv4_ssh_service: AyaHashMap::try_from(ebpf.take_map("IPV4_SSH_SERVICE").ok_or(EbpfError::MapNotFound)?)
.map_err(EbpfError::MapOperationError)?,
ipv6_ssh_service: AyaHashMap::try_from(ebpf.take_map("IPV6_SSH_SERVICE").ok_or(EbpfError::MapNotFound)?)
.map_err(EbpfError::MapOperationError)?,
ipv4_ssh_white_list: AyaHashMap::try_from(
ebpf.take_map("IPV4_SSH_WHITE_LIST").ok_or(EbpfError::MapNotFound)?,
)
.map_err(EbpfError::MapOperationError)?,
ipv6_ssh_white_list: AyaHashMap::try_from(
ebpf.take_map("IPV6_SSH_WHITE_LIST").ok_or(EbpfError::MapNotFound)?,
)
.map_err(EbpfError::MapOperationError)?,
ipv4_ssh_black_list: AyaHashMap::try_from(
ebpf.take_map("IPV4_SSH_BLACK_LIST").ok_or(EbpfError::MapNotFound)?,
)
.map_err(EbpfError::MapOperationError)?,
ipv6_ssh_black_list: AyaHashMap::try_from(
ebpf.take_map("IPV6_SSH_BLACK_LIST").ok_or(EbpfError::MapNotFound)?,
)
.map_err(EbpfError::MapOperationError)?,
};
service
.ssh_white_list_enable
.set(0, 0_u8, 0)
.map_err(EbpfError::MapOperationError)?;
SERVICE.get_or_init(|| RwLock::new(service));
log!(SystemLog::InitializeComplete);
Ok(())
}
#[inline(always)]
pub async fn instance() -> RwLockReadGuard<'static, Service> {
let once_lock = SERVICE.get().unwrap();
once_lock.read().await
}
#[inline(always)]
pub async fn instance_mut() -> RwLockWriteGuard<'static, Service> {
let once_lock = SERVICE.get().unwrap();
once_lock.write().await
}
pub async fn get_ipv4_http_service() -> StdHashMap<SocketAddrV4, Vec<HttpMethod>> {
let service = Service::instance().await;
service
.ipv4_http_service
.iter()
.filter_map(Result::ok)
.map(|(key, value)| {
let address = Ipv4Addr::from(key.ip);
let port = key.port;
(SocketAddrV4::new(address, port), HttpMethod::convert_from_bitmap(value))
})
.collect()
}
pub async fn get_ipv6_http_service() -> StdHashMap<SocketAddrV6, Vec<HttpMethod>> {
let service = Service::instance().await;
service
.ipv6_http_service
.iter()
.filter_map(Result::ok)
.map(|(key, value)| {
let address = Ipv6Addr::from(key.ip);
let port = key.port;
(
SocketAddrV6::new(address, port, 0, 0),
HttpMethod::convert_from_bitmap(value),
)
})
.collect()
}
pub async fn add_ipv4_http_service(address: SocketAddrV4, http_method: Vec<HttpMethod>) -> Result<(), Error> {
let ip: u32 = (*address.ip()).into();
let port = address.port();
let addr_port = AddrPortV4::new(ip, port);
let ebpf_method = HttpMethod::convert_to_bitmap(http_method);
let mut service = Service::instance_mut().await;
service
.ipv4_http_service
.insert(addr_port, ebpf_method, 0)
.map_err(|_| EbpfError::RuleReachLimit)?;
Ok(())
}
pub async fn add_ipv6_http_service(address: SocketAddrV6, http_method: Vec<HttpMethod>) -> Result<(), Error> {
let ip: u128 = (*address.ip()).into();
let port = address.port();
let addr_port = AddrPortV6::new(ip, port);
let ebpf_method = HttpMethod::convert_to_bitmap(http_method);
let mut service = Service::instance_mut().await;
service
.ipv6_http_service
.insert(addr_port, ebpf_method, 0)
.map_err(|_| EbpfError::RuleReachLimit)?;
Ok(())
}
pub async fn remove_ipv4_http_service(
address: SocketAddrV4,
removed_http_method: Vec<HttpMethod>,
) -> Result<(), Error> {
let ip: u32 = (*address.ip()).into();
let port = address.port();
let addr_port = AddrPortV4::new(ip, port);
let mut service = Service::instance_mut().await;
if let Ok(current_http_method) = service.ipv4_http_service.get(&addr_port, 0) {
let mut http_method = HttpMethod::convert_from_bitmap(current_http_method);
http_method.retain(|method| !removed_http_method.contains(method));
if http_method.is_empty() {
service
.ipv4_http_service
.remove(&addr_port)
.map_err(EbpfError::MapOperationError)?;
} else {
let new_http_method = HttpMethod::convert_to_bitmap(http_method);
service
.ipv4_http_service
.insert(&addr_port, new_http_method, 0)
.map_err(EbpfError::MapOperationError)?;
}
Ok(())
} else {
Err(EbpfError::IpDoesNotExist)?
}
}
pub async fn remove_ipv6_http_service(
address: SocketAddrV6,
removed_http_method: Vec<HttpMethod>,
) -> Result<(), Error> {
let ip: u128 = (*address.ip()).into();
let port = address.port();
let addr_port = AddrPortV6::new(ip, port);
let mut service = Service::instance_mut().await;
if let Ok(current_http_method) = service.ipv6_http_service.get(&addr_port, 0) {
let mut http_method = HttpMethod::convert_from_bitmap(current_http_method);
http_method.retain(|method| !removed_http_method.contains(method));
if http_method.is_empty() {
service
.ipv6_http_service
.remove(&addr_port)
.map_err(EbpfError::MapOperationError)?;
} else {
let new_http_method = HttpMethod::convert_to_bitmap(http_method);
service
.ipv6_http_service
.insert(&addr_port, new_http_method, 0)
.map_err(EbpfError::MapOperationError)?;
}
Ok(())
} else {
Err(EbpfError::IpDoesNotExist)?
}
}
pub async fn is_ssh_white_list_enable() -> bool {
let service = Service::instance().await;
match service.ssh_white_list_enable.get(&0, 0) {
Ok(status) => {
if status == 0 {
false
} else {
true
}
}
Err(_) => false,
}
}
pub async fn enable_ssh_white_list() -> Result<(), Error> {
let mut service = Service::instance_mut().await;
service
.ssh_white_list_enable
.set(0, 1_u8, 0)
.map_err(EbpfError::MapOperationError)?;
Ok(())
}
pub async fn disable_ssh_white_list() -> Result<(), Error> {
let mut service = Service::instance_mut().await;
service
.ssh_white_list_enable
.set(0, 0_u8, 0)
.map_err(EbpfError::MapOperationError)?;
Ok(())
}
pub async fn get_ipv4_ssh_service() -> Vec<SocketAddrV4> {
let service = Service::instance().await;
service
.ipv4_ssh_service
.keys()
.filter_map(Result::ok)
.map(|key| key.into_native())
.collect()
}
pub async fn get_ipv6_ssh_service() -> Vec<SocketAddrV6> {
let service = Service::instance().await;
service
.ipv6_ssh_service
.keys()
.filter_map(Result::ok)
.map(|key| key.into_native())
.collect()
}
pub async fn add_ipv4_ssh_service(address: SocketAddrV4) -> Result<(), Error> {
let ip: u32 = (*address.ip()).into();
let port = address.port();
let addr_port = AddrPortV4::new(ip, port);
let mut service = Service::instance_mut().await;
service
.ipv4_ssh_service
.insert(&addr_port, 0_u8, 0)
.map_err(|_| EbpfError::RuleReachLimit)?;
Ok(())
}
pub async fn add_ipv6_ssh_service(address: SocketAddrV6) -> Result<(), Error> {
let ip: u128 = (*address.ip()).into();
let port = address.port();
let addr_port = AddrPortV6::new(ip, port);
let mut service = Service::instance_mut().await;
service
.ipv6_ssh_service
.insert(&addr_port, 0_u8, 0)
.map_err(|_| EbpfError::RuleReachLimit)?;
Ok(())
}
pub async fn remove_ipv4_ssh_service(address: SocketAddrV4) -> Result<(), Error> {
let ip: u32 = (*address.ip()).into();
let port = address.port();
let addr_port = AddrPortV4::new(ip, port);
let mut service = Service::instance_mut().await;
service
.ipv4_ssh_service
.remove(&addr_port)
.map_err(|_| EbpfError::IpDoesNotExist)?;
Ok(())
}
pub async fn remove_ipv6_ssh_service(address: SocketAddrV6) -> Result<(), Error> {
let ip: u128 = (*address.ip()).into();
let port = address.port();
let addr_port = AddrPortV6::new(ip, port);
let mut service = Service::instance_mut().await;
service
.ipv6_ssh_service
.remove(&addr_port)
.map_err(|_| EbpfError::IpDoesNotExist)?;
Ok(())
}
pub async fn get_ipv4_ssh_white_list() -> Vec<Ipv4Addr> {
let service = Service::instance().await;
service
.ipv4_ssh_white_list
.keys()
.filter_map(Result::ok)
.map(|key| Ipv4Addr::from(key))
.collect()
}
pub async fn get_ipv6_ssh_white_list() -> Vec<Ipv6Addr> {
let service = Service::instance().await;
service
.ipv6_ssh_white_list
.keys()
.filter_map(Result::ok)
.map(|key| Ipv6Addr::from(key))
.collect()
}
pub async fn add_ipv4_ssh_white_list(ip: Ipv4Addr) -> Result<(), Error> {
let ip: u32 = ip.into();
let mut service = Service::instance_mut().await;
service
.ipv4_ssh_white_list
.insert(ip, 0_u8, 0)
.map_err(|_| EbpfError::RuleReachLimit)?;
Ok(())
}
pub async fn add_ipv6_ssh_white_list(ip: Ipv6Addr) -> Result<(), Error> {
let ip: u128 = ip.into();
let mut service = Service::instance_mut().await;
service
.ipv6_ssh_white_list
.insert(ip, 0_u8, 0)
.map_err(|_| EbpfError::RuleReachLimit)?;
Ok(())
}
pub async fn remove_ipv4_ssh_white_list(ip: Ipv4Addr) -> Result<(), Error> {
let ip: u32 = ip.into();
let mut service = Service::instance_mut().await;
service
.ipv4_ssh_white_list
.remove(&ip)
.map_err(|_| EbpfError::IpDoesNotExist)?;
Ok(())
}
pub async fn remove_ipv6_ssh_white_list(ip: Ipv6Addr) -> Result<(), Error> {
let ip: u128 = ip.into();
let mut service = Service::instance_mut().await;
service
.ipv6_ssh_white_list
.remove(&ip)
.map_err(|_| EbpfError::IpDoesNotExist)?;
Ok(())
}
pub async fn get_ipv4_ssh_black_list() -> Vec<Ipv4Addr> {
let service = Service::instance().await;
service
.ipv4_ssh_black_list
.keys()
.filter_map(Result::ok)
.map(|key| Ipv4Addr::from(key))
.collect()
}
pub async fn get_ipv6_ssh_black_list() -> Vec<Ipv6Addr> {
let service = Service::instance().await;
service
.ipv6_ssh_black_list
.keys()
.filter_map(Result::ok)
.map(|key| Ipv6Addr::from(key))
.collect()
}
pub async fn add_ipv4_ssh_black_list(ip: Ipv4Addr) -> Result<(), Error> {
let ip: u32 = ip.into();
let mut service = Service::instance_mut().await;
service
.ipv4_ssh_black_list
.insert(ip, 0_u8, 0)
.map_err(|_| EbpfError::RuleReachLimit)?;
Ok(())
}
pub async fn add_ipv6_ssh_black_list(ip: Ipv6Addr) -> Result<(), Error> {
let ip: u128 = ip.into();
let mut service = Service::instance_mut().await;
service
.ipv6_ssh_black_list
.insert(ip, 0_u8, 0)
.map_err(|_| EbpfError::RuleReachLimit)?;
Ok(())
}
pub async fn remove_ipv4_ssh_black_list(ip: Ipv4Addr) -> Result<(), Error> {
let ip: u32 = ip.into();
let mut service = Service::instance_mut().await;
service
.ipv4_ssh_black_list
.remove(&ip)
.map_err(|_| EbpfError::IpDoesNotExist)?;
Ok(())
}
pub async fn remove_ipv6_ssh_black_list(ip: Ipv6Addr) -> Result<(), Error> {
let ip: u128 = ip.into();
let mut service = Service::instance_mut().await;
service
.ipv6_ssh_black_list
.remove(&ip)
.map_err(|_| EbpfError::IpDoesNotExist)?;
Ok(())
}
}

View File

@ -0,0 +1,206 @@
use std::collections::HashMap;
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6};
use aya::maps::{HashMap as AyaHashMap, MapData};
use aya::{Ebpf, Pod};
use common::define::setting::MAX_RULES_PORT;
use common::model::ip_address::{IPv4, IPv6, Port};
use tokio::sync::RwLock;
use crate::model::direction::FlowDirection;
use crate::model::error::ebpf::EbpfError;
use crate::model::error::Error;
use crate::model::ip_address::NativeConvert;
use crate::model::list_type::ListType;
use crate::utils::ip_address::convert_ports_to_vec;
pub struct AccessControl {
ipv4_src_whitelist: RwLock<MapWrapper<IPv4>>,
ipv4_src_blacklist: RwLock<MapWrapper<IPv4>>,
ipv4_dst_whitelist: RwLock<MapWrapper<IPv4>>,
ipv4_dst_blacklist: RwLock<MapWrapper<IPv4>>,
ipv6_src_whitelist: RwLock<MapWrapper<IPv6>>,
ipv6_src_blacklist: RwLock<MapWrapper<IPv6>>,
ipv6_dst_whitelist: RwLock<MapWrapper<IPv6>>,
ipv6_dst_blacklist: RwLock<MapWrapper<IPv6>>,
}
impl AccessControl {
pub fn new(ebpf: &mut Ebpf) -> Result<Self, Error> {
let access_control = Self {
ipv4_src_whitelist: RwLock::new(MapWrapper::new(ebpf, "IPV4_SRC_WHITELIST")?),
ipv4_src_blacklist: RwLock::new(MapWrapper::new(ebpf, "IPV4_SRC_BLACKLIST")?),
ipv4_dst_whitelist: RwLock::new(MapWrapper::new(ebpf, "IPV4_DST_WHITELIST")?),
ipv4_dst_blacklist: RwLock::new(MapWrapper::new(ebpf, "IPV4_DST_BLACKLIST")?),
ipv6_src_whitelist: RwLock::new(MapWrapper::new(ebpf, "IPV6_SRC_WHITELIST")?),
ipv6_src_blacklist: RwLock::new(MapWrapper::new(ebpf, "IPV6_SRC_BLACKLIST")?),
ipv6_dst_whitelist: RwLock::new(MapWrapper::new(ebpf, "IPV6_DST_WHITELIST")?),
ipv6_dst_blacklist: RwLock::new(MapWrapper::new(ebpf, "IPV6_DST_BLACKLIST")?),
};
Ok(access_control)
}
pub async fn get_ipv4_list(&self, direction: FlowDirection, list_type: ListType) -> HashMap<Ipv4Addr, Vec<Port>> {
let map_wrapper = match (direction, list_type) {
(FlowDirection::Source, ListType::White) => self.ipv4_src_whitelist.read().await,
(FlowDirection::Source, ListType::Black) => self.ipv4_src_blacklist.read().await,
(FlowDirection::Destination, ListType::White) => self.ipv4_dst_whitelist.read().await,
(FlowDirection::Destination, ListType::Black) => self.ipv4_dst_blacklist.read().await,
};
map_wrapper.get_list()
}
pub async fn get_ipv6_list(&self, direction: FlowDirection, list_type: ListType) -> HashMap<Ipv6Addr, Vec<Port>> {
let map_wrapper = match (direction, list_type) {
(FlowDirection::Source, ListType::White) => self.ipv6_src_whitelist.read().await,
(FlowDirection::Source, ListType::Black) => self.ipv6_src_blacklist.read().await,
(FlowDirection::Destination, ListType::White) => self.ipv6_dst_whitelist.read().await,
(FlowDirection::Destination, ListType::Black) => self.ipv6_dst_blacklist.read().await,
};
map_wrapper.get_list()
}
pub async fn add_ipv4_list(
&self,
direction: FlowDirection,
list_type: ListType,
address: SocketAddrV4,
) -> Result<(), Error> {
let ip: u32 = (*address.ip()).into();
let port = address.port();
let mut map_wrapper = match (direction, list_type) {
(FlowDirection::Source, ListType::White) => self.ipv4_src_whitelist.write().await,
(FlowDirection::Source, ListType::Black) => self.ipv4_src_blacklist.write().await,
(FlowDirection::Destination, ListType::White) => self.ipv4_dst_whitelist.write().await,
(FlowDirection::Destination, ListType::Black) => self.ipv4_dst_blacklist.write().await,
};
map_wrapper.add(ip, port)
}
pub async fn add_ipv6_list(
&self,
direction: FlowDirection,
list_type: ListType,
address: SocketAddrV6,
) -> Result<(), Error> {
let ip: u128 = (*address.ip()).into();
let port = address.port();
let mut map_wrapper = match (direction, list_type) {
(FlowDirection::Source, ListType::White) => self.ipv6_src_whitelist.write().await,
(FlowDirection::Source, ListType::Black) => self.ipv6_src_blacklist.write().await,
(FlowDirection::Destination, ListType::White) => self.ipv6_dst_whitelist.write().await,
(FlowDirection::Destination, ListType::Black) => self.ipv6_dst_blacklist.write().await,
};
map_wrapper.add(ip, port)
}
pub async fn remove_ipv4_list(
&self,
direction: FlowDirection,
list_type: ListType,
address: SocketAddrV4,
) -> Result<(), Error> {
let ip: u32 = (*address.ip()).into();
let port = address.port();
let mut map_wrapper = match (direction, list_type) {
(FlowDirection::Source, ListType::White) => self.ipv4_src_whitelist.write().await,
(FlowDirection::Source, ListType::Black) => self.ipv4_src_blacklist.write().await,
(FlowDirection::Destination, ListType::White) => self.ipv4_dst_whitelist.write().await,
(FlowDirection::Destination, ListType::Black) => self.ipv4_dst_blacklist.write().await,
};
map_wrapper.remove(ip, port)
}
pub async fn remove_ipv6_list(
&self,
direction: FlowDirection,
list_type: ListType,
address: SocketAddrV6,
) -> Result<(), Error> {
let ip: u128 = (*address.ip()).into();
let port = address.port();
let mut map_wrapper = match (direction, list_type) {
(FlowDirection::Source, ListType::White) => self.ipv6_src_whitelist.write().await,
(FlowDirection::Source, ListType::Black) => self.ipv6_src_blacklist.write().await,
(FlowDirection::Destination, ListType::White) => self.ipv6_dst_whitelist.write().await,
(FlowDirection::Destination, ListType::Black) => self.ipv6_dst_blacklist.write().await,
};
map_wrapper.remove(ip, port)
}
}
struct MapWrapper<T> {
map: AyaHashMap<MapData, T, [Port; MAX_RULES_PORT]>,
}
impl<T: NativeConvert + Pod> MapWrapper<T> {
fn new(ebpf: &mut Ebpf, map_name: &str) -> Result<Self, Error> {
let map = ebpf.take_map(map_name).ok_or(EbpfError::MapNotFound)?;
let map = AyaHashMap::try_from(map).map_err(EbpfError::MapOperationError)?;
Ok(Self { map })
}
fn get_list(&self) -> HashMap<T::Native, Vec<Port>> {
self.map
.iter()
.filter_map(Result::ok)
.map(|(key, value)| (key.into_native(), convert_ports_to_vec(value)))
.collect()
}
fn add(&mut self, ip: T, port: Port) -> Result<(), Error> {
let mut new_ports = [0_u16; MAX_RULES_PORT];
if port == 0 {
new_ports[0] = 0;
} else if let Ok(ports) = self.map.get(&ip, 0) {
if ports[0] == 0 {
return Ok(());
}
let mut index = None;
for (i, &value) in ports.iter().enumerate() {
if value == port {
return Ok(());
}
if index.is_none() && value == 0 {
index = Some(i);
}
}
if index.is_none() {
Err(EbpfError::RuleReachLimit)?;
}
new_ports.copy_from_slice(&ports);
new_ports[index.unwrap()] = port;
} else {
new_ports[0] = port;
}
self.map
.insert(ip, new_ports, 0)
.map_err(EbpfError::MapOperationError)?;
Ok(())
}
fn remove(&mut self, ip: T, port: Port) -> Result<(), Error> {
if let Ok(mut ports) = self.map.get(&ip, 0) {
if port == 0 {
self.map.remove(&ip).map_err(EbpfError::MapOperationError)?;
return Ok(());
}
if let Some(index) = ports.iter().position(|&x| x == port) {
for i in index..(MAX_RULES_PORT - 1) {
ports[i] = ports[i + 1];
}
ports[MAX_RULES_PORT - 1] = 0;
if ports[0] == 0 {
self.map.remove(&ip).map_err(EbpfError::MapOperationError)?;
} else {
self.map.insert(ip, ports, 0).map_err(EbpfError::MapOperationError)?;
}
}
Ok(())
} else {
Err(EbpfError::IpDoesNotExist)?
}
}
}

View File

@ -0,0 +1,62 @@
pub mod access_control;
pub mod service;
pub mod statistics;
pub mod xdp_manager;
use std::sync::Arc;
use aya::Ebpf;
use crossbeam::queue::SegQueue;
use macros::log;
use tokio::sync::oneshot;
use crate::core::ebpf::access_control::AccessControl;
use crate::core::ebpf::service::Service;
use crate::core::ebpf::statistics::Statistics;
use crate::core::ebpf::xdp_manager::XskManager;
use crate::core::infrastructure::app_config::AppConfig;
use crate::model::error::system::SystemError;
use crate::model::error::Error;
pub struct EbpfServices {
pub xsk_manager: Arc<XskManager>,
pub access_control: Arc<AccessControl>,
pub service: Arc<Service>,
pub statistics: Arc<Statistics>,
shutdowns: SegQueue<oneshot::Sender<()>>,
}
impl EbpfServices {
pub fn new(app_config: Arc<AppConfig>, ingress_ebpf: &mut Ebpf, egress_ebpf: &mut Ebpf) -> Result<Self, Error> {
let xsk_manager = XskManager::new(app_config.clone(), ingress_ebpf)?;
let access_control = AccessControl::new(ingress_ebpf)?;
let service = Service::new(ingress_ebpf)?;
let statistics = Statistics::new(app_config, ingress_ebpf, egress_ebpf)?;
let ebpf_services = Self {
xsk_manager: Arc::new(xsk_manager),
access_control: Arc::new(access_control),
service: Arc::new(service),
statistics: Arc::new(statistics),
shutdowns: SegQueue::new(),
};
Ok(ebpf_services)
}
pub async fn run(self: Arc<Self>) -> Result<(), Error> {
let xsk_manager = self.xsk_manager.clone();
let statistics = self.statistics.clone();
xsk_manager.run()?;
let statistics_shutdown = statistics.run().await;
self.shutdowns.push(statistics_shutdown);
Ok(())
}
pub fn terminate(self: Arc<Self>) {
self.xsk_manager.shutdown();
while let Some(shutdown) = self.shutdowns.pop() {
if shutdown.send(()).is_err() {
log!(SystemError::ShutdownSignalFailed);
}
}
}
}

View File

@ -0,0 +1,332 @@
use std::collections::HashMap;
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6};
use aya::maps::{Array as AyaArray, HashMap as AyaHashMap, MapData};
use aya::{Ebpf, Pod};
use common::model::http_method::{HttpMethod, HttpMethodBitmap};
use common::model::ip_address::{AddrPortV4, AddrPortV6, IPv4, IPv6};
use common::model::placeholder::PlaceHolder;
use tokio::sync::RwLock;
use crate::model::error::ebpf::EbpfError;
use crate::model::error::Error;
use crate::model::ip_address::NativeConvert;
pub struct Service {
ipv4_http_service: RwLock<HttpServiceWrapper<AddrPortV4>>,
ipv6_http_service: RwLock<HttpServiceWrapper<AddrPortV6>>,
ssh_white_list_enable: RwLock<WhiteListControl>,
ipv4_ssh_service: RwLock<SshServiceWrapper<AddrPortV4>>,
ipv6_ssh_service: RwLock<SshServiceWrapper<AddrPortV6>>,
ipv4_ssh_white_list: RwLock<SshListWrapper<IPv4>>,
ipv6_ssh_white_list: RwLock<SshListWrapper<IPv6>>,
ipv4_ssh_black_list: RwLock<SshListWrapper<IPv4>>,
ipv6_ssh_black_list: RwLock<SshListWrapper<IPv6>>,
}
impl Service {
pub fn new(ebpf: &mut Ebpf) -> Result<Self, Error> {
let service = Self {
ipv4_http_service: RwLock::new(HttpServiceWrapper::new(ebpf, "IPV4_HTTP_SERVICE")?),
ipv6_http_service: RwLock::new(HttpServiceWrapper::new(ebpf, "IPV6_HTTP_SERVICE")?),
ssh_white_list_enable: RwLock::new(WhiteListControl::new(ebpf, "SSH_WHITE_LIST_ENABLE")?),
ipv4_ssh_service: RwLock::new(SshServiceWrapper::new(ebpf, "IPV4_SSH_SERVICE")?),
ipv6_ssh_service: RwLock::new(SshServiceWrapper::new(ebpf, "IPV6_SSH_SERVICE")?),
ipv4_ssh_white_list: RwLock::new(SshListWrapper::new(ebpf, "IPV4_SSH_WHITE_LIST")?),
ipv6_ssh_white_list: RwLock::new(SshListWrapper::new(ebpf, "IPV6_SSH_WHITE_LIST")?),
ipv4_ssh_black_list: RwLock::new(SshListWrapper::new(ebpf, "IPV4_SSH_BLACK_LIST")?),
ipv6_ssh_black_list: RwLock::new(SshListWrapper::new(ebpf, "IPV6_SSH_BLACK_LIST")?),
};
Ok(service)
}
pub async fn get_ipv4_http_service(&self) -> HashMap<SocketAddrV4, Vec<HttpMethod>> {
self.ipv4_http_service.read().await.get_http_method()
}
pub async fn get_ipv6_http_service(&self) -> HashMap<SocketAddrV6, Vec<HttpMethod>> {
self.ipv6_http_service.read().await.get_http_method()
}
pub async fn add_ipv4_http_service(
&self,
address: SocketAddrV4,
http_method: Vec<HttpMethod>,
) -> Result<(), Error> {
self.ipv4_http_service
.write()
.await
.add_http_service(address, http_method)
}
pub async fn add_ipv6_http_service(
&self,
address: SocketAddrV6,
http_method: Vec<HttpMethod>,
) -> Result<(), Error> {
self.ipv6_http_service
.write()
.await
.add_http_service(address, http_method)
}
pub async fn remove_ipv4_http_service(
&self,
address: SocketAddrV4,
removed_http_method: Vec<HttpMethod>,
) -> Result<(), Error> {
self.ipv4_http_service
.write()
.await
.remove_http_service(address, removed_http_method)
}
pub async fn remove_ipv6_http_service(
&self,
address: SocketAddrV6,
removed_http_method: Vec<HttpMethod>,
) -> Result<(), Error> {
self.ipv6_http_service
.write()
.await
.remove_http_service(address, removed_http_method)
}
pub async fn is_ssh_white_list_enable(&self) -> bool {
self.ssh_white_list_enable.read().await.is_white_list_enable()
}
pub async fn enable_ssh_white_list(&self) -> Result<(), Error> {
self.ssh_white_list_enable.write().await.enable_white_list()
}
pub async fn disable_ssh_white_list(&self) -> Result<(), Error> {
self.ssh_white_list_enable.write().await.disable_white_list()
}
pub async fn get_ipv4_ssh_service(&self) -> Vec<SocketAddrV4> {
self.ipv4_ssh_service.read().await.get_ssh_service()
}
pub async fn get_ipv6_ssh_service(&self) -> Vec<SocketAddrV6> {
self.ipv6_ssh_service.read().await.get_ssh_service()
}
pub async fn add_ipv4_ssh_service(&self, address: SocketAddrV4) -> Result<(), Error> {
self.ipv4_ssh_service.write().await.add_ssh_service(address)
}
pub async fn add_ipv6_ssh_service(&self, address: SocketAddrV6) -> Result<(), Error> {
self.ipv6_ssh_service.write().await.add_ssh_service(address)
}
pub async fn remove_ipv4_ssh_service(&self, address: SocketAddrV4) -> Result<(), Error> {
self.ipv4_ssh_service.write().await.remove_ssh_service(address)
}
pub async fn remove_ipv6_ssh_service(&self, address: SocketAddrV6) -> Result<(), Error> {
self.ipv6_ssh_service.write().await.remove_ssh_service(address)
}
pub async fn get_ipv4_ssh_white_list(&self) -> Vec<Ipv4Addr> {
self.ipv4_ssh_white_list.read().await.get_list()
}
pub async fn get_ipv6_ssh_white_list(&self) -> Vec<Ipv6Addr> {
self.ipv6_ssh_white_list.read().await.get_list()
}
pub async fn add_ipv4_ssh_white_list(&self, ip: Ipv4Addr) -> Result<(), Error> {
self.ipv4_ssh_white_list.write().await.add_list(ip)
}
pub async fn add_ipv6_ssh_white_list(&self, ip: Ipv6Addr) -> Result<(), Error> {
self.ipv6_ssh_white_list.write().await.add_list(ip)
}
pub async fn remove_ipv4_ssh_white_list(&self, ip: Ipv4Addr) -> Result<(), Error> {
self.ipv4_ssh_white_list.write().await.remove_list(ip)
}
pub async fn remove_ipv6_ssh_white_list(&self, ip: Ipv6Addr) -> Result<(), Error> {
self.ipv6_ssh_white_list.write().await.remove_list(ip)
}
pub async fn get_ipv4_ssh_black_list(&self) -> Vec<Ipv4Addr> {
self.ipv4_ssh_black_list.read().await.get_list()
}
pub async fn get_ipv6_ssh_black_list(&self) -> Vec<Ipv6Addr> {
self.ipv6_ssh_black_list.read().await.get_list()
}
pub async fn add_ipv4_ssh_black_list(&self, ip: Ipv4Addr) -> Result<(), Error> {
self.ipv4_ssh_black_list.write().await.add_list(ip)
}
pub async fn add_ipv6_ssh_black_list(&self, ip: Ipv6Addr) -> Result<(), Error> {
self.ipv6_ssh_black_list.write().await.add_list(ip)
}
pub async fn remove_ipv4_ssh_black_list(&self, ip: Ipv4Addr) -> Result<(), Error> {
self.ipv4_ssh_black_list.write().await.remove_list(ip)
}
pub async fn remove_ipv6_ssh_black_list(&self, ip: Ipv6Addr) -> Result<(), Error> {
self.ipv6_ssh_black_list.write().await.remove_list(ip)
}
}
struct WhiteListControl {
map: AyaArray<MapData, PlaceHolder>,
}
impl WhiteListControl {
fn new(ebpf: &mut Ebpf, map_name: &str) -> Result<Self, Error> {
let map = ebpf.take_map(map_name).ok_or(EbpfError::MapNotFound)?;
let map = AyaArray::try_from(map).map_err(EbpfError::MapOperationError)?;
Ok(Self { map })
}
fn is_white_list_enable(&self) -> bool {
match self.map.get(&0, 0) {
Ok(status) => {
if status == 0 {
false
} else {
true
}
}
Err(_) => false,
}
}
fn enable_white_list(&mut self) -> Result<(), Error> {
self.map.set(0, 1_u8, 0).map_err(EbpfError::MapOperationError)?;
Ok(())
}
fn disable_white_list(&mut self) -> Result<(), Error> {
self.map.set(0, 0_u8, 0).map_err(EbpfError::MapOperationError)?;
Ok(())
}
}
struct HttpServiceWrapper<T> {
map: AyaHashMap<MapData, T, HttpMethodBitmap>,
}
impl<T: NativeConvert + Pod> HttpServiceWrapper<T> {
fn new(ebpf: &mut Ebpf, map_name: &str) -> Result<Self, Error> {
let map = ebpf.take_map(map_name).ok_or(EbpfError::MapNotFound)?;
let map = AyaHashMap::try_from(map).map_err(EbpfError::MapOperationError)?;
Ok(Self { map })
}
fn get_http_method(&self) -> HashMap<T::Native, Vec<HttpMethod>> {
self.map
.iter()
.filter_map(Result::ok)
.map(|(key, value)| {
let address = key.into_native();
(address, HttpMethod::convert_from_bitmap(value))
})
.collect()
}
fn add_http_service(&mut self, address: T::Native, http_method: Vec<HttpMethod>) -> Result<(), Error> {
let address = T::from_native(address);
let ebpf_method = HttpMethod::convert_to_bitmap(http_method);
self.map
.insert(address, ebpf_method, 0)
.map_err(|_| EbpfError::RuleReachLimit)?;
Ok(())
}
fn remove_http_service(&mut self, address: T::Native, removed_http_method: Vec<HttpMethod>) -> Result<(), Error> {
let address = T::from_native(address);
if let Ok(current_http_method) = self.map.get(&address, 0) {
let mut http_method = HttpMethod::convert_from_bitmap(current_http_method);
http_method.retain(|method| !removed_http_method.contains(method));
if http_method.is_empty() {
self.map.remove(&address).map_err(EbpfError::MapOperationError)?;
} else {
let new_http_method = HttpMethod::convert_to_bitmap(http_method);
self.map
.insert(&address, new_http_method, 0)
.map_err(EbpfError::MapOperationError)?;
}
Ok(())
} else {
Err(EbpfError::IpDoesNotExist)?
}
}
}
struct SshServiceWrapper<T> {
map: AyaHashMap<MapData, T, PlaceHolder>,
}
impl<T: NativeConvert + Pod> SshServiceWrapper<T> {
fn new(ebpf: &mut Ebpf, map_name: &str) -> Result<Self, Error> {
let map = ebpf.take_map(map_name).ok_or(EbpfError::MapNotFound)?;
let map = AyaHashMap::try_from(map).map_err(EbpfError::MapOperationError)?;
Ok(Self { map })
}
fn get_ssh_service(&self) -> Vec<T::Native> {
self.map
.keys()
.filter_map(Result::ok)
.map(|key| key.into_native())
.collect()
}
fn add_ssh_service(&mut self, address: T::Native) -> Result<(), Error> {
let address = T::from_native(address);
self.map
.insert(address, 0_u8, 0)
.map_err(|_| EbpfError::RuleReachLimit)?;
Ok(())
}
fn remove_ssh_service(&mut self, address: T::Native) -> Result<(), Error> {
let address = T::from_native(address);
self.map.remove(&address).map_err(|_| EbpfError::IpDoesNotExist)?;
Ok(())
}
}
struct SshListWrapper<T> {
map: AyaHashMap<MapData, T, PlaceHolder>,
}
impl<T: NativeConvert + Pod> SshListWrapper<T> {
fn new(ebpf: &mut Ebpf, map_name: &str) -> Result<Self, Error> {
let map = ebpf.take_map(map_name).ok_or(EbpfError::MapNotFound)?;
let map = AyaHashMap::try_from(map).map_err(EbpfError::MapOperationError)?;
Ok(Self { map })
}
fn get_list(&self) -> Vec<T::Native> {
self.map
.keys()
.filter_map(Result::ok)
.map(|key| key.into_native())
.collect()
}
fn add_list(&mut self, address: T::Native) -> Result<(), Error> {
let address = T::from_native(address);
self.map
.insert(address, 0_u8, 0)
.map_err(|_| EbpfError::RuleReachLimit)?;
Ok(())
}
fn remove_list(&mut self, address: T::Native) -> Result<(), Error> {
let address = T::from_native(address);
self.map.remove(&address).map_err(|_| EbpfError::IpDoesNotExist)?;
Ok(())
}
}

View File

@ -1,28 +1,28 @@
use std::collections::HashMap as StdHashMap;
use std::collections::HashMap;
use std::net::{SocketAddrV4, SocketAddrV6};
use std::sync::OnceLock;
use std::sync::Arc;
use aya::maps::{HashMap as AyaHashMap, MapData};
use aya::Pod;
use aya::{Ebpf, Pod};
use common::model::flow_stats::FlowStats;
use common::model::ip_address::{AddrPortV4, AddrPortV6};
use macros::log;
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use tokio::select;
use tokio::sync::{oneshot, RwLock};
use tokio::time::{sleep, Duration};
use crate::core::system::System;
use crate::core::infrastructure::app_config::AppConfig;
use crate::model::direction::{Direction, FlowDirection};
use crate::model::error::ebpf::EbpfError;
use crate::model::error::Error;
use crate::model::ip_address::IntoNative;
use crate::model::log::system::SystemLog;
use crate::model::ip_address::NativeConvert;
use crate::model::time_type::TimeType;
static STATISTICS: OnceLock<RwLock<Statistics>> = OnceLock::new();
use crate::utils::boot_time::boot_time;
pub struct Statistics {
terminate: bool,
ipv4_maps: StdHashMap<(Direction, FlowDirection, TimeType), FlowMap<AddrPortV4>>,
ipv6_maps: StdHashMap<(Direction, FlowDirection, TimeType), FlowMap<AddrPortV6>>,
app_config: Arc<AppConfig>,
boot_time: u64,
ipv4_maps: HashMap<(Direction, FlowDirection, TimeType), RwLock<FlowMap<AddrPortV4>>>,
ipv6_maps: HashMap<(Direction, FlowDirection, TimeType), RwLock<FlowMap<AddrPortV6>>>,
}
impl Statistics {
@ -80,126 +80,89 @@ impl Statistics {
),
];
pub async fn initialize() -> Result<(), Error> {
log!(SystemLog::Initializing);
let mut system = System::instance_mut().await;
let mut ipv4_maps = StdHashMap::new();
let mut ipv6_maps = StdHashMap::new();
let ingress_ebpf = &mut system.ingress_ebpf;
pub fn new(
app_config: Arc<AppConfig>,
ingress_ebpf: &mut Ebpf,
egress_ebpf: &mut Ebpf,
) -> Result<Statistics, Error> {
let boot_time = boot_time();
let mut ipv4_maps = HashMap::new();
let mut ipv6_maps = HashMap::new();
for (key, (ipv4_name, ipv6_name)) in Self::INGRESS_MAPS {
let ipv4_map = ingress_ebpf.take_map(ipv4_name).ok_or(EbpfError::MapNotFound)?;
let ipv6_map = ingress_ebpf.take_map(ipv6_name).ok_or(EbpfError::MapNotFound)?;
ipv4_maps.insert(
key,
FlowMap {
map: AyaHashMap::try_from(ipv4_map).map_err(EbpfError::MapOperationError)?,
},
);
ipv6_maps.insert(
key,
FlowMap {
map: AyaHashMap::try_from(ipv6_map).map_err(EbpfError::MapOperationError)?,
},
);
ipv4_maps.insert(key, RwLock::new(FlowMap::new(ingress_ebpf, ipv4_name)?));
ipv6_maps.insert(key, RwLock::new(FlowMap::new(ingress_ebpf, ipv6_name)?));
}
let egress_ebpf = &mut system.egress_ebpf;
for (key, (ipv4_name, ipv6_name)) in Self::EGRESS_MAPS {
let ipv4_map = egress_ebpf.take_map(ipv4_name).ok_or(EbpfError::MapNotFound)?;
let ipv6_map = egress_ebpf.take_map(ipv6_name).ok_or(EbpfError::MapNotFound)?;
ipv4_maps.insert(
key,
FlowMap {
map: AyaHashMap::try_from(ipv4_map).map_err(EbpfError::MapOperationError)?,
},
);
ipv6_maps.insert(
key,
FlowMap {
map: AyaHashMap::try_from(ipv6_map).map_err(EbpfError::MapOperationError)?,
},
);
ipv4_maps.insert(key, RwLock::new(FlowMap::new(egress_ebpf, ipv4_name)?));
ipv6_maps.insert(key, RwLock::new(FlowMap::new(egress_ebpf, ipv6_name)?));
}
let statistics = Statistics {
terminate: false,
app_config,
boot_time,
ipv4_maps,
ipv6_maps,
};
STATISTICS.get_or_init(|| RwLock::new(statistics));
log!(SystemLog::InitializeComplete);
Ok(())
Ok(statistics)
}
#[inline(always)]
pub async fn instance() -> RwLockReadGuard<'static, Statistics> {
// Initialization has been ensured
let once_lock = STATISTICS.get().unwrap();
// There is no lock acquired multiple times, so this is safe
once_lock.read().await
}
#[inline(always)]
pub async fn instance_mut() -> RwLockWriteGuard<'static, Statistics> {
// Initialization has been ensured
let once_lock = STATISTICS.get().unwrap();
// There is no lock acquired multiple times, so this is safe
once_lock.write().await
}
pub async fn run() {
tokio::spawn(async {
pub async fn run(self: Arc<Self>) -> oneshot::Sender<()> {
let refresh_interval = self.app_config.refresh_interval;
let (sender, receiver) = oneshot::channel();
tokio::spawn(async move {
let mut receiver = receiver;
loop {
Statistics::cleanup_expired_flows().await;
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
select! {
biased;
_ = &mut receiver => break,
_ = sleep(Duration::from_secs(refresh_interval)) => {
self.cleanup_expired_flows().await;
},
}
}
});
sender
}
pub async fn terminate() {
let mut statistics = Statistics::instance_mut().await;
statistics.terminate = true;
}
pub async fn cleanup_expired_flows() {
let mut statistics = Statistics::instance_mut().await;
let boot_time = System::boot_time().await;
pub async fn cleanup_expired_flows(self: &Arc<Self>) {
let boot_time = self.boot_time;
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos() as u64;
statistics
.ipv4_maps
.iter_mut()
.for_each(|((_, _, time_type), map)| map.cleanup(boot_time, now, time_type.duration()));
statistics
.ipv6_maps
.iter_mut()
.for_each(|((_, _, time_type), map)| map.cleanup(boot_time, now, time_type.duration()));
for ((_, _, time_type), map) in self.ipv4_maps.iter() {
map.write().await.cleanup(boot_time, now, time_type.duration())
}
for ((_, _, time_type), map) in self.ipv6_maps.iter() {
map.write().await.cleanup(boot_time, now, time_type.duration())
}
}
pub async fn get_ipv4_flow_data(
&self,
direction: Direction,
flow_direction: FlowDirection,
time_type: TimeType,
) -> StdHashMap<SocketAddrV4, FlowStats> {
let statistics = Statistics::instance().await;
statistics
.ipv4_maps
) -> HashMap<SocketAddrV4, FlowStats> {
self.ipv4_maps
.get(&(direction, flow_direction, time_type))
.map(|map| map.get_map())
.unwrap()
.write()
.await
.get_map()
}
pub async fn get_ipv6_flow_data(
&self,
direction: Direction,
flow_direction: FlowDirection,
time_type: TimeType,
) -> StdHashMap<SocketAddrV6, FlowStats> {
let statistics = Statistics::instance().await;
statistics
.ipv6_maps
) -> HashMap<SocketAddrV6, FlowStats> {
self.ipv6_maps
.get(&(direction, flow_direction, time_type))
.map(|map| map.get_map())
.unwrap()
.write()
.await
.get_map()
}
}
@ -207,8 +170,14 @@ struct FlowMap<T> {
map: AyaHashMap<MapData, T, FlowStats>,
}
impl<T: IntoNative + Pod> FlowMap<T> {
fn get_map(&self) -> StdHashMap<T::Native, FlowStats> {
impl<T: NativeConvert + Pod> FlowMap<T> {
fn new(ebpf: &mut Ebpf, map_name: &str) -> Result<Self, Error> {
let map = ebpf.take_map(map_name).ok_or(EbpfError::MapNotFound)?;
let map = AyaHashMap::try_from(map).map_err(EbpfError::MapOperationError)?;
Ok(Self { map })
}
fn get_map(&self) -> HashMap<T::Native, FlowStats> {
self.map
.iter()
.filter_map(Result::ok)

View File

@ -0,0 +1,257 @@
use std::ffi::CString;
use std::num::NonZero;
use std::os::fd::AsRawFd;
use std::sync::Arc;
use std::time::Duration;
use aya::maps::{MapData, XskMap};
use aya::Ebpf;
use crossbeam::queue::SegQueue;
use macros::log;
use parking_lot::Mutex;
use tokio::select;
use tokio::sync::oneshot;
use tokio::time::sleep;
use xsk_rs::config::{BindFlags, FrameSize, Interface, QueueSize, SocketConfig, UmemConfig};
use xsk_rs::{CompQueue, FillQueue, FrameDesc, RxQueue, Socket, TxQueue, Umem};
use crate::core::infrastructure::app_config::AppConfig;
use crate::model::config::Config;
use crate::model::error::ebpf::EbpfError;
use crate::model::error::system::SystemError;
use crate::model::error::Error;
use crate::model::log::ebpf::EbpfLog;
pub struct XskManager {
app_config: Arc<AppConfig>,
xsk_map: Mutex<XskMap<MapData>>,
shutdowns: SegQueue<oneshot::Sender<()>>,
}
impl XskManager {
pub fn new(app_config: Arc<AppConfig>, ebpf: &mut Ebpf) -> Result<Self, Error> {
let map = ebpf.take_map("XSKS_MAP").ok_or(EbpfError::MapNotFound)?;
let xsk_map = XskMap::try_from(map).map_err(EbpfError::MapOperationError)?;
Ok(Self {
app_config,
xsk_map: Mutex::new(xsk_map),
shutdowns: SegQueue::new(),
})
}
pub fn run(&self) -> Result<(), Error> {
let config = self.app_config.config.clone();
let combined_queue_count = config.combined_queue_count;
let mut xsk_map = self.xsk_map.lock();
for queue_id in 0..combined_queue_count {
let xsk = Xsk::new(config.clone(), &mut xsk_map, queue_id)?;
let shutdown = xsk.run();
self.shutdowns.push(shutdown);
}
Ok(())
}
pub fn shutdown(&self) {
while let Some(sender) = self.shutdowns.pop() {
if sender.send(()).is_err() {
log!(SystemError::ShutdownSignalFailed);
}
}
}
}
pub struct Xsk {
umem: Umem,
fill_queue: FillQueue,
comp_queue: CompQueue,
tx: TxQueue,
rx: RxQueue,
}
impl Xsk {
pub fn new(config: Config, xsk_map: &mut XskMap<MapData>, queue_id: u32) -> Result<Self, Error> {
let ifname = CString::new(config.ingress_ifname.as_str()).map_err(|_| SystemError::UnknownError)?;
let fill_queue_size = QueueSize::new(config.fill_queue_size).map_err(|_| SystemError::InvalidConfig)?;
let comp_queue_size = QueueSize::new(config.comp_queue_size).map_err(|_| SystemError::InvalidConfig)?;
let tx_queue_size = QueueSize::new(config.tx_queue_size).map_err(|_| SystemError::InvalidConfig)?;
let rx_queue_size = QueueSize::new(config.rx_queue_size).map_err(|_| SystemError::InvalidConfig)?;
let frame_size = FrameSize::new(config.frame_size).map_err(|_| SystemError::InvalidConfig)?;
let frame_count = NonZero::new(config.frame_count).ok_or(SystemError::InvalidConfig)?;
let umem_config = UmemConfig::builder()
.fill_queue_size(fill_queue_size)
.comp_queue_size(comp_queue_size)
.frame_size(frame_size)
.frame_headroom(0)
.build()
.map_err(EbpfError::UmemSetFailed)?;
let (umem, frame_descs) = Umem::new(umem_config, frame_count, false).map_err(EbpfError::UmemSetFailed)?;
let socket_config = SocketConfig::builder()
.tx_queue_size(tx_queue_size)
.rx_queue_size(rx_queue_size)
.bind_flags(BindFlags::empty())
.build();
let interface = Interface::new(ifname);
let (tx, rx, queue) =
Socket::new(socket_config, &umem, &interface, queue_id).map_err(EbpfError::SocketSetFailed)?;
let (mut fill_queue, comp_queue) = queue.ok_or(EbpfError::UnknownError)?;
let socket_fd = rx.fd().as_raw_fd();
xsk_map.set(queue_id, socket_fd, 0).map_err(EbpfError::AfXdpSetFailed)?;
let frames: Vec<FrameDesc> = frame_descs
.iter()
.take(config.fill_queue_size as usize)
.copied()
.collect();
let submitted = unsafe { fill_queue.produce(&frames) };
if submitted != frames.len() {
log!(EbpfLog::QueueInitIncomplete);
}
Ok(Self {
umem,
fill_queue,
comp_queue,
tx,
rx,
})
}
pub fn run(mut self) -> oneshot::Sender<()> {
let (sender, receiver) = oneshot::channel();
tokio::spawn(async move {
let mut receiver = receiver;
loop {
select! {
biased;
_ = &mut receiver => break,
_ = self.process_events() => {},
}
}
});
sender
}
async fn process_events(&mut self) {
let mut comp_descs = vec![FrameDesc::default(); 64];
let comp_count = unsafe { self.comp_queue.consume(&mut comp_descs) };
if comp_count > 0 {
let submitted = unsafe { self.fill_queue.produce(&comp_descs[..comp_count]) };
if submitted != comp_count {
log!(EbpfLog::QueueRefillIncomplete);
}
}
let mut packet_count = 0;
let mut tx_descs = Vec::with_capacity(64);
let mut rx_descs = vec![FrameDesc::default(); 64];
let rx_count = unsafe { self.rx.consume(&mut rx_descs) };
for rx_desc in &rx_descs[..rx_count] {
let data = unsafe { self.umem.data(rx_desc) };
let packet_data = &data.contents()[..rx_desc.lengths().data()];
let packet_copy = packet_data.to_vec();
Self::print_packet_info(&packet_copy);
tx_descs.push(*rx_desc);
packet_count += 1;
}
if !tx_descs.is_empty() {
let tx_submitted = unsafe { self.tx.produce(&tx_descs) };
if tx_submitted != tx_descs.len() {
log!(EbpfLog::QueueRefillIncomplete);
for desc in &tx_descs[tx_submitted..] {
unsafe {
let _ = self.fill_queue.produce(&[*desc]);
}
}
} else {
if let Err(err) = self.tx.wakeup() {
log!(EbpfError::WakeupTXFailed(err))
}
}
}
if packet_count > 0 {
println!("Processed {} packets", packet_count);
}
if packet_count == 0 {
sleep(Duration::from_millis(1)).await;
}
}
fn print_packet_info(packet_data: &[u8]) {
if packet_data.len() < 14 {
println!("Packet too small: {} bytes", packet_data.len());
return;
}
println!("Received packet: {} bytes", packet_data.len());
let print_len = std::cmp::min(64, packet_data.len());
print!("Data: ");
for i in 0..print_len {
print!("{:02x} ", packet_data[i]);
if (i + 1) % 16 == 0 {
print!("\n ");
}
}
println!();
let dst_mac = &packet_data[0..6];
let src_mac = &packet_data[6..12];
let eth_type = u16::from_be_bytes([packet_data[12], packet_data[13]]);
println!(
"Ethernet: src={:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}, dst={:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}, type=0x{:04x}",
src_mac[0],
src_mac[1],
src_mac[2],
src_mac[3],
src_mac[4],
src_mac[5],
dst_mac[0],
dst_mac[1],
dst_mac[2],
dst_mac[3],
dst_mac[4],
dst_mac[5],
eth_type
);
match eth_type {
0x0800 => {
println!(" -> IPv4 packet");
if packet_data.len() >= 34 {
let src_ip = &packet_data[26..30];
let dst_ip = &packet_data[30..34];
println!(
" IP: {}.{}.{}.{} -> {}.{}.{}.{}",
src_ip[0], src_ip[1], src_ip[2], src_ip[3], dst_ip[0], dst_ip[1], dst_ip[2], dst_ip[3]
);
}
}
0x86DD => println!(" -> IPv6 packet"),
0x0806 => println!(" -> ARP packet"),
_ => println!(" -> Unknown protocol"),
}
println!("---");
}
}

View File

@ -1,345 +0,0 @@
use std::sync::OnceLock;
use std::time::Duration;
use macros::log;
use sysinfo::{Components, Networks, System};
use tokio::sync::{broadcast, mpsc, RwLock, RwLockReadGuard, RwLockWriteGuard};
use tokio::time::interval;
use crate::core::app_config::AppConfig;
use crate::model::error::misc::MiscError;
use crate::model::healthy::*;
static SYSTEM_HEALTH_INSTANCE: OnceLock<RwLock<SystemHealth>> = OnceLock::new();
pub struct SystemHealth {
system: System,
networks: Networks,
components: Components,
broadcast_tx: broadcast::Sender<SystemHealthMetrics>,
shutdown_tx: mpsc::UnboundedSender<()>,
ingress_interface: String,
egress_interface: String,
management_interface: String,
}
impl SystemHealth {
pub async fn initialize(monitoring_interval: Duration) {
let (broadcast_tx, _) = broadcast::channel(100);
let (shutdown_tx, shutdown_rx) = mpsc::unbounded_channel();
let config = AppConfig::now().await;
let system_health = SystemHealth {
system: System::new_all(),
networks: Networks::new_with_refreshed_list(),
components: Components::new_with_refreshed_list(),
broadcast_tx: broadcast_tx.clone(),
shutdown_tx,
ingress_interface: config.ingress_ifindex.clone(),
egress_interface: config.egress_ifindex.clone(),
management_interface: config.management_ifindex.clone(),
};
SYSTEM_HEALTH_INSTANCE.get_or_init(|| RwLock::new(system_health));
let ingress_interface = config.ingress_ifindex;
let egress_interface = config.egress_ifindex;
let management_interface = config.management_ifindex;
tokio::spawn(async move {
Self::monitoring_loop(
broadcast_tx,
shutdown_rx,
monitoring_interval,
ingress_interface,
egress_interface,
management_interface,
)
.await;
});
}
async fn monitoring_loop(
broadcast_tx: broadcast::Sender<SystemHealthMetrics>,
mut shutdown_rx: mpsc::UnboundedReceiver<()>,
monitoring_interval: Duration,
ingress_interface: String,
egress_interface: String,
management_interface: String,
) {
let mut system = System::new_all();
let mut networks = Networks::new_with_refreshed_list();
let mut components = Components::new_with_refreshed_list();
let mut interval_timer = interval(monitoring_interval);
loop {
tokio::select! {
_ = shutdown_rx.recv() => {
break;
}
_ = interval_timer.tick() => {
system.refresh_all();
networks.refresh(true);
components.refresh(true);
let metrics = Self::collect_metrics(
&system,
&networks,
&components,
&ingress_interface,
&egress_interface,
&management_interface,
);
if broadcast_tx.receiver_count() > 0 {
if let Err(err) = broadcast_tx.send(metrics) {
log!(MiscError::SendMessageError(err))
}
}
}
}
}
}
pub async fn instance() -> RwLockReadGuard<'static, SystemHealth> {
let instance = SYSTEM_HEALTH_INSTANCE.get().unwrap();
instance.read().await
}
pub async fn instance_mut() -> RwLockWriteGuard<'static, SystemHealth> {
let instance = SYSTEM_HEALTH_INSTANCE.get().unwrap();
instance.write().await
}
fn collect_metrics(
system: &System,
networks: &Networks,
components: &Components,
ingress_interface: &str,
egress_interface: &str,
management_interface: &str,
) -> SystemHealthMetrics {
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
let boot_time = System::boot_time();
let uptime_seconds = timestamp - boot_time;
let system_info = Self::collect_system_info(system);
let cpu_details = Self::collect_cpu_details(system);
let memory_usage = MemoryUsage {
total: system.total_memory(),
used: system.used_memory(),
available: system.available_memory(),
usage_percent: (system.used_memory() as f32 / system.total_memory() as f32) * 100.0,
swap_total: system.total_swap(),
swap_used: system.used_swap(),
};
let network_stats =
Self::collect_configured_network_stats(networks, ingress_interface, egress_interface, management_interface);
let load_average = System::load_average();
let load_average = if load_average.one != 0.0 || load_average.five != 0.0 || load_average.fifteen != 0.0 {
Some(LoadAverage {
one_minute: load_average.one,
five_minute: load_average.five,
fifteen_minute: load_average.fifteen,
})
} else {
None
};
let temperature = components
.iter()
.find(|component| {
let label = component.label().to_lowercase();
label.contains("cpu") || label.contains("core") || label.contains("processor")
})
.and_then(|component| component.temperature());
SystemHealthMetrics {
timestamp,
boot_time,
uptime_seconds,
system_info,
cpu_details,
memory_usage,
network_stats,
load_average,
temperature,
}
}
fn collect_system_info(system: &System) -> SystemInfo {
SystemInfo {
kernel_version: System::kernel_version(),
os_name: System::name(),
os_version: System::os_version(),
architecture: std::env::consts::ARCH.to_string(),
total_processes: system.processes().len(),
}
}
fn collect_cpu_details(system: &System) -> CpuDetails {
let cpus = system.cpus();
let cpu_usage = cpus.iter().map(|cpu| cpu.cpu_usage()).sum::<f32>() / cpus.len() as f32;
let cores: Vec<CpuCoreInfo> = cpus
.iter()
.enumerate()
.map(|(index, cpu)| CpuCoreInfo {
core_id: index,
usage_percent: cpu.cpu_usage(),
frequency: cpu.frequency(),
})
.collect();
let cpu_brand = cpus
.first()
.map(|cpu| cpu.brand().to_string())
.unwrap_or_else(|| "Unknown".to_string());
let avg_frequency = if !cores.is_empty() {
cores.iter().map(|core| core.frequency).sum::<u64>() / cores.len() as u64
} else {
0
};
CpuDetails {
cpu_brand,
core_count: cores.len(),
cpu_usage,
cpu_frequency: avg_frequency,
cores,
}
}
fn collect_configured_network_stats(
networks: &Networks,
ingress_interface: &str,
egress_interface: &str,
management_interface: &str,
) -> ConfiguredNetworkStats {
let create_network_stats = |interface_name: &str| -> Option<NetworkStats> {
networks.get(interface_name).map(|network| NetworkStats {
interface: interface_name.to_string(),
bytes_received: network.total_received(),
bytes_transmitted: network.total_transmitted(),
packets_received: network.total_packets_received(),
packets_transmitted: network.total_packets_transmitted(),
errors_received: network.total_errors_on_received(),
errors_transmitted: network.total_errors_on_transmitted(),
})
};
let ingress = create_network_stats(ingress_interface);
let egress = create_network_stats(egress_interface);
let management = create_network_stats(management_interface);
if ingress.is_none() {
log!(MiscError::NetworkInterfaceNotFound(ingress_interface));
}
if egress.is_none() {
log!(MiscError::NetworkInterfaceNotFound(egress_interface));
}
if management.is_none() {
log!(MiscError::NetworkInterfaceNotFound(management_interface));
}
ConfiguredNetworkStats {
ingress,
egress,
management,
}
}
pub async fn get_current_metrics() -> SystemHealthMetrics {
let mut instance = Self::instance_mut().await;
instance.system.refresh_all();
instance.networks.refresh(true);
instance.components.refresh(true);
Self::collect_metrics(
&instance.system,
&instance.networks,
&instance.components,
&instance.ingress_interface,
&instance.egress_interface,
&instance.management_interface,
)
}
pub async fn subscribe_to_metrics() -> broadcast::Receiver<SystemHealthMetrics> {
let instance = Self::instance().await;
instance.broadcast_tx.subscribe()
}
pub async fn shutdown() {
if let Ok(instance) = SYSTEM_HEALTH_INSTANCE.get().unwrap().try_read() {
let _ = instance.shutdown_tx.send(());
}
}
pub async fn is_system_healthy() -> SystemHealthStatus {
let metrics = Self::get_current_metrics().await;
let mut status = SystemHealthStatus {
overall_healthy: true,
issues: Vec::new(),
warnings: Vec::new(),
};
if metrics.cpu_details.cpu_usage > 90.0 {
status.overall_healthy = false;
status
.issues
.push(format!("High CPU usage: {:.1}%", metrics.cpu_details.cpu_usage));
} else if metrics.cpu_details.cpu_usage > 75.0 {
status
.warnings
.push(format!("Moderate CPU usage: {:.1}%", metrics.cpu_details.cpu_usage));
}
if metrics.memory_usage.usage_percent > 95.0 {
status.overall_healthy = false;
status.issues.push(format!(
"Critical memory usage: {:.1}%",
metrics.memory_usage.usage_percent
));
} else if metrics.memory_usage.usage_percent > 80.0 {
status
.warnings
.push(format!("High memory usage: {:.1}%", metrics.memory_usage.usage_percent));
}
if let Some(temp) = metrics.temperature {
if temp > 80.0 {
status.overall_healthy = false;
status.issues.push(format!("High CPU temperature: {:.1}°C", temp));
} else if temp > 70.0 {
status.warnings.push(format!("Elevated CPU temperature: {:.1}°C", temp));
}
}
if metrics.network_stats.ingress.is_none() {
status.overall_healthy = false;
status.issues.push("Ingress interface not available".to_string());
}
if metrics.network_stats.egress.is_none() {
status.overall_healthy = false;
status.issues.push("Egress interface not available".to_string());
}
if metrics.network_stats.management.is_none() {
status.warnings.push("Management interface not available".to_string());
}
status
}
}

View File

@ -0,0 +1,39 @@
use std::fs;
use std::ops::Deref;
use crate::model::config::{Config, ConfigTable};
use crate::model::error::system::SystemError;
use crate::model::error::Error;
pub struct AppConfig {
pub config: Config,
}
impl AppConfig {
pub fn new() -> Result<Self, Error> {
let toml_string = fs::read_to_string("./config.toml").map_err(SystemError::ConfigNotFound)?;
let config_table = toml::from_str::<ConfigTable>(&toml_string).map_err(|_| SystemError::InvalidConfig)?;
let config = config_table.config;
if !Self::validate(&config) {
Err(SystemError::InvalidConfig)?
} else {
Ok(Self { config })
}
}
fn validate(config: &Config) -> bool {
Self::validate_second(config.refresh_interval)
}
fn validate_second(second: u64) -> bool {
second <= 3600
}
}
impl Deref for AppConfig {
type Target = Config;
fn deref(&self) -> &Self::Target {
&self.config
}
}

View File

@ -0,0 +1 @@
pub mod app_config;

View File

@ -1,5 +1,3 @@
pub mod app_config;
pub mod control;
pub mod health;
pub mod statistics;
pub mod ebpf;
pub mod infrastructure;
pub mod system;

View File

@ -1,35 +1,30 @@
use std::sync::OnceLock;
use std::time::Duration;
use std::sync::Arc;
use actix_web::web::route;
use actix_web::{App, HttpServer};
use actix_web::{web, App, HttpServer};
use aya::maps::{MapData, ProgramArray};
use aya::programs::{Xdp, XdpFlags};
use aya::Ebpf;
use aya_log::EbpfLogger;
use common::define::program_array::*;
use macros::log;
use sysinfo::System as SystemInfo;
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use crate::core::app_config::AppConfig;
use crate::core::control::Control;
use crate::core::health::SystemHealth;
use crate::core::statistics::Statistics;
use crate::core::ebpf::EbpfServices;
use crate::core::infrastructure::app_config::AppConfig;
use crate::model::error::ebpf::EbpfError;
use crate::model::error::http::HttpError;
use crate::model::error::misc::MiscError;
use crate::model::error::Error;
use crate::model::log::ebpf::EbpfLog;
use crate::model::log::system::SystemLog;
use crate::utils::logging::Logging;
use crate::web::api::{control, default, health, misc, statistics};
static SYSTEM: OnceLock<RwLock<System>> = OnceLock::new();
use crate::web::api::{control, default, misc};
pub struct System {
pub app_config: Arc<AppConfig>,
pub ebpf_services: Arc<EbpfServices>,
pub ingress_ebpf: Ebpf,
pub egress_ebpf: Ebpf,
pub boot_time: u64,
#[allow(dead_code)]
ingress_program_array: ProgramArray<MapData>,
#[allow(dead_code)]
@ -37,57 +32,107 @@ pub struct System {
}
impl System {
pub async fn initialize() -> Result<(), Error> {
Logging::initialize().await?;
log!(SystemLog::Initializing);
AppConfig::initialization().await?;
SystemHealth::initialize(Duration::from_secs(5)).await;
System::ebpf_initialize().await?;
Statistics::initialize().await?;
Control::initialize().await?;
log!(SystemLog::InitializeComplete);
Ok(())
}
async fn ebpf_initialize() -> Result<(), Error> {
let config = AppConfig::now().await;
let ingress_interface = config.ingress_ifindex;
let egress_interface = config.egress_ifindex;
let boot_time = SystemInfo::boot_time() * 1_000_000_000;
Self::set_memory_limit()?;
pub async fn new() -> Result<Self, Error> {
let (mut ingress_ebpf, ingress_program_array) = System::get_ingress_ebpf()?;
let ingress_program: &mut Xdp = ingress_ebpf
.program_mut("net_guardia")
.ok_or(EbpfError::ProgramNotFound)?
.try_into()
.map_err(EbpfError::GetProgramFailed)?;
let (mut egress_ebpf, egress_program_array) = System::get_egress_ebpf()?;
let egress_program: &mut Xdp = egress_ebpf
.program_mut("net_guardia")
.ok_or(EbpfError::ProgramNotFound)?
.try_into()
.map_err(EbpfError::GetProgramFailed)?;
ingress_program.load().map_err(EbpfError::LoadProgramFailed)?;
ingress_program
.attach(&ingress_interface, XdpFlags::default())
.map_err(EbpfError::AttachProgramFailed)?;
egress_program.load().map_err(EbpfError::LoadProgramFailed)?;
egress_program
.attach(&egress_interface, XdpFlags::default())
.map_err(EbpfError::AttachProgramFailed)?;
let app_config = Arc::new(AppConfig::new()?);
let ebpf_services = Arc::new(EbpfServices::new(
app_config.clone(),
&mut ingress_ebpf,
&mut egress_ebpf,
)?);
let system = System {
app_config,
ebpf_services,
ingress_ebpf,
egress_ebpf,
boot_time,
ingress_program_array,
egress_program_array,
};
SYSTEM.get_or_init(|| RwLock::new(system));
log!(EbpfLog::AttachProgramSuccess);
Ok(system)
}
pub async fn run(&mut self) -> Result<(), Error> {
let ebpf_services = self.ebpf_services.clone();
Logging::initialize()?;
log!(SystemLog::Initializing);
self.aya_log_init()?;
log!(SystemLog::InitializeComplete);
self.attach_ebpf()?;
ebpf_services.run().await?;
self.run_http_server().await?;
Ok(())
}
pub async fn terminate(&self) -> Result<(), Error> {
let ebpf_services = self.ebpf_services.clone();
log!(SystemLog::Terminating);
ebpf_services.terminate();
log!(SystemLog::TerminateComplete);
Ok(())
}
fn aya_log_init(&mut self) -> Result<(), Error> {
EbpfLogger::init(&mut self.ingress_ebpf).map_err(EbpfError::LoggerInitFailed)?;
EbpfLogger::init(&mut self.egress_ebpf).map_err(EbpfError::LoggerInitFailed)?;
Ok(())
}
fn attach_ebpf(&mut self) -> Result<(), Error> {
let config = self.app_config.config.clone();
let ingress_ifname = config.ingress_ifname;
let egress_ifname = config.egress_ifname;
Self::set_memory_limit()?;
let ingress_xdp: &mut Xdp = self
.ingress_ebpf
.program_mut("net_guardia")
.ok_or(EbpfError::ProgramNotFound)?
.try_into()
.map_err(EbpfError::GetProgramFailed)?;
let egress_xdp: &mut Xdp = self
.egress_ebpf
.program_mut("net_guardia")
.ok_or(EbpfError::ProgramNotFound)?
.try_into()
.map_err(EbpfError::GetProgramFailed)?;
ingress_xdp.load().map_err(EbpfError::LoadProgramFailed)?;
ingress_xdp
.attach(&ingress_ifname, XdpFlags::DRV_MODE)
.map_err(EbpfError::AttachProgramFailed)?;
egress_xdp.load().map_err(EbpfError::LoadProgramFailed)?;
egress_xdp
.attach(&egress_ifname, XdpFlags::DRV_MODE)
.map_err(EbpfError::AttachProgramFailed)?;
Ok(())
}
async fn run_http_server(&self) -> Result<(), Error> {
let app_config = self.app_config.clone();
let access_control = self.ebpf_services.access_control.clone();
let service = self.ebpf_services.service.clone();
let statistics = self.ebpf_services.statistics.clone();
let port = self.app_config.http_server_bind_port;
HttpServer::new(move || {
let cors = actix_cors::Cors::default()
.allow_any_origin()
.allow_any_method()
.allow_any_header()
.max_age(3600);
App::new()
.wrap(cors)
.app_data(web::Data::from(app_config.clone()))
.app_data(web::Data::from(access_control.clone()))
.app_data(web::Data::from(service.clone()))
.app_data(web::Data::from(statistics.clone()))
.service(control::initialize())
.service(misc::initialize())
.default_service(route().to(default::default_route))
})
.bind(format!("0.0.0.0:{}", port))
.map_err(HttpError::BindPortError)?
.run()
.await
.map_err(HttpError::ServerPanic)?;
Ok(())
}
@ -97,12 +142,12 @@ impl System {
"/net-guardia-ingress"
)))
.map_err(EbpfError::EbpfNotFound)?;
EbpfLogger::init(&mut ingress_ebpf).map_err(EbpfError::LoggerInitFailed)?;
let program_array = ingress_ebpf.take_map("PROGRAM_ARRAY").ok_or(EbpfError::MapNotFound)?;
let mut program_array = ProgramArray::try_from(program_array).map_err(EbpfError::MapOperationError)?;
Self::load_program(&mut ingress_ebpf, &mut program_array, "access_control", 0)?;
Self::load_program(&mut ingress_ebpf, &mut program_array, "service", 1)?;
Self::load_program(&mut ingress_ebpf, &mut program_array, "statistics", 2)?;
Self::load_program(&mut ingress_ebpf, &mut program_array, "access_control", ingress::ACCESS_CONTROL)?;
Self::load_program(&mut ingress_ebpf, &mut program_array, "service", ingress::SERVICE)?;
Self::load_program(&mut ingress_ebpf, &mut program_array, "statistics", ingress::STATISTICS)?;
Self::load_program(&mut ingress_ebpf, &mut program_array, "transmission", ingress::TRANSMISSION)?;
Ok((ingress_ebpf, program_array))
}
@ -112,25 +157,12 @@ impl System {
"/net-guardia-egress"
)))
.map_err(EbpfError::EbpfNotFound)?;
EbpfLogger::init(&mut egress_ebpf).map_err(EbpfError::LoggerInitFailed)?;
let program_array = egress_ebpf.take_map("PROGRAM_ARRAY").ok_or(EbpfError::MapNotFound)?;
let mut program_array = ProgramArray::try_from(program_array).map_err(EbpfError::MapOperationError)?;
Self::load_program(&mut egress_ebpf, &mut program_array, "statistics", 0)?;
Self::load_program(&mut egress_ebpf, &mut program_array, "statistics", egress::STATISTICS)?;
Ok((egress_ebpf, program_array))
}
fn set_memory_limit() -> Result<(), Error> {
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 {
Err(MiscError::RamLimitUnlockError(ret))?
}
Ok(())
}
fn load_program(
ebpf: &mut Ebpf,
program_array: &mut ProgramArray<MapData>,
@ -148,60 +180,15 @@ impl System {
Ok(())
}
pub async fn run() -> Result<(), Error> {
log!(SystemLog::Online);
Statistics::run().await;
let config = AppConfig::now().await;
HttpServer::new(|| {
let cors = actix_cors::Cors::default()
.allow_any_origin()
.allow_any_method()
.allow_any_header()
.max_age(3600);
App::new()
.wrap(cors)
.service(statistics::initialize())
.service(control::initialize())
.service(misc::initialize())
.service(health::initialize())
.default_service(route().to(default::default_route))
})
.bind(format!("0.0.0.0:{}", config.http_server_bind_port))
.map_err(HttpError::BindPortError)?
.run()
.await
.map_err(HttpError::ServerPanic)?;
fn set_memory_limit() -> Result<(), Error> {
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 {
Err(MiscError::RamLimitUnlockError(ret))?
}
Ok(())
}
pub async fn terminate() -> Result<(), Error> {
log!(SystemLog::Terminating);
Statistics::terminate().await;
SystemHealth::shutdown().await;
log!(SystemLog::TerminateComplete);
Ok(())
}
pub async fn instance() -> RwLockReadGuard<'static, System> {
// Initialization has been ensured
let once_lock = SYSTEM.get().unwrap();
// There is no lock acquired multiple times, so this is safe
once_lock.read().await
}
pub async fn instance_mut() -> RwLockWriteGuard<'static, System> {
// Initialization has been ensured
let once_lock = SYSTEM.get().unwrap();
// There is no lock acquired multiple times, so this is safe
once_lock.write().await
}
pub async fn boot_time() -> u64 {
let system = System::instance().await;
system.boot_time
}
}

View File

@ -8,8 +8,8 @@ use crate::model::error::Error;
#[actix_web::main]
async fn main() -> Result<(), Error> {
System::initialize().await?;
System::run().await?;
System::terminate().await?;
let mut system = System::new().await?;
system.run().await?;
system.terminate().await?;
Ok(())
}

View File

@ -1,5 +1,3 @@
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize)]
@ -10,10 +8,15 @@ pub struct ConfigTable {
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Config {
pub ingress_ifindex: String,
pub egress_ifindex: String,
pub management_ifindex: String,
pub alert_path: PathBuf,
pub ingress_ifname: String,
pub egress_ifname: String,
pub combined_queue_count: u32,
pub fill_queue_size: u32,
pub comp_queue_size: u32,
pub tx_queue_size: u32,
pub rx_queue_size: u32,
pub frame_size: u32,
pub frame_count: u32,
pub refresh_interval: u64,
pub http_server_bind_port: u16,
}

View File

@ -22,6 +22,18 @@ traceable! {
#[error("Failed to attach the XDP program")]
AttachProgramFailed => tracing::Level::ERROR,
#[error("Failed to set umem")]
UmemSetFailed => tracing::Level::ERROR,
#[error("Failed to set AF_XDP socket")]
SocketSetFailed => tracing::Level::ERROR,
#[error("Failed to set AF_XDP")]
AfXdpSetFailed => tracing::Level::ERROR,
#[error("Failed to wakeup TX")]
WakeupTXFailed => tracing::Level::ERROR,
#[no_source]
#[error("Map not found")]
MapNotFound => tracing::Level::ERROR,

View File

@ -1,81 +0,0 @@
use serde::Serialize;
#[derive(Debug, Clone, Serialize)]
pub struct SystemHealthMetrics {
pub timestamp: u64,
pub boot_time: u64,
pub uptime_seconds: u64,
pub system_info: SystemInfo,
pub cpu_details: CpuDetails,
pub memory_usage: MemoryUsage,
pub network_stats: ConfiguredNetworkStats,
pub load_average: Option<LoadAverage>,
pub temperature: Option<f32>,
}
#[derive(Debug, Clone, Serialize)]
pub struct SystemInfo {
pub kernel_version: Option<String>,
pub os_name: Option<String>,
pub os_version: Option<String>,
pub architecture: String,
pub total_processes: usize,
}
#[derive(Debug, Clone, Serialize)]
pub struct CpuDetails {
pub cpu_brand: String,
pub core_count: usize,
pub cpu_usage: f32,
pub cpu_frequency: u64,
pub cores: Vec<CpuCoreInfo>,
}
#[derive(Debug, Clone, Serialize)]
pub struct CpuCoreInfo {
pub core_id: usize,
pub usage_percent: f32,
pub frequency: u64,
}
#[derive(Debug, Clone, Serialize)]
pub struct MemoryUsage {
pub total: u64,
pub used: u64,
pub available: u64,
pub usage_percent: f32,
pub swap_total: u64,
pub swap_used: u64,
}
#[derive(Debug, Clone, Serialize)]
pub struct ConfiguredNetworkStats {
pub ingress: Option<NetworkStats>,
pub egress: Option<NetworkStats>,
pub management: Option<NetworkStats>,
}
#[derive(Debug, Clone, Serialize)]
pub struct NetworkStats {
pub interface: String,
pub bytes_received: u64,
pub bytes_transmitted: u64,
pub packets_received: u64,
pub packets_transmitted: u64,
pub errors_received: u64,
pub errors_transmitted: u64,
}
#[derive(Debug, Clone, Serialize)]
pub struct LoadAverage {
pub one_minute: f64,
pub five_minute: f64,
pub fifteen_minute: f64,
}
#[derive(Debug, Clone, Serialize)]
pub struct SystemHealthStatus {
pub overall_healthy: bool,
pub issues: Vec<String>,
pub warnings: Vec<String>,
}

View File

@ -3,39 +3,56 @@ use std::net::{Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6};
use common::model::ip_address::*;
pub trait IntoNative: Copy {
pub trait NativeConvert: Copy {
type Native: Eq + PartialEq + Hash;
fn into_native(self) -> Self::Native;
fn from_native(native: Self::Native) -> Self;
}
impl IntoNative for IPv4 {
impl NativeConvert for IPv4 {
type Native = Ipv4Addr;
fn into_native(self) -> Self::Native {
Ipv4Addr::from(self)
}
fn from_native(native: Self::Native) -> Self {
native.to_bits()
}
}
impl IntoNative for IPv6 {
impl NativeConvert for IPv6 {
type Native = Ipv6Addr;
fn into_native(self) -> Self::Native {
Ipv6Addr::from(self)
}
fn from_native(native: Self::Native) -> Self {
native.to_bits()
}
}
impl IntoNative for AddrPortV4 {
impl NativeConvert for AddrPortV4 {
type Native = SocketAddrV4;
fn into_native(self) -> Self::Native {
SocketAddrV4::new(Ipv4Addr::from(self.ip), self.port)
SocketAddrV4::new(Ipv4Addr::from(self.ip()), self.port())
}
fn from_native(native: Self::Native) -> Self {
AddrPortV4::new((*native.ip()).to_bits(), native.port())
}
}
impl IntoNative for AddrPortV6 {
impl NativeConvert for AddrPortV6 {
type Native = SocketAddrV6;
fn into_native(self) -> Self::Native {
SocketAddrV6::new(Ipv6Addr::from(self.ip), self.port, 0, 0)
SocketAddrV6::new(Ipv6Addr::from(self.ip()), self.port(), 0, 0)
}
fn from_native(native: Self::Native) -> Self {
AddrPortV6::new((*native.ip()).to_bits(), native.port())
}
}

View File

@ -5,5 +5,14 @@ loggable! {
EbpfLog {
#[error("Attach XDP program success")]
AttachProgramSuccess => tracing::Level::INFO,
#[error("Queue initialization incomplete")]
QueueInitIncomplete => tracing::Level::WARN,
#[error("Queue refill incomplete")]
QueueRefillIncomplete => tracing::Level::WARN,
#[error("No frames submit to queue")]
NoFrameSubmit => tracing::Level::WARN,
}
}

View File

@ -1,7 +1,6 @@
pub mod config;
pub mod direction;
pub mod error;
pub mod healthy;
pub mod ip_address;
pub mod list_type;
pub mod log;

View File

@ -0,0 +1,5 @@
use sysinfo::System as SystemInfo;
pub fn boot_time() -> u64 {
SystemInfo::boot_time() * 1_000_000_000
}

View File

@ -1,4 +1,4 @@
use tokio::fs;
use std::fs;
use tracing::Level;
use tracing_appender::rolling::{RollingFileAppender, Rotation};
use tracing_subscriber::filter::EnvFilter;
@ -11,10 +11,9 @@ use crate::model::error::Error;
pub struct Logging;
impl Logging {
pub async fn initialize() -> Result<(), Error> {
pub fn initialize() -> Result<(), Error> {
let log_directory = "logs";
fs::create_dir_all(log_directory)
.await
.map_err(|err| IOError::CreateDirectoryFailed(log_directory, err))?;
let file_appender = RollingFileAppender::new(Rotation::DAILY, log_directory, "NetGuardia");
@ -34,10 +33,16 @@ impl Logging {
.with_ansi(false)
.with_writer(file_appender);
let level = if cfg!(debug_assertions) {
Level::DEBUG
} else {
Level::INFO
};
tracing_subscriber::registry()
.with(stdout_layer)
.with(file_layer)
.with(EnvFilter::from_default_env().add_directive(Level::INFO.into()))
.with(EnvFilter::from_default_env().add_directive(level.into()))
.init();
Ok(())

View File

@ -1,3 +1,4 @@
pub mod ip_address;
pub mod logging;
pub mod static_files;
pub mod boot_time;

View File

@ -2,7 +2,7 @@ use std::net::{SocketAddrV4, SocketAddrV6};
use actix_web::{delete, get, put, web, HttpResponse, Responder, Scope};
use crate::core::control::access_control::AccessControl;
use crate::core::ebpf::access_control::AccessControl;
use crate::model::direction::FlowDirection;
use crate::model::list_type::ListType;
@ -17,34 +17,48 @@ pub fn initialize() -> Scope {
}
#[get("/ipv4/{direction}/{list_type}")]
async fn get_ipv4_list(path: web::Path<(FlowDirection, ListType)>) -> impl Responder {
async fn get_ipv4_list(
path: web::Path<(FlowDirection, ListType)>,
access_control: web::Data<AccessControl>,
) -> impl Responder {
let (direction, list_type) = path.into_inner();
let list = AccessControl::get_ipv4_list(direction, list_type).await;
let list = access_control.get_ipv4_list(direction, list_type).await;
HttpResponse::Ok().json(list)
}
#[get("/ipv6/{direction}/{list_type}")]
async fn get_ipv6_list(path: web::Path<(FlowDirection, ListType)>) -> impl Responder {
async fn get_ipv6_list(
path: web::Path<(FlowDirection, ListType)>,
access_control: web::Data<AccessControl>,
) -> impl Responder {
let (direction, list_type) = path.into_inner();
let list = AccessControl::get_ipv6_list(direction, list_type).await;
let list = access_control.get_ipv6_list(direction, list_type).await;
HttpResponse::Ok().json(list)
}
#[put("/ipv4/{direction}/{list_type}")]
async fn add_ipv4_list(address: web::Json<SocketAddrV4>, path: web::Path<(FlowDirection, ListType)>) -> impl Responder {
async fn add_ipv4_list(
address: web::Json<SocketAddrV4>,
path: web::Path<(FlowDirection, ListType)>,
access_control: web::Data<AccessControl>,
) -> impl Responder {
let address = address.into_inner();
let (direction, list_type) = path.into_inner();
match AccessControl::add_ipv4_list(direction, list_type, address).await {
match access_control.add_ipv4_list(direction, list_type, address).await {
Ok(_) => HttpResponse::Ok().finish(),
Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
}
}
#[put("/ipv6/{direction}/{list_type}")]
async fn add_ipv6_list(address: web::Json<SocketAddrV6>, path: web::Path<(FlowDirection, ListType)>) -> impl Responder {
async fn add_ipv6_list(
address: web::Json<SocketAddrV6>,
path: web::Path<(FlowDirection, ListType)>,
access_control: web::Data<AccessControl>,
) -> impl Responder {
let address = address.into_inner();
let (direction, list_type) = path.into_inner();
match AccessControl::add_ipv6_list(direction, list_type, address).await {
match access_control.add_ipv6_list(direction, list_type, address).await {
Ok(_) => HttpResponse::Ok().finish(),
Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
}
@ -54,10 +68,11 @@ async fn add_ipv6_list(address: web::Json<SocketAddrV6>, path: web::Path<(FlowDi
async fn remove_ipv4_list(
address: web::Json<SocketAddrV4>,
path: web::Path<(FlowDirection, ListType)>,
access_control: web::Data<AccessControl>,
) -> impl Responder {
let address = address.into_inner();
let (direction, list_type) = path.into_inner();
match AccessControl::remove_ipv4_list(direction, list_type, address).await {
match access_control.remove_ipv4_list(direction, list_type, address).await {
Ok(_) => HttpResponse::Ok().finish(),
Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
}
@ -67,10 +82,11 @@ async fn remove_ipv4_list(
async fn remove_ipv6_list(
address: web::Json<SocketAddrV6>,
path: web::Path<(FlowDirection, ListType)>,
access_control: web::Data<AccessControl>,
) -> impl Responder {
let address = address.into_inner();
let (direction, list_type) = path.into_inner();
match AccessControl::remove_ipv6_list(direction, list_type, address).await {
match access_control.remove_ipv6_list(direction, list_type, address).await {
Ok(_) => HttpResponse::Ok().finish(),
Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
}

View File

@ -1,10 +1,12 @@
use actix_web::{web, Scope};
pub mod access_control;
pub mod service;
pub mod statistics;
use actix_web::{web, Scope};
pub fn initialize() -> Scope {
web::scope("/control")
web::scope("/ebpf")
.service(access_control::initialize())
.service(service::initialize())
.service(statistics::initialize())
}

View File

@ -3,7 +3,7 @@ use std::net::{Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6};
use actix_web::{delete, get, post, put, web, HttpResponse, Responder, Scope};
use common::model::http_method::HttpMethod;
use crate::core::control::service::Service;
use crate::core::ebpf::service::Service;
pub fn initialize() -> Scope {
web::scope("/service")
@ -37,202 +37,214 @@ pub fn initialize() -> Scope {
}
#[get("/ipv4/http_service")]
async fn get_ipv4_http_service() -> impl Responder {
let list = Service::get_ipv4_http_service().await;
async fn get_ipv4_http_service(service: web::Data<Service>) -> impl Responder {
let list = service.get_ipv4_http_service().await;
HttpResponse::Ok().json(web::Json(list))
}
#[get("/ipv6/http_service")]
async fn get_ipv6_http_service() -> impl Responder {
let list = Service::get_ipv6_http_service().await;
async fn get_ipv6_http_service(service: web::Data<Service>) -> impl Responder {
let list = service.get_ipv6_http_service().await;
HttpResponse::Ok().json(web::Json(list))
}
#[put("/ipv4/http_service")]
async fn add_ipv4_http_service(payload: web::Json<(SocketAddrV4, Vec<HttpMethod>)>) -> impl Responder {
async fn add_ipv4_http_service(
payload: web::Json<(SocketAddrV4, Vec<HttpMethod>)>,
service: web::Data<Service>,
) -> impl Responder {
let (addr, methods) = payload.into_inner();
match Service::add_ipv4_http_service(addr, methods).await {
match service.add_ipv4_http_service(addr, methods).await {
Ok(_) => HttpResponse::Ok().finish(),
Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
}
}
#[put("/ipv6/http_service")]
async fn add_ipv6_http_service(payload: web::Json<(SocketAddrV6, Vec<HttpMethod>)>) -> impl Responder {
async fn add_ipv6_http_service(
payload: web::Json<(SocketAddrV6, Vec<HttpMethod>)>,
service: web::Data<Service>,
) -> impl Responder {
let (addr, methods) = payload.into_inner();
match Service::add_ipv6_http_service(addr, methods).await {
match service.add_ipv6_http_service(addr, methods).await {
Ok(_) => HttpResponse::Ok().finish(),
Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
}
}
#[delete("/ipv4/http_service")]
async fn remove_ipv4_http_service(payload: web::Json<(SocketAddrV4, Vec<HttpMethod>)>) -> impl Responder {
async fn remove_ipv4_http_service(
payload: web::Json<(SocketAddrV4, Vec<HttpMethod>)>,
service: web::Data<Service>,
) -> impl Responder {
let (addr, methods) = payload.into_inner();
match Service::remove_ipv4_http_service(addr, methods).await {
match service.remove_ipv4_http_service(addr, methods).await {
Ok(_) => HttpResponse::Ok().finish(),
Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
}
}
#[delete("/ipv6/http_service")]
async fn remove_ipv6_http_service(payload: web::Json<(SocketAddrV6, Vec<HttpMethod>)>) -> impl Responder {
async fn remove_ipv6_http_service(
payload: web::Json<(SocketAddrV6, Vec<HttpMethod>)>,
service: web::Data<Service>,
) -> impl Responder {
let (addr, methods) = payload.into_inner();
match Service::remove_ipv6_http_service(addr, methods).await {
match service.remove_ipv6_http_service(addr, methods).await {
Ok(_) => HttpResponse::Ok().finish(),
Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
}
}
#[get("/ssh_white_list")]
async fn is_ssh_white_list_enable() -> impl Responder {
let enabled = Service::is_ssh_white_list_enable().await;
async fn is_ssh_white_list_enable(service: web::Data<Service>) -> impl Responder {
let enabled = service.is_ssh_white_list_enable().await;
HttpResponse::Ok().json(enabled)
}
#[post("/ssh_white_list/enable")]
async fn enable_ssh_white_list() -> impl Responder {
match Service::enable_ssh_white_list().await {
async fn enable_ssh_white_list(service: web::Data<Service>) -> impl Responder {
match service.enable_ssh_white_list().await {
Ok(_) => HttpResponse::Ok().finish(),
Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
}
}
#[post("/ssh_white_list/disable")]
async fn disable_ssh_white_list() -> impl Responder {
match Service::disable_ssh_white_list().await {
async fn disable_ssh_white_list(service: web::Data<Service>) -> impl Responder {
match service.disable_ssh_white_list().await {
Ok(_) => HttpResponse::Ok().finish(),
Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
}
}
#[get("/ipv4/ssh_service")]
async fn get_ipv4_ssh_service() -> impl Responder {
let list = Service::get_ipv4_ssh_service().await;
async fn get_ipv4_ssh_service(service: web::Data<Service>) -> impl Responder {
let list = service.get_ipv4_ssh_service().await;
HttpResponse::Ok().json(web::Json(list))
}
#[get("/ipv6/ssh_service")]
async fn get_ipv6_ssh_service() -> impl Responder {
let list = Service::get_ipv6_ssh_service().await;
async fn get_ipv6_ssh_service(service: web::Data<Service>) -> impl Responder {
let list = service.get_ipv6_ssh_service().await;
HttpResponse::Ok().json(web::Json(list))
}
#[put("/ipv4/ssh_service")]
async fn add_ipv4_ssh_service(ip_addr: web::Json<SocketAddrV4>) -> impl Responder {
match Service::add_ipv4_ssh_service(ip_addr.into_inner()).await {
async fn add_ipv4_ssh_service(ip_addr: web::Json<SocketAddrV4>, service: web::Data<Service>) -> impl Responder {
match service.add_ipv4_ssh_service(ip_addr.into_inner()).await {
Ok(_) => HttpResponse::Ok().finish(),
Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
}
}
#[put("/ipv6/ssh_service")]
async fn add_ipv6_ssh_service(ip_addr: web::Json<SocketAddrV6>) -> impl Responder {
match Service::add_ipv6_ssh_service(ip_addr.into_inner()).await {
async fn add_ipv6_ssh_service(ip_addr: web::Json<SocketAddrV6>, service: web::Data<Service>) -> impl Responder {
match service.add_ipv6_ssh_service(ip_addr.into_inner()).await {
Ok(_) => HttpResponse::Ok().finish(),
Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
}
}
#[delete("/ipv4/ssh_service")]
async fn remove_ipv4_ssh_service(ip_addr: web::Json<SocketAddrV4>) -> impl Responder {
match Service::remove_ipv4_ssh_service(ip_addr.into_inner()).await {
async fn remove_ipv4_ssh_service(ip_addr: web::Json<SocketAddrV4>, service: web::Data<Service>) -> impl Responder {
match service.remove_ipv4_ssh_service(ip_addr.into_inner()).await {
Ok(_) => HttpResponse::Ok().finish(),
Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
}
}
#[delete("/ipv6/ssh_service")]
async fn remove_ipv6_ssh_service(ip_addr: web::Json<SocketAddrV6>) -> impl Responder {
match Service::remove_ipv6_ssh_service(ip_addr.into_inner()).await {
async fn remove_ipv6_ssh_service(ip_addr: web::Json<SocketAddrV6>, service: web::Data<Service>) -> impl Responder {
match service.remove_ipv6_ssh_service(ip_addr.into_inner()).await {
Ok(_) => HttpResponse::Ok().finish(),
Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
}
}
#[get("/ipv4/ssh_white_list")]
async fn get_ipv4_ssh_white_list() -> impl Responder {
let list = Service::get_ipv4_ssh_white_list().await;
async fn get_ipv4_ssh_white_list(service: web::Data<Service>) -> impl Responder {
let list = service.get_ipv4_ssh_white_list().await;
HttpResponse::Ok().json(web::Json(list))
}
#[get("/ipv6/ssh_white_list")]
async fn get_ipv6_ssh_white_list() -> impl Responder {
let list = Service::get_ipv6_ssh_white_list().await;
async fn get_ipv6_ssh_white_list(service: web::Data<Service>) -> impl Responder {
let list = service.get_ipv6_ssh_white_list().await;
HttpResponse::Ok().json(web::Json(list))
}
#[put("/ipv4/ssh_white_list")]
async fn add_ipv4_ssh_white_list(ip_addr: web::Json<Ipv4Addr>) -> impl Responder {
match Service::add_ipv4_ssh_white_list(ip_addr.into_inner()).await {
async fn add_ipv4_ssh_white_list(ip_addr: web::Json<Ipv4Addr>, service: web::Data<Service>) -> impl Responder {
match service.add_ipv4_ssh_white_list(ip_addr.into_inner()).await {
Ok(_) => HttpResponse::Ok().finish(),
Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
}
}
#[put("/ipv6/ssh_white_list")]
async fn add_ipv6_ssh_white_list(ip_addr: web::Json<Ipv6Addr>) -> impl Responder {
match Service::add_ipv6_ssh_white_list(ip_addr.into_inner()).await {
async fn add_ipv6_ssh_white_list(ip_addr: web::Json<Ipv6Addr>, service: web::Data<Service>) -> impl Responder {
match service.add_ipv6_ssh_white_list(ip_addr.into_inner()).await {
Ok(_) => HttpResponse::Ok().finish(),
Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
}
}
#[delete("/ipv4/ssh_white_list")]
async fn remove_ipv4_ssh_white_list(ip_addr: web::Json<Ipv4Addr>) -> impl Responder {
match Service::remove_ipv4_ssh_white_list(ip_addr.into_inner()).await {
async fn remove_ipv4_ssh_white_list(ip_addr: web::Json<Ipv4Addr>, service: web::Data<Service>) -> impl Responder {
match service.remove_ipv4_ssh_white_list(ip_addr.into_inner()).await {
Ok(_) => HttpResponse::Ok().finish(),
Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
}
}
#[delete("/ipv6/ssh_white_list")]
async fn remove_ipv6_ssh_white_list(ip_addr: web::Json<Ipv6Addr>) -> impl Responder {
match Service::remove_ipv6_ssh_white_list(ip_addr.into_inner()).await {
async fn remove_ipv6_ssh_white_list(ip_addr: web::Json<Ipv6Addr>, service: web::Data<Service>) -> impl Responder {
match service.remove_ipv6_ssh_white_list(ip_addr.into_inner()).await {
Ok(_) => HttpResponse::Ok().finish(),
Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
}
}
#[get("/ipv4/ssh_black_list")]
async fn get_ipv4_ssh_black_list() -> impl Responder {
let list = Service::get_ipv4_ssh_black_list().await;
async fn get_ipv4_ssh_black_list(service: web::Data<Service>) -> impl Responder {
let list = service.get_ipv4_ssh_black_list().await;
HttpResponse::Ok().json(web::Json(list))
}
#[get("/ipv6/ssh_black_list")]
async fn get_ipv6_ssh_black_list() -> impl Responder {
let list = Service::get_ipv6_ssh_black_list().await;
async fn get_ipv6_ssh_black_list(service: web::Data<Service>) -> impl Responder {
let list = service.get_ipv6_ssh_black_list().await;
HttpResponse::Ok().json(web::Json(list))
}
#[put("/ipv4/ssh_black_list")]
async fn add_ipv4_ssh_black_list(ip_addr: web::Json<Ipv4Addr>) -> impl Responder {
match Service::add_ipv4_ssh_black_list(ip_addr.into_inner()).await {
async fn add_ipv4_ssh_black_list(ip_addr: web::Json<Ipv4Addr>, service: web::Data<Service>) -> impl Responder {
match service.add_ipv4_ssh_black_list(ip_addr.into_inner()).await {
Ok(_) => HttpResponse::Ok().finish(),
Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
}
}
#[put("/ipv6/ssh_black_list")]
async fn add_ipv6_ssh_black_list(ip_addr: web::Json<Ipv6Addr>) -> impl Responder {
match Service::add_ipv6_ssh_black_list(ip_addr.into_inner()).await {
async fn add_ipv6_ssh_black_list(ip_addr: web::Json<Ipv6Addr>, service: web::Data<Service>) -> impl Responder {
match service.add_ipv6_ssh_black_list(ip_addr.into_inner()).await {
Ok(_) => HttpResponse::Ok().finish(),
Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
}
}
#[delete("/ipv4/ssh_black_list")]
async fn remove_ipv4_ssh_black_list(ip_addr: web::Json<Ipv4Addr>) -> impl Responder {
match Service::remove_ipv4_ssh_black_list(ip_addr.into_inner()).await {
async fn remove_ipv4_ssh_black_list(ip_addr: web::Json<Ipv4Addr>, service: web::Data<Service>) -> impl Responder {
match service.remove_ipv4_ssh_black_list(ip_addr.into_inner()).await {
Ok(_) => HttpResponse::Ok().finish(),
Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
}
}
#[delete("/ipv6/ssh_black_list")]
async fn remove_ipv6_ssh_black_list(ip_addr: web::Json<Ipv6Addr>) -> impl Responder {
match Service::remove_ipv6_ssh_black_list(ip_addr.into_inner()).await {
async fn remove_ipv6_ssh_black_list(ip_addr: web::Json<Ipv6Addr>, service: web::Data<Service>) -> impl Responder {
match service.remove_ipv6_ssh_black_list(ip_addr.into_inner()).await {
Ok(_) => HttpResponse::Ok().finish(),
Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
}

View File

@ -1,6 +1,9 @@
use std::sync::Arc;
use actix_web::{get, web, HttpRequest, HttpResponse, Responder, Scope};
use crate::core::statistics::Statistics;
use crate::core::ebpf::statistics::Statistics;
use crate::core::infrastructure::app_config::AppConfig;
use crate::model::direction::{Direction, FlowDirection};
use crate::model::time_type::TimeType;
use crate::web::websocket::flow_websocket;
@ -14,16 +17,26 @@ pub fn initialize() -> Scope {
}
#[get("/get/ipv4/{direction}/{flow_direction}/{time_type}")]
async fn get_ipv4_flow(path: web::Path<(Direction, FlowDirection, TimeType)>) -> impl Responder {
async fn get_ipv4_flow(
path: web::Path<(Direction, FlowDirection, TimeType)>,
statistics: web::Data<Arc<Statistics>>,
) -> impl Responder {
let (direction, flow_direction, time_type) = path.into_inner();
let flow_data = Statistics::get_ipv4_flow_data(direction, flow_direction, time_type).await;
let flow_data = statistics
.get_ipv4_flow_data(direction, flow_direction, time_type)
.await;
HttpResponse::Ok().json(web::Json(flow_data))
}
#[get("/get/ipv6/{direction}/{flow_direction}/{time_type}")]
async fn get_ipv6_flow(path: web::Path<(Direction, FlowDirection, TimeType)>) -> impl Responder {
async fn get_ipv6_flow(
path: web::Path<(Direction, FlowDirection, TimeType)>,
statistics: web::Data<Arc<Statistics>>,
) -> impl Responder {
let (direction, flow_direction, time_type) = path.into_inner();
let flow_data = Statistics::get_ipv6_flow_data(direction, flow_direction, time_type).await;
let flow_data = statistics
.get_ipv6_flow_data(direction, flow_direction, time_type)
.await;
HttpResponse::Ok().json(web::Json(flow_data))
}
@ -32,8 +45,10 @@ async fn websocket_ipv4(
req: HttpRequest,
stream: web::Payload,
path: web::Path<(Direction, FlowDirection, TimeType)>,
app_config: web::Data<AppConfig>,
statistics: web::Data<Statistics>,
) -> impl Responder {
match flow_websocket::websocket_ipv4_flow(req, stream, path).await {
match flow_websocket::websocket_ipv4_flow(req, stream, path, app_config, statistics).await {
Ok(response) => response,
Err(err) => HttpResponse::InternalServerError().body(format!("WebSocket error: {}", err)),
}
@ -44,8 +59,10 @@ async fn websocket_ipv6(
req: HttpRequest,
stream: web::Payload,
path: web::Path<(Direction, FlowDirection, TimeType)>,
app_config: web::Data<AppConfig>,
statistics: web::Data<Statistics>,
) -> impl Responder {
match flow_websocket::websocket_ipv6_flow(req, stream, path).await {
match flow_websocket::websocket_ipv6_flow(req, stream, path, app_config, statistics).await {
Ok(response) => response,
Err(err) => HttpResponse::InternalServerError().body(format!("WebSocket error: {}", err)),
}

View File

@ -1,32 +0,0 @@
use actix_web::{get, web, HttpRequest, HttpResponse, Responder, Scope};
use crate::core::health::SystemHealth;
use crate::web::websocket::health_websocket;
pub fn initialize() -> Scope {
web::scope("/health")
.service(get_current_metrics)
.service(get_health_status)
.service(websocket_metrics)
}
#[get("/metrics")]
async fn get_current_metrics() -> impl Responder {
let metrics = SystemHealth::get_current_metrics().await;
HttpResponse::Ok().json(metrics)
}
#[get("/status")]
async fn get_health_status() -> impl Responder {
let status = SystemHealth::is_system_healthy().await;
HttpResponse::Ok().json(status)
}
#[get("/websocket/system_health")]
async fn websocket_metrics(req: HttpRequest, body: web::Payload) -> impl Responder {
let broadcast_rx = SystemHealth::subscribe_to_metrics().await;
match health_websocket::websocket_system_health(req, body, broadcast_rx).await {
Ok(response) => response,
Err(err) => HttpResponse::InternalServerError().body(format!("WebSocket error: {}", err)),
}
}

View File

@ -1,13 +1,14 @@
use actix_web::{get, web, HttpResponse, Responder, Scope};
use crate::core::system::System;
use crate::utils::boot_time::boot_time;
pub fn initialize() -> Scope {
web::scope("/misc").service(boot_time)
web::scope("/misc")
.service(get_boot_time)
}
#[get("/boot_time")]
async fn boot_time() -> impl Responder {
let boot_time = System::boot_time().await;
async fn get_boot_time() -> impl Responder {
let boot_time = boot_time();
HttpResponse::Ok().json(boot_time)
}

View File

@ -1,5 +1,3 @@
pub mod control;
pub mod default;
pub mod health;
pub mod misc;
pub mod statistics;

View File

@ -1,11 +1,13 @@
use std::sync::Arc;
use actix_web::{web, HttpRequest, HttpResponse, Result};
use actix_ws::{handle, Message, MessageStream, Session};
use futures_util::StreamExt;
use macros::log;
use tokio::time::{interval, Duration};
use crate::core::app_config::AppConfig;
use crate::core::statistics::Statistics;
use crate::core::ebpf::statistics::Statistics;
use crate::core::infrastructure::app_config::AppConfig;
use crate::model::direction::{Direction, FlowDirection};
use crate::model::error::http::HttpError;
use crate::model::error::misc::MiscError;
@ -15,12 +17,25 @@ pub async fn websocket_ipv4_flow(
req: HttpRequest,
body: web::Payload,
path: web::Path<(Direction, FlowDirection, TimeType)>,
app_config: web::Data<AppConfig>,
statistics: web::Data<Statistics>,
) -> Result<HttpResponse> {
let app_config = app_config.into_inner();
let statistics = statistics.into_inner();
let (direction, flow_direction, time_type) = path.into_inner();
let (response, session, msg_stream) = handle(&req, body)?;
actix_web::rt::spawn(async move {
handle_ipv4_flow_connection(session, msg_stream, direction, flow_direction, time_type).await;
handle_ipv4_flow_connection(
app_config,
statistics,
session,
msg_stream,
direction,
flow_direction,
time_type,
)
.await;
});
Ok(response)
@ -30,28 +45,42 @@ pub async fn websocket_ipv6_flow(
req: HttpRequest,
body: web::Payload,
path: web::Path<(Direction, FlowDirection, TimeType)>,
app_config: web::Data<AppConfig>,
statistics: web::Data<Statistics>,
) -> Result<HttpResponse> {
let app_config = app_config.into_inner();
let statistics = statistics.into_inner();
let (direction, flow_direction, time_type) = path.into_inner();
let (response, session, msg_stream) = handle(&req, body)?;
actix_web::rt::spawn(async move {
handle_ipv6_flow_connection(session, msg_stream, direction, flow_direction, time_type).await;
handle_ipv6_flow_connection(
app_config,
statistics,
session,
msg_stream,
direction,
flow_direction,
time_type,
)
.await;
});
Ok(response)
}
async fn handle_ipv4_flow_connection(
app_config: Arc<AppConfig>,
statistics: Arc<Statistics>,
mut session: Session,
mut msg_stream: MessageStream,
direction: Direction,
flow_direction: FlowDirection,
time_type: TimeType,
) {
let config = AppConfig::now_blocking();
let config = app_config.config.clone();
let refresh_interval = Duration::from_secs(config.refresh_interval);
let mut data_interval = interval(refresh_interval);
let mut ping_interval = interval(Duration::from_secs(30));
loop {
tokio::select! {
@ -61,15 +90,10 @@ async fn handle_ipv4_flow_connection(
}
},
_ = data_interval.tick() => {
if !send_ipv4_flow_data(&mut session, direction, flow_direction, time_type).await {
if !send_ipv4_flow_data(&statistics, &mut session, direction, flow_direction, time_type).await {
break;
}
},
_ = ping_interval.tick() => {
if session.ping(b"heartbeat").await.is_err() {
break;
}
}
}
}
@ -77,16 +101,17 @@ async fn handle_ipv4_flow_connection(
}
async fn handle_ipv6_flow_connection(
app_config: Arc<AppConfig>,
statistics: Arc<Statistics>,
mut session: Session,
mut msg_stream: MessageStream,
direction: Direction,
flow_direction: FlowDirection,
time_type: TimeType,
) {
let config = AppConfig::now_blocking();
let config = app_config.config.clone();
let refresh_interval = Duration::from_secs(config.refresh_interval);
let mut data_interval = interval(refresh_interval);
let mut ping_interval = interval(Duration::from_secs(30));
loop {
tokio::select! {
@ -96,15 +121,10 @@ async fn handle_ipv6_flow_connection(
}
},
_ = data_interval.tick() => {
if !send_ipv6_flow_data(&mut session, direction, flow_direction, time_type).await {
if !send_ipv6_flow_data(&statistics, &mut session, direction, flow_direction, time_type).await {
break;
}
},
_ = ping_interval.tick() => {
if session.ping(b"heartbeat").await.is_err() {
break;
}
}
}
}
@ -116,21 +136,7 @@ async fn handle_client_message(
msg_result: Option<Result<Message, actix_ws::ProtocolError>>,
) -> bool {
match msg_result {
Some(Ok(Message::Text(text))) => {
let text = text.trim();
match text {
"ping" => session.text("pong").await.is_ok(),
_ => {
let error_msg = serde_json::json!({
"available_commands": ["ping"]
});
match serde_json::to_string(&error_msg) {
Ok(error_json) => session.text(error_json).await.is_ok(),
Err(_) => true,
}
}
}
}
Some(Ok(Message::Text(_))) => true,
Some(Ok(Message::Ping(bytes))) => session.pong(&bytes).await.is_ok(),
Some(Ok(Message::Close(reason))) => {
let _ = (session.clone()).close(reason).await;
@ -146,12 +152,15 @@ async fn handle_client_message(
}
async fn send_ipv4_flow_data(
statistics: &Arc<Statistics>,
session: &mut Session,
direction: Direction,
flow_direction: FlowDirection,
time_type: TimeType,
) -> bool {
let flow_data = Statistics::get_ipv4_flow_data(direction, flow_direction, time_type).await;
let flow_data = statistics
.get_ipv4_flow_data(direction, flow_direction, time_type)
.await;
match serde_json::to_string(&flow_data) {
Ok(json) => session.text(json).await.is_ok(),
Err(err) => {
@ -162,12 +171,15 @@ async fn send_ipv4_flow_data(
}
async fn send_ipv6_flow_data(
statistics: &Arc<Statistics>,
session: &mut Session,
direction: Direction,
flow_direction: FlowDirection,
time_type: TimeType,
) -> bool {
let flow_data = Statistics::get_ipv6_flow_data(direction, flow_direction, time_type).await;
let flow_data = statistics
.get_ipv6_flow_data(direction, flow_direction, time_type)
.await;
match serde_json::to_string(&flow_data) {
Ok(json) => session.text(json).await.is_ok(),
Err(err) => {

View File

@ -1,136 +0,0 @@
use actix_web::{web, HttpRequest, HttpResponse, Result};
use actix_ws::{handle, Message, Session};
use futures_util::StreamExt;
use macros::log;
use tokio::sync::broadcast;
use tokio::time::{interval, Duration};
use crate::model::error::http::HttpError;
use crate::model::error::misc::MiscError;
use crate::model::healthy::SystemHealthMetrics;
use crate::model::log::http::HttpLog;
pub async fn websocket_system_health(
req: HttpRequest,
body: web::Payload,
broadcast_rx: broadcast::Receiver<SystemHealthMetrics>,
) -> Result<HttpResponse> {
let (response, session, msg_stream) = handle(&req, body)?;
actix_web::rt::spawn(async move {
handle_websocket_connection(session, msg_stream, broadcast_rx).await;
});
Ok(response)
}
async fn handle_websocket_connection(
mut session: Session,
mut msg_stream: actix_ws::MessageStream,
mut broadcast_rx: broadcast::Receiver<SystemHealthMetrics>,
) {
let mut ping_interval = interval(Duration::from_secs(30));
loop {
tokio::select! {
msg_result = msg_stream.next() => {
if !handle_client_message(&mut session, msg_result).await {
break;
}
},
metrics_result = broadcast_rx.recv() => {
if !handle_broadcast_message(&mut session, metrics_result).await {
break;
}
},
_ = ping_interval.tick() => {
if session.ping(b"heartbeat").await.is_err() {
break;
}
}
}
}
let _ = session.close(None).await;
}
async fn handle_client_message(
session: &mut Session,
msg_result: Option<Result<Message, actix_ws::ProtocolError>>,
) -> bool {
match msg_result {
Some(Ok(Message::Text(text))) => {
let text = text.trim();
match text {
"ping" => session.text("pong").await.is_ok(),
"get_current" => {
let current_metrics = crate::core::health::SystemHealth::get_current_metrics().await;
match serde_json::to_string(&current_metrics) {
Ok(json) => session.text(json).await.is_ok(),
Err(err) => {
log!(MiscError::SerializeError(err));
true
}
}
}
_ => {
let error_msg = serde_json::json!({
"available_commands": ["ping", "get_current"]
});
match serde_json::to_string(&error_msg) {
Ok(error_json) => session.text(error_json).await.is_ok(),
Err(_) => true,
}
}
}
}
Some(Ok(Message::Ping(bytes))) => session.pong(&bytes).await.is_ok(),
Some(Ok(Message::Close(reason))) => {
let _ = (session.clone()).close(reason).await;
false
}
Some(Err(err)) => {
log!(HttpError::WebSocketError(err));
false
}
None => false,
_ => true,
}
}
async fn handle_broadcast_message(
session: &mut Session,
metrics_result: Result<SystemHealthMetrics, broadcast::error::RecvError>,
) -> bool {
match metrics_result {
Ok(metrics) => {
let message = serde_json::json!(metrics);
match serde_json::to_string(&message) {
Ok(json) => session.text(json).await.is_ok(),
Err(err) => {
log!(MiscError::SerializeError(err));
true
}
}
}
Err(broadcast::error::RecvError::Lagged(skipped)) => {
log!(HttpLog::WebSocketLaged(skipped));
let lag_msg = serde_json::json!({
"message": format!("Connection lagged, skipped {} messages", skipped)
});
match serde_json::to_string(&lag_msg) {
Ok(json) => session.text(json).await.is_ok(),
Err(_) => true,
}
}
Err(broadcast::error::RecvError::Closed) => {
let close_msg = serde_json::json!({
"message": "Health monitoring stopped"
});
if let Ok(json) = serde_json::to_string(&close_msg) {
let _ = session.text(json).await;
}
false
}
}
}

View File

@ -1,2 +1 @@
pub mod flow_websocket;
pub mod health_websocket;