Feat/ml api (#13)

* wip: implement ml inference api

* wip: adjust code

* feat: Add ML alert api components

* add: Add private ip analysis

* fix: Fix compile error

* refactor: Change compilation place of frontend

* refactor: Change compilation place of frontend

* feat: Using cic2018 models and fix feature extraction

* feat: Using the torch models

* wip: Continuous optimization model

* wip: Add TODO

* wip: Add csv record

* wip: Continuously improve the inference

* fix: use actual bulk duration for Fwd/Bwd Avg Bulk Rate

* chore: adjust code
This commit is contained in:
ParrotXray 2026-03-18 15:25:28 +08:00 committed by GitHub
parent 55edf01113
commit 26f2e1851b
56 changed files with 1722 additions and 2044 deletions

2
.gitignore vendored
View File

@ -10,7 +10,7 @@ target/
.idea
logs
.env
TODO
.log
.txt
net-guardia/static/web

331
Cargo.lock generated
View File

@ -61,9 +61,9 @@ dependencies = [
[[package]]
name = "actix-http"
version = "3.11.1"
version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44cceded2fb55f3c4b67068fa64962e2ca59614edc5b03167de9ff82ae803da0"
checksum = "f860ee6746d0c5b682147b2f7f8ef036d4f92fe518251a3a35ffa3650eafdf0e"
dependencies = [
"actix-codec",
"actix-rt",
@ -110,9 +110,9 @@ dependencies = [
[[package]]
name = "actix-router"
version = "0.5.3"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8"
checksum = "14f8c75c51892f18d9c46150c5ac7beb81c95f78c8b83a634d49f4ca32551fe7"
dependencies = [
"bytestring",
"cfg-if",
@ -173,9 +173,9 @@ dependencies = [
[[package]]
name = "actix-web"
version = "4.11.0"
version = "4.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a597b77b5c6d6a1e1097fddde329a83665e25c5437c696a3a9a4aa514a614dea"
checksum = "ff87453bc3b56e9b2b23c1cc0b1be8797184accf51d2abe0f8a33ec275d316bf"
dependencies = [
"actix-codec",
"actix-http",
@ -208,7 +208,7 @@ dependencies = [
"serde_json",
"serde_urlencoded",
"smallvec",
"socket2 0.5.8",
"socket2 0.6.0",
"time",
"tracing",
"url",
@ -228,16 +228,18 @@ dependencies = [
[[package]]
name = "actix-ws"
version = "0.3.0"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3a1fb4f9f2794b0aadaf2ba5f14a6f034c7e86957b458c506a8cb75953f2d99"
checksum = "decf53c3cdd63dd6f289980b430238f9a2f6d19f8bce8e418272e08d3da43f0f"
dependencies = [
"actix-codec",
"actix-http",
"actix-web",
"bytestring",
"futures-core",
"futures-sink",
"tokio",
"tokio-util",
]
[[package]]
@ -251,15 +253,6 @@ dependencies = [
"syn 2.0.98",
]
[[package]]
name = "addr2line"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
dependencies = [
"gimli",
]
[[package]]
name = "adler2"
version = "2.0.0"
@ -465,21 +458,6 @@ dependencies = [
"thiserror 1.0.69",
]
[[package]]
name = "backtrace"
version = "0.3.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
dependencies = [
"addr2line",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
"windows-targets",
]
[[package]]
name = "base64"
version = "0.22.1"
@ -596,31 +574,14 @@ dependencies = [
"serde",
]
[[package]]
name = "cargo-util-schemas"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dc1a6f7b5651af85774ae5a34b4e8be397d9cf4bc063b7e6dbd99a841837830"
dependencies = [
"semver",
"serde",
"serde-untagged",
"serde-value",
"thiserror 2.0.16",
"toml 0.8.20",
"unicode-xid",
"url",
]
[[package]]
name = "cargo_metadata"
version = "0.22.0"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c3f56c207c76c07652489840ff98687dcf213de178ac0974660d6fefeaf5ec6"
checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9"
dependencies = [
"camino",
"cargo-platform",
"cargo-util-schemas",
"semver",
"serde",
"serde_json",
@ -920,16 +881,6 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "erased-serde"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7"
dependencies = [
"serde",
"typeid",
]
[[package]]
name = "errno"
version = "0.3.13"
@ -1116,12 +1067,6 @@ dependencies = [
"wasi 0.14.3+wasi-0.2.4",
]
[[package]]
name = "gimli"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "glob"
version = "0.3.3"
@ -1130,9 +1075,9 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
[[package]]
name = "h2"
version = "0.3.26"
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8"
checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d"
dependencies = [
"bytes",
"fnv",
@ -1370,12 +1315,12 @@ checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2"
[[package]]
name = "indexmap"
version = "2.7.1"
version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
dependencies = [
"equivalent",
"hashbrown 0.15.2",
"hashbrown 0.16.1",
]
[[package]]
@ -1389,17 +1334,6 @@ dependencies = [
"which",
]
[[package]]
name = "io-uring"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b"
dependencies = [
"bitflags",
"cfg-if",
"libc",
]
[[package]]
name = "ipnetwork"
version = "0.21.1"
@ -1503,7 +1437,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
dependencies = [
"cfg-if",
"windows-link 0.2.0",
"windows-link",
]
[[package]]
@ -1800,7 +1734,7 @@ dependencies = [
"thiserror 2.0.16",
"tokio",
"tokio-tungstenite",
"toml 0.9.5",
"toml",
"tracing",
"tracing-appender",
"tracing-subscriber",
@ -1967,15 +1901,6 @@ version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "ordered-float"
version = "2.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c"
dependencies = [
"num-traits",
]
[[package]]
name = "parking_lot"
version = "0.12.5"
@ -1996,7 +1921,7 @@ dependencies = [
"libc",
"redox_syscall 0.5.8",
"smallvec",
"windows-link 0.2.0",
"windows-link",
]
[[package]]
@ -2359,12 +2284,6 @@ dependencies = [
"walkdir",
]
[[package]]
name = "rustc-demangle"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "rustc-hash"
version = "2.1.1"
@ -2455,39 +2374,28 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.219"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde-untagged"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34836a629bcbc6f1afdf0907a744870039b1e14c0561cb26094fa683b158eff3"
dependencies = [
"erased-serde",
"serde",
"typeid",
]
[[package]]
name = "serde-value"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c"
dependencies = [
"ordered-float",
"serde",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
@ -2508,20 +2416,11 @@ dependencies = [
[[package]]
name = "serde_spanned"
version = "0.6.8"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776"
dependencies = [
"serde",
]
[[package]]
name = "serde_spanned"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83"
dependencies = [
"serde",
"serde_core",
]
[[package]]
@ -2681,9 +2580,9 @@ dependencies = [
[[package]]
name = "sysinfo"
version = "0.37.0"
version = "0.38.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07cec4dc2d2e357ca1e610cfb07de2fa7a10fc3e9fe89f72545f3d244ea87753"
checksum = "1efc19935b4b66baa6f654ac7924c192f55b175c00a7ab72410fc24284dacda8"
dependencies = [
"libc",
"memchr",
@ -2812,29 +2711,26 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.47.1"
version = "1.49.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
dependencies = [
"backtrace",
"bytes",
"io-uring",
"libc",
"mio",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"slab",
"socket2 0.6.0",
"tokio-macros",
"windows-sys 0.59.0",
"windows-sys 0.61.1",
]
[[package]]
name = "tokio-macros"
version = "2.5.0"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
dependencies = [
"proc-macro2",
"quote",
@ -2862,32 +2758,22 @@ dependencies = [
"bytes",
"futures-core",
"futures-sink",
"futures-util",
"hashbrown 0.14.5",
"pin-project-lite",
"tokio",
]
[[package]]
name = "toml"
version = "0.8.20"
version = "1.0.3+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148"
dependencies = [
"serde",
"serde_spanned 0.6.8",
"toml_datetime 0.6.8",
"toml_edit",
]
[[package]]
name = "toml"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8"
checksum = "c7614eaf19ad818347db24addfa201729cf2a9b6fdfd9eb0ab870fcacc606c0c"
dependencies = [
"indexmap",
"serde",
"serde_spanned 1.0.0",
"toml_datetime 0.7.0",
"serde_core",
"serde_spanned",
"toml_datetime",
"toml_parser",
"toml_writer",
"winnow",
@ -2895,49 +2781,27 @@ dependencies = [
[[package]]
name = "toml_datetime"
version = "0.6.8"
version = "1.0.0+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e"
dependencies = [
"serde",
]
[[package]]
name = "toml_datetime"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
dependencies = [
"indexmap",
"serde",
"serde_spanned 0.6.8",
"toml_datetime 0.6.8",
"winnow",
"serde_core",
]
[[package]]
name = "toml_parser"
version = "1.0.2"
version = "1.0.9+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10"
checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4"
dependencies = [
"winnow",
]
[[package]]
name = "toml_writer"
version = "1.0.2"
version = "1.0.6+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64"
checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607"
[[package]]
name = "tracing"
@ -3183,12 +3047,6 @@ dependencies = [
"utf-8",
]
[[package]]
name = "typeid"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
[[package]]
name = "typenum"
version = "1.17.0"
@ -3345,55 +3203,54 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.61.3"
version = "0.62.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
checksum = "49e6c4a1f363c8210c6f77ba24f645c61c6fb941eccf013da691f7e09515b8ac"
dependencies = [
"windows-collections",
"windows-core",
"windows-future",
"windows-link 0.1.3",
"windows-numerics",
]
[[package]]
name = "windows-collections"
version = "0.2.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
checksum = "123e712f464a8a60ce1a13f4c446d2d43ab06464cb5842ff68f5c71b6fb7852e"
dependencies = [
"windows-core",
]
[[package]]
name = "windows-core"
version = "0.61.2"
version = "0.62.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
checksum = "6844ee5416b285084d3d3fffd743b925a6c9385455f64f6d4fa3031c4c2749a9"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link 0.1.3",
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-future"
version = "0.2.1"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
checksum = "68f3db6b24b120200d649cd4811b4947188ed3a8d2626f7075146c5d178a9a4a"
dependencies = [
"windows-core",
"windows-link 0.1.3",
"windows-link",
"windows-threading",
]
[[package]]
name = "windows-implement"
version = "0.60.0"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
"quote",
@ -3402,21 +3259,15 @@ dependencies = [
[[package]]
name = "windows-interface"
version = "0.59.1"
version = "0.59.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
]
[[package]]
name = "windows-link"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]]
name = "windows-link"
version = "0.2.0"
@ -3425,30 +3276,30 @@ checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
[[package]]
name = "windows-numerics"
version = "0.2.0"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
checksum = "2ce3498fe0aba81e62e477408383196b4b0363db5e0c27646f932676283b43d8"
dependencies = [
"windows-core",
"windows-link 0.1.3",
"windows-link",
]
[[package]]
name = "windows-result"
version = "0.3.4"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f"
dependencies = [
"windows-link 0.1.3",
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.4.2"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda"
dependencies = [
"windows-link 0.1.3",
"windows-link",
]
[[package]]
@ -3469,6 +3320,15 @@ dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.61.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
@ -3487,11 +3347,11 @@ dependencies = [
[[package]]
name = "windows-threading"
version = "0.1.0"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
checksum = "ab47f085ad6932defa48855254c758cdd0e2f2d48e62a34118a268d8f345e118"
dependencies = [
"windows-link 0.1.3",
"windows-link",
]
[[package]]
@ -3547,9 +3407,6 @@ name = "winnow"
version = "0.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
dependencies = [
"memchr",
]
[[package]]
name = "winsafe"

View File

@ -7,8 +7,8 @@ default-members = ["net-guardia", "common"]
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 }
aya-log-ebpf = { version = "0.1.0", default-features = false }
cargo_metadata = { version = "0.23.1", default-features = false }
libc = { version = "0.2.159", default-features = false }
network-types = "0.1.0"
serde = { version = "1.0.215", features = ["derive"] }

View File

@ -7,4 +7,5 @@ pub mod ingress {
pub mod egress {
pub const STATISTICS: u32 = 0;
pub const TRANSMISSION: u32 = 1;
}

View File

@ -44,28 +44,28 @@ unsafe fn parse_ipv4_packet(start: usize, end: usize, target: *mut Event) -> Res
core::ptr::write(ipv4_data_ptr as *mut IpProto, ipv4.proto);
core::ptr::copy_nonoverlapping(
ipv4.src_addr.as_ptr(),
ipv4_data_ptr.add(core::mem::offset_of!(IPv4Event, source_ip)),
ipv4_data_ptr.add(core::mem::offset_of!(IPv4Event, src_ip)),
4,
);
core::ptr::copy_nonoverlapping(
ipv4.dst_addr.as_ptr(),
ipv4_data_ptr.add(core::mem::offset_of!(IPv4Event, destination_ip)),
ipv4_data_ptr.add(core::mem::offset_of!(IPv4Event, dst_ip)),
4,
);
core::ptr::write(
ipv4_data_ptr.add(core::mem::offset_of!(IPv4Event, source_port)) as *mut u16,
ipv4_data_ptr.add(core::mem::offset_of!(IPv4Event, src_port)) as *mut u16,
source_port,
);
core::ptr::write(
ipv4_data_ptr.add(core::mem::offset_of!(IPv4Event, destination_port)) as *mut u16,
ipv4_data_ptr.add(core::mem::offset_of!(IPv4Event, dst_port)) as *mut u16,
destination_port,
);
core::ptr::write(
ipv4_data_ptr.add(core::mem::offset_of!(IPv4Event, len)) as *mut u32,
ipv4_data_ptr.add(core::mem::offset_of!(IPv4Event, packet_length)) as *mut u32,
(end - start) as u32,
);
core::ptr::write(
ipv4_data_ptr.add(core::mem::offset_of!(IPv4Event, timestamp)) as *mut u64,
ipv4_data_ptr.add(core::mem::offset_of!(IPv4Event, timestamp_us)) as *mut u64,
bpf_ktime_get_ns(),
);
@ -95,28 +95,28 @@ unsafe fn parse_ipv6_packet(start: usize, end: usize, target: *mut Event) -> Res
core::ptr::write(ipv6_data_ptr as *mut IpProto, ipv6.next_hdr);
core::ptr::copy_nonoverlapping(
ipv6.src_addr.as_ptr(),
ipv6_data_ptr.add(core::mem::offset_of!(IPv6Event, source_ip)),
ipv6_data_ptr.add(core::mem::offset_of!(IPv6Event, src_ip)),
16,
);
core::ptr::copy_nonoverlapping(
ipv6.dst_addr.as_ptr(),
ipv6_data_ptr.add(core::mem::offset_of!(IPv6Event, destination_ip)),
ipv6_data_ptr.add(core::mem::offset_of!(IPv6Event, dst_ip)),
16,
);
core::ptr::write(
ipv6_data_ptr.add(core::mem::offset_of!(IPv6Event, source_port)) as *mut u16,
ipv6_data_ptr.add(core::mem::offset_of!(IPv6Event, src_port)) as *mut u16,
source_port,
);
core::ptr::write(
ipv6_data_ptr.add(core::mem::offset_of!(IPv6Event, destination_port)) as *mut u16,
ipv6_data_ptr.add(core::mem::offset_of!(IPv6Event, dst_port)) as *mut u16,
destination_port,
);
core::ptr::write(
ipv6_data_ptr.add(core::mem::offset_of!(IPv6Event, len)) as *mut u32,
ipv6_data_ptr.add(core::mem::offset_of!(IPv6Event, packet_length)) as *mut u32,
(end - start) as u32,
);
core::ptr::write(
ipv6_data_ptr.add(core::mem::offset_of!(IPv6Event, timestamp)) as *mut u64,
ipv6_data_ptr.add(core::mem::offset_of!(IPv6Event, timestamp_us)) as *mut u64,
bpf_ktime_get_ns(),
);

View File

@ -1,4 +1,3 @@
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use network_types::ip::IpProto;
use crate::model::ip_address::{AddrPortV4, AddrPortV6};
@ -10,6 +9,13 @@ pub enum Event {
IPv6(IPv6Event),
}
#[repr(C, align(8))]
#[derive(Clone)]
pub enum RawIp {
V4(u32),
V6(u128),
}
impl Event {
pub fn timestamp_us(&self) -> u64 {
match self {
@ -67,25 +73,17 @@ impl Event {
}
}
pub fn src_ip(&self) -> IpAddr {
pub fn src_ip(&self) -> RawIp {
match self {
Event::IPv4(e) => {
IpAddr::V4(Ipv4Addr::from(e.src_ip))
}
Event::IPv6(e) => {
IpAddr::V6(Ipv6Addr::from(e.src_ip))
}
Event::IPv4(e) => RawIp::V4(e.src_ip),
Event::IPv6(e) => RawIp::V6(e.src_ip),
}
}
pub fn dst_ip(&self) -> IpAddr {
pub fn dst_ip(&self) -> RawIp {
match self {
Event::IPv4(e) => {
IpAddr::V4(Ipv4Addr::from(e.dst_ip))
}
Event::IPv6(e) => {
IpAddr::V6(Ipv6Addr::from(e.dst_ip))
}
Event::IPv4(e) => RawIp::V4(e.dst_ip),
Event::IPv6(e) => RawIp::V6(e.dst_ip),
}
}
@ -170,6 +168,7 @@ impl IPv6Event {
}
}
#[repr(C, align(8))]
#[derive(Debug, Clone, Default)]
pub struct TcpFlags {
pub fin: bool,

View File

@ -3,8 +3,7 @@ ingress_ifname = "enp4s0f1" # Ingress NIC Name
egress_ifname = "enp4s0f0" # Egress NIC Name
geoip_db_name = "GeoLite2-City.mmdb"
deep_autoencoder_name = "deep_autoencoder.onnx"
random_forest_name = "random_forest.onnx"
mlp_name = "mlp.onnx"
classifier_name = "classifier.onnx"
models_config_name = "inference_config.json"
combined_queue_count = 8 # NIC Combined Queue Count (ethtool -l <NIC>)
channel_size = 4096
@ -19,4 +18,9 @@ refresh_interval = 5 # Statistics Refresh Time
max_concurrent_flows = 10000 # max_flows: track up to 10000 concurrent flows
min_packets_for_inference = 5 # min_packets: minimum 10 packets per flow for inference
inference_interval_secs = 5 # interval_secs: run inference every 5 seconds
inference_interval_secs = 5 # interval_secs: run inference every 5 seconds
aggregator_window_secs = 30
inference_batch_size = 200
traffic_logging_mode = true # When true, disables ML inference and records all ingress/egress packets to CSV
traffic_log_csv_path = "traffic_log.csv" # Output CSV file path for traffic logging mode

View File

@ -60,14 +60,14 @@ pub fn ipv6_update_stats(event: &IPv6Event) {
unsafe fn ipv4_update_flow_stats(map: &LruHashMap<AddrPortV4, FlowStats>, key: &AddrPortV4, event: &IPv4Event) {
unsafe {
if let Some(status) = map.get_ptr_mut(key) {
(*status).bytes += event.len as u64;
(*status).bytes += event.packet_length as u64;
(*status).packets += 1;
(*status).last_seen = event.timestamp;
(*status).last_seen = event.timestamp_us;
} else {
let new_stats = FlowStats {
bytes: event.len as u64,
bytes: event.packet_length as u64,
packets: 1,
last_seen: event.timestamp,
last_seen: event.timestamp_us,
};
let _ = map.insert(key, &new_stats, 0);
}
@ -78,14 +78,14 @@ unsafe fn ipv4_update_flow_stats(map: &LruHashMap<AddrPortV4, FlowStats>, key: &
unsafe fn ipv6_update_flow_status(map: &LruHashMap<AddrPortV6, FlowStats>, key: &AddrPortV6, event: &IPv6Event) {
unsafe {
if let Some(status) = map.get_ptr_mut(key) {
(*status).bytes += event.len as u64;
(*status).bytes += event.packet_length as u64;
(*status).packets += 1;
(*status).last_seen = event.timestamp;
(*status).last_seen = event.timestamp_us;
} else {
let new_stats = FlowStats {
bytes: event.len as u64,
bytes: event.packet_length as u64,
packets: 1,
last_seen: event.timestamp,
last_seen: event.timestamp_us,
};
let _ = map.insert(key, &new_stats, 0);
}

View File

@ -5,7 +5,7 @@ mod action;
use action::statistics;
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;
#[allow(unused_imports)]
use aya_log_ebpf::info;
@ -16,6 +16,8 @@ use common::{ebpf::parsing, model::event::Event};
static PROGRAM_ARRAY: ProgramArray = ProgramArray::with_max_entries(8, 0);
#[map]
static PARSED_PACKET: PerCpuArray<Event> = PerCpuArray::with_max_entries(1, 0);
#[map]
static EGRESS_XSKS_MAP: XskMap = XskMap::pinned(64, 0);
#[xdp]
pub fn net_guardia(ctx: XdpContext) -> u32 {
@ -39,12 +41,12 @@ unsafe fn packet_intake(ctx: XdpContext) -> Result<u32, ()> {
#[xdp]
pub fn statistics(ctx: XdpContext) -> u32 {
unsafe {
let _ = try_statistics(ctx);
let _ = try_statistics(&ctx);
xdp_action::XDP_PASS
}
}
unsafe fn try_statistics(_: XdpContext) -> Result<u32, ()> {
unsafe fn try_statistics(ctx: &XdpContext) -> Result<u32, ()> {
unsafe {
let ptr = PARSED_PACKET.get_ptr(0).ok_or(())?;
let parsed_packet = &*ptr;
@ -56,10 +58,20 @@ unsafe fn try_statistics(_: XdpContext) -> Result<u32, ()> {
statistics::ipv6_update_stats(event);
}
}
let _ = PROGRAM_ARRAY.tail_call(ctx, TRANSMISSION);
Ok(xdp_action::XDP_PASS)
}
}
#[xdp]
pub fn transmission(ctx: XdpContext) -> u32 {
let queue_id = unsafe { (*ctx.ctx).rx_queue_index };
match EGRESS_XSKS_MAP.redirect(queue_id, 0) {
Ok(action) => action,
Err(_) => xdp_action::XDP_PASS,
}
}
#[cfg(not(test))]
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {

View File

@ -23,13 +23,13 @@ static IPV6_DST_BLACKLIST: HashMap<IPv6, [Port; MAX_RULES_PORT]> = HashMap::with
pub fn ipv4_is_whitelisted(event: &IPv4Event) -> bool {
unsafe {
if let Some(ports) = IPV4_SRC_WHITELIST.get(&event.source_ip) {
if is_port_exist(ports, event.source_port) {
if let Some(ports) = IPV4_SRC_WHITELIST.get(&event.src_ip) {
if is_port_exist(ports, event.src_port) {
return true;
}
}
if let Some(ports) = IPV4_DST_WHITELIST.get(&event.destination_ip) {
if is_port_exist(ports, event.destination_port) {
if let Some(ports) = IPV4_DST_WHITELIST.get(&event.dst_ip) {
if is_port_exist(ports, event.dst_port) {
return true;
}
}
@ -39,13 +39,13 @@ pub fn ipv4_is_whitelisted(event: &IPv4Event) -> bool {
pub fn ipv6_is_whitelisted(event: &IPv6Event) -> bool {
unsafe {
if let Some(ports) = IPV6_SRC_WHITELIST.get(&event.source_ip) {
if is_port_exist(ports, event.source_port) {
if let Some(ports) = IPV6_SRC_WHITELIST.get(&event.src_ip) {
if is_port_exist(ports, event.src_port) {
return true;
}
}
if let Some(ports) = IPV6_DST_WHITELIST.get(&event.destination_ip) {
if is_port_exist(ports, event.destination_port) {
if let Some(ports) = IPV6_DST_WHITELIST.get(&event.dst_ip) {
if is_port_exist(ports, event.dst_port) {
return true;
}
}
@ -55,13 +55,13 @@ pub fn ipv6_is_whitelisted(event: &IPv6Event) -> bool {
pub fn ipv4_is_blacklisted(event: &IPv4Event) -> bool {
unsafe {
if let Some(ports) = IPV4_SRC_BLACKLIST.get(&event.source_ip) {
if is_port_exist(ports, event.source_port) {
if let Some(ports) = IPV4_SRC_BLACKLIST.get(&event.src_ip) {
if is_port_exist(ports, event.src_port) {
return true;
}
}
if let Some(ports) = IPV4_DST_BLACKLIST.get(&event.destination_ip) {
if is_port_exist(ports, event.destination_port) {
if let Some(ports) = IPV4_DST_BLACKLIST.get(&event.dst_ip) {
if is_port_exist(ports, event.dst_port) {
return true;
}
}
@ -71,13 +71,13 @@ pub fn ipv4_is_blacklisted(event: &IPv4Event) -> bool {
pub fn ipv6_is_blacklisted(event: &IPv6Event) -> bool {
unsafe {
if let Some(ports) = IPV6_SRC_BLACKLIST.get(&event.source_ip) {
if is_port_exist(ports, event.source_port) {
if let Some(ports) = IPV6_SRC_BLACKLIST.get(&event.src_ip) {
if is_port_exist(ports, event.src_port) {
return true;
}
}
if let Some(ports) = IPV6_DST_BLACKLIST.get(&event.destination_ip) {
if is_port_exist(ports, event.destination_port) {
if let Some(ports) = IPV6_DST_BLACKLIST.get(&event.dst_ip) {
if is_port_exist(ports, event.dst_port) {
return true;
}
}

View File

@ -60,14 +60,14 @@ pub fn ipv6_update_stats(event: &IPv6Event) {
unsafe fn ipv4_update_flow_stats(map: &LruHashMap<AddrPortV4, FlowStats>, key: &AddrPortV4, event: &IPv4Event) {
unsafe {
if let Some(status) = map.get_ptr_mut(key) {
(*status).bytes += event.len as u64;
(*status).bytes += event.packet_length as u64;
(*status).packets += 1;
(*status).last_seen = event.timestamp;
(*status).last_seen = event.timestamp_us;
} else {
let new_stats = FlowStats {
bytes: event.len as u64,
bytes: event.packet_length as u64,
packets: 1,
last_seen: event.timestamp,
last_seen: event.timestamp_us,
};
let _ = map.insert(key, &new_stats, 0);
}
@ -78,14 +78,14 @@ unsafe fn ipv4_update_flow_stats(map: &LruHashMap<AddrPortV4, FlowStats>, key: &
unsafe fn ipv6_update_flow_status(map: &LruHashMap<AddrPortV6, FlowStats>, key: &AddrPortV6, event: &IPv6Event) {
unsafe {
if let Some(status) = map.get_ptr_mut(key) {
(*status).bytes += event.len as u64;
(*status).bytes += event.packet_length as u64;
(*status).packets += 1;
(*status).last_seen = event.timestamp;
(*status).last_seen = event.timestamp_us;
} else {
let new_stats = FlowStats {
bytes: event.len as u64,
bytes: event.packet_length as u64,
packets: 1,
last_seen: event.timestamp,
last_seen: event.timestamp_us,
};
let _ = map.insert(key, &new_stats, 0);
}

View File

@ -19,7 +19,7 @@ static PROGRAM_ARRAY: ProgramArray = ProgramArray::with_max_entries(8, 0);
#[map]
static PARSED_PACKET: PerCpuArray<Event> = PerCpuArray::with_max_entries(1, 0);
#[map]
static XSKS_MAP: XskMap = XskMap::pinned(64, 0);
static INGRESS_XSKS_MAP: XskMap = XskMap::pinned(64, 0);
#[xdp]
pub fn net_guardia(ctx: XdpContext) -> u32 {
@ -151,7 +151,7 @@ unsafe fn try_statistics(ctx: &XdpContext) -> Result<u32, ()> {
#[xdp]
pub fn transmission(ctx: XdpContext) -> u32 {
let queue_id = unsafe { (*ctx.ctx).rx_queue_index };
match XSKS_MAP.redirect(queue_id, 0) {
match INGRESS_XSKS_MAP.redirect(queue_id, 0) {
Ok(action) => action,
Err(_) => xdp_action::XDP_PASS,
}

BIN
models/classifier.onnx Normal file

Binary file not shown.

Binary file not shown.

View File

@ -1,577 +1,277 @@
{
"created_at": "2026-01-27T13:05:39.258903",
"created_at": "2026-03-05T14:04:08.736531",
"framework": "PyTorch",
"model": {
"deep_autoencoder": {
"file": "deep_autoencoder.onnx",
"input_dim": 78,
"encoding_dim": 16
"input_dim": 27,
"encoding_dim": 16,
"ae_feature_names": [
"flow_duration",
"fwd_packets",
"bwd_packets",
"fwd_bytes",
"bwd_bytes",
"flow_bytes_per_sec",
"flow_pkts_per_sec",
"fwd_win_bytes",
"bwd_win_bytes",
"fwd_pkt_len_mean",
"bwd_pkt_len_mean",
"fwd_iat_mean",
"bwd_iat_mean",
"flow_iat_mean",
"pkt_len_mean",
"dst_port",
"protocol",
"psh_flag_cnt",
"ack_flag_cnt",
"syn_flag_cnt",
"fin_flag_cnt",
"rst_flag_cnt",
"pkt_len_std",
"fwd_pkt_len_std",
"bwd_pkt_len_std",
"fwd_seg_size_min",
"fwd_act_data_pkts"
],
"ae_threshold": 0.17166012525558472
},
"random_forest": {
"file": "random_forest.onnx",
"n_estimators": 100,
"n_features": 78
},
"mlp_classifier": {
"file": "mlp.onnx",
"input_dim": 78,
"n_classes": 10
"classifier": {
"file": "classifier.onnx",
"type": "ResNet MLP",
"n_features": 28,
"n_classes": 5,
"classifier_feature_names": [
"flow_duration",
"fwd_packets",
"bwd_packets",
"fwd_bytes",
"bwd_bytes",
"flow_bytes_per_sec",
"flow_pkts_per_sec",
"fwd_win_bytes",
"bwd_win_bytes",
"fwd_pkt_len_mean",
"bwd_pkt_len_mean",
"fwd_iat_mean",
"bwd_iat_mean",
"flow_iat_mean",
"pkt_len_mean",
"dst_port",
"protocol",
"psh_flag_cnt",
"ack_flag_cnt",
"syn_flag_cnt",
"fin_flag_cnt",
"rst_flag_cnt",
"pkt_len_std",
"fwd_pkt_len_std",
"bwd_pkt_len_std",
"fwd_seg_size_min",
"fwd_act_data_pkts",
"ae_anomaly_score"
]
}
},
"preprocessing": {
"clip_params": {
"Destination Port": {
"lower": 22.0,
"upper": 63734.0
},
"Flow Duration": {
"ae_clip_params": {
"flow_duration": {
"lower": 1.0,
"upper": 118756600.72000001
"upper": 118485085.19000001
},
"Total Fwd Packets": {
"lower": 1.0,
"upper": 98.0
},
"Total Backward Packets": {
"fwd_packets": {
"lower": 0.0,
"upper": 126.0
"upper": 54.0
},
"Total Length of Fwd Packets": {
"bwd_packets": {
"lower": 0.0,
"upper": 13119.520000000019
"upper": 82.0
},
"Total Length of Bwd Packets": {
"fwd_bytes": {
"lower": 0.0,
"upper": 186394.64000000013
"upper": 6960.0
},
"Fwd Packet Length Max": {
"bwd_bytes": {
"lower": 0.0,
"upper": 5840.0
"upper": 89558.6950000003
},
"Fwd Packet Length Min": {
"flow_bytes_per_sec": {
"lower": 0.0,
"upper": 98.0
"upper": 12500000.0
},
"Fwd Packet Length Mean": {
"lower": 0.0,
"upper": 1932.5
},
"Fwd Packet Length Std": {
"lower": 0.0,
"upper": 2376.49858
},
"Bwd Packet Length Max": {
"lower": 0.0,
"upper": 4380.0
},
"Bwd Packet Length Min": {
"lower": 0.0,
"upper": 308.0
},
"Bwd Packet Length Mean": {
"lower": 0.0,
"upper": 1715.628244226656
},
"Bwd Packet Length Std": {
"lower": 0.0,
"upper": 1167.9936145600002
},
"Flow Bytes\/s": {
"lower": 0.0,
"upper": 37000000.0
},
"Flow Packets\/s": {
"lower": 0.041637660508,
"flow_pkts_per_sec": {
"lower": 0.0226829350855,
"upper": 2000000.0
},
"Flow IAT Mean": {
"lower": 1.0,
"upper": 30800000.0
},
"Flow IAT Std": {
"lower": 0.0,
"upper": 44253994.319940574
},
"Flow IAT Max": {
"lower": 1.0,
"upper": 96199268.00000003
},
"Flow IAT Min": {
"lower": 0.0,
"upper": 1999969.04
},
"Fwd IAT Total": {
"lower": 0.0,
"upper": 119000000.0
},
"Fwd IAT Mean": {
"lower": 0.0,
"upper": 74300000.0
},
"Fwd IAT Std": {
"lower": 0.0,
"upper": 35894248.669783354
},
"Fwd IAT Max": {
"lower": 0.0,
"upper": 97800000.0
},
"Fwd IAT Min": {
"lower": 0.0,
"upper": 74300000.0
},
"Bwd IAT Total": {
"lower": 0.0,
"upper": 118000000.0
},
"Bwd IAT Mean": {
"lower": 0.0,
"upper": 73734410.76000024
},
"Bwd IAT Std": {
"lower": 0.0,
"upper": 30100000.0
},
"Bwd IAT Max": {
"lower": 0.0,
"upper": 93800000.0
},
"Bwd IAT Min": {
"lower": 0.0,
"upper": 73734410.76000024
},
"Fwd PSH Flags": {
"lower": 0.0,
"upper": 1.0
},
"Bwd PSH Flags": {
"lower": 0.0,
"upper": 0.0
},
"Fwd URG Flags": {
"lower": 0.0,
"upper": 0.0
},
"Bwd URG Flags": {
"lower": 0.0,
"upper": 0.0
},
"Fwd Header Length": {
"lower": 20.0,
"upper": 2376.0
},
"Bwd Header Length": {
"lower": 0.0,
"upper": 3312.0
},
"Fwd Packets\/s": {
"lower": 0.02417299188,
"upper": 2000000.0
},
"Bwd Packets\/s": {
"lower": 0.0,
"upper": 142857.1429
},
"Min Packet Length": {
"lower": 0.0,
"upper": 89.0
},
"Max Packet Length": {
"lower": 0.0,
"upper": 5840.0
},
"Packet Length Mean": {
"lower": 0.0,
"upper": 1153.4362532
},
"Packet Length Std": {
"lower": 0.0,
"upper": 1845.154148000012
},
"Packet Length Variance": {
"lower": 0.0,
"upper": 3404593.931120044
},
"FIN Flag Count": {
"lower": 0.0,
"upper": 1.0
},
"SYN Flag Count": {
"lower": 0.0,
"upper": 1.0
},
"RST Flag Count": {
"lower": 0.0,
"upper": 0.0
},
"PSH Flag Count": {
"lower": 0.0,
"upper": 1.0
},
"ACK Flag Count": {
"lower": 0.0,
"upper": 1.0
},
"URG Flag Count": {
"lower": 0.0,
"upper": 1.0
},
"CWE Flag Count": {
"lower": 0.0,
"upper": 0.0
},
"ECE Flag Count": {
"lower": 0.0,
"upper": 0.0
},
"Down\/Up Ratio": {
"lower": 0.0,
"upper": 5.0
},
"Average Packet Size": {
"lower": 0.0,
"upper": 1182.362785582229
},
"Avg Fwd Segment Size": {
"lower": 0.0,
"upper": 1932.5
},
"Avg Bwd Segment Size": {
"lower": 0.0,
"upper": 1715.628244226656
},
"Fwd Header Length.1": {
"lower": 20.0,
"upper": 2376.0
},
"Fwd Avg Bytes\/Bulk": {
"lower": 0.0,
"upper": 0.0
},
"Fwd Avg Packets\/Bulk": {
"lower": 0.0,
"upper": 0.0
},
"Fwd Avg Bulk Rate": {
"lower": 0.0,
"upper": 0.0
},
"Bwd Avg Bytes\/Bulk": {
"lower": 0.0,
"upper": 0.0
},
"Bwd Avg Packets\/Bulk": {
"lower": 0.0,
"upper": 0.0
},
"Bwd Avg Bulk Rate": {
"lower": 0.0,
"upper": 0.0
},
"Subflow Fwd Packets": {
"lower": 1.0,
"upper": 98.0
},
"Subflow Fwd Bytes": {
"lower": 0.0,
"upper": 13119.520000000019
},
"Subflow Bwd Packets": {
"lower": 0.0,
"upper": 126.0
},
"Subflow Bwd Bytes": {
"lower": 0.0,
"upper": 186394.64000000013
},
"Init_Win_bytes_forward": {
"fwd_win_bytes": {
"lower": -1.0,
"upper": 65535.0
},
"Init_Win_bytes_backward": {
"bwd_win_bytes": {
"lower": -1.0,
"upper": 65535.0
"upper": 64000.0
},
"act_data_pkt_fwd": {
"fwd_pkt_len_mean": {
"lower": 0.0,
"upper": 49.0
"upper": 744.7272727272729
},
"min_seg_size_forward": {
"lower": 20.0,
"bwd_pkt_len_mean": {
"lower": 0.0,
"upper": 1393.9601153594992
},
"fwd_iat_mean": {
"lower": 0.0,
"upper": 86366722.035
},
"bwd_iat_mean": {
"lower": 0.0,
"upper": 21700000.0
},
"flow_iat_mean": {
"lower": 1.0,
"upper": 86353400.64500001
},
"pkt_len_mean": {
"lower": 0.0,
"upper": 945.0294113974604
},
"dst_port": {
"lower": 0.0,
"upper": 64499.0
},
"protocol": {
"lower": 0.0,
"upper": 17.0
},
"psh_flag_cnt": {
"lower": 0.0,
"upper": 240.0
},
"ack_flag_cnt": {
"lower": 0.0,
"upper": 655.0
},
"syn_flag_cnt": {
"lower": 0.0,
"upper": 4.0
},
"fin_flag_cnt": {
"lower": 0.0,
"upper": 2.0
},
"rst_flag_cnt": {
"lower": 0.0,
"upper": 1.0
},
"pkt_len_std": {
"lower": 0.0,
"upper": 943.9221842600019
},
"fwd_pkt_len_std": {
"lower": 0.0,
"upper": 721.4199648708689
},
"bwd_pkt_len_std": {
"lower": 0.0,
"upper": 911.0272448420104
},
"fwd_seg_size_min": {
"lower": 0.0,
"upper": 40.0
},
"Active Mean": {
"fwd_act_data_pkts": {
"lower": 0.0,
"upper": 3410152.040000004
},
"Active Std": {
"lower": 0.0,
"upper": 1944723.8431600018
},
"Active Max": {
"lower": 0.0,
"upper": 5717825.840000004
},
"Active Min": {
"lower": 0.0,
"upper": 2918887.7600000002
},
"Idle Mean": {
"lower": 0.0,
"upper": 94100000.0
},
"Idle Std": {
"lower": 0.0,
"upper": 20712312.015200634
},
"Idle Max": {
"lower": 0.0,
"upper": 94800000.0
},
"Idle Min": {
"lower": 0.0,
"upper": 94100000.0
"upper": 58.0
}
},
"scaler": {
"ae_scaler": {
"mean": [
9415.502043247605,
11217541.457604988,
5.41509711200182,
5.010050165039152,
508.74335117243135,
3302.166099044608,
217.24886707430434,
20.105687086824716,
65.40745127720037,
68.76070937185386,
394.97690859650953,
49.88046220640826,
159.85952962743096,
121.94653847258593,
872411.6556228747,
64701.11201063558,
840255.3525863544,
1516373.8724808302,
4422301.697900706,
24666.656195771673,
10897580.686685171,
1785372.7881935516,
1206278.3042178946,
4276119.216091527,
1025069.5899462275,
10158901.07420493,
1666490.9735317046,
939816.6614413982,
3689910.1546691586,
1003727.4123986625,
0.054886351088404936,
0.0,
0.0,
0.0,
140.54050707030981,
133.63606216540694,
58358.168874140014,
4983.791329731815,
19.812641079549177,
480.4855221752525,
108.29833052637488,
144.4437888623996,
99406.03379366906,
0.01811977227544623,
0.054886351088404936,
0.0,
0.25800834720207716,
0.28694244020382764,
0.11596161536441252,
0.0,
0.0,
0.6969992921551522,
122.46836156918589,
65.40745127720037,
159.85952962743843,
140.54050707030981,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
5.41509711200182,
508.74335117243135,
5.010050165039152,
3302.166099044608,
6891.551141020379,
2404.6474695096604,
2.6520584031389776,
25.79473555242033,
61328.50855670946,
36311.697575423306,
137445.81057140988,
37549.65545208145,
3689065.3106244486,
167385.3556173429,
3883594.4613142335,
3458258.8992590285
9783470.902880548,
3.6532605393470496,
3.2219949045990472,
311.35281195367173,
1407.508354811673,
298997.28705051675,
32378.36735569276,
6158.3256759444475,
6125.023325143747,
58.1683929498305,
149.72168204849987,
2991407.86700246,
543337.1970583026,
2727511.340181723,
105.03829359866097,
13463.473246959382,
8.920585011866685,
5.457814320208867,
12.214631683524651,
0.4885976379312313,
0.246598391240437,
0.08478201266637703,
151.71809799252043,
76.89649795414182,
143.21014925828936,
21.447338538177014,
3.2607463735155715
],
"std": [
19743.81786383566,
30111390.85802201,
10.612016675700064,
12.872110268702288,
1653.7982707262067,
17387.56157755931,
645.437387791972,
22.356844266679282,
193.61621129020307,
244.137250250046,
801.628533600607,
65.65797274491328,
277.7559653593753,
269.57514761088515,
3622907.235127058,
237431.11489053545,
3293477.440659972,
5309087.889319858,
14143198.815741453,
160458.44923675407,
29985211.254190512,
8130184.699376457,
4247601.6170286415,
14271787.10745024,
7769581.505106356,
29154202.839787327,
8031691.1024753135,
3622760.030182759,
13368925.151392205,
7684129.1959599955,
0.22775829195136954,
1.0,
1.0,
1.0,
271.11142624961946,
342.17520432711024,
231133.09437362824,
14026.707464402314,
22.005832334437958,
970.4322795025132,
179.9690556845557,
280.2535015764328,
343919.1355100174,
0.13338457979891152,
0.22775829195136954,
1.0,
0.4375386154114052,
0.4523344738284939,
0.32017888613474904,
1.0,
1.0,
0.6456077980707177,
185.26118186235436,
193.61621129020307,
277.7559653594075,
271.11142624961946,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
10.612016675700064,
1653.7982707262067,
12.872110268702288,
17387.56157755931,
14922.88749872485,
9322.158869764009,
5.784504103070997,
6.252894021045679,
322010.01591284445,
201975.98444339563,
641116.7578408231,
245957.32066446543,
13068297.83388166,
1617503.1921646637,
13724469.425904194,
12765671.772576509
27895851.239715174,
6.4263677035863696,
8.326275195918974,
739.2548064174216,
7898.711708881367,
1187804.3239195002,
190280.1548511611,
15556.664474188405,
17012.235322987635,
98.89458775902833,
242.79522892109904,
13146157.964503227,
2242915.166631539,
12878527.145353919,
151.95316919115754,
21264.915159327567,
4.943209145195101,
23.02750953458606,
57.0633472601567,
1.2662749497077417,
0.4553262784517569,
0.278557037237646,
211.1373807979287,
143.47790203327,
228.24531573376805,
9.323458649060111,
7.228913366949205
],
"feature_names": [
"Destination Port",
"Flow Duration",
"Total Fwd Packets",
"Total Backward Packets",
"Total Length of Fwd Packets",
"Total Length of Bwd Packets",
"Fwd Packet Length Max",
"Fwd Packet Length Min",
"Fwd Packet Length Mean",
"Fwd Packet Length Std",
"Bwd Packet Length Max",
"Bwd Packet Length Min",
"Bwd Packet Length Mean",
"Bwd Packet Length Std",
"Flow Bytes\/s",
"Flow Packets\/s",
"Flow IAT Mean",
"Flow IAT Std",
"Flow IAT Max",
"Flow IAT Min",
"Fwd IAT Total",
"Fwd IAT Mean",
"Fwd IAT Std",
"Fwd IAT Max",
"Fwd IAT Min",
"Bwd IAT Total",
"Bwd IAT Mean",
"Bwd IAT Std",
"Bwd IAT Max",
"Bwd IAT Min",
"Fwd PSH Flags",
"Bwd PSH Flags",
"Fwd URG Flags",
"Bwd URG Flags",
"Fwd Header Length",
"Bwd Header Length",
"Fwd Packets\/s",
"Bwd Packets\/s",
"Min Packet Length",
"Max Packet Length",
"Packet Length Mean",
"Packet Length Std",
"Packet Length Variance",
"FIN Flag Count",
"SYN Flag Count",
"RST Flag Count",
"PSH Flag Count",
"ACK Flag Count",
"URG Flag Count",
"CWE Flag Count",
"ECE Flag Count",
"Down\/Up Ratio",
"Average Packet Size",
"Avg Fwd Segment Size",
"Avg Bwd Segment Size",
"Fwd Header Length.1",
"Fwd Avg Bytes\/Bulk",
"Fwd Avg Packets\/Bulk",
"Fwd Avg Bulk Rate",
"Bwd Avg Bytes\/Bulk",
"Bwd Avg Packets\/Bulk",
"Bwd Avg Bulk Rate",
"Subflow Fwd Packets",
"Subflow Fwd Bytes",
"Subflow Bwd Packets",
"Subflow Bwd Bytes",
"Init_Win_bytes_forward",
"Init_Win_bytes_backward",
"act_data_pkt_fwd",
"min_seg_size_forward",
"Active Mean",
"Active Std",
"Active Max",
"Active Min",
"Idle Mean",
"Idle Std",
"Idle Max",
"Idle Min"
"flow_duration",
"fwd_packets",
"bwd_packets",
"fwd_bytes",
"bwd_bytes",
"flow_bytes_per_sec",
"flow_pkts_per_sec",
"fwd_win_bytes",
"bwd_win_bytes",
"fwd_pkt_len_mean",
"bwd_pkt_len_mean",
"fwd_iat_mean",
"bwd_iat_mean",
"flow_iat_mean",
"pkt_len_mean",
"dst_port",
"protocol",
"psh_flag_cnt",
"ack_flag_cnt",
"syn_flag_cnt",
"fin_flag_cnt",
"rst_flag_cnt",
"pkt_len_std",
"fwd_pkt_len_std",
"bwd_pkt_len_std",
"fwd_seg_size_min",
"fwd_act_data_pkts"
]
},
"post_scaling_clip": {
@ -579,114 +279,11 @@
"max": 5.0
}
},
"ensemble": {
"strategy_name": "Max",
"threshold": 0.3603835832054485,
"tpr": 0.9985636048676042,
"fpr": 0.0029998719808261593,
"precision": 0.9879023673677936,
"f1": 0.9932043770233031
},
"ae_normalization": {
"min": 1.480184057280627e-5,
"max": 1.1978646574008398,
"mean": 0.0023598657051511107,
"std": 0.008826375460685048,
"median": 0.00031163828850514576,
"p90": 0.004909278753678756,
"p95": 0.010077479060604477,
"p99": 0.03354822433745485
},
"attack_labels": {
"0": "Bot",
"0": "Brute Force",
"1": "DDoS",
"2": "DoS GoldenEye",
"3": "DoS Hulk",
"4": "DoS Slowhttptest",
"5": "DoS slowloris",
"6": "FTP-Patator",
"7": "PortScan",
"8": "SSH-Patator",
"9": "Web Attack"
},
"feature_order": [
"Destination Port",
"Flow Duration",
"Total Fwd Packets",
"Total Backward Packets",
"Total Length of Fwd Packets",
"Total Length of Bwd Packets",
"Fwd Packet Length Max",
"Fwd Packet Length Min",
"Fwd Packet Length Mean",
"Fwd Packet Length Std",
"Bwd Packet Length Max",
"Bwd Packet Length Min",
"Bwd Packet Length Mean",
"Bwd Packet Length Std",
"Flow Bytes\/s",
"Flow Packets\/s",
"Flow IAT Mean",
"Flow IAT Std",
"Flow IAT Max",
"Flow IAT Min",
"Fwd IAT Total",
"Fwd IAT Mean",
"Fwd IAT Std",
"Fwd IAT Max",
"Fwd IAT Min",
"Bwd IAT Total",
"Bwd IAT Mean",
"Bwd IAT Std",
"Bwd IAT Max",
"Bwd IAT Min",
"Fwd PSH Flags",
"Bwd PSH Flags",
"Fwd URG Flags",
"Bwd URG Flags",
"Fwd Header Length",
"Bwd Header Length",
"Fwd Packets\/s",
"Bwd Packets\/s",
"Min Packet Length",
"Max Packet Length",
"Packet Length Mean",
"Packet Length Std",
"Packet Length Variance",
"FIN Flag Count",
"SYN Flag Count",
"RST Flag Count",
"PSH Flag Count",
"ACK Flag Count",
"URG Flag Count",
"CWE Flag Count",
"ECE Flag Count",
"Down\/Up Ratio",
"Average Packet Size",
"Avg Fwd Segment Size",
"Avg Bwd Segment Size",
"Fwd Header Length.1",
"Fwd Avg Bytes\/Bulk",
"Fwd Avg Packets\/Bulk",
"Fwd Avg Bulk Rate",
"Bwd Avg Bytes\/Bulk",
"Bwd Avg Packets\/Bulk",
"Bwd Avg Bulk Rate",
"Subflow Fwd Packets",
"Subflow Fwd Bytes",
"Subflow Bwd Packets",
"Subflow Bwd Bytes",
"Init_Win_bytes_forward",
"Init_Win_bytes_backward",
"act_data_pkt_fwd",
"min_seg_size_forward",
"Active Mean",
"Active Std",
"Active Max",
"Active Min",
"Idle Mean",
"Idle Std",
"Idle Max",
"Idle Min"
]
"2": "DoS",
"3": "Exploitation",
"4": "Reconnaissance"
}
}

View File

@ -1,582 +1,239 @@
{
"threshold": 0.3603835832054485,
"strategy_name": "Max",
"clip_params": {
"Destination Port": {
"lower": 22.0,
"upper": 63734.0
},
"Flow Duration": {
"ae_feature_names": [
"flow_duration",
"fwd_packets",
"bwd_packets",
"fwd_bytes",
"bwd_bytes",
"flow_bytes_per_sec",
"flow_pkts_per_sec",
"fwd_win_bytes",
"bwd_win_bytes",
"fwd_pkt_len_mean",
"bwd_pkt_len_mean",
"fwd_iat_mean",
"bwd_iat_mean",
"flow_iat_mean",
"pkt_len_mean",
"dst_port",
"protocol",
"psh_flag_cnt",
"ack_flag_cnt",
"syn_flag_cnt",
"fin_flag_cnt",
"rst_flag_cnt",
"pkt_len_std",
"fwd_pkt_len_std",
"bwd_pkt_len_std",
"fwd_seg_size_min",
"fwd_act_data_pkts"
],
"ae_clip_params": {
"flow_duration": {
"lower": 1.0,
"upper": 118756600.72000001
"upper": 118485085.19000001
},
"Total Fwd Packets": {
"lower": 1.0,
"upper": 98.0
},
"Total Backward Packets": {
"fwd_packets": {
"lower": 0.0,
"upper": 126.0
"upper": 54.0
},
"Total Length of Fwd Packets": {
"bwd_packets": {
"lower": 0.0,
"upper": 13119.520000000019
"upper": 82.0
},
"Total Length of Bwd Packets": {
"fwd_bytes": {
"lower": 0.0,
"upper": 186394.64000000013
"upper": 6960.0
},
"Fwd Packet Length Max": {
"bwd_bytes": {
"lower": 0.0,
"upper": 5840.0
"upper": 89558.6950000003
},
"Fwd Packet Length Min": {
"flow_bytes_per_sec": {
"lower": 0.0,
"upper": 98.0
"upper": 12500000.0
},
"Fwd Packet Length Mean": {
"lower": 0.0,
"upper": 1932.5
},
"Fwd Packet Length Std": {
"lower": 0.0,
"upper": 2376.49858
},
"Bwd Packet Length Max": {
"lower": 0.0,
"upper": 4380.0
},
"Bwd Packet Length Min": {
"lower": 0.0,
"upper": 308.0
},
"Bwd Packet Length Mean": {
"lower": 0.0,
"upper": 1715.628244226656
},
"Bwd Packet Length Std": {
"lower": 0.0,
"upper": 1167.9936145600002
},
"Flow Bytes\/s": {
"lower": 0.0,
"upper": 37000000.0
},
"Flow Packets\/s": {
"lower": 0.041637660508,
"flow_pkts_per_sec": {
"lower": 0.0226829350855,
"upper": 2000000.0
},
"Flow IAT Mean": {
"lower": 1.0,
"upper": 30800000.0
},
"Flow IAT Std": {
"lower": 0.0,
"upper": 44253994.319940574
},
"Flow IAT Max": {
"lower": 1.0,
"upper": 96199268.00000003
},
"Flow IAT Min": {
"lower": 0.0,
"upper": 1999969.04
},
"Fwd IAT Total": {
"lower": 0.0,
"upper": 119000000.0
},
"Fwd IAT Mean": {
"lower": 0.0,
"upper": 74300000.0
},
"Fwd IAT Std": {
"lower": 0.0,
"upper": 35894248.669783354
},
"Fwd IAT Max": {
"lower": 0.0,
"upper": 97800000.0
},
"Fwd IAT Min": {
"lower": 0.0,
"upper": 74300000.0
},
"Bwd IAT Total": {
"lower": 0.0,
"upper": 118000000.0
},
"Bwd IAT Mean": {
"lower": 0.0,
"upper": 73734410.76000024
},
"Bwd IAT Std": {
"lower": 0.0,
"upper": 30100000.0
},
"Bwd IAT Max": {
"lower": 0.0,
"upper": 93800000.0
},
"Bwd IAT Min": {
"lower": 0.0,
"upper": 73734410.76000024
},
"Fwd PSH Flags": {
"lower": 0.0,
"upper": 1.0
},
"Bwd PSH Flags": {
"lower": 0.0,
"upper": 0.0
},
"Fwd URG Flags": {
"lower": 0.0,
"upper": 0.0
},
"Bwd URG Flags": {
"lower": 0.0,
"upper": 0.0
},
"Fwd Header Length": {
"lower": 20.0,
"upper": 2376.0
},
"Bwd Header Length": {
"lower": 0.0,
"upper": 3312.0
},
"Fwd Packets\/s": {
"lower": 0.02417299188,
"upper": 2000000.0
},
"Bwd Packets\/s": {
"lower": 0.0,
"upper": 142857.1429
},
"Min Packet Length": {
"lower": 0.0,
"upper": 89.0
},
"Max Packet Length": {
"lower": 0.0,
"upper": 5840.0
},
"Packet Length Mean": {
"lower": 0.0,
"upper": 1153.4362532
},
"Packet Length Std": {
"lower": 0.0,
"upper": 1845.154148000012
},
"Packet Length Variance": {
"lower": 0.0,
"upper": 3404593.931120044
},
"FIN Flag Count": {
"lower": 0.0,
"upper": 1.0
},
"SYN Flag Count": {
"lower": 0.0,
"upper": 1.0
},
"RST Flag Count": {
"lower": 0.0,
"upper": 0.0
},
"PSH Flag Count": {
"lower": 0.0,
"upper": 1.0
},
"ACK Flag Count": {
"lower": 0.0,
"upper": 1.0
},
"URG Flag Count": {
"lower": 0.0,
"upper": 1.0
},
"CWE Flag Count": {
"lower": 0.0,
"upper": 0.0
},
"ECE Flag Count": {
"lower": 0.0,
"upper": 0.0
},
"Down\/Up Ratio": {
"lower": 0.0,
"upper": 5.0
},
"Average Packet Size": {
"lower": 0.0,
"upper": 1182.362785582229
},
"Avg Fwd Segment Size": {
"lower": 0.0,
"upper": 1932.5
},
"Avg Bwd Segment Size": {
"lower": 0.0,
"upper": 1715.628244226656
},
"Fwd Header Length.1": {
"lower": 20.0,
"upper": 2376.0
},
"Fwd Avg Bytes\/Bulk": {
"lower": 0.0,
"upper": 0.0
},
"Fwd Avg Packets\/Bulk": {
"lower": 0.0,
"upper": 0.0
},
"Fwd Avg Bulk Rate": {
"lower": 0.0,
"upper": 0.0
},
"Bwd Avg Bytes\/Bulk": {
"lower": 0.0,
"upper": 0.0
},
"Bwd Avg Packets\/Bulk": {
"lower": 0.0,
"upper": 0.0
},
"Bwd Avg Bulk Rate": {
"lower": 0.0,
"upper": 0.0
},
"Subflow Fwd Packets": {
"lower": 1.0,
"upper": 98.0
},
"Subflow Fwd Bytes": {
"lower": 0.0,
"upper": 13119.520000000019
},
"Subflow Bwd Packets": {
"lower": 0.0,
"upper": 126.0
},
"Subflow Bwd Bytes": {
"lower": 0.0,
"upper": 186394.64000000013
},
"Init_Win_bytes_forward": {
"fwd_win_bytes": {
"lower": -1.0,
"upper": 65535.0
},
"Init_Win_bytes_backward": {
"bwd_win_bytes": {
"lower": -1.0,
"upper": 65535.0
"upper": 64000.0
},
"act_data_pkt_fwd": {
"fwd_pkt_len_mean": {
"lower": 0.0,
"upper": 49.0
"upper": 744.7272727272729
},
"min_seg_size_forward": {
"lower": 20.0,
"bwd_pkt_len_mean": {
"lower": 0.0,
"upper": 1393.9601153594992
},
"fwd_iat_mean": {
"lower": 0.0,
"upper": 86366722.035
},
"bwd_iat_mean": {
"lower": 0.0,
"upper": 21700000.0
},
"flow_iat_mean": {
"lower": 1.0,
"upper": 86353400.64500001
},
"pkt_len_mean": {
"lower": 0.0,
"upper": 945.0294113974604
},
"dst_port": {
"lower": 0.0,
"upper": 64499.0
},
"protocol": {
"lower": 0.0,
"upper": 17.0
},
"psh_flag_cnt": {
"lower": 0.0,
"upper": 240.0
},
"ack_flag_cnt": {
"lower": 0.0,
"upper": 655.0
},
"syn_flag_cnt": {
"lower": 0.0,
"upper": 4.0
},
"fin_flag_cnt": {
"lower": 0.0,
"upper": 2.0
},
"rst_flag_cnt": {
"lower": 0.0,
"upper": 1.0
},
"pkt_len_std": {
"lower": 0.0,
"upper": 943.9221842600019
},
"fwd_pkt_len_std": {
"lower": 0.0,
"upper": 721.4199648708689
},
"bwd_pkt_len_std": {
"lower": 0.0,
"upper": 911.0272448420104
},
"fwd_seg_size_min": {
"lower": 0.0,
"upper": 40.0
},
"Active Mean": {
"fwd_act_data_pkts": {
"lower": 0.0,
"upper": 3410152.040000004
},
"Active Std": {
"lower": 0.0,
"upper": 1944723.8431600018
},
"Active Max": {
"lower": 0.0,
"upper": 5717825.840000004
},
"Active Min": {
"lower": 0.0,
"upper": 2918887.7600000002
},
"Idle Mean": {
"lower": 0.0,
"upper": 94100000.0
},
"Idle Std": {
"lower": 0.0,
"upper": 20712312.015200634
},
"Idle Max": {
"lower": 0.0,
"upper": 94800000.0
},
"Idle Min": {
"lower": 0.0,
"upper": 94100000.0
"upper": 58.0
}
},
"scaler_mean": [
9415.502043247605,
11217541.457604988,
5.41509711200182,
5.010050165039152,
508.74335117243135,
3302.166099044608,
217.24886707430434,
20.105687086824716,
65.40745127720037,
68.76070937185386,
394.97690859650953,
49.88046220640826,
159.85952962743096,
121.94653847258593,
872411.6556228747,
64701.11201063558,
840255.3525863544,
1516373.8724808302,
4422301.697900706,
24666.656195771673,
10897580.686685171,
1785372.7881935516,
1206278.3042178946,
4276119.216091527,
1025069.5899462275,
10158901.07420493,
1666490.9735317046,
939816.6614413982,
3689910.1546691586,
1003727.4123986625,
0.054886351088404936,
0.0,
0.0,
0.0,
140.54050707030981,
133.63606216540694,
58358.168874140014,
4983.791329731815,
19.812641079549177,
480.4855221752525,
108.29833052637488,
144.4437888623996,
99406.03379366906,
0.01811977227544623,
0.054886351088404936,
0.0,
0.25800834720207716,
0.28694244020382764,
0.11596161536441252,
0.0,
0.0,
0.6969992921551522,
122.46836156918589,
65.40745127720037,
159.85952962743843,
140.54050707030981,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
5.41509711200182,
508.74335117243135,
5.010050165039152,
3302.166099044608,
6891.551141020379,
2404.6474695096604,
2.6520584031389776,
25.79473555242033,
61328.50855670946,
36311.697575423306,
137445.81057140988,
37549.65545208145,
3689065.3106244486,
167385.3556173429,
3883594.4613142335,
3458258.8992590285
"ae_scaler_mean": [
9783470.902880548,
3.6532605393470496,
3.2219949045990472,
311.35281195367173,
1407.508354811673,
298997.28705051675,
32378.36735569276,
6158.3256759444475,
6125.023325143747,
58.1683929498305,
149.72168204849987,
2991407.86700246,
543337.1970583026,
2727511.340181723,
105.03829359866097,
13463.473246959382,
8.920585011866685,
5.457814320208867,
12.214631683524651,
0.4885976379312313,
0.246598391240437,
0.08478201266637703,
151.71809799252043,
76.89649795414182,
143.21014925828936,
21.447338538177014,
3.2607463735155715
],
"scaler_std": [
19743.81786383566,
30111390.85802201,
10.612016675700064,
12.872110268702288,
1653.7982707262067,
17387.56157755931,
645.437387791972,
22.356844266679282,
193.61621129020307,
244.137250250046,
801.628533600607,
65.65797274491328,
277.7559653593753,
269.57514761088515,
3622907.235127058,
237431.11489053545,
3293477.440659972,
5309087.889319858,
14143198.815741453,
160458.44923675407,
29985211.254190512,
8130184.699376457,
4247601.6170286415,
14271787.10745024,
7769581.505106356,
29154202.839787327,
8031691.1024753135,
3622760.030182759,
13368925.151392205,
7684129.1959599955,
0.22775829195136954,
1.0,
1.0,
1.0,
271.11142624961946,
342.17520432711024,
231133.09437362824,
14026.707464402314,
22.005832334437958,
970.4322795025132,
179.9690556845557,
280.2535015764328,
343919.1355100174,
0.13338457979891152,
0.22775829195136954,
1.0,
0.4375386154114052,
0.4523344738284939,
0.32017888613474904,
1.0,
1.0,
0.6456077980707177,
185.26118186235436,
193.61621129020307,
277.7559653594075,
271.11142624961946,
1.0,
1.0,
1.0,
1.0,
1.0,
1.0,
10.612016675700064,
1653.7982707262067,
12.872110268702288,
17387.56157755931,
14922.88749872485,
9322.158869764009,
5.784504103070997,
6.252894021045679,
322010.01591284445,
201975.98444339563,
641116.7578408231,
245957.32066446543,
13068297.83388166,
1617503.1921646637,
13724469.425904194,
12765671.772576509
"ae_scaler_std": [
27895851.239715174,
6.4263677035863696,
8.326275195918974,
739.2548064174216,
7898.711708881367,
1187804.3239195002,
190280.1548511611,
15556.664474188405,
17012.235322987635,
98.89458775902833,
242.79522892109904,
13146157.964503227,
2242915.166631539,
12878527.145353919,
151.95316919115754,
21264.915159327567,
4.943209145195101,
23.02750953458606,
57.0633472601567,
1.2662749497077417,
0.4553262784517569,
0.278557037237646,
211.1373807979287,
143.47790203327,
228.24531573376805,
9.323458649060111,
7.228913366949205
],
"ae_post_clip_min": -5.0,
"ae_post_clip_max": 5.0,
"ae_threshold": 0.17166012525558472,
"classifier_feature_names": [
"flow_duration",
"fwd_packets",
"bwd_packets",
"fwd_bytes",
"bwd_bytes",
"flow_bytes_per_sec",
"flow_pkts_per_sec",
"fwd_win_bytes",
"bwd_win_bytes",
"fwd_pkt_len_mean",
"bwd_pkt_len_mean",
"fwd_iat_mean",
"bwd_iat_mean",
"flow_iat_mean",
"pkt_len_mean",
"dst_port",
"protocol",
"psh_flag_cnt",
"ack_flag_cnt",
"syn_flag_cnt",
"fin_flag_cnt",
"rst_flag_cnt",
"pkt_len_std",
"fwd_pkt_len_std",
"bwd_pkt_len_std",
"fwd_seg_size_min",
"fwd_act_data_pkts",
"ae_anomaly_score"
],
"post_clip_min": -5.0,
"post_clip_max": 5.0,
"ae_normalization": {
"min": 1.480184057280627e-5,
"max": 1.1978646574008398,
"mean": 0.0023598657051511107,
"std": 0.008826375460685048,
"median": 0.00031163828850514576,
"p90": 0.004909278753678756,
"p95": 0.010077479060604477,
"p99": 0.03354822433745485
},
"attack_labels": {
"0": "Bot",
"0": "Brute Force",
"1": "DDoS",
"2": "DoS GoldenEye",
"3": "DoS Hulk",
"4": "DoS Slowhttptest",
"5": "DoS slowloris",
"6": "FTP-Patator",
"7": "PortScan",
"8": "SSH-Patator",
"9": "Web Attack"
},
"feature_names": [
"Destination Port",
"Flow Duration",
"Total Fwd Packets",
"Total Backward Packets",
"Total Length of Fwd Packets",
"Total Length of Bwd Packets",
"Fwd Packet Length Max",
"Fwd Packet Length Min",
"Fwd Packet Length Mean",
"Fwd Packet Length Std",
"Bwd Packet Length Max",
"Bwd Packet Length Min",
"Bwd Packet Length Mean",
"Bwd Packet Length Std",
"Flow Bytes\/s",
"Flow Packets\/s",
"Flow IAT Mean",
"Flow IAT Std",
"Flow IAT Max",
"Flow IAT Min",
"Fwd IAT Total",
"Fwd IAT Mean",
"Fwd IAT Std",
"Fwd IAT Max",
"Fwd IAT Min",
"Bwd IAT Total",
"Bwd IAT Mean",
"Bwd IAT Std",
"Bwd IAT Max",
"Bwd IAT Min",
"Fwd PSH Flags",
"Bwd PSH Flags",
"Fwd URG Flags",
"Bwd URG Flags",
"Fwd Header Length",
"Bwd Header Length",
"Fwd Packets\/s",
"Bwd Packets\/s",
"Min Packet Length",
"Max Packet Length",
"Packet Length Mean",
"Packet Length Std",
"Packet Length Variance",
"FIN Flag Count",
"SYN Flag Count",
"RST Flag Count",
"PSH Flag Count",
"ACK Flag Count",
"URG Flag Count",
"CWE Flag Count",
"ECE Flag Count",
"Down\/Up Ratio",
"Average Packet Size",
"Avg Fwd Segment Size",
"Avg Bwd Segment Size",
"Fwd Header Length.1",
"Fwd Avg Bytes\/Bulk",
"Fwd Avg Packets\/Bulk",
"Fwd Avg Bulk Rate",
"Bwd Avg Bytes\/Bulk",
"Bwd Avg Packets\/Bulk",
"Bwd Avg Bulk Rate",
"Subflow Fwd Packets",
"Subflow Fwd Bytes",
"Subflow Bwd Packets",
"Subflow Bwd Bytes",
"Init_Win_bytes_forward",
"Init_Win_bytes_backward",
"act_data_pkt_fwd",
"min_seg_size_forward",
"Active Mean",
"Active Std",
"Active Max",
"Active Min",
"Idle Mean",
"Idle Std",
"Idle Max",
"Idle Min"
]
"2": "DoS",
"3": "Exploitation",
"4": "Reconnaissance"
}
}

Binary file not shown.

Binary file not shown.

@ -1 +1 @@
Subproject commit bf27f315f2536025fa9068b9027a151a160bb70d
Subproject commit 55da05e710cb00bbc9bef85804667624b0ddf377

View File

@ -10,7 +10,7 @@ macros = { path = "../macros" }
actix = "0.13.5"
actix-cors = "0.7.1"
actix-web = "4.11.0"
actix-ws = "0.3.0"
actix-ws = "0.4.0"
aya = { workspace = true }
aya-log = { workspace = true }
network-types = { workspace = true }
@ -22,11 +22,11 @@ parking_lot = "0.12.5"
rust-embed = "8.7.2"
serde = { workspace = true }
serde_json = "1.0.143"
sysinfo = "0.37.0"
sysinfo = "0.38.2"
thiserror = "2.0.3"
tokio = { version = "1.40.0", features = ["full", "macros"] }
tokio-tungstenite = "0.28.0"
toml = "0.9.5"
toml = "1.0.3"
tracing = "0.1.41"
tracing-appender = "0.2.3"
tracing-subscriber = { version = "0.3.20", features = ["env-filter"] }
@ -36,6 +36,8 @@ maxminddb = "0.27.1"
lru = "0.16.2"
futures = "0.3.31"
tract-onnx = "0.22.0"
#csv = "1.4.0"
#anyhow = "1.0.100"
[build-dependencies]
cargo_metadata = { workspace = true }

View File

@ -255,13 +255,18 @@ fn build_egress_ebpf() {
fn build_frontend() {
let _ = dotenvy::dotenv();
let Some(frontend_dir) = env::var_os("FRONTEND_DIR") else {
panic!("FRONTEND_DIR environment variable is required but not set");
};
// let Some(frontend_dir) = env::var_os("FRONTEND_DIR") else {
// panic!("FRONTEND_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 project_root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let static_dir = project_root.join("static").join("web");
let project_name = project_root.file_name().unwrap().to_string_lossy();
let frontend_dir = project_root
.parent()
.unwrap()
.join(format!("{}-frontend", project_name));
if !frontend_dir.exists() {
panic!("Frontend directory {:?} does not exist", frontend_dir);

View File

@ -4,7 +4,6 @@ pub mod statistics;
pub mod xsk_manager;
use std::sync::Arc;
use std::time::Duration;
use aya::Ebpf;
use crossbeam::queue::SegQueue;
@ -15,7 +14,6 @@ use crate::core::ebpf::access_control::AccessControl;
use crate::core::ebpf::service::Service;
use crate::core::ebpf::statistics::Statistics;
use crate::core::ebpf::xsk_manager::XskManager;
use crate::core::infrastructure::health::SystemHealth;
use crate::core::infrastructure::app_config::AppConfig;
use crate::model::error::system::SystemError;
use crate::model::error::Error;
@ -26,8 +24,7 @@ pub struct EbpfServices {
pub access_control: Arc<AccessControl>,
pub service: Arc<Service>,
pub statistics: Arc<Statistics>,
pub health: Arc<SystemHealth>,
shutdowns: SegQueue<oneshot::Sender<()>>,
pub shutdowns: SegQueue<oneshot::Sender<()>>,
}
impl EbpfServices {
@ -36,9 +33,8 @@ impl EbpfServices {
ingress_ebpf: &mut Ebpf,
egress_ebpf: &mut Ebpf,
) -> Result<Self, Error> {
let xsk_manager = XskManager::new(app_config.clone(), ingress_ebpf)?;
let xsk_manager = XskManager::new(app_config.clone(), ingress_ebpf, egress_ebpf)?;
let access_control = AccessControl::new(ingress_ebpf)?;
let health = SystemHealth::new(app_config.clone())?;
let service = Service::new(ingress_ebpf)?;
let statistics = Statistics::new(app_config.clone(), ingress_ebpf, egress_ebpf)?;
let ebpf_services = Self {
@ -46,7 +42,6 @@ impl EbpfServices {
access_control: Arc::new(access_control),
service: Arc::new(service),
statistics: Arc::new(statistics),
health: Arc::new(health),
shutdowns: SegQueue::new(),
};
Ok(ebpf_services)
@ -55,20 +50,16 @@ impl EbpfServices {
pub async fn run(self: Arc<Self>, ml_engine: Arc<Engine>) -> Result<(), Error> {
let xsk_manager = self.xsk_manager.clone();
let statistics = self.statistics.clone();
let health = self.health.clone();
xsk_manager.run(Some(ml_engine))?;
xsk_manager.run(Some(ml_engine), &self.shutdowns)?;
let statistics_shutdown = statistics.run().await;
let health_shutdown = health.run(Duration::from_secs(3)).await;
self.shutdowns.push(statistics_shutdown);
self.shutdowns.push(health_shutdown);
Ok(())
}
pub fn terminate(self: Arc<Self>) {
self.xsk_manager.shutdown();
while let Some(shutdown) = self.shutdowns.pop() {
if shutdown.send(()).is_err() {
log!(SystemError::ShutdownSignalFailed);

View File

@ -6,9 +6,9 @@ use std::sync::Arc;
use std::thread;
use std::time::Duration;
use aya::Ebpf;
use aya::maps::{MapData, XskMap};
use crossbeam::channel::{Receiver, Sender, bounded};
use aya::Ebpf;
use crossbeam::channel::{bounded, Receiver, Sender};
use crossbeam::queue::SegQueue;
use macros::log;
use parking_lot::Mutex;
@ -17,36 +17,38 @@ use xsk_rs::config::{BindFlags, FrameSize, Interface, LibxdpFlags, QueueSize, So
use xsk_rs::{CompQueue, FillQueue, FrameDesc, RxQueue, Socket, TxQueue, Umem};
use crate::core::infrastructure::app_config::AppConfig;
use crate::model::direction::Direction;
use crate::ml::engine::{Engine, PacketProcessor};
use crate::model::config::Config;
use crate::model::error::Error;
use crate::model::direction::Direction;
use crate::model::error::ebpf::EbpfError;
use crate::model::error::system::SystemError;
use crate::model::error::Error;
use crate::model::log::ebpf::EbpfLog;
use crate::ml::engine::{Engine, PacketProcessor};
pub struct XskManager {
app_config: Arc<AppConfig>,
xsk_map: Mutex<XskMap<MapData>>,
shutdowns: SegQueue<oneshot::Sender<()>>,
egress_xsk_map: Mutex<XskMap<MapData>>,
}
impl XskManager {
pub fn new(
app_config: Arc<AppConfig>,
ebpf: &mut Ebpf,
) -> Result<Self, Error> {
let map = ebpf.take_map("XSKS_MAP").ok_or(EbpfError::MapNotFound)?;
pub fn new(app_config: Arc<AppConfig>, ingress_ebpf: &mut Ebpf, egress_ebpf: &mut Ebpf) -> Result<Self, Error> {
let map = ingress_ebpf
.take_map("INGRESS_XSKS_MAP")
.ok_or(EbpfError::MapNotFound)?;
let xsk_map = XskMap::try_from(map).map_err(EbpfError::MapOperationError)?;
let egress_map = egress_ebpf.take_map("EGRESS_XSKS_MAP").ok_or(EbpfError::MapNotFound)?;
let egress_xsk_map = XskMap::try_from(egress_map).map_err(EbpfError::MapOperationError)?;
Ok(Self {
app_config,
xsk_map: Mutex::new(xsk_map),
shutdowns: SegQueue::new(),
egress_xsk_map: Mutex::new(egress_xsk_map),
})
}
pub fn run(&self, ml_engine: Option<Arc<Engine>>) -> Result<(), Error> {
pub fn run(&self, ml_engine: Option<Arc<Engine>>, shutdowns: &SegQueue<oneshot::Sender<()>>) -> Result<(), Error> {
let config = self.app_config.config.clone();
let combined_queue_count = config.combined_queue_count;
@ -71,7 +73,7 @@ impl XskManager {
&config.egress_ifname,
&config.ingress_ifname,
Direction::Egress,
None,
packet_processor.clone(),
)?;
let mut xsk_map = self.xsk_map.lock();
@ -81,25 +83,24 @@ impl XskManager {
.map_err(EbpfError::AfXdpSetFailed)?;
drop(xsk_map);
let mut egress_xsk_map = self.egress_xsk_map.lock();
let egress_fd = egress_xsk.rx.fd().as_raw_fd();
egress_xsk_map
.set(queue_id, egress_fd, 0)
.map_err(EbpfError::AfXdpSetFailed)?;
drop(egress_xsk_map);
let ingress_shutdown = ingress_xsk.run(ingress_to_egress_tx, egress_to_ingress_rx)?;
self.shutdowns.push(ingress_shutdown);
shutdowns.push(ingress_shutdown);
let egress_shutdown = egress_xsk.run(egress_to_ingress_tx, ingress_to_egress_rx)?;
self.shutdowns.push(egress_shutdown);
shutdowns.push(egress_shutdown);
log!(EbpfLog::QueuePairStarted(queue_id));
}
Ok(())
}
pub fn shutdown(&self) {
while let Some(sender) = self.shutdowns.pop() {
if sender.send(()).is_err() {
log!(SystemError::ShutdownSignalFailed);
}
}
}
}
pub struct XskPair {
@ -109,7 +110,7 @@ pub struct XskPair {
comp_queue: CompQueue,
tx: TxQueue,
rx: RxQueue,
frame_pool: Arc<Mutex<Vec<FrameDesc>>>,
frame_pool: Arc<Mutex<Vec<FrameDesc>>>, // SegQueue
packet_processor: Option<Arc<PacketProcessor>>,
}
@ -150,10 +151,8 @@ impl XskPair {
let interface = Interface::new(rx_ifname_c);
let (tx, rx, queue) = unsafe {
Socket::new(socket_config, &umem, &interface, queue_id)
.map_err(EbpfError::SocketSetFailed)?
};
let (tx, rx, queue) =
unsafe { Socket::new(socket_config, &umem, &interface, queue_id).map_err(EbpfError::SocketSetFailed)? };
let (mut fill_queue, comp_queue) = queue.ok_or(EbpfError::UnknownError)?;
@ -278,7 +277,7 @@ impl XskPair {
let packet_data = data.contents()[..packet_len].to_vec();
if let Some(ref processor) = self.packet_processor {
processor.process(&packet_data);
processor.process(&packet_data, self.direction == Direction::Ingress);
}
if let Err(e) = forward_tx.try_send(packet_data) {
@ -366,4 +365,4 @@ impl XskPair {
Ok(nb_submitted)
}
}
}

View File

@ -7,8 +7,9 @@ use tokio::sync::RwLock;
use lru::LruCache;
use std::num::NonZeroUsize;
use tokio::task;
use crate::model::geo_stats::GeoLocation;
use crate::model::geo_stats::GeoLocation;
use crate::utils::ip_address;
pub struct GeoIpService {
reader: Arc<Reader<Vec<u8>>>,
@ -36,6 +37,17 @@ impl GeoIpService {
}
pub async fn lookup(&self, ip: IpAddr) -> Result<Option<GeoLocation>, MaxMindDbError> {
if ip_address::is_private_ip(&ip) {
return Ok(Some(GeoLocation {
country: Some("Local IP".into()),
country_code: Some("Local".into()),
city: None,
latitude: None,
longitude: None,
timezone: None,
}));
}
{
let cache = self.cache.read().await;
if let Some(cached) = cache.peek(&ip) {

View File

@ -2,14 +2,25 @@
use std::sync::Arc;
use std::time::Duration;
use serde::Serialize;
use sysinfo::{Components, Networks, System};
use tokio::sync::{broadcast, oneshot, RwLock};
use tokio::time::interval;
use tracing::{info, error, warn};
use macros::log;
use crate::core::infrastructure::app_config::AppConfig;
use crate::model::log::health::Health;
use crate::model::error::Error;
use crate::model::health::{
ConfiguredNetworkStats,
CpuCoreInfo,
CpuDetails,
LoadAverage,
MemoryUsage,
NetworkStats,
SystemHealthMetrics,
SystemHealthStatus,
SystemInfo
};
pub struct SystemHealth {
system: RwLock<System>,
@ -21,85 +32,6 @@ pub struct SystemHealth {
// management_interface: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct SystemHealthMetrics {
pub timestamp: u64,
pub boot_time: u64,
pub uptime_seconds: u64,
pub system_info: SystemInfo,
pub cpu_details: CpuDetails,
pub memory_usage: MemoryUsage,
pub network_stats: ConfiguredNetworkStats,
pub load_average: Option<LoadAverage>,
pub temperature: Option<f32>,
}
#[derive(Debug, Clone, Serialize)]
pub struct SystemInfo {
pub kernel_version: Option<String>,
pub os_name: Option<String>,
pub os_version: Option<String>,
pub architecture: String,
pub total_processes: usize,
}
#[derive(Debug, Clone, Serialize)]
pub struct CpuDetails {
pub cpu_brand: String,
pub core_count: usize,
pub cpu_usage: f32,
pub cpu_frequency: u64,
pub cores: Vec<CpuCoreInfo>,
}
#[derive(Debug, Clone, Serialize)]
pub struct CpuCoreInfo {
pub core_id: usize,
pub usage_percent: f32,
pub frequency: u64,
}
#[derive(Debug, Clone, Serialize)]
pub struct MemoryUsage {
pub total: u64,
pub used: u64,
pub available: u64,
pub usage_percent: f32,
pub swap_total: u64,
pub swap_used: u64,
}
#[derive(Debug, Clone, Serialize)]
pub struct ConfiguredNetworkStats {
pub ingress: Option<NetworkStats>,
pub egress: Option<NetworkStats>,
// pub management: Option<NetworkStats>,
}
#[derive(Debug, Clone, Serialize)]
pub struct NetworkStats {
pub interface: String,
pub bytes_received: u64,
pub bytes_transmitted: u64,
pub packets_received: u64,
pub packets_transmitted: u64,
pub errors_received: u64,
pub errors_transmitted: u64,
}
#[derive(Debug, Clone, Serialize)]
pub struct LoadAverage {
pub one_minute: f64,
pub five_minute: f64,
pub fifteen_minute: f64,
}
#[derive(Debug, Clone, Serialize)]
pub struct SystemHealthStatus {
pub overall_healthy: bool,
pub issues: Vec<String>,
pub warnings: Vec<String>,
}
impl SystemHealth {
pub fn new(config: Arc<AppConfig>) -> Result<Self, Error> {
@ -115,11 +47,6 @@ impl SystemHealth {
// management_interface: config.management_ifindex.clone(),
};
info!(
"System health monitoring initialized with interfaces: ingress={}, egress={}",
health.ingress_interface, health.egress_interface
);
Ok(health)
}
@ -147,12 +74,10 @@ impl SystemHealth {
}
async fn refresh_and_broadcast(&self) {
// Refresh all system information
self.system.write().await.refresh_all();
self.networks.write().await.refresh(true);
self.components.write().await.refresh(true);
// Collect metrics
let system = self.system.read().await;
let networks = self.networks.read().await;
let components = self.components.read().await;
@ -170,10 +95,9 @@ impl SystemHealth {
drop(networks);
drop(components);
// Broadcast metrics if there are subscribers
if self.broadcast_tx.receiver_count() > 0 {
if let Err(e) = self.broadcast_tx.send(metrics) {
error!("Failed to broadcast system health metrics: {}", e);
log!(Health::BroadcastFailed(e.to_string()));
}
}
}
@ -313,10 +237,10 @@ impl SystemHealth {
// let management = create_network_stats(management_interface);
if ingress.is_none() {
warn!("Ingress interface '{}' not found", ingress_interface);
log!(Health::InterfaceNotFound("Ingress".to_string(), ingress_interface.to_string()));
}
if egress.is_none() {
warn!("Egress interface '{}' not found", egress_interface);
log!(Health::InterfaceNotFound("Egress".to_string(), egress_interface.to_string()));
}
// if management.is_none() {
// warn!("Management interface '{}' not found", management_interface);

View File

@ -0,0 +1,80 @@
use serde::Serialize;
use tokio::sync::broadcast;
use tracing::error;
use crate::model::ml_detection::DetectionResult;
#[derive(Debug, Clone, Serialize)]
pub struct AlertMessage {
pub timestamp: u64,
pub flow_key: String,
pub src_ip: String,
pub dst_ip: String,
pub src_port: u16,
pub dst_port: u16,
pub protocol: u8,
pub is_attack: bool,
pub attack_type: Option<String>,
pub confidence: f32,
pub ae_score: f32,
}
impl AlertMessage {
pub fn from_detection_result(result: &DetectionResult) -> Self {
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
Self {
timestamp,
flow_key: result.flow_key.clone(),
src_ip: result.flow_key_raw.src_ip.clone(),
dst_ip: result.flow_key_raw.dst_ip.clone(),
src_port: result.flow_key_raw.src_port,
dst_port: result.flow_key_raw.dst_port,
protocol: result.flow_key_raw.protocol,
is_attack: result.is_attack,
attack_type: result.attack_type.clone(),
confidence: result.confidence,
ae_score: result.ae_score,
}
}
}
pub struct MLAlert {
broadcast_tx: broadcast::Sender<AlertMessage>,
}
impl MLAlert {
pub fn new() -> Self {
let (broadcast_tx, _) = broadcast::channel(100);
MLAlert {
broadcast_tx,
}
}
pub fn subscribe_to_alerts(&self) -> broadcast::Receiver<AlertMessage> {
self.broadcast_tx.subscribe()
}
pub fn broadcast_alert(&self, result: &DetectionResult) {
if self.broadcast_tx.receiver_count() > 0 {
let alert = AlertMessage::from_detection_result(result);
if let Err(e) = self.broadcast_tx.send(alert) {
error!("Failed to broadcast ML alert: {}", e);
}
}
}
pub fn has_subscribers(&self) -> bool {
self.broadcast_tx.receiver_count() > 0
}
}
impl Default for MLAlert {
fn default() -> Self {
Self::new()
}
}

View File

@ -1,3 +1,95 @@
pub mod app_config;
pub mod health;
pub mod geoip;
pub mod ml_alert;
use std::sync::Arc;
use std::time::Duration;
use crossbeam::queue::SegQueue;
use macros::log;
use tokio::sync::oneshot;
use crate::core::infrastructure::app_config::AppConfig;
use crate::core::infrastructure::health::SystemHealth;
use crate::core::infrastructure::ml_alert::MLAlert;
use crate::ml::config_loader::InferenceConfig;
use crate::ml::engine::Engine;
use crate::ml::feature_extractor::FlowFeatures;
use crate::ml::model_loader::MLModels;
use crate::model::error::misc::MiscError;
use crate::model::error::system::SystemError;
use crate::model::error::Error;
use crate::model::log::system::SystemLog;
use crate::ml::traffic_logger::TrafficLogger;
pub struct AppServices {
pub health: Arc<SystemHealth>,
pub ml_alert: Arc<MLAlert>,
pub ml_models: Arc<MLModels>,
pub ml_engine: Arc<Engine>,
shutdowns: SegQueue<oneshot::Sender<()>>,
}
impl AppServices {
pub fn new(app_config: Arc<AppConfig>, inference_config: Arc<InferenceConfig>) -> Result<Self, Error> {
let health = SystemHealth::new(app_config.clone())?;
let ml_models = Arc::new(MLModels::load_models(&app_config, &inference_config)?);
let ml_alert = Arc::new(MLAlert::new());
let traffic_logger = if app_config.traffic_logging_mode {
let csv_path = app_config.traffic_log_csv_path.clone();
let mut header = FlowFeatures::all_feature_names_owned();
header.push("Label".to_string());
let logger = TrafficLogger::new(&csv_path, header)
.map_err(|e| MiscError::TrafficLogCreateError(csv_path.clone(), e.to_string()))?;
log!(SystemLog::TrafficLoggingEnabled(csv_path));
Some(Arc::new(logger))
} else {
None
};
let ml_engine = Arc::new(Engine::new(
ml_models.clone(),
inference_config.clone(),
ml_alert.clone(),
app_config.max_concurrent_flows,
app_config.min_packets_for_inference,
app_config.inference_batch_size,
app_config.inference_interval_secs,
app_config.aggregator_window_secs,
traffic_logger,
));
Ok(Self {
health: Arc::new(health),
ml_alert,
ml_models,
ml_engine,
shutdowns: SegQueue::new(),
})
}
pub async fn run(&self) -> Result<(), Error> {
let health = self.health.clone();
let ml_engine = self.ml_engine.clone();
let health_shutdown = health.run(Duration::from_secs(3)).await;
self.shutdowns.push(health_shutdown);
let ml_shutdown = ml_engine.run().await;
self.shutdowns.push(ml_shutdown);
Ok(())
}
pub fn terminate(&self) {
while let Some(shutdown) = self.shutdowns.pop() {
if shutdown.send(()).is_err() {
log!(SystemError::ShutdownSignalFailed);
}
}
}
}

View File

@ -11,6 +11,8 @@ use macros::log;
use crate::core::ebpf::EbpfServices;
use crate::core::infrastructure::app_config::AppConfig;
use crate::core::infrastructure::AppServices;
use crate::ml::config_loader::InferenceConfig;
use crate::model::error::ebpf::EbpfError;
use crate::model::error::http::HttpError;
use crate::model::error::misc::MiscError;
@ -18,19 +20,15 @@ use crate::model::error::Error;
use crate::model::log::ml::MLLog;
use crate::model::log::system::SystemLog;
use crate::utils::logging::Logging;
use crate::web::api::{control, default, misc};
use crate::ml::model_loader::MLModels;
use crate::ml::config_loader::InferenceConfig;
use crate::ml::engine::Engine;
use crate::web::api::{control, default, health, misc, ml_alert};
pub struct System {
pub app_config: Arc<AppConfig>,
pub inference_config: Arc<InferenceConfig>,
pub ebpf_services: Arc<EbpfServices>,
pub app_services: Arc<AppServices>,
pub ingress_ebpf: Ebpf,
pub egress_ebpf: Ebpf,
pub ml_models: Arc<MLModels>,
pub inference_config: Arc<InferenceConfig>,
pub ml_engine: Arc<Engine>,
#[allow(dead_code)]
ingress_program_array: ProgramArray<MapData>,
#[allow(dead_code)]
@ -45,36 +43,21 @@ impl System {
let inference_config = Arc::new(InferenceConfig::load_file(&app_config.models_config_name)?);
let ml_models = Arc::new(MLModels::load_models(&app_config, inference_config.num_features())?);
let ebpf_services = Arc::new(EbpfServices::new(
app_config.clone(),
&mut ingress_ebpf,
&mut egress_ebpf,
)?);
let ml_engine = Arc::new(Engine::new(
ml_models.clone(),
inference_config.clone(),
app_config.max_concurrent_flows,
app_config.min_packets_for_inference,
app_config.inference_interval_secs,
));
log!(MLLog::EngineStarted {
max_flows: app_config.max_concurrent_flows,
min_packets: app_config.min_packets_for_inference,
interval_secs: app_config.inference_interval_secs
});
let app_services = Arc::new(AppServices::new(app_config.clone(), inference_config.clone())?);
let system = System {
app_config,
inference_config,
ebpf_services,
app_services,
ingress_ebpf,
egress_ebpf,
ml_models,
inference_config,
ml_engine,
ingress_program_array,
egress_program_array,
};
@ -83,15 +66,19 @@ impl System {
pub async fn run(&mut self) -> Result<(), Error> {
let ebpf_services = self.ebpf_services.clone();
let app_services = self.app_services.clone();
Logging::initialize()?;
log!(SystemLog::Initializing);
log!(MLLog::ModelsLoaded { info: self.ml_models.get_model_info("deep_autoencoder") });
log!(MLLog::ModelsLoaded { info: self.ml_models.get_model_info("random_forest") });
log!(MLLog::ModelsLoaded { info: self.ml_models.get_model_info("mlp") });
log!(MLLog::ModelsLoaded(
self.app_services.ml_models.get_model_info("deep_autoencoder")
));
log!(MLLog::ModelsLoaded(
self.app_services.ml_models.get_model_info("classifier")
));
log!(MLLog::ConfigLoaded {
features: self.inference_config.num_features(),
features: self.inference_config.num_ae_features(),
attacks: self.inference_config.num_attack_types()
});
@ -99,19 +86,19 @@ impl System {
log!(SystemLog::InitializeComplete);
self.attach_ebpf()?;
let _ml_handle = self.ml_engine.clone().start();
ebpf_services.run(self.ml_engine.clone()).await?;
ebpf_services.run(app_services.ml_engine.clone()).await?;
app_services.run().await?;
self.run_http_server().await?;
Ok(())
}
pub async fn terminate(&self) -> Result<(), Error> {
let ebpf_services = self.ebpf_services.clone();
let app_services = self.app_services.clone();
log!(SystemLog::Terminating);
ebpf_services.terminate();
app_services.terminate();
log!(SystemLog::TerminateComplete);
Ok(())
}
@ -152,12 +139,12 @@ impl System {
async fn run_http_server(&self) -> Result<(), Error> {
let app_config = self.app_config.clone();
let inference_config = self.inference_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 health = self.ebpf_services.health.clone();
let ml_models = self.ml_models.clone();
let inference_config = self.inference_config.clone();
let health = self.app_services.health.clone();
let ml_alert = self.app_services.ml_alert.clone();
let port = self.app_config.http_server_bind_port;
HttpServer::new(move || {
let cors = actix_cors::Cors::default()
@ -168,21 +155,23 @@ impl System {
App::new()
.wrap(cors)
.app_data(web::Data::from(app_config.clone()))
.app_data(web::Data::from(inference_config.clone()))
.app_data(web::Data::from(access_control.clone()))
.app_data(web::Data::from(service.clone()))
.app_data(web::Data::from(statistics.clone()))
.app_data(web::Data::from(health.clone()))
.app_data(web::Data::from(ml_models.clone()))
.app_data(web::Data::from(inference_config.clone()))
.app_data(web::Data::from(ml_alert.clone()))
.service(control::initialize())
.service(ml_alert::initialize())
.service(health::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)?;
.bind(format!("0.0.0.0:{}", port))
.map_err(HttpError::BindPortError)?
.run()
.await
.map_err(HttpError::ServerPanic)?;
Ok(())
}
@ -191,13 +180,23 @@ impl System {
env!("OUT_DIR"),
"/net-guardia-ingress"
)))
.map_err(EbpfError::EbpfNotFound)?;
.map_err(EbpfError::EbpfNotFound)?;
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", ingress::ACCESS_CONTROL)?;
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)?;
Self::load_program(
&mut ingress_ebpf,
&mut program_array,
"transmission",
ingress::TRANSMISSION,
)?;
Ok((ingress_ebpf, program_array))
}
@ -206,10 +205,16 @@ impl System {
env!("OUT_DIR"),
"/net-guardia-egress"
)))
.map_err(EbpfError::EbpfNotFound)?;
.map_err(EbpfError::EbpfNotFound)?;
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", egress::STATISTICS)?;
Self::load_program(
&mut egress_ebpf,
&mut program_array,
"transmission",
egress::TRANSMISSION,
)?;
Ok((egress_ebpf, program_array))
}
@ -241,4 +246,4 @@ impl System {
}
Ok(())
}
}
}

View File

@ -20,12 +20,12 @@ impl AttackAggregator {
}
}
pub fn should_alert(&mut self, flow_key: &FlowKey, ensemble_score: f32, threshold: f32) -> bool {
pub fn should_alert(&mut self, flow_key: &FlowKey, score: f32, threshold: f32) -> bool {
let now = Instant::now();
let detections = self.detections.entry(flow_key.clone()).or_default();
detections.retain(|(time, _)| now.duration_since(*time) < self.window_duration);
detections.push((now, ensemble_score));
detections.push((now, score));
if detections.len() >= self.min_detections {
let avg_score: f32 =

View File

@ -4,20 +4,19 @@ use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use crate::model::error::ml::MLError;
use crate::model::ml_detection::{AENormalization, ClipParams};
use crate::model::ml_detection::{AENormalization, ClipParams, PrecisionLevels};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InferenceConfig {
pub threshold: f64,
pub strategy_name: String,
pub clip_params: HashMap<String, ClipParams>,
pub scaler_mean: Vec<f64>,
pub scaler_std: Vec<f64>,
pub post_clip_min: f64,
pub post_clip_max: f64,
pub ae_normalization: AENormalization,
pub ae_feature_names: Vec<String>,
pub ae_clip_params: HashMap<String, ClipParams>,
pub ae_scaler_mean: Vec<f64>,
pub ae_scaler_std: Vec<f64>,
pub ae_post_clip_min: f64,
pub ae_post_clip_max: f64,
pub ae_threshold: f32,
pub classifier_feature_names: Vec<String>,
pub attack_labels: HashMap<String, String>,
pub feature_names: Vec<String>,
}
impl InferenceConfig {
@ -30,8 +29,12 @@ impl InferenceConfig {
Ok(config)
}
pub fn num_features(&self) -> usize {
self.feature_names.len()
pub fn num_ae_features(&self) -> usize {
self.ae_feature_names.len()
}
pub fn num_classifier_features(&self) -> usize {
self.classifier_feature_names.len()
}
pub fn num_attack_types(&self) -> usize {

View File

@ -1,140 +1,154 @@
use std::time::{Duration, Instant};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use macros::log;
use tokio::sync::oneshot;
use tokio::time::interval;
use super::aggregator::AttackAggregator;
use super::config_loader::InferenceConfig;
use super::feature_extractor::FlowFeatures;
use super::flow_tracker::FlowTracker;
use super::inference::Inference;
use super::model_loader::MLModels;
use super::aggregator::AttackAggregator;
use super::traffic_logger::TrafficLogger;
use crate::utils::packet_parser::parse_packet;
use crate::core::infrastructure::ml_alert::MLAlert;
use crate::model::log::ml::MLLog;
use crate::model::ml_detection::{EngineStats, InferenceStats};
use crate::utils::packet_parser::parse_packet;
pub struct Engine {
flow_tracker: Arc<FlowTracker>,
inference_pipeline: Arc<Inference>,
aggregator: Arc<Mutex<AttackAggregator>>,
ml_alert: Arc<MLAlert>,
min_packets: usize,
batch_size: usize,
inference_interval_secs: u64,
traffic_logger: Option<Arc<TrafficLogger>>,
}
impl Engine {
pub fn new(
models: Arc<MLModels>,
config: Arc<InferenceConfig>,
ml_alert: Arc<MLAlert>,
max_flows: usize,
min_packets: usize,
batch_size: usize,
interval_secs: u64,
window_secs: u64,
traffic_logger: Option<Arc<TrafficLogger>>,
) -> Self {
let flow_tracker = Arc::new(FlowTracker::new(max_flows));
let inference_pipeline = Arc::new(Inference::new(models, config));
let aggregator = Arc::new(Mutex::new(AttackAggregator::new(30, 10)));
let min_detections = ((window_secs / interval_secs) / 2).max(1) as usize;
let aggregator = Arc::new(Mutex::new(AttackAggregator::new(window_secs, min_detections)));
Self {
flow_tracker,
inference_pipeline,
aggregator,
ml_alert,
min_packets,
batch_size,
inference_interval_secs: interval_secs,
traffic_logger,
}
}
pub fn start(self: Arc<Self>) -> tokio::task::JoinHandle<()> {
pub async fn run(self: Arc<Self>) -> oneshot::Sender<()> {
let (shutdown_tx, shutdown_rx) = oneshot::channel();
tokio::spawn(async move {
self.run_inference_loop().await;
})
self.run_inference_loop(shutdown_rx).await;
});
shutdown_tx
}
pub fn get_flow_tracker(&self) -> Arc<FlowTracker> {
self.flow_tracker.clone()
}
async fn run_inference_loop(&self) {
async fn run_inference_loop(&self, mut shutdown_rx: oneshot::Receiver<()>) {
let mut ticker = interval(Duration::from_secs(self.inference_interval_secs));
loop {
ticker.tick().await;
let start = Instant::now();
tokio::select! {
_ = &mut shutdown_rx => break,
_ = ticker.tick() => {}
}
let total_flows = self.flow_tracker.flow_count();
let all_flows = self.flow_tracker.get_flows_snapshot();
let packet_counts: Vec<usize> = all_flows.iter().map(|f| f.packet_count()).collect();
let flows = self
.flow_tracker
.get_flows_for_inference(self.min_packets);
let flows = self.flow_tracker.get_flows_for_inference(self.min_packets);
log!(
MLLog::FlowStats(
total_flows,
flows.len(),
self.min_packets,
format!("{:?}", packet_counts)
)
);
log!(MLLog::FlowStats(
total_flows,
flows.len(),
self.min_packets,
format!("{:?}", packet_counts)
));
if flows.is_empty() {
log!(
MLLog::InferenceSkipped(
format!(
"No flows with sufficient packets (total flows: {}, min packets: {})",
total_flows,
self.min_packets
)
)
);
log!(MLLog::InferenceSkipped(format!(
"No flows with sufficient packets (total flows: {}, min packets: {})",
total_flows, self.min_packets
)));
continue;
}
let batch_size = flows.len().min(200);
let batch = &flows[..batch_size];
log!(MLLog::RunningInference(batch_size));
let results = self.inference_pipeline.infer_batch(batch);
let elapsed_us = start.elapsed().as_micros() as u64;
let stats = InferenceStats::from_results(&results, elapsed_us);
if results.len() != batch_size {
log!(MLLog::InferenceResults(batch_size, results.len()));
if let Some(ref logger) = self.traffic_logger {
let feature_names = FlowFeatures::all_feature_names_owned();
for flow in &flows {
let features = FlowFeatures::extract(flow, &feature_names);
logger.log_row(features.to_csv_record());
}
self.flow_tracker.cleanup_old_flows(60_000_000);
continue;
}
log!(
MLLog::InferenceCompleted(
stats.total_flows,
stats.malicious_flows,
stats.benign_flows,(elapsed_us as f64 / 1000.0) as u32,
stats.flows_per_second
)
);
let batch = &flows[..flows.len().min(self.batch_size)];
log!(MLLog::RunningInference(batch.len()));
let start = Instant::now();
let results = self.inference_pipeline.infer_batch(batch);
let elapsed_us = start.elapsed().as_micros() as u64;
let stats = InferenceStats::from_results(&results, elapsed_us);
if results.len() != batch.len() {
log!(MLLog::InferenceResults(batch.len(), results.len()));
}
log!(MLLog::InferenceCompleted(
stats.total_flows,
stats.malicious_flows,
stats.benign_flows,
(elapsed_us as f64 / 1000.0) as u32,
stats.flows_per_second
));
if let Ok(mut aggregator) = self.aggregator.lock() {
for result in &results {
if result.is_attack {
let should_alert = aggregator.should_alert(
&result.flow_key_raw,
result.ensemble_score,
self.inference_pipeline.config.threshold as f32,
);
let should_alert =
aggregator.should_alert(&result.flow_key_raw, result.ae_score, result.threshold);
if should_alert {
log!(
MLLog::ThreatDetected(
result.flow_key.clone(),
result.attack_type.clone().unwrap_or_else(|| "UNKNOWN".to_string()),
result.confidence,
result.ae_score,
result.rf_score,
result.ensemble_score,
)
);
log!(MLLog::ThreatDetected(
format!("{:?}", result.direction),
result.flow_key.clone(),
result.attack_type.clone().unwrap_or_else(|| "UNKNOWN".to_string()),
result.confidence,
result.ae_score,
));
self.ml_alert.broadcast_alert(result);
}
}
}
@ -146,14 +160,13 @@ impl Engine {
}
}
pub fn process_packet(&self, packet_data: &[u8]) {
pub fn process_packet(&self, packet_data: &[u8], is_ingress: bool) {
match parse_packet(packet_data) {
Some(packet_info) => {
self.flow_tracker.process_packet(packet_info);
}
None => {
log!(MLLog::ParsePacketFailed(packet_data.len()))
Some((packet_info, payload_start)) => {
let payload = packet_data.get(payload_start..).unwrap_or(&[]);
self.flow_tracker.process_packet(packet_info, is_ingress, payload);
}
None => log!(MLLog::ParsePacketFailed(packet_data.len())),
}
}
@ -173,13 +186,13 @@ impl PacketProcessor {
Self { ml_engine }
}
pub fn process(&self, packet_data: &[u8]) {
self.ml_engine.process_packet(packet_data);
pub fn process(&self, packet_data: &[u8], is_ingress: bool) {
self.ml_engine.process_packet(packet_data, is_ingress);
}
pub fn process_batch(&self, packets: &[Vec<u8>]) {
pub fn process_batch(&self, packets: &[Vec<u8>], is_ingress: bool) {
for packet in packets {
self.process(packet);
self.process(packet, is_ingress);
}
}
}
}

View File

@ -1,6 +1,7 @@
use std::collections::HashMap;
use crate::model::ml_detection::{PacketData, ClipParams};
use super::flow_tracker::FlowData;
use crate::model::ml_detection::{ClipParams, PacketData};
#[derive(Debug, Clone)]
pub struct FlowFeatures {
@ -46,17 +47,17 @@ impl FlowFeatures {
// 16-19
let flow_iats = compute_flow_iats(&flow.fwd_packets, &flow.bwd_packets);
let (flow_iat_mean, flow_iat_std, flow_iat_max, flow_iat_min) = compute_stats(&flow_iats);
let (flow_iat_max, flow_iat_min, flow_iat_mean, flow_iat_std) = compute_stats(&flow_iats);
// 20-24
let fwd_iats = compute_iats(&flow.fwd_packets);
let fwd_iat_total: f64 = fwd_iats.iter().sum();
let (fwd_iat_mean, fwd_iat_std, fwd_iat_max, fwd_iat_min) = compute_stats(&fwd_iats);
let (fwd_iat_max, fwd_iat_min, fwd_iat_mean, fwd_iat_std) = compute_stats(&fwd_iats);
// 25-29
let bwd_iats = compute_iats(&flow.bwd_packets);
let bwd_iat_total: f64 = bwd_iats.iter().sum();
let (bwd_iat_mean, bwd_iat_std, bwd_iat_max, bwd_iat_min) = compute_stats(&bwd_iats);
let (bwd_iat_max, bwd_iat_min, bwd_iat_mean, bwd_iat_std) = compute_stats(&bwd_iats);
// 30-37
let fwd_psh = flow.fwd_packets.iter().filter(|p| p.flags.psh).count() as f64;
@ -79,58 +80,50 @@ impl FlowFeatures {
let bwd_bulk = &flow.bwd_bulk_state;
// 68-69
let fwd_seg_sizes: Vec<f64> = flow.fwd_packets
let fwd_seg_sizes: Vec<f64> = flow
.fwd_packets
.iter()
.filter(|p| p.payload_length > 0)
.map(|p| p.header_length as f64)
.collect();
// 70-73
let (active_mean, active_std, active_max, active_min) = compute_stats(
&flow
.active_periods
.iter()
.map(|&x| x as f64)
.collect::<Vec<_>>(),
);
let (active_max, active_min, active_mean, active_std) =
compute_stats(&flow.active_periods.iter().map(|&x| x as f64).collect::<Vec<_>>());
// 74-77
let (idle_mean, idle_std, idle_max, idle_min) = compute_stats(
&flow
.idle_periods
.iter()
.map(|&x| x as f64)
.collect::<Vec<_>>(),
);
let (idle_max, idle_min, idle_mean, idle_std) =
compute_stats(&flow.idle_periods.iter().map(|&x| x as f64).collect::<Vec<_>>());
match feature_name {
"Destination Port" => flow.flow_key.dst_port as f64,
"Flow Duration" => duration_us,
"Total Fwd Packets" => fwd_count,
"Total Backward Packets" => bwd_count,
"Total Length of Fwd Packets" => flow.fwd_total_bytes as f64,
"Total Length of Bwd Packets" => flow.bwd_total_bytes as f64,
"Destination Port" | "Dst Port" | "dst_port" => flow.flow_key.dst_port as f64,
"Protocol" | "protocol" => flow.flow_key.protocol as f64,
"Flow Duration" | "flow_duration" => duration_us,
"Total Fwd Packets" | "Tot Fwd Pkts" | "fwd_packets" => fwd_count,
"Total Backward Packets" | "Tot Bwd Pkts" | "bwd_packets" => bwd_count,
"Total Length of Fwd Packets" | "TotLen Fwd Pkts" | "fwd_bytes" => flow.fwd_total_bytes as f64,
"Total Length of Bwd Packets" | "TotLen Bwd Pkts" | "bwd_bytes" => flow.bwd_total_bytes as f64,
"Fwd Packet Length Max" => fwd_max,
"Fwd Packet Length Min" => fwd_min,
"Fwd Packet Length Mean" => fwd_mean,
"Fwd Packet Length Std" => fwd_std,
"Fwd Packet Length Mean" | "Fwd Pkt Len Mean" | "fwd_pkt_len_mean" => fwd_mean,
"Fwd Packet Length Std" | "Fwd Pkt Len Std" | "fwd_pkt_len_std" => fwd_std,
"Bwd Packet Length Max" => bwd_max,
"Bwd Packet Length Min" => bwd_min,
"Bwd Packet Length Mean" => bwd_mean,
"Bwd Packet Length Std" => bwd_std,
"Flow Bytes/s" => safe_div(total_bytes, duration_s),
"Flow Packets/s" => safe_div(total_count, duration_s),
"Flow IAT Mean" => flow_iat_mean,
"Bwd Packet Length Mean" | "Bwd Pkt Len Mean" | "bwd_pkt_len_mean" => bwd_mean,
"Bwd Packet Length Std" | "Bwd Pkt Len Std" | "bwd_pkt_len_std" => bwd_std,
"Flow Bytes/s" | "Flow Byts/s" | "flow_bytes_per_sec" => safe_div(total_bytes, duration_s),
"Flow Packets/s" | "Flow Pkts/s" | "flow_pkts_per_sec" => safe_div(total_count, duration_s),
"Flow IAT Mean" | "flow_iat_mean" => flow_iat_mean,
"Flow IAT Std" => flow_iat_std,
"Flow IAT Max" => flow_iat_max,
"Flow IAT Min" => flow_iat_min,
"Fwd IAT Total" => fwd_iat_total,
"Fwd IAT Mean" => fwd_iat_mean,
"Fwd IAT Mean" | "fwd_iat_mean" => fwd_iat_mean,
"Fwd IAT Std" => fwd_iat_std,
"Fwd IAT Max" => fwd_iat_max,
"Fwd IAT Min" => fwd_iat_min,
"Bwd IAT Total" => bwd_iat_total,
"Bwd IAT Mean" => bwd_iat_mean,
"Bwd IAT Mean" | "bwd_iat_mean" => bwd_iat_mean,
"Bwd IAT Std" => bwd_iat_std,
"Bwd IAT Max" => bwd_iat_max,
"Bwd IAT Min" => bwd_iat_min,
@ -144,14 +137,14 @@ impl FlowFeatures {
"Bwd Packets/s" => safe_div(bwd_count, duration_s),
"Min Packet Length" => min_len,
"Max Packet Length" => max_len,
"Packet Length Mean" => mean_len,
"Packet Length Std" => std_len,
"Packet Length Mean" | "Pkt Len Mean" | "pkt_len_mean" => mean_len,
"Packet Length Std" | "Pkt Len Std" | "pkt_len_std" => std_len,
"Packet Length Variance" => std_len * std_len,
"FIN Flag Count" => flow.fin_count as f64,
"SYN Flag Count" => flow.syn_count as f64,
"RST Flag Count" => flow.rst_count as f64,
"PSH Flag Count" => flow.psh_count as f64,
"ACK Flag Count" => flow.ack_count as f64,
"FIN Flag Count" | "FIN Flag Cnt" | "fin_flag_cnt" => flow.fin_count as f64,
"SYN Flag Count" | "SYN Flag Cnt" | "syn_flag_cnt" => flow.syn_count as f64,
"RST Flag Count" | "RST Flag Cnt" | "rst_flag_cnt" => flow.rst_count as f64,
"PSH Flag Count" | "PSH Flag Cnt" | "psh_flag_cnt" => flow.psh_count as f64,
"ACK Flag Count" | "ACK Flag Cnt" | "ack_flag_cnt" => flow.ack_count as f64,
"URG Flag Count" => flow.urg_count as f64,
"CWE Flag Count" => flow.cwe_count as f64,
"ECE Flag Count" => flow.ece_count as f64,
@ -162,18 +155,25 @@ impl FlowFeatures {
"Fwd Header Length.1" => flow.fwd_header_bytes as f64,
"Fwd Avg Bytes/Bulk" => safe_div(fwd_bulk.total_bytes as f64, fwd_bulk.bulk_count as f64),
"Fwd Avg Packets/Bulk" => safe_div(fwd_bulk.total_packets as f64, fwd_bulk.bulk_count as f64),
"Fwd Avg Bulk Rate" => safe_div(fwd_bulk.total_bytes as f64, duration_s),
"Fwd Avg Bulk Rate" => safe_div(
fwd_bulk.total_bytes as f64,
fwd_bulk.total_duration_us as f64 / 1_000_000.0,
),
"Bwd Avg Bytes/Bulk" => safe_div(bwd_bulk.total_bytes as f64, bwd_bulk.bulk_count as f64),
"Bwd Avg Packets/Bulk" => safe_div(bwd_bulk.total_packets as f64, bwd_bulk.bulk_count as f64),
"Bwd Avg Bulk Rate" => safe_div(bwd_bulk.total_bytes as f64, duration_s),
"Bwd Avg Bulk Rate" => safe_div(
bwd_bulk.total_bytes as f64,
bwd_bulk.total_duration_us as f64 / 1_000_000.0,
),
"Subflow Fwd Packets" => fwd_count,
"Subflow Fwd Bytes" => flow.fwd_total_bytes as f64,
"Subflow Bwd Packets" => bwd_count,
"Subflow Bwd Bytes" => flow.bwd_total_bytes as f64,
"Init_Win_bytes_forward" => flow.init_win_bytes_fwd as f64,
"Init_Win_bytes_backward" => flow.init_win_bytes_bwd as f64,
"act_data_pkt_fwd" => fwd_seg_sizes.len() as f64,
"min_seg_size_forward" => fwd_seg_sizes.iter()
"Init_Win_bytes_forward" | "Init Fwd Win Byts" | "fwd_win_bytes" => flow.init_win_bytes_fwd as f64,
"Init_Win_bytes_backward" | "Init Bwd Win Byts" | "bwd_win_bytes" => flow.init_win_bytes_bwd as f64,
"act_data_pkt_fwd" | "Fwd Act Data Pkts" | "fwd_act_data_pkts" => fwd_seg_sizes.len() as f64,
"min_seg_size_forward" | "Fwd Seg Size Min" | "fwd_seg_size_min" => fwd_seg_sizes
.iter()
.min_by(|a, b| a.total_cmp(b))
.copied()
.unwrap_or(0.0),
@ -186,9 +186,7 @@ impl FlowFeatures {
"Idle Max" => idle_max,
"Idle Min" => idle_min,
_ => {
0.0
}
_ => 0.0,
}
}
@ -208,7 +206,6 @@ impl FlowFeatures {
}
}
/// Winsorization: Clip each feature according to its specific bounds from clip_params
pub fn winsorize(&mut self, clip_params: &HashMap<String, ClipParams>, feature_names: &[String]) {
for (i, feature_name) in feature_names.iter().enumerate() {
if i < self.feature_num {
@ -218,6 +215,104 @@ impl FlowFeatures {
}
}
}
pub fn get_features_content(&self) -> &Vec<f64> {
&self.features
}
pub fn all_feature_names() -> Vec<&'static str> {
vec![
"Destination Port",
"Protocol",
"Flow Duration",
"Total Fwd Packets",
"Total Backward Packets",
"Total Length of Fwd Packets",
"Total Length of Bwd Packets",
"Fwd Packet Length Max",
"Fwd Packet Length Min",
"Fwd Packet Length Mean",
"Fwd Packet Length Std",
"Bwd Packet Length Max",
"Bwd Packet Length Min",
"Bwd Packet Length Mean",
"Bwd Packet Length Std",
"Flow Bytes/s",
"Flow Packets/s",
"Flow IAT Mean",
"Flow IAT Std",
"Flow IAT Max",
"Flow IAT Min",
"Fwd IAT Total",
"Fwd IAT Mean",
"Fwd IAT Std",
"Fwd IAT Max",
"Fwd IAT Min",
"Bwd IAT Total",
"Bwd IAT Mean",
"Bwd IAT Std",
"Bwd IAT Max",
"Bwd IAT Min",
"Fwd PSH Flags",
"Bwd PSH Flags",
"Fwd URG Flags",
"Bwd URG Flags",
"Fwd Header Length",
"Bwd Header Length",
"Fwd Packets/s",
"Bwd Packets/s",
"Min Packet Length",
"Max Packet Length",
"Packet Length Mean",
"Packet Length Std",
"Packet Length Variance",
"FIN Flag Count",
"SYN Flag Count",
"RST Flag Count",
"PSH Flag Count",
"ACK Flag Count",
"URG Flag Count",
"CWE Flag Count",
"ECE Flag Count",
"Down/Up Ratio",
"Average Packet Size",
"Avg Fwd Segment Size",
"Avg Bwd Segment Size",
"Fwd Header Length.1",
"Fwd Avg Bytes/Bulk",
"Fwd Avg Packets/Bulk",
"Fwd Avg Bulk Rate",
"Bwd Avg Bytes/Bulk",
"Bwd Avg Packets/Bulk",
"Bwd Avg Bulk Rate",
"Subflow Fwd Packets",
"Subflow Fwd Bytes",
"Subflow Bwd Packets",
"Subflow Bwd Bytes",
"Init_Win_bytes_forward",
"Init_Win_bytes_backward",
"act_data_pkt_fwd",
"min_seg_size_forward",
"Active Mean",
"Active Std",
"Active Max",
"Active Min",
"Idle Mean",
"Idle Std",
"Idle Max",
"Idle Min",
]
}
pub fn all_feature_names_owned() -> Vec<String> {
Self::all_feature_names().iter().map(|s| s.to_string()).collect()
}
pub fn to_csv_record(&self) -> Vec<String> {
let mut record: Vec<String> = self.features.iter().map(|f| f.to_string()).collect();
record.push("BENIGN".to_string());
record
}
}
fn compute_stats(values: &[f64]) -> (f64, f64, f64, f64) {
@ -261,4 +356,4 @@ fn compute_flow_iats(fwd_packets: &[PacketData], bwd_packets: &[PacketData]) ->
.windows(2)
.map(|w| (w[1].timestamp_us - w[0].timestamp_us) as f64)
.collect()
}
}

View File

@ -2,12 +2,15 @@ use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::time;
use crate::model::ml_detection::{BulkState, FlowKey, PacketData};
use common::model::event::Event;
use crate::model::direction::Direction;
use crate::model::ml_detection::{BulkState, FlowKey, PacketData};
#[derive(Debug, Clone)]
pub struct FlowData {
pub flow_key: FlowKey,
pub direction: Direction,
pub start_time_us: u64,
pub last_time_us: u64,
pub fwd_packets: Vec<PacketData>,
@ -34,9 +37,10 @@ pub struct FlowData {
}
impl FlowData {
pub fn new(flow_key: FlowKey, first_packet: &Event) -> Self {
pub fn new(flow_key: FlowKey, first_packet: &Event, direction: Direction) -> Self {
Self {
flow_key,
direction,
start_time_us: first_packet.timestamp_us(),
last_time_us: first_packet.timestamp_us(),
fwd_packets: Vec::new(),
@ -149,9 +153,12 @@ impl FlowData {
bulk_state.in_bulk = true;
bulk_state.last_bulk_bytes = packet.length as u64;
bulk_state.last_bulk_packets = 1;
bulk_state.last_bulk_start_us = packet.timestamp_us;
bulk_state.last_bulk_packet_us = packet.timestamp_us;
} else {
bulk_state.last_bulk_bytes += packet.length as u64;
bulk_state.last_bulk_packets += 1;
bulk_state.last_bulk_packet_us = packet.timestamp_us;
}
} else {
if bulk_state.in_bulk
@ -161,10 +168,15 @@ impl FlowData {
bulk_state.bulk_count += 1;
bulk_state.total_bytes += bulk_state.last_bulk_bytes;
bulk_state.total_packets += bulk_state.last_bulk_packets;
bulk_state.total_duration_us += bulk_state
.last_bulk_packet_us
.saturating_sub(bulk_state.last_bulk_start_us);
}
bulk_state.in_bulk = false;
bulk_state.last_bulk_bytes = 0;
bulk_state.last_bulk_packets = 0;
bulk_state.last_bulk_start_us = 0;
bulk_state.last_bulk_packet_us = 0;
}
}
@ -190,27 +202,61 @@ impl FlowTracker {
}
}
pub fn process_packet(&self, mut packet: Event) {
let flow_key = FlowKey::from_packet(&packet);
let reverse_key = flow_key.reverse();
pub fn process_packet(&self, mut packet: Event, is_ingress: bool, payload: &[u8]) {
let direction = if is_ingress {
Direction::Ingress
} else {
Direction::Egress
};
let packet_key = FlowKey::from_packet(&packet);
let proto = packet_key.protocol;
let src_port = packet_key.src_port;
let dst_port = packet_key.dst_port;
let reversed_key = packet_key.clone().reverse();
let Ok(mut flows) = self.flows.lock() else {
return;
};
let (actual_key, is_forward) = if flows.contains_key(&flow_key) {
(flow_key, true)
} else if flows.contains_key(&reverse_key) {
(reverse_key, false)
// Try-both: canonical key is whichever orientation already exists in the flow table.
// For new flows, identify the initiator using (in priority order):
// 1. TCP SYN / SYN+ACK flags
// 2. DPI: TLS ClientHello/ServerHello, HTTP request/response, DNS QR bit
// 3. Best effort: use packet as-is
let (actual_key, is_forward) = if flows.contains_key(&packet_key) {
(packet_key, true)
} else if flows.contains_key(&reversed_key) {
(reversed_key, false)
} else {
(flow_key, true)
let flags = packet.tcp_flags();
if flags.syn && flags.ack {
// Normal: Server (egress side) sends SYN+ACK, packet arrives on ingress → reverse
// Bot attack: Client (egress side) sends SYN+ACK, packet arrives on egress → keep as-is
if is_ingress {
(reversed_key, false)
} else {
(packet_key, true)
}
} else if flags.syn {
(packet_key, true)
} else {
match detect_initiator(payload, proto, src_port, dst_port) {
Some(true) => (packet_key, true),
Some(false) => (reversed_key, false),
None => (packet_key, true),
}
}
};
packet.set_is_forward(is_forward);
let flow = flows.entry(actual_key.clone()).or_insert_with(|| {
FlowData::new(actual_key, &packet)
});
// `direction` should reflect the initiator's interface.
// If this packet is backward (is_forward = false), the initiator is on the opposite side.
let initiator_direction = if is_forward { direction } else { direction.flip() };
let flow = flows
.entry(actual_key.clone())
.or_insert_with(|| FlowData::new(actual_key, &packet, initiator_direction));
flow.add_packet(&packet);
@ -248,9 +294,7 @@ impl FlowTracker {
let Ok(mut flows) = self.flows.lock() else {
return;
};
flows.retain(|_, flow| {
now.saturating_sub(flow.last_time_us) < max_age_us
});
flows.retain(|_, flow| now.saturating_sub(flow.last_time_us) < max_age_us);
}
pub fn flow_count(&self) -> usize {
@ -259,4 +303,50 @@ impl FlowTracker {
};
flows.len()
}
}
}
/// Inspect payload bytes to determine which side is the flow initiator.
/// Returns Some(true) if this packet is from the initiator, Some(false) if from the responder,
/// or None if the payload gives no useful signal.
fn detect_initiator(payload: &[u8], protocol: u8, src_port: u16, dst_port: u16) -> Option<bool> {
if payload.is_empty() {
return None;
}
// TLS: record type 0x16 (Handshake), byte 5 = handshake type
// 0x01 = ClientHello → this side is the initiator
// 0x02 = ServerHello → this side is the responder
if payload.len() >= 6 && payload[0] == 0x16 {
return match payload[5] {
0x01 => Some(true),
0x02 => Some(false),
_ => None,
};
}
// HTTP: request line starts with a method verb (initiator),
// response starts with "HTTP/" (responder)
if payload.len() >= 5 {
if payload.starts_with(b"GET ")
|| payload.starts_with(b"POST ")
|| payload.starts_with(b"PUT ")
|| payload.starts_with(b"HEAD ")
|| payload.starts_with(b"DELETE ")
|| payload.starts_with(b"OPTIONS ")
|| payload.starts_with(b"PATCH ")
{
return Some(true);
}
if payload.starts_with(b"HTTP/") {
return Some(false);
}
}
// DNS over UDP (port 53): flags byte 2, MSB = QR bit
// 0 = query (initiator), 1 = response (responder)
if protocol == 17 && (src_port == 53 || dst_port == 53) && payload.len() >= 3 {
return Some((payload[2] >> 7) == 0);
}
None
}

View File

@ -1,14 +1,14 @@
use std::sync::Arc;
use tract_onnx::prelude::*;
use macros::log;
use std::sync::{Arc, Mutex};
use macros::log;
use tract_onnx::prelude::*;
use super::flow_tracker::FlowData;
use super::config_loader::InferenceConfig;
use super::feature_extractor::FlowFeatures;
use super::flow_tracker::FlowData;
use super::model_loader::MLModels;
use crate::model::ml_detection::DetectionResult;
use crate::model::log::ml::MLLog;
use crate::model::ml_detection::DetectionResult;
pub struct Inference {
pub models: Arc<MLModels>,
@ -21,31 +21,16 @@ impl Inference {
}
pub fn infer_batch(&self, flows: &[FlowData]) -> Vec<DetectionResult> {
flows
.iter()
.filter_map(|flow| self.infer_single(flow))
.collect()
flows.iter().filter_map(|flow| self.infer_single(flow)).collect()
}
pub fn infer_single(&self, flow: &FlowData) -> Option<DetectionResult> {
// extract
let mut features = FlowFeatures::extract(
flow,
&self.config.feature_names
);
let ae_features = self.preprocess_ae_features(flow);
// pre-process
features.winsorize(&self.config.clip_params, &self.config.feature_names);
features.normalize(&self.config.scaler_mean, &self.config.scaler_std);
features.clip(self.config.post_clip_min, self.config.post_clip_max);
// input tensor
let input = tract_ndarray::Array2::from_shape_fn((1, self.config.num_features()), |(_, j)| {
features.features[j] as f32
});
// Deep Autoencoder
let ae_score = match self.run_autoencoder(&input) {
// 2. Deep Autoencoder
let ae_input = Self::vec_to_array2(&ae_features);
let ae_score = match self.run_autoencoder(&ae_input) {
Ok(score) => score,
Err(e) => {
log!(MLLog::InferenceFailed("DeepAutoEncoder".to_string(), e.to_string()));
@ -53,129 +38,83 @@ impl Inference {
}
};
// Random Forest
let rf_score = match self.run_random_forest(&input) {
Ok(score) => score,
let cls_input = self.build_classifier_input(&ae_features, ae_score);
let (attack_type, confidence) = match self.run_classifier(&cls_input) {
Ok(result) => result,
Err(e) => {
log!(MLLog::InferenceFailed("RandomForest".to_string(), e.to_string()));
log!(MLLog::InferenceFailed("LightGBM".to_string(), e.to_string()));
return None;
}
};
// Ensemble score
let ensemble_score = self.compute_ensemble_score(ae_score, rf_score);
let is_anomaly = ensemble_score > self.config.threshold as f32;
let is_attack = ae_score >= self.config.ae_threshold;
let flow_key = format!(
"{}:{} -> {}:{} (proto {})",
"{}:{} -> {}:{} (proto {}) [{}]",
flow.flow_key.src_ip,
flow.flow_key.src_port,
flow.flow_key.dst_ip,
flow.flow_key.dst_port,
flow.flow_key.protocol
flow.flow_key.protocol,
flow.direction
);
let flow_key_raw = flow.flow_key.clone();
Some(DetectionResult {
flow_key,
flow_key_raw: flow.flow_key.clone(),
direction: flow.direction,
is_attack,
attack_type: if is_attack { Some(attack_type) } else { None },
confidence,
ae_score,
threshold: self.config.ae_threshold,
})
}
if is_anomaly {
// MLP
let (attack_type, confidence) = match self.run_mlp(&input) {
Ok((attack_type, conf)) => (attack_type, conf),
Err(e) => {
log!(MLLog::InferenceFailed("MLP".to_string(), e.to_string()));
("UNKNOWN".to_string(), ensemble_score)
}
};
fn preprocess_ae_features(&self, flow: &FlowData) -> Vec<f32> {
let mut features = FlowFeatures::extract(flow, &self.config.ae_feature_names);
features.winsorize(&self.config.ae_clip_params, &self.config.ae_feature_names);
features.normalize(&self.config.ae_scaler_mean, &self.config.ae_scaler_std);
features.clip(self.config.ae_post_clip_min, self.config.ae_post_clip_max);
features.features.iter().map(|&x| x as f32).collect()
}
if confidence < 0.75 {
return Some(DetectionResult {
flow_key,
flow_key_raw,
is_attack: false,
attack_type: None,
confidence: 1.0 - ensemble_score,
ae_score,
rf_score,
ensemble_score,
});
fn vec_to_array2(v: &[f32]) -> tract_ndarray::Array2<f32> {
tract_ndarray::Array2::from_shape_fn((1, v.len()), |(_, j)| v[j])
}
/// Classifier 輸入 = 已預處理的 ae_features ++ [ae_anomaly_score]
fn build_classifier_input(&self, ae_features: &[f32], ae_score: f32) -> tract_ndarray::Array2<f32> {
let n = ae_features.len() + 1;
tract_ndarray::Array2::from_shape_fn((1, n), |(_, j)| {
if j < ae_features.len() {
ae_features[j]
} else {
ae_score
}
Some(DetectionResult {
flow_key,
flow_key_raw,
is_attack: true,
attack_type: Some(attack_type),
confidence,
ae_score,
rf_score,
ensemble_score,
})
} else {
Some(DetectionResult {
flow_key,
flow_key_raw,
is_attack: false,
attack_type: None,
confidence: 1.0 - ensemble_score,
ae_score,
rf_score,
ensemble_score,
})
}
})
}
fn run_autoencoder(&self, input: &tract_ndarray::Array2<f32>) -> TractResult<f32> {
let input_tensor = input.clone().into_tensor();
let result = self
.models
.deep_autoencoder
.run(tvec![input_tensor.into()])?;
.run(tvec![input.clone().into_tensor().into()])?;
let output = result[0]
.to_array_view::<f32>()?
.into_dimensionality::<tract_ndarray::Ix2>()?;
let diff = input - &output;
let squared_errors = &diff * &diff;
let mse = squared_errors.sum() / self.config.num_features() as f32;
let mse = (&diff * &diff).sum() / self.config.ae_feature_names.len() as f32;
let ae_norm = &self.config.ae_normalization;
let ae_score = (mse - ae_norm.min as f32) / (ae_norm.max as f32 - ae_norm.min as f32 + 1e-10);
let ae_score = ae_score.clamp(0.0, 1.0);
Ok(ae_score)
Ok(mse)
}
fn run_random_forest(&self, input: &tract_ndarray::Array2<f32>) -> TractResult<f32> {
fn run_classifier(&self, input: &tract_ndarray::Array2<f32>) -> TractResult<(String, f32)> {
let input_tensor = input.clone().into_tensor();
let result = self.models.random_forest.run(tvec![input_tensor.into()])?;
// output[0] = output_label (i64)
// output[1] = output_probability (sequence of maps)
if result.len() > 1 {
if let Ok(proba) = result[1].to_array_view::<f32>() {
if proba.len() > 1 {
return Ok(proba.iter().nth(1).copied().unwrap_or(0.0));
} else if proba.len() == 1 {
return Ok(proba.iter().next().copied().unwrap_or(0.0));
}
}
}
let label = result[0].to_array_view::<i64>()?;
let prediction = label.iter().next().copied().unwrap_or(0);
Ok(if prediction != 0 { 1.0 } else { 0.0 })
}
fn run_mlp(&self, input: &tract_ndarray::Array2<f32>) -> TractResult<(String, f32)> {
let input_tensor = input.clone().into_tensor();
let result = self.models.mlp.run(tvec![input_tensor.into()])?;
let result = self.models.classifier.run(tvec![input_tensor.into()])?;
let output = result[0].to_array_view::<f32>()?;
@ -189,7 +128,8 @@ impl Inference {
}
}
let attack_type = self.config
let attack_type = self
.config
.attack_labels
.get(&predicted_class.to_string())
.cloned()
@ -197,30 +137,4 @@ impl Inference {
Ok((attack_type, max_prob))
}
fn compute_ensemble_score(&self, ae_score: f32, rf_score: f32) -> f32 {
let strategy = &self.config.strategy_name;
if strategy.starts_with("W_") {
if let Some(weights_str) = strategy.strip_prefix("W_") {
let parts: Vec<&str> = weights_str.split(':').collect();
if parts.len() == 2 {
if let (Ok(w1), Ok(w2)) = (parts[0].parse::<f32>(), parts[1].parse::<f32>()) {
let w1 = w1 / 10.0;
let w2 = w2 / 10.0;
return w1 * ae_score + w2 * rf_score;
}
}
}
(ae_score + rf_score) / 2.0
} else {
match strategy.as_str() {
"Average" => (ae_score + rf_score) / 2.0,
"Max" => ae_score.max(rf_score),
"Min" => ae_score.min(rf_score),
"Product" => ae_score * rf_score,
_ => (ae_score + rf_score) / 2.0,
}
}
}
}
}

View File

@ -4,4 +4,5 @@ pub mod flow_tracker;
pub mod feature_extractor;
pub mod inference;
pub mod engine;
pub mod aggregator;
pub mod aggregator;
pub mod traffic_logger;

View File

@ -5,17 +5,17 @@ use crate::core::infrastructure::app_config::AppConfig;
use crate::model::error::ml::MLError;
use crate::model::ml_detection::RunnableModel;
use super::config_loader::InferenceConfig;
pub struct MLModels {
pub deep_autoencoder: RunnableModel,
pub random_forest: RunnableModel,
pub mlp: RunnableModel,
pub classifier: RunnableModel,
}
impl MLModels {
pub fn load_models(app_config: &Arc<AppConfig>, features: usize) -> Result<Self, MLError> {
pub fn load_models(app_config: &Arc<AppConfig>, inference_config: &Arc<InferenceConfig>) -> Result<Self, MLError> {
Ok(Self {
deep_autoencoder: Self::loader(&app_config.deep_autoencoder_name, features)?,
random_forest: Self::loader(&app_config.random_forest_name, features)?,
mlp: Self::loader(&app_config.mlp_name, features)?,
deep_autoencoder: Self::loader(&app_config.deep_autoencoder_name, inference_config.num_ae_features())?,
classifier: Self::loader(&app_config.classifier_name, inference_config.num_classifier_features())?
})
}
@ -49,8 +49,7 @@ impl MLModels {
pub fn get_model_info(&self, name: &str) -> String {
let model = match name {
"deep_autoencoder" => &self.deep_autoencoder,
"random_forest" => &self.random_forest,
"mlp" => &self.mlp,
"classifier" => &self.classifier,
_ => return "unknown model".to_string(),
};

View File

@ -0,0 +1,46 @@
use std::fs::OpenOptions;
use std::io::{BufWriter, Write};
use std::thread;
use crossbeam::channel::{bounded, Sender, TrySendError};
pub struct TrafficLogger {
sender: Sender<Vec<String>>,
}
impl TrafficLogger {
pub fn new(csv_path: &str, header: Vec<String>) -> Result<Self, std::io::Error> {
let file = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(csv_path)?;
let mut writer = BufWriter::new(file);
writeln!(writer, "{}", header.join(","))?;
writer.flush()?;
let (sender, receiver) = bounded::<Vec<String>>(65536);
thread::Builder::new()
.name("traffic-logger".to_string())
.spawn(move || {
for record in receiver {
if let Err(e) = writeln!(writer, "{}", record.join(",")) {
eprintln!("[traffic-logger] write error: {}", e);
}
}
let _ = writer.flush();
})?;
Ok(Self { sender })
}
pub fn log_row(&self, record: Vec<String>) {
match self.sender.try_send(record) {
Ok(_) => {}
Err(TrySendError::Full(_)) => {}
Err(TrySendError::Disconnected(_)) => {}
}
}
}

View File

@ -12,8 +12,7 @@ pub struct Config {
pub egress_ifname: String,
pub geoip_db_name: String,
pub deep_autoencoder_name: String,
pub random_forest_name: String,
pub mlp_name: String,
pub classifier_name: String,
pub models_config_name: String,
pub combined_queue_count: u32,
pub channel_size: usize,
@ -28,4 +27,8 @@ pub struct Config {
pub max_concurrent_flows: usize,
pub min_packets_for_inference: usize,
pub inference_interval_secs: u64,
}
pub aggregator_window_secs: u64,
pub inference_batch_size: usize,
pub traffic_logging_mode: bool,
pub traffic_log_csv_path: String,
}

View File

@ -7,6 +7,24 @@ pub enum Direction {
Egress,
}
impl Direction {
pub fn flip(self) -> Self {
match self {
Direction::Ingress => Direction::Egress,
Direction::Egress => Direction::Ingress,
}
}
}
impl std::fmt::Display for Direction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Direction::Ingress => write!(f, "Ingress"),
Direction::Egress => write!(f, "Egress"),
}
}
}
#[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Hash)]
#[serde(rename_all = "lowercase")]
pub enum FlowDirection {

View File

@ -21,5 +21,9 @@ traceable! {
#[error("Invalid GeoIP configuration")]
InvalidGeoIPConfiguration => tracing::Level::ERROR,
#[no_source]
#[error("Failed to create traffic log file '{path}': {reason}")]
TrafficLogCreateError { path: String, reason: String } => tracing::Level::ERROR,
}
}
}

View File

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

View File

@ -0,0 +1,11 @@
use macros::loggable;
loggable! {
Health {
#[error("{ifname} interface '{interface}' not found")]
InterfaceNotFound { ifname: String, interface: String } => tracing::Level::WARN,
#[error("Failed to broadcast system health metrics: {error}")]
BroadcastFailed { error: String } => tracing::Level::ERROR,
}
}

View File

@ -18,9 +18,6 @@ loggable! {
#[error("Machine Learning detection ready")]
Ready => tracing::Level::INFO,
#[error("Machine Learning detection shutdown")]
Shutdown => tracing::Level::INFO,
#[error("Machine Learning channel disconnected")]
ChannelDisconnected => tracing::Level::WARN,
@ -48,17 +45,14 @@ loggable! {
#[error("Inference configuration loaded: {features} features, {attacks} attack types")]
ConfigLoaded { features: usize, attacks: usize } => tracing::Level::INFO,
#[error("ML Engine started: max_flows={max_flows}, min_packets={min_packets}, interval={interval_secs}s")]
EngineStarted { max_flows: usize, min_packets: usize, interval_secs: u64 } => tracing::Level::INFO,
#[error("Inference completed: {total_flows} flows ({anomaly} anomaly, {benign} benign) in {duration_ms}ms ({throughput:.1} flows/s)")]
InferenceCompleted { total_flows: usize, anomaly: usize, benign: usize, duration_ms: u32, throughput: f32 } => tracing::Level::INFO,
#[error("Inference skipped: {reason}")]
InferenceSkipped { reason: String } => tracing::Level::INFO,
#[error("Threat detected: {flow} -> {attack_type} (confidence: {confidence:.2}, ae_score: {ae_score:.4}, rf_score: {rf_score:.4}, ensemble: {ensemble_score:.4})")]
ThreatDetected { flow: String, attack_type: String, confidence: f32, ae_score: f32, rf_score: f32, ensemble_score: f32 } => tracing::Level::WARN,
#[error("Threat detected [{direction}]: {flow} -> {attack_type} (confidence: {confidence:.2}, ae_score: {ae_score:.4})")]
ThreatDetected { direction: String, flow: String, attack_type: String, confidence: f32, ae_score: f32 } => tracing::Level::WARN,
#[error("Flow stats: total={total_flows}, qualified={flows_len}, min_packets={min_packets}, packet_counts: {counts}")]
FlowStats { total_flows: usize, flows_len: usize, min_packets: usize, counts: String } => tracing::Level::INFO,

View File

@ -3,3 +3,4 @@ pub mod http;
pub mod ml;
pub mod system;
pub mod misc;
pub mod health;

View File

@ -23,5 +23,8 @@ loggable! {
#[error("Configuration not found")]
ConfigNotFound => tracing::Level::INFO,
#[error("Traffic logging mode enabled — writing packets to: {path}")]
TrafficLoggingEnabled { path: String } => tracing::Level::INFO,
}
}
}

View File

@ -1,6 +1,8 @@
use common::model::event::{Event, TcpFlags};
use serde::{Deserialize, Serialize};
use tract_onnx::prelude::{Graph, SimplePlan, TypedFact, TypedOp};
use common::model::event::{Event, TcpFlags};
use crate::model::direction::Direction;
use crate::utils::packet_parser::{format_ipv4, format_ipv6};
pub type RunnableModel = SimplePlan<TypedFact, Box<dyn TypedOp>, Graph<TypedFact, Box<dyn TypedOp>>>;
@ -15,6 +17,7 @@ pub struct ClipParams {
pub struct AENormalization {
pub min: f64,
pub max: f64,
pub norm_max: f64,
pub mean: f64,
pub std: f64,
pub median: f64,
@ -23,7 +26,13 @@ pub struct AENormalization {
pub p99: f64,
}
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PrecisionLevels {
pub threshold: f64,
pub precision: f64,
}
#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub struct FlowKey {
pub src_ip: String,
pub dst_ip: String,
@ -77,8 +86,11 @@ pub struct BulkState {
pub bulk_count: u32,
pub total_bytes: u64,
pub total_packets: u64,
pub total_duration_us: u64,
pub last_bulk_bytes: u64,
pub last_bulk_packets: u64,
pub last_bulk_start_us: u64,
pub last_bulk_packet_us: u64,
pub in_bulk: bool,
}
@ -86,12 +98,12 @@ pub struct BulkState {
pub struct DetectionResult {
pub flow_key: String,
pub flow_key_raw: FlowKey,
pub direction: Direction,
pub is_attack: bool,
pub attack_type: Option<String>,
pub confidence: f32,
pub ae_score: f32,
pub rf_score: f32,
pub ensemble_score: f32,
pub threshold: f32,
}
#[derive(Debug, Clone, Default)]
@ -128,4 +140,4 @@ impl InferenceStats {
#[derive(Debug, Clone)]
pub struct EngineStats {
pub active_flows: usize,
}
}

View File

@ -7,3 +7,4 @@ pub mod list_type;
pub mod log;
pub mod time_type;
pub mod ml_detection;
pub mod health;

View File

@ -1,3 +1,5 @@
use std::net::IpAddr;
use common::define::setting::MAX_RULES_PORT;
use common::model::ip_address::Port;
@ -8,3 +10,20 @@ pub fn convert_ports_to_vec(ports: [u16; MAX_RULES_PORT]) -> Vec<Port> {
}
filtered_ports
}
pub fn is_private_ip(ip: &IpAddr) -> bool {
match ip {
IpAddr::V4(v4) => {
v4.is_private()
|| v4.is_loopback()
|| v4.is_link_local()
|| v4.is_broadcast()
}
IpAddr::V6(v6) => {
v6.is_loopback()
|| v6.is_unique_local() // fc00::/7
|| v6.is_unspecified()
|| v6.is_multicast()
}
}
}

View File

@ -1,10 +1,10 @@
use std::time;
use std::mem;
use std::time;
use common::model::event::{Event, IPv4Event, IPv6Event, TcpFlags};
use network_types::ip::IpProto;
pub fn parse_packet(packet_data: &[u8]) -> Option<Event> {
pub fn parse_packet(packet_data: &[u8]) -> Option<(Event, usize)> {
if packet_data.len() < 14 {
return None;
}
@ -23,7 +23,7 @@ pub fn parse_packet(packet_data: &[u8]) -> Option<Event> {
}
}
fn parse_ipv4(packet_data: &[u8], timestamp_us: u64) -> Option<Event> {
fn parse_ipv4(packet_data: &[u8], timestamp_us: u64) -> Option<(Event, usize)> {
if packet_data.len() < 34 {
return None;
}
@ -36,6 +36,10 @@ fn parse_ipv4(packet_data: &[u8], timestamp_us: u64) -> Option<Event> {
let src_ip = u32::from_be_bytes([ip_header[12], ip_header[13], ip_header[14], ip_header[15]]);
let dst_ip = u32::from_be_bytes([ip_header[16], ip_header[17], ip_header[18], ip_header[19]]);
if protocol_byte != 6 && protocol_byte != 17 {
return None;
}
let ihl = (ip_header[0] & 0x0F) as usize * 4;
let total_len = u16::from_be_bytes([ip_header[2], ip_header[3]]) as u32;
@ -64,6 +68,7 @@ fn parse_ipv4(packet_data: &[u8], timestamp_us: u64) -> Option<Event> {
};
let payload_length = total_len.saturating_sub(ihl as u32 + header_length as u32);
let payload_start = 14 + ihl + header_length as usize;
let event = IPv4Event {
protocol,
@ -80,10 +85,10 @@ fn parse_ipv4(packet_data: &[u8], timestamp_us: u64) -> Option<Event> {
is_forward: false,
};
Some(Event::IPv4(event))
Some((Event::IPv4(event), payload_start))
}
fn parse_ipv6(packet_data: &[u8], timestamp_us: u64) -> Option<Event> {
fn parse_ipv6(packet_data: &[u8], timestamp_us: u64) -> Option<(Event, usize)> {
if packet_data.len() < 54 {
return None;
}
@ -101,6 +106,10 @@ fn parse_ipv6(packet_data: &[u8], timestamp_us: u64) -> Option<Event> {
dest_ip_bytes.copy_from_slice(&ip_header[24..40]);
let dst_ip = u128::from_be_bytes(dest_ip_bytes);
if protocol_byte != 6 && protocol_byte != 17 {
return None;
}
let payload_len = u16::from_be_bytes([ip_header[4], ip_header[5]]) as u32;
let total_len = payload_len + 40;
@ -129,6 +138,7 @@ fn parse_ipv6(packet_data: &[u8], timestamp_us: u64) -> Option<Event> {
};
let payload_length = total_len.saturating_sub(40 + header_length as u32);
let payload_start = 14 + 40 + header_length as usize;
let event = IPv6Event {
protocol,
@ -145,24 +155,33 @@ fn parse_ipv6(packet_data: &[u8], timestamp_us: u64) -> Option<Event> {
is_forward: false,
};
Some(Event::IPv6(event))
Some((Event::IPv6(event), payload_start))
}
pub fn format_ipv4(addr: u32) -> String {
let bytes = addr.to_be_bytes();
format!(
"{}.{}.{}.{}",
bytes[0], bytes[1], bytes[2], bytes[3],
)
format!("{}.{}.{}.{}", bytes[0], bytes[1], bytes[2], bytes[3],)
}
pub fn format_ipv6(addr: u128) -> String {
let bytes = addr.to_be_bytes();
format!(
"{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}",
bytes[0], bytes[1], bytes[2], bytes[3],
bytes[4], bytes[5], bytes[6], bytes[7],
bytes[8], bytes[9], bytes[10], bytes[11],
bytes[12], bytes[13], bytes[14], bytes[15]
bytes[0],
bytes[1],
bytes[2],
bytes[3],
bytes[4],
bytes[5],
bytes[6],
bytes[7],
bytes[8],
bytes[9],
bytes[10],
bytes[11],
bytes[12],
bytes[13],
bytes[14],
bytes[15]
)
}
}

View File

@ -1,7 +1,6 @@
pub mod access_control;
pub mod service;
pub mod statistics;
pub mod health;
use actix_web::{web, Scope};
@ -10,5 +9,4 @@ pub fn initialize() -> Scope {
.service(access_control::initialize())
.service(service::initialize())
.service(statistics::initialize())
.service(health::initialize())
}

View File

@ -0,0 +1,22 @@
use crate::core::infrastructure::ml_alert::MLAlert;
use crate::web::websocket::alert_websocket;
use actix_web::{get, web, HttpRequest, HttpResponse, Responder, Scope};
pub fn initialize() -> Scope {
web::scope("/ml")
.service(websocket_alert)
}
#[get("/websocket/alert")]
async fn websocket_alert(
req: HttpRequest,
stream: web::Payload,
ai: web::Data<MLAlert>,
) -> impl Responder {
match alert_websocket::websocket_alert(req, stream, ai).await {
Ok(response) => response,
Err(err) => {
HttpResponse::InternalServerError().body(format!("WebSocket error: {}", err))
}
}
}

View File

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

View File

@ -0,0 +1,90 @@
use actix_web::{web, HttpRequest, HttpResponse, Result};
use actix_ws::{handle, Message, MessageStream, Session};
use futures_util::StreamExt;
use macros::log;
use tokio::sync::broadcast;
use crate::core::infrastructure::ml_alert::{MLAlert, AlertMessage};
use crate::model::error::http::HttpError;
use crate::model::error::misc::MiscError;
use crate::model::log::http::HttpLog;
pub async fn websocket_alert(
req: HttpRequest,
body: web::Payload,
ai: web::Data<MLAlert>,
) -> Result<HttpResponse> {
let (response, session, msg_stream) = handle(&req, body)?;
let broadcast_rx = ai.subscribe_to_alerts();
actix_web::rt::spawn(async move {
handle_alert_connection(session, msg_stream, broadcast_rx).await;
});
Ok(response)
}
async fn handle_alert_connection(
mut session: Session,
mut msg_stream: MessageStream,
mut broadcast_rx: broadcast::Receiver<AlertMessage>,
) {
loop {
tokio::select! {
msg_result = msg_stream.next() => {
if !handle_client_message(&mut session, msg_result).await {
break;
}
},
broadcast_result = broadcast_rx.recv() => {
match broadcast_result {
Ok(alert) => {
if !send_alert(&mut session, &alert).await {
break;
}
}
Err(broadcast::error::RecvError::Lagged(skipped)) => {
log!(HttpLog::WebSocketLaged(skipped));
continue;
}
Err(broadcast::error::RecvError::Closed) => {
break;
}
}
},
}
}
let _ = session.close(None).await;
}
async fn handle_client_message(
session: &mut Session,
msg_result: Option<Result<Message, actix_ws::ProtocolError>>,
) -> bool {
match msg_result {
Some(Ok(Message::Text(_))) => 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 send_alert(session: &mut Session, alert: &AlertMessage) -> bool {
match serde_json::to_string(alert) {
Ok(json) => session.text(json).await.is_ok(),
Err(err) => {
log!(MiscError::SerializeError(err));
false
}
}
}

View File

@ -4,10 +4,11 @@ use futures_util::StreamExt;
use macros::log;
use tokio::sync::broadcast;
use crate::core::infrastructure::health::{SystemHealth, SystemHealthMetrics};
use crate::core::infrastructure::health::SystemHealth;
use crate::model::error::http::HttpError;
use crate::model::error::misc::MiscError;
use crate::model::log::http::HttpLog;
use crate::model::health::SystemHealthMetrics;
pub async fn websocket_system_health(
req: HttpRequest,

View File

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