From 97d511d17a8ef7d1ae4ed67062cd6775c013c2d7 Mon Sep 17 00:00:00 2001 From: DaLaw2 Date: Mon, 13 Oct 2025 13:16:44 +0800 Subject: [PATCH] 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 --- Cargo.lock | 132 ++++-- Cargo.toml | 4 +- common/src/define/mod.rs | 1 + common/src/define/program_array.rs | 10 + common/src/model/event.rs | 23 +- common/src/model/ip_address.rs | 72 ++- common/src/model/packet.rs | 13 +- config.toml | 17 +- egress-ebpf/src/main.rs | 29 +- ingress-ebpf/src/action/mod.rs | 1 - ingress-ebpf/src/action/service.rs | 8 +- ingress-ebpf/src/action/transmission.rs | 29 -- ingress-ebpf/src/main.rs | 94 ++-- net-guardia/Cargo.toml | 8 +- net-guardia/build.rs | 28 +- net-guardia/src/core/app_config.rs | 60 --- .../src/core/control/access_control.rs | 222 --------- net-guardia/src/core/control/mod.rs | 15 - net-guardia/src/core/control/service.rs | 422 ------------------ net-guardia/src/core/ebpf/access_control.rs | 206 +++++++++ net-guardia/src/core/ebpf/mod.rs | 62 +++ net-guardia/src/core/ebpf/service.rs | 332 ++++++++++++++ net-guardia/src/core/{ => ebpf}/statistics.rs | 169 +++---- net-guardia/src/core/ebpf/xdp_manager.rs | 257 +++++++++++ net-guardia/src/core/health.rs | 345 -------------- .../src/core/infrastructure/app_config.rs | 39 ++ net-guardia/src/core/infrastructure/mod.rs | 1 + net-guardia/src/core/mod.rs | 6 +- net-guardia/src/core/system.rs | 245 +++++----- net-guardia/src/main.rs | 6 +- net-guardia/src/model/config.rs | 15 +- net-guardia/src/model/error/ebpf.rs | 12 + net-guardia/src/model/healthy.rs | 81 ---- net-guardia/src/model/ip_address.rs | 31 +- net-guardia/src/model/log/ebpf.rs | 9 + net-guardia/src/model/mod.rs | 1 - net-guardia/src/utils/boot_time.rs | 5 + net-guardia/src/utils/logging.rs | 13 +- net-guardia/src/utils/mod.rs | 1 + .../src/web/api/control/access_control.rs | 38 +- net-guardia/src/web/api/control/mod.rs | 8 +- net-guardia/src/web/api/control/service.rs | 122 ++--- .../src/web/api/{ => control}/statistics.rs | 31 +- net-guardia/src/web/api/health.rs | 32 -- net-guardia/src/web/api/misc.rs | 9 +- net-guardia/src/web/api/mod.rs | 2 - .../src/web/websocket/flow_websocket.rs | 86 ++-- .../src/web/websocket/health_websocket.rs | 136 ------ net-guardia/src/web/websocket/mod.rs | 1 - 49 files changed, 1643 insertions(+), 1846 deletions(-) create mode 100644 common/src/define/program_array.rs delete mode 100644 ingress-ebpf/src/action/transmission.rs delete mode 100644 net-guardia/src/core/app_config.rs delete mode 100644 net-guardia/src/core/control/access_control.rs delete mode 100644 net-guardia/src/core/control/mod.rs delete mode 100644 net-guardia/src/core/control/service.rs create mode 100644 net-guardia/src/core/ebpf/access_control.rs create mode 100644 net-guardia/src/core/ebpf/mod.rs create mode 100644 net-guardia/src/core/ebpf/service.rs rename net-guardia/src/core/{ => ebpf}/statistics.rs (50%) create mode 100644 net-guardia/src/core/ebpf/xdp_manager.rs delete mode 100644 net-guardia/src/core/health.rs create mode 100644 net-guardia/src/core/infrastructure/app_config.rs create mode 100644 net-guardia/src/core/infrastructure/mod.rs delete mode 100644 net-guardia/src/model/healthy.rs create mode 100644 net-guardia/src/utils/boot_time.rs rename net-guardia/src/web/api/{ => control}/statistics.rs (60%) delete mode 100644 net-guardia/src/web/api/health.rs delete mode 100644 net-guardia/src/web/websocket/health_websocket.rs diff --git a/Cargo.lock b/Cargo.lock index 8b5eb70..b8568c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 29b28cb..8cc6470 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/common/src/define/mod.rs b/common/src/define/mod.rs index 9b4aafe..f2cdfca 100644 --- a/common/src/define/mod.rs +++ b/common/src/define/mod.rs @@ -1,3 +1,4 @@ pub mod offset; pub mod other; +pub mod program_array; pub mod setting; diff --git a/common/src/define/program_array.rs b/common/src/define/program_array.rs new file mode 100644 index 0000000..1a6a1ba --- /dev/null +++ b/common/src/define/program_array.rs @@ -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; +} diff --git a/common/src/model/event.rs b/common/src/model/event.rs index 82ea285..9901452 100644 --- a/common/src/model/event.rs +++ b/common/src/model/event.rs @@ -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) } } diff --git a/common/src/model/ip_address.rs b/common/src/model/ip_address.rs index 53a3e49..84a0e99 100644 --- a/common/src/model/ip_address.rs +++ b/common/src/model/ip_address.rs @@ -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) } } diff --git a/common/src/model/packet.rs b/common/src/model/packet.rs index 34ec767..dd7f95f 100644 --- a/common/src/model/packet.rs +++ b/common/src/model/packet.rs @@ -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::() + STANDARD_MTU]); diff --git a/config.toml b/config.toml index ac90aa5..7b1f574 100644 --- a/config.toml +++ b/config.toml @@ -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 ) +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 diff --git a/egress-ebpf/src/main.rs b/egress-ebpf/src/main.rs index f35ce1c..791108a 100644 --- a/egress-ebpf/src/main.rs +++ b/egress-ebpf/src/main.rs @@ -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 = 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 { - 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 { diff --git a/ingress-ebpf/src/action/mod.rs b/ingress-ebpf/src/action/mod.rs index 9aea63f..501fc26 100644 --- a/ingress-ebpf/src/action/mod.rs +++ b/ingress-ebpf/src/action/mod.rs @@ -1,4 +1,3 @@ pub mod access_control; pub mod service; pub mod statistics; -pub mod transmission; diff --git a/ingress-ebpf/src/action/service.rs b/ingress-ebpf/src/action/service.rs index 0c92c9a..28ae4fc 100644 --- a/ingress-ebpf/src/action/service.rs +++ b/ingress-ebpf/src/action/service.rs @@ -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 diff --git a/ingress-ebpf/src/action/transmission.rs b/ingress-ebpf/src/action/transmission.rs deleted file mode 100644 index ce50e31..0000000 --- a/ingress-ebpf/src/action/transmission.rs +++ /dev/null @@ -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::(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::(), - ); - core::ptr::copy_nonoverlapping(start as *const u8, (*packet_ptr).raw_data.as_mut_ptr(), STANDARD_MTU); - } - entry.submit(0); - Ok(()) - } else { - Err(()) - } -} diff --git a/ingress-ebpf/src/main.rs b/ingress-ebpf/src/main.rs index e0d32c3..07a8c4f 100644 --- a/ingress-ebpf/src/main.rs +++ b/ingress-ebpf/src/main.rs @@ -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 = 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 { - 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 { + 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 { +#[inline(always)] +unsafe fn try_access_control(ctx: &XdpContext) -> Result { 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 { } 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 { +#[inline(always)] +unsafe fn try_service(ctx: &XdpContext) -> Result { unsafe { let start = ctx.data(); let end = ctx.data_end(); @@ -91,19 +117,21 @@ unsafe fn try_service(ctx: XdpContext) -> Result { } } } - 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 { +#[inline(always)] +unsafe fn try_statistics(ctx: &XdpContext) -> Result { unsafe { let ptr = PARSED_PACKET.get_ptr(0).ok_or(())?; let parsed_packet = &*ptr; @@ -115,7 +143,17 @@ unsafe fn try_statistics(_: XdpContext) -> Result { 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, } } diff --git a/net-guardia/Cargo.toml b/net-guardia/Cargo.toml index 6574b3c..cf49b87 100644 --- a/net-guardia/Cargo.toml +++ b/net-guardia/Cargo.toml @@ -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 } diff --git a/net-guardia/build.rs b/net-guardia/build.rs index 4d683b5..bab5c5b 100644 --- a/net-guardia/build.rs +++ b/net-guardia/build.rs @@ -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() diff --git a/net-guardia/src/core/app_config.rs b/net-guardia/src/core/app_config.rs deleted file mode 100644 index 44eeb08..0000000 --- a/net-guardia/src/core/app_config.rs +++ /dev/null @@ -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> = OnceLock::new(); -static ASYNC_CONFIG: OnceLock> = 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 { - let toml_string = fs::read_to_string("./config.toml").map_err(SystemError::ConfigNotFound)?; - let config_table = toml::from_str::(&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 - } -} diff --git a/net-guardia/src/core/control/access_control.rs b/net-guardia/src/core/control/access_control.rs deleted file mode 100644 index a9544ca..0000000 --- a/net-guardia/src/core/control/access_control.rs +++ /dev/null @@ -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> = OnceLock::new(); - -pub struct AccessControl { - ipv4_maps: StdHashMap<(FlowDirection, ListType), AccessMap>, - ipv6_maps: StdHashMap<(FlowDirection, ListType), AccessMap>, -} - -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> { - 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> { - 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 { - map: AyaHashMap, -} - -impl AccessMap { - fn get_list(&self) -> StdHashMap> { - 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)? - } - } -} diff --git a/net-guardia/src/core/control/mod.rs b/net-guardia/src/core/control/mod.rs deleted file mode 100644 index 1b0eee0..0000000 --- a/net-guardia/src/core/control/mod.rs +++ /dev/null @@ -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 - } -} diff --git a/net-guardia/src/core/control/service.rs b/net-guardia/src/core/control/service.rs deleted file mode 100644 index 43400f4..0000000 --- a/net-guardia/src/core/control/service.rs +++ /dev/null @@ -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> = OnceLock::new(); - -pub struct Service { - ipv4_http_service: AyaHashMap, - ipv6_http_service: AyaHashMap, - ssh_white_list_enable: AyaArray, - ipv4_ssh_service: AyaHashMap, - ipv6_ssh_service: AyaHashMap, - ipv4_ssh_white_list: AyaHashMap, - ipv6_ssh_white_list: AyaHashMap, - ipv4_ssh_black_list: AyaHashMap, - ipv6_ssh_black_list: AyaHashMap, -} - -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> { - 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> { - 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) -> 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) -> 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, - ) -> 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, - ) -> 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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(()) - } -} diff --git a/net-guardia/src/core/ebpf/access_control.rs b/net-guardia/src/core/ebpf/access_control.rs new file mode 100644 index 0000000..96cb2ee --- /dev/null +++ b/net-guardia/src/core/ebpf/access_control.rs @@ -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>, + ipv4_src_blacklist: RwLock>, + ipv4_dst_whitelist: RwLock>, + ipv4_dst_blacklist: RwLock>, + ipv6_src_whitelist: RwLock>, + ipv6_src_blacklist: RwLock>, + ipv6_dst_whitelist: RwLock>, + ipv6_dst_blacklist: RwLock>, +} + +impl AccessControl { + pub fn new(ebpf: &mut Ebpf) -> Result { + 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> { + 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> { + 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 { + map: AyaHashMap, +} + +impl MapWrapper { + fn new(ebpf: &mut Ebpf, map_name: &str) -> Result { + 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> { + 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)? + } + } +} diff --git a/net-guardia/src/core/ebpf/mod.rs b/net-guardia/src/core/ebpf/mod.rs new file mode 100644 index 0000000..0d05a1f --- /dev/null +++ b/net-guardia/src/core/ebpf/mod.rs @@ -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, + pub access_control: Arc, + pub service: Arc, + pub statistics: Arc, + shutdowns: SegQueue>, +} + +impl EbpfServices { + pub fn new(app_config: Arc, ingress_ebpf: &mut Ebpf, egress_ebpf: &mut Ebpf) -> Result { + 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) -> 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.xsk_manager.shutdown(); + while let Some(shutdown) = self.shutdowns.pop() { + if shutdown.send(()).is_err() { + log!(SystemError::ShutdownSignalFailed); + } + } + } +} diff --git a/net-guardia/src/core/ebpf/service.rs b/net-guardia/src/core/ebpf/service.rs new file mode 100644 index 0000000..7afe782 --- /dev/null +++ b/net-guardia/src/core/ebpf/service.rs @@ -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>, + ipv6_http_service: RwLock>, + ssh_white_list_enable: RwLock, + ipv4_ssh_service: RwLock>, + ipv6_ssh_service: RwLock>, + ipv4_ssh_white_list: RwLock>, + ipv6_ssh_white_list: RwLock>, + ipv4_ssh_black_list: RwLock>, + ipv6_ssh_black_list: RwLock>, +} + +impl Service { + pub fn new(ebpf: &mut Ebpf) -> Result { + 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> { + self.ipv4_http_service.read().await.get_http_method() + } + + pub async fn get_ipv6_http_service(&self) -> HashMap> { + self.ipv6_http_service.read().await.get_http_method() + } + + pub async fn add_ipv4_http_service( + &self, + address: SocketAddrV4, + http_method: Vec, + ) -> 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, + ) -> 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, + ) -> 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, + ) -> 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 { + self.ipv4_ssh_service.read().await.get_ssh_service() + } + + pub async fn get_ipv6_ssh_service(&self) -> Vec { + 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 { + self.ipv4_ssh_white_list.read().await.get_list() + } + + pub async fn get_ipv6_ssh_white_list(&self) -> Vec { + 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 { + self.ipv4_ssh_black_list.read().await.get_list() + } + + pub async fn get_ipv6_ssh_black_list(&self) -> Vec { + 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, +} + +impl WhiteListControl { + fn new(ebpf: &mut Ebpf, map_name: &str) -> Result { + 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 { + map: AyaHashMap, +} + +impl HttpServiceWrapper { + fn new(ebpf: &mut Ebpf, map_name: &str) -> Result { + 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> { + 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) -> 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) -> 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 { + map: AyaHashMap, +} + +impl SshServiceWrapper { + fn new(ebpf: &mut Ebpf, map_name: &str) -> Result { + 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 { + 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 { + map: AyaHashMap, +} + +impl SshListWrapper { + fn new(ebpf: &mut Ebpf, map_name: &str) -> Result { + 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 { + 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(()) + } +} diff --git a/net-guardia/src/core/statistics.rs b/net-guardia/src/core/ebpf/statistics.rs similarity index 50% rename from net-guardia/src/core/statistics.rs rename to net-guardia/src/core/ebpf/statistics.rs index 355b1b5..b2d7374 100644 --- a/net-guardia/src/core/statistics.rs +++ b/net-guardia/src/core/ebpf/statistics.rs @@ -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> = OnceLock::new(); +use crate::utils::boot_time::boot_time; pub struct Statistics { - terminate: bool, - ipv4_maps: StdHashMap<(Direction, FlowDirection, TimeType), FlowMap>, - ipv6_maps: StdHashMap<(Direction, FlowDirection, TimeType), FlowMap>, + app_config: Arc, + boot_time: u64, + ipv4_maps: HashMap<(Direction, FlowDirection, TimeType), RwLock>>, + ipv6_maps: HashMap<(Direction, FlowDirection, TimeType), RwLock>>, } 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, + ingress_ebpf: &mut Ebpf, + egress_ebpf: &mut Ebpf, + ) -> Result { + 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) -> 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) { + 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 { - let statistics = Statistics::instance().await; - statistics - .ipv4_maps + ) -> HashMap { + 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 { - let statistics = Statistics::instance().await; - statistics - .ipv6_maps + ) -> HashMap { + 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 { map: AyaHashMap, } -impl FlowMap { - fn get_map(&self) -> StdHashMap { +impl FlowMap { + fn new(ebpf: &mut Ebpf, map_name: &str) -> Result { + 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 { self.map .iter() .filter_map(Result::ok) diff --git a/net-guardia/src/core/ebpf/xdp_manager.rs b/net-guardia/src/core/ebpf/xdp_manager.rs new file mode 100644 index 0000000..46394f3 --- /dev/null +++ b/net-guardia/src/core/ebpf/xdp_manager.rs @@ -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, + xsk_map: Mutex>, + shutdowns: SegQueue>, +} + +impl XskManager { + pub fn new(app_config: Arc, ebpf: &mut Ebpf) -> Result { + 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, queue_id: u32) -> Result { + 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 = 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!("---"); + } +} diff --git a/net-guardia/src/core/health.rs b/net-guardia/src/core/health.rs deleted file mode 100644 index 825836b..0000000 --- a/net-guardia/src/core/health.rs +++ /dev/null @@ -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> = OnceLock::new(); - -pub struct SystemHealth { - system: System, - networks: Networks, - components: Components, - broadcast_tx: broadcast::Sender, - 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, - 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::() / cpus.len() as f32; - - let cores: Vec = 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::() / 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 { - 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 { - 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 - } -} diff --git a/net-guardia/src/core/infrastructure/app_config.rs b/net-guardia/src/core/infrastructure/app_config.rs new file mode 100644 index 0000000..7f67ee0 --- /dev/null +++ b/net-guardia/src/core/infrastructure/app_config.rs @@ -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 { + let toml_string = fs::read_to_string("./config.toml").map_err(SystemError::ConfigNotFound)?; + let config_table = toml::from_str::(&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 + } +} diff --git a/net-guardia/src/core/infrastructure/mod.rs b/net-guardia/src/core/infrastructure/mod.rs new file mode 100644 index 0000000..62a6f82 --- /dev/null +++ b/net-guardia/src/core/infrastructure/mod.rs @@ -0,0 +1 @@ +pub mod app_config; diff --git a/net-guardia/src/core/mod.rs b/net-guardia/src/core/mod.rs index 063718e..18d067e 100644 --- a/net-guardia/src/core/mod.rs +++ b/net-guardia/src/core/mod.rs @@ -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; diff --git a/net-guardia/src/core/system.rs b/net-guardia/src/core/system.rs index ffb889c..445348a 100644 --- a/net-guardia/src/core/system.rs +++ b/net-guardia/src/core/system.rs @@ -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> = OnceLock::new(); +use crate::web::api::{control, default, misc}; pub struct System { + pub app_config: Arc, + pub ebpf_services: Arc, + pub ingress_ebpf: Ebpf, pub egress_ebpf: Ebpf, - pub boot_time: u64, #[allow(dead_code)] ingress_program_array: ProgramArray, #[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 { 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, @@ -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 - } } diff --git a/net-guardia/src/main.rs b/net-guardia/src/main.rs index 33e4d29..3f9d173 100644 --- a/net-guardia/src/main.rs +++ b/net-guardia/src/main.rs @@ -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(()) } diff --git a/net-guardia/src/model/config.rs b/net-guardia/src/model/config.rs index 3afbac6..56a16a9 100644 --- a/net-guardia/src/model/config.rs +++ b/net-guardia/src/model/config.rs @@ -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, } diff --git a/net-guardia/src/model/error/ebpf.rs b/net-guardia/src/model/error/ebpf.rs index 655bede..9515767 100644 --- a/net-guardia/src/model/error/ebpf.rs +++ b/net-guardia/src/model/error/ebpf.rs @@ -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, diff --git a/net-guardia/src/model/healthy.rs b/net-guardia/src/model/healthy.rs deleted file mode 100644 index 687954f..0000000 --- a/net-guardia/src/model/healthy.rs +++ /dev/null @@ -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, - pub temperature: Option, -} - -#[derive(Debug, Clone, Serialize)] -pub struct SystemInfo { - pub kernel_version: Option, - pub os_name: Option, - pub os_version: Option, - 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, -} - -#[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, - pub egress: Option, - pub management: Option, -} - -#[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, - pub warnings: Vec, -} diff --git a/net-guardia/src/model/ip_address.rs b/net-guardia/src/model/ip_address.rs index 1557844..ecec51d 100644 --- a/net-guardia/src/model/ip_address.rs +++ b/net-guardia/src/model/ip_address.rs @@ -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()) } } diff --git a/net-guardia/src/model/log/ebpf.rs b/net-guardia/src/model/log/ebpf.rs index 3f15f41..0a27280 100644 --- a/net-guardia/src/model/log/ebpf.rs +++ b/net-guardia/src/model/log/ebpf.rs @@ -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, } } diff --git a/net-guardia/src/model/mod.rs b/net-guardia/src/model/mod.rs index de10b05..dd6e28f 100644 --- a/net-guardia/src/model/mod.rs +++ b/net-guardia/src/model/mod.rs @@ -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; diff --git a/net-guardia/src/utils/boot_time.rs b/net-guardia/src/utils/boot_time.rs new file mode 100644 index 0000000..86b9cdb --- /dev/null +++ b/net-guardia/src/utils/boot_time.rs @@ -0,0 +1,5 @@ +use sysinfo::System as SystemInfo; + +pub fn boot_time() -> u64 { + SystemInfo::boot_time() * 1_000_000_000 +} diff --git a/net-guardia/src/utils/logging.rs b/net-guardia/src/utils/logging.rs index eef47a6..c755fb4 100644 --- a/net-guardia/src/utils/logging.rs +++ b/net-guardia/src/utils/logging.rs @@ -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(()) diff --git a/net-guardia/src/utils/mod.rs b/net-guardia/src/utils/mod.rs index 9f66aa7..45ea5ee 100644 --- a/net-guardia/src/utils/mod.rs +++ b/net-guardia/src/utils/mod.rs @@ -1,3 +1,4 @@ pub mod ip_address; pub mod logging; pub mod static_files; +pub mod boot_time; diff --git a/net-guardia/src/web/api/control/access_control.rs b/net-guardia/src/web/api/control/access_control.rs index b16a7cf..37bd1a4 100644 --- a/net-guardia/src/web/api/control/access_control.rs +++ b/net-guardia/src/web/api/control/access_control.rs @@ -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, +) -> 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, +) -> 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, path: web::Path<(FlowDirection, ListType)>) -> impl Responder { +async fn add_ipv4_list( + address: web::Json, + path: web::Path<(FlowDirection, ListType)>, + access_control: web::Data, +) -> 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, path: web::Path<(FlowDirection, ListType)>) -> impl Responder { +async fn add_ipv6_list( + address: web::Json, + path: web::Path<(FlowDirection, ListType)>, + access_control: web::Data, +) -> 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, path: web::Path<(FlowDi async fn remove_ipv4_list( address: web::Json, path: web::Path<(FlowDirection, ListType)>, + access_control: web::Data, ) -> 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, path: web::Path<(FlowDirection, ListType)>, + access_control: web::Data, ) -> 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()), } diff --git a/net-guardia/src/web/api/control/mod.rs b/net-guardia/src/web/api/control/mod.rs index 38e955e..d3036e2 100644 --- a/net-guardia/src/web/api/control/mod.rs +++ b/net-guardia/src/web/api/control/mod.rs @@ -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()) } diff --git a/net-guardia/src/web/api/control/service.rs b/net-guardia/src/web/api/control/service.rs index f16f95e..3b1df29 100644 --- a/net-guardia/src/web/api/control/service.rs +++ b/net-guardia/src/web/api/control/service.rs @@ -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) -> 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) -> 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)>) -> impl Responder { +async fn add_ipv4_http_service( + payload: web::Json<(SocketAddrV4, Vec)>, + service: web::Data, +) -> 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)>) -> impl Responder { +async fn add_ipv6_http_service( + payload: web::Json<(SocketAddrV6, Vec)>, + service: web::Data, +) -> 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)>) -> impl Responder { +async fn remove_ipv4_http_service( + payload: web::Json<(SocketAddrV4, Vec)>, + service: web::Data, +) -> 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)>) -> impl Responder { +async fn remove_ipv6_http_service( + payload: web::Json<(SocketAddrV6, Vec)>, + service: web::Data, +) -> 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) -> 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) -> 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) -> 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) -> 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) -> 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) -> impl Responder { - match Service::add_ipv4_ssh_service(ip_addr.into_inner()).await { +async fn add_ipv4_ssh_service(ip_addr: web::Json, service: web::Data) -> 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) -> impl Responder { - match Service::add_ipv6_ssh_service(ip_addr.into_inner()).await { +async fn add_ipv6_ssh_service(ip_addr: web::Json, service: web::Data) -> 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) -> impl Responder { - match Service::remove_ipv4_ssh_service(ip_addr.into_inner()).await { +async fn remove_ipv4_ssh_service(ip_addr: web::Json, service: web::Data) -> 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) -> impl Responder { - match Service::remove_ipv6_ssh_service(ip_addr.into_inner()).await { +async fn remove_ipv6_ssh_service(ip_addr: web::Json, service: web::Data) -> 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) -> 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) -> 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) -> 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, service: web::Data) -> 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) -> 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, service: web::Data) -> 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) -> 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, service: web::Data) -> 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) -> 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, service: web::Data) -> 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) -> 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) -> 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) -> 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, service: web::Data) -> 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) -> 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, service: web::Data) -> 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) -> 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, service: web::Data) -> 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) -> 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, service: web::Data) -> 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()), } diff --git a/net-guardia/src/web/api/statistics.rs b/net-guardia/src/web/api/control/statistics.rs similarity index 60% rename from net-guardia/src/web/api/statistics.rs rename to net-guardia/src/web/api/control/statistics.rs index b4b8d51..b74f1a4 100644 --- a/net-guardia/src/web/api/statistics.rs +++ b/net-guardia/src/web/api/control/statistics.rs @@ -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>, +) -> 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>, +) -> 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, + statistics: web::Data, ) -> 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, + statistics: web::Data, ) -> 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)), } diff --git a/net-guardia/src/web/api/health.rs b/net-guardia/src/web/api/health.rs deleted file mode 100644 index e8c64e6..0000000 --- a/net-guardia/src/web/api/health.rs +++ /dev/null @@ -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)), - } -} diff --git a/net-guardia/src/web/api/misc.rs b/net-guardia/src/web/api/misc.rs index efe7596..1682654 100644 --- a/net-guardia/src/web/api/misc.rs +++ b/net-guardia/src/web/api/misc.rs @@ -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) } diff --git a/net-guardia/src/web/api/mod.rs b/net-guardia/src/web/api/mod.rs index 058f737..6a2badd 100644 --- a/net-guardia/src/web/api/mod.rs +++ b/net-guardia/src/web/api/mod.rs @@ -1,5 +1,3 @@ pub mod control; pub mod default; -pub mod health; pub mod misc; -pub mod statistics; diff --git a/net-guardia/src/web/websocket/flow_websocket.rs b/net-guardia/src/web/websocket/flow_websocket.rs index 7dc9a89..434ace5 100644 --- a/net-guardia/src/web/websocket/flow_websocket.rs +++ b/net-guardia/src/web/websocket/flow_websocket.rs @@ -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, + statistics: web::Data, ) -> Result { + 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, + statistics: web::Data, ) -> Result { + 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, + statistics: Arc, 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, + statistics: Arc, 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>, ) -> 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, 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, 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) => { diff --git a/net-guardia/src/web/websocket/health_websocket.rs b/net-guardia/src/web/websocket/health_websocket.rs deleted file mode 100644 index 0c89e04..0000000 --- a/net-guardia/src/web/websocket/health_websocket.rs +++ /dev/null @@ -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, -) -> Result { - 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, -) { - 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>, -) -> 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(¤t_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, -) -> 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 - } - } -} diff --git a/net-guardia/src/web/websocket/mod.rs b/net-guardia/src/web/websocket/mod.rs index b9d4328..eae45eb 100644 --- a/net-guardia/src/web/websocket/mod.rs +++ b/net-guardia/src/web/websocket/mod.rs @@ -1,2 +1 @@ pub mod flow_websocket; -pub mod health_websocket;