diff --git a/.github/images/accessControl.png b/.github/images/accessControl.png
deleted file mode 100644
index e815dd8..0000000
Binary files a/.github/images/accessControl.png and /dev/null differ
diff --git a/.github/images/aiDetection.png b/.github/images/aiDetection.png
deleted file mode 100644
index 81eb510..0000000
Binary files a/.github/images/aiDetection.png and /dev/null differ
diff --git a/.github/images/architecture.png b/.github/images/architecture.png
new file mode 100644
index 0000000..c92bbfa
Binary files /dev/null and b/.github/images/architecture.png differ
diff --git a/.github/images/architecture.svg b/.github/images/architecture.svg
new file mode 100644
index 0000000..69f5e04
--- /dev/null
+++ b/.github/images/architecture.svg
@@ -0,0 +1,105 @@
+
+
\ No newline at end of file
diff --git a/.github/images/dashboard.png b/.github/images/dashboard.png
deleted file mode 100644
index 30569c7..0000000
Binary files a/.github/images/dashboard.png and /dev/null differ
diff --git a/.github/images/index.png b/.github/images/index.png
deleted file mode 100644
index 316c4bd..0000000
Binary files a/.github/images/index.png and /dev/null differ
diff --git a/.github/images/map.png b/.github/images/map.png
deleted file mode 100644
index 6070b8d..0000000
Binary files a/.github/images/map.png and /dev/null differ
diff --git a/.github/images/statistics.png b/.github/images/statistics.png
deleted file mode 100644
index 0ac5f6c..0000000
Binary files a/.github/images/statistics.png and /dev/null differ
diff --git a/.github/images/ui/access-control.png b/.github/images/ui/access-control.png
new file mode 100644
index 0000000..0a0ed01
Binary files /dev/null and b/.github/images/ui/access-control.png differ
diff --git a/.github/images/ui/account-management.png b/.github/images/ui/account-management.png
new file mode 100644
index 0000000..3519dd5
Binary files /dev/null and b/.github/images/ui/account-management.png differ
diff --git a/.github/images/ui/api-keys.png b/.github/images/ui/api-keys.png
new file mode 100644
index 0000000..af263da
Binary files /dev/null and b/.github/images/ui/api-keys.png differ
diff --git a/.github/images/ui/audit-log.png b/.github/images/ui/audit-log.png
new file mode 100644
index 0000000..d1f68eb
Binary files /dev/null and b/.github/images/ui/audit-log.png differ
diff --git a/.github/images/ui/auto-response.png b/.github/images/ui/auto-response.png
new file mode 100644
index 0000000..ae43f26
Binary files /dev/null and b/.github/images/ui/auto-response.png differ
diff --git a/.github/images/ui/detection.png b/.github/images/ui/detection.png
new file mode 100644
index 0000000..35f41e1
Binary files /dev/null and b/.github/images/ui/detection.png differ
diff --git a/.github/images/ui/dns-filter.png b/.github/images/ui/dns-filter.png
new file mode 100644
index 0000000..ff9ea55
Binary files /dev/null and b/.github/images/ui/dns-filter.png differ
diff --git a/.github/images/ui/drop-monitor.png b/.github/images/ui/drop-monitor.png
new file mode 100644
index 0000000..43c4c6c
Binary files /dev/null and b/.github/images/ui/drop-monitor.png differ
diff --git a/.github/images/ui/flow-trace.png b/.github/images/ui/flow-trace.png
new file mode 100644
index 0000000..057fad1
Binary files /dev/null and b/.github/images/ui/flow-trace.png differ
diff --git a/.github/images/ui/geoip-block.png b/.github/images/ui/geoip-block.png
new file mode 100644
index 0000000..7351fa5
Binary files /dev/null and b/.github/images/ui/geoip-block.png differ
diff --git a/.github/images/ui/logs.png b/.github/images/ui/logs.png
new file mode 100644
index 0000000..a35a3d1
Binary files /dev/null and b/.github/images/ui/logs.png differ
diff --git a/.github/images/ui/map.png b/.github/images/ui/map.png
new file mode 100644
index 0000000..f3c2759
Binary files /dev/null and b/.github/images/ui/map.png differ
diff --git a/.github/images/ui/protocol-filter.png b/.github/images/ui/protocol-filter.png
new file mode 100644
index 0000000..ee2db1f
Binary files /dev/null and b/.github/images/ui/protocol-filter.png differ
diff --git a/.github/images/ui/rate-limit.png b/.github/images/ui/rate-limit.png
new file mode 100644
index 0000000..666d0cf
Binary files /dev/null and b/.github/images/ui/rate-limit.png differ
diff --git a/.github/images/ui/security-report.png b/.github/images/ui/security-report.png
new file mode 100644
index 0000000..f2db7a7
Binary files /dev/null and b/.github/images/ui/security-report.png differ
diff --git a/.github/images/ui/statistics.png b/.github/images/ui/statistics.png
new file mode 100644
index 0000000..6aef05d
Binary files /dev/null and b/.github/images/ui/statistics.png differ
diff --git a/.github/images/ui/system-settings.png b/.github/images/ui/system-settings.png
new file mode 100644
index 0000000..5a0d103
Binary files /dev/null and b/.github/images/ui/system-settings.png differ
diff --git a/.github/images/ui/system-status.png b/.github/images/ui/system-status.png
new file mode 100644
index 0000000..29847e5
Binary files /dev/null and b/.github/images/ui/system-status.png differ
diff --git a/.gitignore b/.gitignore
index 342a607..306a301 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,7 +41,9 @@ interfaces.txt
traffic_log.csv
# Project docs (local only)
-CLAUDE.md
+# CLAUDE.md — tracked on dev branches; MUST be untracked before PR to master
+# (see CLAUDE.md "Branch discipline" section)
+# CLAUDE.md
DESIGN.md
TODOS.md
VERSION
@@ -51,7 +53,9 @@ CHANGELOG.md
benchmark/
# Generated docs
-docs/
+# docs/ — tracked on dev branches; MUST be untracked before PR to master
+# (see CLAUDE.md "Branch discipline" section)
+# docs/
# SQLite database files
*.db
diff --git a/Cargo.lock b/Cargo.lock
index ebee79c..cc41b96 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -11,7 +11,7 @@ dependencies = [
"actix-macros",
"actix-rt",
"actix_derive",
- "bitflags",
+ "bitflags 2.11.0",
"bytes",
"crossbeam-channel",
"futures-core",
@@ -33,7 +33,7 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a"
dependencies = [
- "bitflags",
+ "bitflags 2.11.0",
"bytes",
"futures-core",
"futures-sink",
@@ -52,13 +52,36 @@ checksum = "daa239b93927be1ff123eebada5a3ff23e89f0124ccb8609234e5103d5a5ae6d"
dependencies = [
"actix-utils",
"actix-web",
- "derive_more",
+ "derive_more 2.1.1",
"futures-util",
"log",
"once_cell",
"smallvec",
]
+[[package]]
+name = "actix-files"
+version = "0.6.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df8c4f30e3272d7c345f88ae0aac3848507ef5ba871f9cc2a41c8085a0f0523b"
+dependencies = [
+ "actix-http",
+ "actix-service",
+ "actix-utils",
+ "actix-web",
+ "bitflags 2.11.0",
+ "bytes",
+ "derive_more 2.1.1",
+ "futures-core",
+ "http-range",
+ "log",
+ "mime",
+ "mime_guess",
+ "percent-encoding",
+ "pin-project-lite",
+ "v_htmlescape",
+]
+
[[package]]
name = "actix-http"
version = "3.12.0"
@@ -70,11 +93,11 @@ dependencies = [
"actix-service",
"actix-utils",
"base64",
- "bitflags",
+ "bitflags 2.11.0",
"brotli",
"bytes",
"bytestring",
- "derive_more",
+ "derive_more 2.1.1",
"encoding_rs",
"flate2",
"foldhash 0.1.5",
@@ -108,6 +131,44 @@ dependencies = [
"syn 2.0.117",
]
+[[package]]
+name = "actix-multipart"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5118a26dee7e34e894f7e85aa0ee5080ae4c18bf03c0e30d49a80e418f00a53"
+dependencies = [
+ "actix-multipart-derive",
+ "actix-utils",
+ "actix-web",
+ "derive_more 0.99.20",
+ "futures-core",
+ "futures-util",
+ "httparse",
+ "local-waker",
+ "log",
+ "memchr",
+ "mime",
+ "rand 0.8.5",
+ "serde",
+ "serde_json",
+ "serde_plain",
+ "tempfile",
+ "tokio",
+]
+
+[[package]]
+name = "actix-multipart-derive"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e11eb847f49a700678ea2fa73daeb3208061afa2b9d1a8527c03390f4c4a1c6b"
+dependencies = [
+ "darling",
+ "parse-size",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
[[package]]
name = "actix-router"
version = "0.5.4"
@@ -189,7 +250,7 @@ dependencies = [
"bytestring",
"cfg-if",
"cookie",
- "derive_more",
+ "derive_more 2.1.1",
"encoding_rs",
"foldhash 0.1.5",
"futures-core",
@@ -413,12 +474,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "170433209e817da6aae2c51aa0dd443009a613425dd041ebfb2492d1c4c11a25"
[[package]]
-name = "ar_archive_writer"
-version = "0.5.1"
+name = "arc-swap"
+version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b"
+checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207"
dependencies = [
- "object 0.37.3",
+ "rustversion",
]
[[package]]
@@ -470,11 +531,11 @@ checksum = "d18bc4e506fbb85ab7392ed993a7db4d1a452c71b75a246af4a80ab8c9d2dd50"
dependencies = [
"assert_matches",
"aya-obj",
- "bitflags",
+ "bitflags 2.11.0",
"bytes",
"libc",
"log",
- "object 0.36.7",
+ "object",
"once_cell",
"thiserror 1.0.69",
"tokio",
@@ -585,7 +646,7 @@ dependencies = [
"core-error",
"hashbrown 0.15.5",
"log",
- "object 0.36.7",
+ "object",
"thiserror 1.0.69",
]
@@ -607,7 +668,7 @@ version = "0.72.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
dependencies = [
- "bitflags",
+ "bitflags 2.11.0",
"cexpr",
"clang-sys",
"itertools 0.13.0",
@@ -636,6 +697,12 @@ version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
[[package]]
name = "bitflags"
version = "2.11.0"
@@ -743,9 +810,9 @@ dependencies = [
[[package]]
name = "cc"
-version = "1.2.57"
+version = "1.2.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423"
+checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20"
dependencies = [
"find-msvc-tools",
"jobserver",
@@ -796,16 +863,6 @@ dependencies = [
"windows-link",
]
-[[package]]
-name = "chumsky"
-version = "0.9.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9"
-dependencies = [
- "hashbrown 0.14.5",
- "stacker",
-]
-
[[package]]
name = "cipher"
version = "0.4.4"
@@ -883,6 +940,12 @@ dependencies = [
"serde",
]
+[[package]]
+name = "convert_case"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
+
[[package]]
name = "convert_case"
version = "0.10.0"
@@ -1027,6 +1090,41 @@ dependencies = [
"cipher",
]
+[[package]]
+name = "darling"
+version = "0.20.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.20.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.20.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn 2.0.117",
+]
+
[[package]]
name = "dashmap"
version = "6.1.0"
@@ -1067,6 +1165,19 @@ dependencies = [
"syn 1.0.109",
]
+[[package]]
+name = "derive_more"
+version = "0.99.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f"
+dependencies = [
+ "convert_case 0.4.0",
+ "proc-macro2",
+ "quote",
+ "rustc_version",
+ "syn 2.0.117",
+]
+
[[package]]
name = "derive_more"
version = "2.1.1"
@@ -1082,7 +1193,7 @@ version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
dependencies = [
- "convert_case",
+ "convert_case 0.10.0",
"proc-macro2",
"quote",
"rustc_version",
@@ -1191,7 +1302,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
- "windows-sys 0.61.2",
+ "windows-sys 0.52.0",
]
[[package]]
@@ -1208,9 +1319,9 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "fastrand"
-version = "2.3.0"
+version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
+checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
[[package]]
name = "filetime"
@@ -1266,6 +1377,15 @@ dependencies = [
"percent-encoding",
]
+[[package]]
+name = "fsevent-sys"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "futures-channel"
version = "0.3.32"
@@ -1431,7 +1551,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [
"ahash",
- "allocator-api2",
]
[[package]]
@@ -1456,6 +1575,12 @@ dependencies = [
"foldhash 0.2.0",
]
+[[package]]
+name = "hashbrown"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
+
[[package]]
name = "hashlink"
version = "0.10.0"
@@ -1544,6 +1669,12 @@ dependencies = [
"pin-project-lite",
]
+[[package]]
+name = "http-range"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
+
[[package]]
name = "httparse"
version = "1.10.1"
@@ -1558,9 +1689,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "hyper"
-version = "1.8.1"
+version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11"
+checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca"
dependencies = [
"atomic-waker",
"bytes",
@@ -1571,7 +1702,6 @@ dependencies = [
"httparse",
"itoa",
"pin-project-lite",
- "pin-utils",
"smallvec",
"tokio",
"want",
@@ -1611,7 +1741,7 @@ dependencies = [
"libc",
"percent-encoding",
"pin-project-lite",
- "socket2 0.6.3",
+ "socket2 0.5.10",
"tokio",
"tower-service",
"tracing",
@@ -1643,12 +1773,13 @@ dependencies = [
[[package]]
name = "icu_collections"
-version = "2.1.1"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
+checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"
dependencies = [
"displaydoc",
"potential_utf",
+ "utf8_iter",
"yoke",
"zerofrom",
"zerovec",
@@ -1656,9 +1787,9 @@ dependencies = [
[[package]]
name = "icu_locale_core"
-version = "2.1.1"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
+checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
dependencies = [
"displaydoc",
"litemap",
@@ -1669,9 +1800,9 @@ dependencies = [
[[package]]
name = "icu_normalizer"
-version = "2.1.1"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
+checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"
dependencies = [
"icu_collections",
"icu_normalizer_data",
@@ -1683,15 +1814,15 @@ dependencies = [
[[package]]
name = "icu_normalizer_data"
-version = "2.1.1"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
+checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"
[[package]]
name = "icu_properties"
-version = "2.1.2"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec"
+checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"
dependencies = [
"icu_collections",
"icu_locale_core",
@@ -1703,15 +1834,15 @@ dependencies = [
[[package]]
name = "icu_properties_data"
-version = "2.1.2"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af"
+checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"
[[package]]
name = "icu_provider"
-version = "2.1.1"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
+checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"
dependencies = [
"displaydoc",
"icu_locale_core",
@@ -1728,6 +1859,12 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
[[package]]
name = "idna"
version = "1.1.0"
@@ -1757,12 +1894,12 @@ checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2"
[[package]]
name = "indexmap"
-version = "2.13.0"
+version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
+checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [
"equivalent",
- "hashbrown 0.16.1",
+ "hashbrown 0.17.0",
"serde",
"serde_core",
]
@@ -1778,6 +1915,26 @@ dependencies = [
"which",
]
+[[package]]
+name = "inotify"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fdd168d97690d0b8c412d6b6c10360277f4d7ee495c5d0d5d5fe0854923255cc"
+dependencies = [
+ "bitflags 1.3.2",
+ "inotify-sys",
+ "libc",
+]
+
+[[package]]
+name = "inotify-sys"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "inout"
version = "0.1.4"
@@ -1787,6 +1944,15 @@ dependencies = [
"generic-array",
]
+[[package]]
+name = "instant"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
+dependencies = [
+ "cfg-if",
+]
+
[[package]]
name = "ipnet"
version = "2.12.0"
@@ -1801,9 +1967,9 @@ checksum = "cf370abdafd54d13e54a620e8c3e1145f28e46cc9d704bc6d94414559df41763"
[[package]]
name = "iri-string"
-version = "0.7.10"
+version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a"
+checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20"
dependencies = [
"memchr",
"serde",
@@ -1853,9 +2019,9 @@ dependencies = [
[[package]]
name = "itoa"
-version = "1.0.17"
+version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
+checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "jobserver"
@@ -1869,10 +2035,12 @@ dependencies = [
[[package]]
name = "js-sys"
-version = "0.3.91"
+version = "0.3.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c"
+checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca"
dependencies = [
+ "cfg-if",
+ "futures-util",
"once_cell",
"wasm-bindgen",
]
@@ -1892,6 +2060,26 @@ dependencies = [
"simple_asn1",
]
+[[package]]
+name = "kqueue"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a"
+dependencies = [
+ "kqueue-sys",
+ "libc",
+]
+
+[[package]]
+name = "kqueue-sys"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b"
+dependencies = [
+ "bitflags 1.3.2",
+ "libc",
+]
+
[[package]]
name = "kstring"
version = "2.0.2"
@@ -1922,13 +2110,12 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
name = "lettre"
-version = "0.11.19"
+version = "0.11.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e13e10e8818f8b2a60f52cb127041d388b89f3a96a62be9ceaffa22262fef7f"
+checksum = "dabda5859ee7c06b995b9d1165aa52c39110e079ef609db97178d86aeb051fa7"
dependencies = [
"async-trait",
"base64",
- "chumsky",
"email-encoding",
"email_address",
"fastrand",
@@ -1984,14 +2171,14 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
[[package]]
name = "libredox"
-version = "0.1.14"
+version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a"
+checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c"
dependencies = [
- "bitflags",
+ "bitflags 2.11.0",
"libc",
"plain",
- "redox_syscall 0.7.3",
+ "redox_syscall 0.7.4",
]
[[package]]
@@ -2078,9 +2265,9 @@ dependencies = [
[[package]]
name = "litemap"
-version = "0.8.1"
+version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
+checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
[[package]]
name = "local-channel"
@@ -2247,9 +2434,9 @@ dependencies = [
[[package]]
name = "mio"
-version = "1.1.1"
+version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
+checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
dependencies = [
"libc",
"log",
@@ -2257,6 +2444,23 @@ dependencies = [
"windows-sys 0.61.2",
]
+[[package]]
+name = "moka"
+version = "0.12.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046"
+dependencies = [
+ "crossbeam-channel",
+ "crossbeam-epoch",
+ "crossbeam-utils",
+ "equivalent",
+ "parking_lot",
+ "portable-atomic",
+ "smallvec",
+ "tagptr",
+ "uuid",
+]
+
[[package]]
name = "ndarray"
version = "0.16.1"
@@ -2278,9 +2482,12 @@ version = "0.1.0"
dependencies = [
"actix",
"actix-cors",
+ "actix-files",
+ "actix-multipart",
"actix-web",
"actix-ws",
"aes-gcm",
+ "arc-swap",
"argon2",
"async-trait",
"aya",
@@ -2294,6 +2501,7 @@ dependencies = [
"dotenvy",
"futures-util",
"hkdf",
+ "hmac",
"ipnetwork",
"jsonwebtoken",
"lettre",
@@ -2303,7 +2511,9 @@ dependencies = [
"macros",
"maxminddb",
"mime_guess",
+ "moka",
"network-types",
+ "notify",
"parking_lot",
"r2d2",
"r2d2_sqlite",
@@ -2314,6 +2524,7 @@ dependencies = [
"sd-notify",
"serde",
"serde_json",
+ "serde_yaml_ng",
"sha2",
"sysinfo",
"thiserror 2.0.18",
@@ -2325,6 +2536,7 @@ dependencies = [
"tracing-subscriber",
"tract-onnx",
"url",
+ "uuid",
"which",
"xsk-rs",
]
@@ -2356,7 +2568,7 @@ version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [
- "bitflags",
+ "bitflags 2.11.0",
"cfg-if",
"cfg_aliases",
"libc",
@@ -2390,6 +2602,34 @@ dependencies = [
"nom 8.0.0",
]
+[[package]]
+name = "notify"
+version = "7.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c533b4c39709f9ba5005d8002048266593c1cfaf3c5f0739d5b8ab0c6c504009"
+dependencies = [
+ "bitflags 2.11.0",
+ "filetime",
+ "fsevent-sys",
+ "inotify",
+ "kqueue",
+ "libc",
+ "log",
+ "mio",
+ "notify-types",
+ "walkdir",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "notify-types"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "585d3cb5e12e01aed9e8a1f70d5c6b5e86fe2a6e48fc8cd0b3e0b8df6f6eb174"
+dependencies = [
+ "instant",
+]
+
[[package]]
name = "ntapi"
version = "0.4.3"
@@ -2429,9 +2669,9 @@ dependencies = [
[[package]]
name = "num-conv"
-version = "0.2.0"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
+checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967"
[[package]]
name = "num-integer"
@@ -2479,7 +2719,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
dependencies = [
- "bitflags",
+ "bitflags 2.11.0",
]
[[package]]
@@ -2504,15 +2744,6 @@ dependencies = [
"memchr",
]
-[[package]]
-name = "object"
-version = "0.37.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
-dependencies = [
- "memchr",
-]
-
[[package]]
name = "once_cell"
version = "1.21.4"
@@ -2554,6 +2785,12 @@ dependencies = [
"windows-link",
]
+[[package]]
+name = "parse-size"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "487f2ccd1e17ce8c1bfab3a65c89525af41cfad4c8659021a1e9a2aacd73b89b"
+
[[package]]
name = "password-hash"
version = "0.5.0"
@@ -2636,12 +2873,6 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
-[[package]]
-name = "pin-utils"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
-
[[package]]
name = "pkg-config"
version = "0.3.32"
@@ -2683,9 +2914,9 @@ dependencies = [
[[package]]
name = "potential_utf"
-version = "0.1.4"
+version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
+checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
dependencies = [
"zerovec",
]
@@ -2768,16 +2999,6 @@ dependencies = [
"syn 1.0.109",
]
-[[package]]
-name = "psm"
-version = "0.1.30"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3852766467df634d74f0b2d7819bf8dc483a0eb2e3b0f50f756f9cfe8b0d18d8"
-dependencies = [
- "ar_archive_writer",
- "cc",
-]
-
[[package]]
name = "quinn"
version = "0.11.9"
@@ -2791,7 +3012,7 @@ dependencies = [
"quinn-udp",
"rustc-hash",
"rustls",
- "socket2 0.6.3",
+ "socket2 0.5.10",
"thiserror 2.0.18",
"tokio",
"tracing",
@@ -2828,9 +3049,9 @@ dependencies = [
"cfg_aliases",
"libc",
"once_cell",
- "socket2 0.6.3",
+ "socket2 0.5.10",
"tracing",
- "windows-sys 0.59.0",
+ "windows-sys 0.52.0",
]
[[package]]
@@ -2844,9 +3065,9 @@ dependencies = [
[[package]]
name = "quoted_printable"
-version = "0.5.1"
+version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73"
+checksum = "478e0585659a122aa407eb7e3c0e1fa51b1d8a870038bd29f0cf4a8551eea972"
[[package]]
name = "r-efi"
@@ -2980,16 +3201,16 @@ version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
- "bitflags",
+ "bitflags 2.11.0",
]
[[package]]
name = "redox_syscall"
-version = "0.7.3"
+version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16"
+checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a"
dependencies = [
- "bitflags",
+ "bitflags 2.11.0",
]
[[package]]
@@ -3085,7 +3306,7 @@ version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37e34486da88d8e051c7c0e23c3f15fd806ea8546260aa2fec247e97242ec143"
dependencies = [
- "bitflags",
+ "bitflags 2.11.0",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
@@ -3129,9 +3350,9 @@ dependencies = [
[[package]]
name = "rustc-hash"
-version = "2.1.1"
+version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
+checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
[[package]]
name = "rustc_version"
@@ -3162,11 +3383,11 @@ version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [
- "bitflags",
+ "bitflags 2.11.0",
"errno",
"libc",
"linux-raw-sys",
- "windows-sys 0.61.2",
+ "windows-sys 0.52.0",
]
[[package]]
@@ -3271,9 +3492,9 @@ dependencies = [
[[package]]
name = "semver"
-version = "1.0.27"
+version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
+checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
dependencies = [
"serde",
"serde_core",
@@ -3323,10 +3544,19 @@ dependencies = [
]
[[package]]
-name = "serde_spanned"
-version = "1.0.4"
+name = "serde_plain"
+version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776"
+checksum = "9ce1fc6db65a611022b23a0dec6975d63fb80a302cb3388835ff02c097258d50"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde_spanned"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
dependencies = [
"serde_core",
]
@@ -3343,6 +3573,19 @@ dependencies = [
"serde",
]
+[[package]]
+name = "serde_yaml_ng"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b4db627b98b36d4203a7b458cf3573730f2bb591b28871d916dfa9efabfd41f"
+dependencies = [
+ "indexmap",
+ "itoa",
+ "ryu",
+ "serde",
+ "unsafe-libyaml",
+]
+
[[package]]
name = "sha1"
version = "0.10.6"
@@ -3392,9 +3635,9 @@ dependencies = [
[[package]]
name = "simd-adler32"
-version = "0.3.8"
+version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
+checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
[[package]]
name = "simple_asn1"
@@ -3446,19 +3689,6 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
-[[package]]
-name = "stacker"
-version = "0.1.23"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08d74a23609d509411d10e2176dc2a4346e3b4aea2e7b1869f19fdedbc71c013"
-dependencies = [
- "cc",
- "cfg-if",
- "libc",
- "psm",
- "windows-sys 0.59.0",
-]
-
[[package]]
name = "static_assertions"
version = "1.1.0"
@@ -3551,16 +3781,35 @@ dependencies = [
]
[[package]]
-name = "tar"
-version = "0.4.44"
+name = "tagptr"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a"
+checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417"
+
+[[package]]
+name = "tar"
+version = "0.4.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973"
dependencies = [
"filetime",
"libc",
"xattr",
]
+[[package]]
+name = "tempfile"
+version = "3.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
+dependencies = [
+ "fastrand",
+ "getrandom 0.3.4",
+ "once_cell",
+ "rustix",
+ "windows-sys 0.52.0",
+]
+
[[package]]
name = "thiserror"
version = "1.0.69"
@@ -3643,9 +3892,9 @@ dependencies = [
[[package]]
name = "tinystr"
-version = "0.8.2"
+version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
+checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
dependencies = [
"displaydoc",
"zerovec",
@@ -3732,9 +3981,9 @@ dependencies = [
[[package]]
name = "toml"
-version = "1.0.7+spec-1.1.0"
+version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd28d57d8a6f6e458bc0b8784f8fdcc4b99a437936056fa122cb234f18656a96"
+checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee"
dependencies = [
"indexmap",
"serde_core",
@@ -3747,27 +3996,27 @@ dependencies = [
[[package]]
name = "toml_datetime"
-version = "1.0.1+spec-1.1.0"
+version = "1.1.1+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b320e741db58cac564e26c607d3cc1fdc4a88fd36c879568c07856ed83ff3e9"
+checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
dependencies = [
"serde_core",
]
[[package]]
name = "toml_parser"
-version = "1.0.10+spec-1.1.0"
+version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420"
+checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
dependencies = [
"winnow",
]
[[package]]
name = "toml_writer"
-version = "1.0.7+spec-1.1.0"
+version = "1.1.1+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f17aaa1c6e3dc22b1da4b6bba97d066e354c7945cac2f7852d4e4e7ca7a6b56d"
+checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db"
[[package]]
name = "tower"
@@ -3790,7 +4039,7 @@ version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
dependencies = [
- "bitflags",
+ "bitflags 2.11.0",
"bytes",
"futures-util",
"http 1.4.0",
@@ -4099,9 +4348,9 @@ dependencies = [
[[package]]
name = "unicode-segmentation"
-version = "1.12.0"
+version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
+checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c"
[[package]]
name = "unicode-xid"
@@ -4119,6 +4368,12 @@ dependencies = [
"subtle",
]
+[[package]]
+name = "unsafe-libyaml"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
+
[[package]]
name = "untrusted"
version = "0.9.0"
@@ -4167,6 +4422,12 @@ dependencies = [
"wasm-bindgen",
]
+[[package]]
+name = "v_htmlescape"
+version = "0.15.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e8257fbc510f0a46eb602c10215901938b5c2a7d5e70fc11483b1d3c9b5b18c"
+
[[package]]
name = "valuable"
version = "0.1.1"
@@ -4230,9 +4491,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen"
-version = "0.2.114"
+version = "0.2.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e"
+checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89"
dependencies = [
"cfg-if",
"once_cell",
@@ -4243,23 +4504,19 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
-version = "0.4.64"
+version = "0.4.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8"
+checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8"
dependencies = [
- "cfg-if",
- "futures-util",
"js-sys",
- "once_cell",
"wasm-bindgen",
- "web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.114"
+version = "0.2.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6"
+checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -4267,9 +4524,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.114"
+version = "0.2.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3"
+checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904"
dependencies = [
"bumpalo",
"proc-macro2",
@@ -4280,9 +4537,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.114"
+version = "0.2.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16"
+checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129"
dependencies = [
"unicode-ident",
]
@@ -4315,7 +4572,7 @@ version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [
- "bitflags",
+ "bitflags 2.11.0",
"hashbrown 0.15.5",
"indexmap",
"semver",
@@ -4323,9 +4580,9 @@ dependencies = [
[[package]]
name = "web-sys"
-version = "0.3.91"
+version = "0.3.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9"
+checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -4381,7 +4638,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
- "windows-sys 0.61.2",
+ "windows-sys 0.52.0",
]
[[package]]
@@ -4500,15 +4757,6 @@ dependencies = [
"windows-targets",
]
-[[package]]
-name = "windows-sys"
-version = "0.59.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
-dependencies = [
- "windows-targets",
-]
-
[[package]]
name = "windows-sys"
version = "0.61.2"
@@ -4593,9 +4841,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
-version = "1.0.0"
+version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8"
+checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5"
[[package]]
name = "wit-bindgen"
@@ -4655,7 +4903,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [
"anyhow",
- "bitflags",
+ "bitflags 2.11.0",
"indexmap",
"log",
"serde",
@@ -4687,9 +4935,9 @@ dependencies = [
[[package]]
name = "writeable"
-version = "0.6.2"
+version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
+checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
[[package]]
name = "xattr"
@@ -4707,7 +4955,7 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1fef46e3505c5055082f52ada0a7f8e5dcaebdbb9eccf8e978c32382c159270"
dependencies = [
- "bitflags",
+ "bitflags 2.11.0",
"cfg-if",
"libc",
"libxdp-sys",
@@ -4716,9 +4964,9 @@ dependencies = [
[[package]]
name = "yoke"
-version = "0.8.1"
+version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
+checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca"
dependencies = [
"stable_deref_trait",
"yoke-derive",
@@ -4727,9 +4975,9 @@ dependencies = [
[[package]]
name = "yoke-derive"
-version = "0.8.1"
+version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
+checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
dependencies = [
"proc-macro2",
"quote",
@@ -4739,18 +4987,18 @@ dependencies = [
[[package]]
name = "zerocopy"
-version = "0.8.42"
+version = "0.8.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3"
+checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
-version = "0.8.42"
+version = "0.8.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f"
+checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
dependencies = [
"proc-macro2",
"quote",
@@ -4759,18 +5007,18 @@ dependencies = [
[[package]]
name = "zerofrom"
-version = "0.1.6"
+version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
+checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
-version = "0.1.6"
+version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
+checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
dependencies = [
"proc-macro2",
"quote",
@@ -4786,9 +5034,9 @@ checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
[[package]]
name = "zerotrie"
-version = "0.2.3"
+version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
+checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
dependencies = [
"displaydoc",
"yoke",
@@ -4797,9 +5045,9 @@ dependencies = [
[[package]]
name = "zerovec"
-version = "0.11.5"
+version = "0.11.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
+checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
dependencies = [
"yoke",
"zerofrom",
@@ -4808,9 +5056,9 @@ dependencies = [
[[package]]
name = "zerovec-derive"
-version = "0.11.2"
+version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
+checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
dependencies = [
"proc-macro2",
"quote",
diff --git a/Cargo.toml b/Cargo.toml
index 23f61fb..3b12232 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -20,9 +20,10 @@ libxdp-sys = { version = "0.2.4", features = ["use_cc_build", "use_precompiled_b
# Serialization
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.149"
+serde_yaml_ng = "0.10.0"
# Async runtime
-tokio = { version = "1.50.0", features = ["rt-multi-thread", "macros", "sync", "time"] }
+tokio = { version = "1.50.0", features = ["rt-multi-thread", "macros", "sync", "time", "process", "io-util", "fs", "signal"] }
# Web framework
actix = "0.13.5"
diff --git a/README.md b/README.md
index 7b47c7d..8420fbc 100644
--- a/README.md
+++ b/README.md
@@ -1,60 +1,76 @@
# NetGuardia
-## Project Overview
+Inline network security platform built on eBPF/XDP. Runs four independent detectors (per-packet ML, temporal beaconing, graph correlation, Suricata) over the same data plane, fuses their verdicts, drives SOAR playbooks, and writes every decision into a WORM audit chain.
-**NetGuardia** is a high-performance network security solution that combines eBPF XDP technology with deep learning models to provide advanced network protection. The system operates as a standalone network appliance that can run on any Ubuntu-based system with compatible network hardware.
+## Stack
-## Core Technologies
+- **Data plane** — eBPF / XDP / AF_XDP (aya, xsk-rs)
+- **Detection** — Rust + tract-onnx for ML, custom temporal / graph engines, Suricata `eve.json` ingest
+- **Control plane** — actix-web REST + WebSocket, SQLite + SQLCipher, argon2 / JWT / CSRF, per-playbook SOAR
+- **Frontend** — Vue 3 + Pinia + Vue-i18n (en / zh-TW / zh-CN / ja)
+- **Architecture** — hexagonal: `adapter/` · `core/` · `infrastructure/` · `interface/` · `model/`
-- **eBPF XDP Technology** - Provides high-performance packet processing directly at the data link layer
-- **Deep Learning Models** - Identifies and predicts potential network attacks with intelligent threat detection
-- **Hardware Integration** - Designed to work with Intel i350 T2 and similar enterprise-grade network interface cards
+## Screens
-## Functional Modules
+
+
+ Traffic statistics (per-IP bytes/packets) |
+ Live geographic flow map |
+ Real-time drop monitor |
+
+
+ Fused threat detection + ML status |
+ IPv4/IPv6 allow + block lists |
+ GeoIP country block |
+
+
+ DNS blacklist |
+ Per-class DDoS rate limits |
+ HTTP / SSH service rules |
+
+
+ SOAR playbooks + dry-run |
+ Security report (PDF / email) |
+ WORM-chained audit log |
+
+
+ Users + groups + RBAC |
+ API keys |
+ Rotated flow recording |
+
+
+ Live + archived logs |
+ CPU / memory / NIC counters |
+ Mode / theme / HTTP / engine |
+
+
-### Resource Overview
-
-- Real-time control system occupancy rate
+## Architecture
-### Dashboard Overview
-
-- Real-time network traffic monitoring and visualization
-- Recent traffic statistics and trend analysis
+
-### Detailed Traffic Statistics
-
-- Detailed traffic usage information per IP address
+## Requirements
-### Network Access Control
-
-- IPv4/IPv6 whitelist and blacklist management
-- Precise port-level access control
+Linux kernel with eBPF **and** a NIC driver that implements AF_XDP on that kernel. No single "minimum kernel" — it depends on the NIC.
-[//]: # (### AI Attack Detection)
+| Driver | NIC family | Min kernel for AF_XDP |
+|---|---|---|
+| `mlx5` | Mellanox ConnectX-4/5/6/7 | 5.x |
+| `ixgbe` | Intel 82599, X520, X540, X550 | 5.x |
+| `i40e` | Intel X710, XL710, XXV710 | 5.x |
+| `ice` | Intel E810 | 5.5+ |
+| `igb` | Intel i350 T2 (reference HW) | **6.17** |
+| `igc` | Intel I225/I226 | 6.x |
+| `virtio_net` | QEMU/KVM | varies |
-[//]: # ()
+Check with `ethtool -i ` before deploying. 8 GB RAM minimum, 16 GB+ for high-traffic.
-[//]: # (- AI-based attack detection engine)
+## Build
-## System Features
-
-- **High Performance** - Low-latency packet processing with minimal network performance impact
-- **User-Friendly** - Cross-platform web management interface with intuitive operation
-- **Reliability** - Hardware-accelerated processing ensures stable operation
-- **Scalability** - Modular design supports functional expansion
-
-## System Requirements
-
-- Ubuntu-based operating system (Ubuntu 24.04 LTS or newer recommended)
-- Dual-port network interface card (Intel i350 T2 or compatible XDP-capable NIC)
-- Root/sudo access for eBPF program loading
-
-## Hardware Compatibility
-NetGuardia is designed to work on any Ubuntu-based system meeting the following requirements:
-
-- Network Interface: Any dual-port NIC supporting XDP native or offload mode (Intel i350 T2 recommended)
-- CPU: Multi-core processor recommended for optimal performance
-- Memory: 8GB RAM minimum, 16GB or more for high-traffic environments
-
-The system is not limited to embedded platforms and can be deployed on standard server hardware, virtual machines, or dedicated appliances running Ubuntu.
+```sh
+cargo build --release --package net-guardia
+sudo ./target/release/net-guardia
+# open http://:8080 — setup wizard issues the admin password on first boot
+```
+Systemd unit: [`deploy/netguardia.service`](deploy/netguardia.service).
diff --git a/license-generator/Cargo.toml b/license-generator/Cargo.toml
deleted file mode 100644
index e4f16d8..0000000
--- a/license-generator/Cargo.toml
+++ /dev/null
@@ -1,13 +0,0 @@
-[package]
-name = "license-generator"
-version = "0.1.0"
-edition = "2024"
-
-[dependencies]
-ed25519-dalek = { version = "2", features = ["std", "rand_core"] }
-base64 = "0.22"
-serde = { version = "1", features = ["derive"] }
-serde_json = "1"
-rand = "0.9"
-clap = { version = "4", features = ["derive"] }
-pnet = "0.36"
diff --git a/license-generator/src/main.rs b/license-generator/src/main.rs
deleted file mode 100644
index b65ff49..0000000
--- a/license-generator/src/main.rs
+++ /dev/null
@@ -1,217 +0,0 @@
-use std::fs;
-
-use base64::Engine;
-use base64::engine::general_purpose::STANDARD as BASE64;
-use clap::{Parser, Subcommand};
-use ed25519_dalek::{Signer, SigningKey, Verifier, VerifyingKey, Signature};
-use pnet::datalink;
-use rand::rngs::OsRng;
-use serde::{Deserialize, Serialize};
-
-#[derive(Parser)]
-#[command(name = "license-generator", about = "NetGuardia license generator")]
-struct Cli {
- #[command(subcommand)]
- command: Commands,
-}
-
-#[derive(Subcommand)]
-enum Commands {
- /// Generate a new Ed25519 keypair
- Keygen {
- #[arg(short, long, default_value = "license")]
- prefix: String,
- },
- /// Issue a signed license bound to NIC MACs
- Issue {
- #[arg(short = 'k', long)]
- private_key: String,
- /// Ingress interface name (e.g. ng-ext)
- #[arg(long)]
- ingress: String,
- /// Egress interface name (e.g. ng-int)
- #[arg(long)]
- egress: String,
- /// Expiry date (YYYY-MM-DD)
- #[arg(short, long)]
- expires: String,
- /// Comma-separated list of features
- #[arg(short, long, default_value = "")]
- features: String,
- /// Output license file path
- #[arg(short, long, default_value = "license.key")]
- output: String,
- },
- /// Verify a license file
- Verify {
- #[arg(short = 'k', long)]
- public_key: String,
- #[arg(short, long)]
- license: String,
- },
-}
-
-#[derive(Serialize, Deserialize, Debug)]
-struct LicensePayload {
- ingress_mac: String,
- egress_mac: String,
- expires: String,
- features: Vec,
-}
-
-fn get_mac(ifname: &str) -> String {
- for iface in datalink::interfaces() {
- if iface.name == ifname {
- if let Some(mac) = iface.mac {
- return format!(
- "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
- mac.0, mac.1, mac.2, mac.3, mac.4, mac.5
- );
- }
- }
- }
- eprintln!("Interface '{}' not found or has no MAC address", ifname);
- eprintln!("Available interfaces:");
- for iface in datalink::interfaces() {
- if let Some(mac) = iface.mac {
- eprintln!(" {} — {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
- iface.name, mac.0, mac.1, mac.2, mac.3, mac.4, mac.5);
- }
- }
- std::process::exit(1);
-}
-
-fn main() {
- let cli = Cli::parse();
-
- match cli.command {
- Commands::Keygen { prefix } => keygen(&prefix),
- Commands::Issue { private_key, ingress, egress, expires, features, output } => {
- issue(&private_key, &ingress, &egress, &expires, &features, &output)
- }
- Commands::Verify { public_key, license } => verify(&public_key, &license),
- }
-}
-
-fn keygen(prefix: &str) {
- let mut csprng = OsRng;
- let signing_key = SigningKey::generate(&mut csprng);
- let verifying_key = signing_key.verifying_key();
-
- let priv_hex = hex_encode(signing_key.as_bytes());
- let pub_hex = hex_encode(verifying_key.as_bytes());
-
- let priv_path = format!("{}_priv.key", prefix);
- let pub_path = format!("{}_pub.key", prefix);
-
- fs::write(&priv_path, &priv_hex).expect("Failed to write private key");
- fs::write(&pub_path, &pub_hex).expect("Failed to write public key");
-
- println!("Keypair generated:");
- println!(" Private key: {}", priv_path);
- println!(" Public key: {}", pub_path);
- println!();
- println!("Public key hex (embed in validator.rs):");
- println!(" {}", pub_hex);
-}
-
-fn issue(private_key_path: &str, ingress: &str, egress: &str, expires: &str, features: &str, output: &str) {
- let ingress_mac = get_mac(ingress);
- let egress_mac = get_mac(egress);
-
- println!("Detected MACs:");
- println!(" {} — {}", ingress, ingress_mac);
- println!(" {} — {}", egress, egress_mac);
-
- let priv_hex = fs::read_to_string(private_key_path)
- .expect("Failed to read private key")
- .trim()
- .to_string();
- let priv_bytes = hex_decode(&priv_hex).expect("Invalid hex");
- let priv_array: [u8; 32] = priv_bytes.try_into().expect("Key must be 32 bytes");
- let signing_key = SigningKey::from_bytes(&priv_array);
-
- let feature_list: Vec = if features.is_empty() {
- vec![]
- } else {
- features.split(',').map(|s| s.trim().to_string()).collect()
- };
-
- let payload = LicensePayload {
- ingress_mac: ingress_mac.clone(),
- egress_mac: egress_mac.clone(),
- expires: expires.to_string(),
- features: feature_list,
- };
-
- let payload_json = serde_json::to_string(&payload).expect("Failed to serialize");
- let payload_b64 = BASE64.encode(payload_json.as_bytes());
- let signature: Signature = signing_key.sign(payload_b64.as_bytes());
- let sig_b64 = BASE64.encode(signature.to_bytes());
-
- let license_content = format!("{}.{}", payload_b64, sig_b64);
- fs::write(output, &license_content).expect("Failed to write license");
-
- println!();
- println!("License issued:");
- println!(" Ingress MAC: {}", ingress_mac);
- println!(" Egress MAC: {}", egress_mac);
- println!(" Expires: {}", expires);
- println!(" Features: {:?}", payload.features);
- println!(" Output: {}", output);
-}
-
-fn verify(public_key_path: &str, license_path: &str) {
- let pub_hex = fs::read_to_string(public_key_path)
- .expect("Failed to read public key")
- .trim()
- .to_string();
- let pub_bytes = hex_decode(&pub_hex).expect("Invalid hex");
- let pub_array: [u8; 32] = pub_bytes.try_into().expect("Key must be 32 bytes");
- let verifying_key = VerifyingKey::from_bytes(&pub_array).expect("Invalid public key");
-
- let contents = fs::read_to_string(license_path)
- .expect("Failed to read license")
- .trim()
- .to_string();
-
- let parts: Vec<&str> = contents.splitn(2, '.').collect();
- if parts.len() != 2 {
- eprintln!("Invalid license format");
- std::process::exit(1);
- }
-
- let sig_bytes = BASE64.decode(parts[1]).expect("Invalid signature");
- let sig_array: [u8; 64] = sig_bytes.try_into().expect("Signature must be 64 bytes");
- let signature = Signature::from_bytes(&sig_array);
-
- match verifying_key.verify(parts[0].as_bytes(), &signature) {
- Ok(()) => {
- let payload_bytes = BASE64.decode(parts[0]).expect("Invalid payload");
- let payload: LicensePayload = serde_json::from_slice(&payload_bytes).expect("Invalid JSON");
- println!("License VALID:");
- println!(" Ingress MAC: {}", payload.ingress_mac);
- println!(" Egress MAC: {}", payload.egress_mac);
- println!(" Expires: {}", payload.expires);
- println!(" Features: {:?}", payload.features);
- }
- Err(e) => {
- eprintln!("License INVALID: {}", e);
- std::process::exit(1);
- }
- }
-}
-
-fn hex_encode(bytes: &[u8]) -> String {
- bytes.iter().map(|b| format!("{:02x}", b)).collect()
-}
-
-fn hex_decode(hex: &str) -> Result, String> {
- if hex.len() % 2 != 0 {
- return Err("Odd-length hex string".to_string());
- }
- (0..hex.len())
- .step_by(2)
- .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).map_err(|e| e.to_string()))
- .collect()
-}
diff --git a/models/classifier.onnx b/models/classifier.onnx
index 43b003c..cc19997 100644
Binary files a/models/classifier.onnx and b/models/classifier.onnx differ
diff --git a/models/deep_autoencoder.onnx b/models/deep_autoencoder.onnx
index b5ffc06..f25a646 100644
Binary files a/models/deep_autoencoder.onnx and b/models/deep_autoencoder.onnx differ
diff --git a/models/full_config.json b/models/full_config.json
index b590d5d..381bc0a 100644
--- a/models/full_config.json
+++ b/models/full_config.json
@@ -1,11 +1,12 @@
{
- "created_at": "2026-03-05T14:04:08.736531",
+ "created_at": "2026-04-07T02:27:17.281069",
"framework": "PyTorch",
+ "model_type": "MultiTaskModel",
"model": {
"deep_autoencoder": {
"file": "deep_autoencoder.onnx",
- "input_dim": 27,
- "encoding_dim": 16,
+ "input_dim": 31,
+ "encoding_dim": 12,
"ae_feature_names": [
"flow_duration",
"fwd_packets",
@@ -33,15 +34,24 @@
"fwd_pkt_len_std",
"bwd_pkt_len_std",
"fwd_seg_size_min",
- "fwd_act_data_pkts"
+ "fwd_act_data_pkts",
+ "fwd_iat_std",
+ "bwd_iat_std",
+ "fwd_bwd_bytes_ratio",
+ "iat_cv"
],
- "ae_threshold": 0.17166012525558472
+ "ae_threshold": 0.23011694848537445
},
"classifier": {
"file": "classifier.onnx",
- "type": "ResNet MLP",
- "n_features": 28,
- "n_classes": 5,
+ "type": "MultiTaskModel",
+ "n_features": 32,
+ "n_classes": 10,
+ "outputs": [
+ "anomaly",
+ "class_probs",
+ "c2_score"
+ ],
"classifier_feature_names": [
"flow_duration",
"fwd_packets",
@@ -70,6 +80,10 @@
"bwd_pkt_len_std",
"fwd_seg_size_min",
"fwd_act_data_pkts",
+ "fwd_iat_std",
+ "bwd_iat_std",
+ "fwd_bwd_bytes_ratio",
+ "iat_cv",
"ae_anomaly_score"
]
}
@@ -77,68 +91,68 @@
"preprocessing": {
"ae_clip_params": {
"flow_duration": {
- "lower": 1.0,
- "upper": 118485085.19000001
+ "lower": 0.0,
+ "upper": 115669365.2
},
"fwd_packets": {
"lower": 0.0,
- "upper": 54.0
+ "upper": 120.0
},
"bwd_packets": {
"lower": 0.0,
- "upper": 82.0
+ "upper": 126.0
},
"fwd_bytes": {
"lower": 0.0,
- "upper": 6960.0
+ "upper": 19557.400390625
},
"bwd_bytes": {
"lower": 0.0,
- "upper": 89558.6950000003
+ "upper": 85164.0
},
"flow_bytes_per_sec": {
"lower": 0.0,
- "upper": 12500000.0
+ "upper": 1627586.8125000005
},
"flow_pkts_per_sec": {
- "lower": 0.0226829350855,
- "upper": 2000000.0
+ "lower": 0.0,
+ "upper": 23809.5234375
},
"fwd_win_bytes": {
- "lower": -1.0,
- "upper": 65535.0
+ "lower": 0.0,
+ "upper": 65280.0
},
"bwd_win_bytes": {
- "lower": -1.0,
- "upper": 64000.0
+ "lower": 0.0,
+ "upper": 65535.0
},
"fwd_pkt_len_mean": {
"lower": 0.0,
- "upper": 744.7272727272729
+ "upper": 1500.0
},
"bwd_pkt_len_mean": {
"lower": 0.0,
- "upper": 1393.9601153594992
+ "upper": 822.0007794189461
},
"fwd_iat_mean": {
"lower": 0.0,
- "upper": 86366722.035
+ "upper": 54051113.24
},
"bwd_iat_mean": {
"lower": 0.0,
- "upper": 21700000.0
+ "upper": 6912790.715000001
},
"flow_iat_mean": {
- "lower": 1.0,
- "upper": 86353400.64500001
+ "lower": 0.0,
+ "upper": 166521472.0
},
"pkt_len_mean": {
"lower": 0.0,
- "upper": 945.0294113974604
+ "upper": 957.2035284423835
},
"dst_port": {
"lower": 0.0,
- "upper": 64499.0
+ "upper": 63005.0
},
"protocol": {
"lower": 0.0,
@@ -146,11 +160,11 @@
},
"psh_flag_cnt": {
"lower": 0.0,
- "upper": 240.0
+ "upper": 52.0
},
"ack_flag_cnt": {
"lower": 0.0,
- "upper": 655.0
+ "upper": 107.0
},
"syn_flag_cnt": {
"lower": 0.0,
@@ -158,91 +172,115 @@
},
"fin_flag_cnt": {
"lower": 0.0,
- "upper": 2.0
+ "upper": 1.0
},
"rst_flag_cnt": {
"lower": 0.0,
- "upper": 1.0
+ "upper": 0.0
},
"pkt_len_std": {
"lower": 0.0,
- "upper": 943.9221842600019
+ "upper": 818.4579974365238
},
"fwd_pkt_len_std": {
"lower": 0.0,
- "upper": 721.4199648708689
+ "upper": 256.8401712036142
},
"bwd_pkt_len_std": {
"lower": 0.0,
- "upper": 911.0272448420104
+ "upper": 676.0667114257812
},
"fwd_seg_size_min": {
"lower": 0.0,
- "upper": 40.0
+ "upper": 1026743.0693750025
},
"fwd_act_data_pkts": {
"lower": 0.0,
- "upper": 58.0
+ "upper": 12.0
+ },
+ "fwd_iat_std": {
+ "lower": 0.0,
+ "upper": 6691987.085000001
+ },
+ "bwd_iat_std": {
+ "lower": 0.0,
+ "upper": 5136363.065000001
+ },
+ "fwd_bwd_bytes_ratio": {
+ "lower": 0.0,
+ "upper": 1.0
+ },
+ "iat_cv": {
+ "lower": 0.0,
+ "upper": 0.0
}
},
"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
+ 1775639.8280735926,
+ 5.542641564702501,
+ 4.873551306800804,
+ 673.3628917399571,
+ 1776.7162625946232,
+ 22595.98254433581,
+ 457.7205079100132,
+ 5370.204067202503,
+ 9979.409366405764,
+ 310.21111530262294,
+ 64.0225296706552,
+ 421007.8046985764,
+ 74954.24060464761,
+ 1927814.4699678936,
+ 18.27810422291583,
+ 10009.108501605231,
+ 9.275900254913996,
+ 0.6270530398000678,
+ 1.4141691028300247,
+ 0.08438195832759936,
+ 0.0413846397252831,
+ 0.0,
+ 20.458150398533718,
+ 3.321724142251631,
+ 13.033036407393814,
+ 8569.843223681366,
+ 0.21485243990919378,
+ 94146.99057411935,
+ 68775.38334652747,
+ 0.47595050130443944,
+ 0.0
],
"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
+ 12196313.175317517,
+ 13.15004546194127,
+ 13.245688945052057,
+ 1969.9160973703263,
+ 8250.018585629565,
+ 161540.2407042193,
+ 2518.5714986775442,
+ 12103.605209582642,
+ 20308.80085611352,
+ 476.8868336260097,
+ 95.72217224515282,
+ 4199427.656867863,
+ 616719.9756782106,
+ 16437665.614047276,
+ 97.4688620675193,
+ 18194.33523865822,
+ 5.182633726939228,
+ 4.994559084102198,
+ 10.431975160428792,
+ 0.558462828085695,
+ 0.19916731632770637,
+ 1.0,
+ 106.06236469581468,
+ 24.362268530572912,
+ 85.37603561474889,
+ 84340.18813365103,
+ 1.4442281045462682,
+ 646928.7442307192,
+ 525550.9737726098,
+ 0.3431291415218137,
+ 1.0
],
"feature_names": [
"flow_duration",
@@ -271,7 +309,11 @@
"fwd_pkt_len_std",
"bwd_pkt_len_std",
"fwd_seg_size_min",
- "fwd_act_data_pkts"
+ "fwd_act_data_pkts",
+ "fwd_iat_std",
+ "bwd_iat_std",
+ "fwd_bwd_bytes_ratio",
+ "iat_cv"
]
},
"post_scaling_clip": {
@@ -280,10 +322,15 @@
}
},
"attack_labels": {
- "0": "Brute Force",
- "1": "DDoS",
- "2": "DoS",
- "3": "Exploitation",
- "4": "Reconnaissance"
+ "0": "Bot",
+ "1": "Brute Force",
+ "2": "C2 Communication",
+ "3": "DNS Tunneling",
+ "4": "DoS\/DDoS",
+ "5": "Exploitation",
+ "6": "Malware",
+ "7": "Normal",
+ "8": "Reconnaissance",
+ "9": "Web Attack"
}
}
\ No newline at end of file
diff --git a/models/inference_config.json b/models/inference_config.json
index 1d42207..9204c8e 100644
--- a/models/inference_config.json
+++ b/models/inference_config.json
@@ -26,72 +26,76 @@
"fwd_pkt_len_std",
"bwd_pkt_len_std",
"fwd_seg_size_min",
- "fwd_act_data_pkts"
+ "fwd_act_data_pkts",
+ "fwd_iat_std",
+ "bwd_iat_std",
+ "fwd_bwd_bytes_ratio",
+ "iat_cv"
],
"ae_clip_params": {
"flow_duration": {
- "lower": 1.0,
- "upper": 118485085.19000001
+ "lower": 0.0,
+ "upper": 115669365.2
},
"fwd_packets": {
"lower": 0.0,
- "upper": 54.0
+ "upper": 120.0
},
"bwd_packets": {
"lower": 0.0,
- "upper": 82.0
+ "upper": 126.0
},
"fwd_bytes": {
"lower": 0.0,
- "upper": 6960.0
+ "upper": 19557.400390625
},
"bwd_bytes": {
"lower": 0.0,
- "upper": 89558.6950000003
+ "upper": 85164.0
},
"flow_bytes_per_sec": {
"lower": 0.0,
- "upper": 12500000.0
+ "upper": 1627586.8125000005
},
"flow_pkts_per_sec": {
- "lower": 0.0226829350855,
- "upper": 2000000.0
+ "lower": 0.0,
+ "upper": 23809.5234375
},
"fwd_win_bytes": {
- "lower": -1.0,
- "upper": 65535.0
+ "lower": 0.0,
+ "upper": 65280.0
},
"bwd_win_bytes": {
- "lower": -1.0,
- "upper": 64000.0
+ "lower": 0.0,
+ "upper": 65535.0
},
"fwd_pkt_len_mean": {
"lower": 0.0,
- "upper": 744.7272727272729
+ "upper": 1500.0
},
"bwd_pkt_len_mean": {
"lower": 0.0,
- "upper": 1393.9601153594992
+ "upper": 822.0007794189461
},
"fwd_iat_mean": {
"lower": 0.0,
- "upper": 86366722.035
+ "upper": 54051113.24
},
"bwd_iat_mean": {
"lower": 0.0,
- "upper": 21700000.0
+ "upper": 6912790.715000001
},
"flow_iat_mean": {
- "lower": 1.0,
- "upper": 86353400.64500001
+ "lower": 0.0,
+ "upper": 166521472.0
},
"pkt_len_mean": {
"lower": 0.0,
- "upper": 945.0294113974604
+ "upper": 957.2035284423835
},
"dst_port": {
"lower": 0.0,
- "upper": 64499.0
+ "upper": 63005.0
},
"protocol": {
"lower": 0.0,
@@ -99,11 +103,11 @@
},
"psh_flag_cnt": {
"lower": 0.0,
- "upper": 240.0
+ "upper": 52.0
},
"ack_flag_cnt": {
"lower": 0.0,
- "upper": 655.0
+ "upper": 107.0
},
"syn_flag_cnt": {
"lower": 0.0,
@@ -111,94 +115,118 @@
},
"fin_flag_cnt": {
"lower": 0.0,
- "upper": 2.0
+ "upper": 1.0
},
"rst_flag_cnt": {
"lower": 0.0,
- "upper": 1.0
+ "upper": 0.0
},
"pkt_len_std": {
"lower": 0.0,
- "upper": 943.9221842600019
+ "upper": 818.4579974365238
},
"fwd_pkt_len_std": {
"lower": 0.0,
- "upper": 721.4199648708689
+ "upper": 256.8401712036142
},
"bwd_pkt_len_std": {
"lower": 0.0,
- "upper": 911.0272448420104
+ "upper": 676.0667114257812
},
"fwd_seg_size_min": {
"lower": 0.0,
- "upper": 40.0
+ "upper": 1026743.0693750025
},
"fwd_act_data_pkts": {
"lower": 0.0,
- "upper": 58.0
+ "upper": 12.0
+ },
+ "fwd_iat_std": {
+ "lower": 0.0,
+ "upper": 6691987.085000001
+ },
+ "bwd_iat_std": {
+ "lower": 0.0,
+ "upper": 5136363.065000001
+ },
+ "fwd_bwd_bytes_ratio": {
+ "lower": 0.0,
+ "upper": 1.0
+ },
+ "iat_cv": {
+ "lower": 0.0,
+ "upper": 0.0
}
},
"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
+ 1775639.8280735926,
+ 5.542641564702501,
+ 4.873551306800804,
+ 673.3628917399571,
+ 1776.7162625946232,
+ 22595.98254433581,
+ 457.7205079100132,
+ 5370.204067202503,
+ 9979.409366405764,
+ 310.21111530262294,
+ 64.0225296706552,
+ 421007.8046985764,
+ 74954.24060464761,
+ 1927814.4699678936,
+ 18.27810422291583,
+ 10009.108501605231,
+ 9.275900254913996,
+ 0.6270530398000678,
+ 1.4141691028300247,
+ 0.08438195832759936,
+ 0.0413846397252831,
+ 0.0,
+ 20.458150398533718,
+ 3.321724142251631,
+ 13.033036407393814,
+ 8569.843223681366,
+ 0.21485243990919378,
+ 94146.99057411935,
+ 68775.38334652747,
+ 0.47595050130443944,
+ 0.0
],
"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
+ 12196313.175317517,
+ 13.15004546194127,
+ 13.245688945052057,
+ 1969.9160973703263,
+ 8250.018585629565,
+ 161540.2407042193,
+ 2518.5714986775442,
+ 12103.605209582642,
+ 20308.80085611352,
+ 476.8868336260097,
+ 95.72217224515282,
+ 4199427.656867863,
+ 616719.9756782106,
+ 16437665.614047276,
+ 97.4688620675193,
+ 18194.33523865822,
+ 5.182633726939228,
+ 4.994559084102198,
+ 10.431975160428792,
+ 0.558462828085695,
+ 0.19916731632770637,
+ 1.0,
+ 106.06236469581468,
+ 24.362268530572912,
+ 85.37603561474889,
+ 84340.18813365103,
+ 1.4442281045462682,
+ 646928.7442307192,
+ 525550.9737726098,
+ 0.3431291415218137,
+ 1.0
],
"ae_post_clip_min": -5.0,
"ae_post_clip_max": 5.0,
- "ae_threshold": 0.17166012525558472,
+ "ae_threshold": 0.23011694848537445,
"classifier_feature_names": [
"flow_duration",
"fwd_packets",
@@ -227,13 +255,64 @@
"bwd_pkt_len_std",
"fwd_seg_size_min",
"fwd_act_data_pkts",
+ "fwd_iat_std",
+ "bwd_iat_std",
+ "fwd_bwd_bytes_ratio",
+ "iat_cv",
"ae_anomaly_score"
],
"attack_labels": {
- "0": "Brute Force",
- "1": "DDoS",
- "2": "DoS",
- "3": "Exploitation",
- "4": "Reconnaissance"
- }
+ "0": "Bot",
+ "1": "Brute Force",
+ "2": "C2 Communication",
+ "3": "DNS Tunneling",
+ "4": "DoS/DDoS",
+ "5": "Exploitation",
+ "6": "Malware",
+ "7": "Normal",
+ "8": "Reconnaissance",
+ "9": "Web Attack"
+ },
+ "anomaly_threshold": 0.9179317355155945,
+ "c2_threshold": 0.9085615873336792,
+ "model_type": "MultiTaskModel",
+ "output_names": [
+ "anomaly",
+ "class_probs",
+ "c2_score"
+ ],
+ "ae_feature_weights": {
+ "flow_duration": 1.0,
+ "fwd_packets": 1.0,
+ "bwd_packets": 1.0,
+ "fwd_bytes": 1.0,
+ "bwd_bytes": 1.0,
+ "flow_bytes_per_sec": 1.0,
+ "flow_pkts_per_sec": 1.0,
+ "fwd_win_bytes": 4.0,
+ "bwd_win_bytes": 4.0,
+ "fwd_pkt_len_mean": 1.0,
+ "bwd_pkt_len_mean": 1.0,
+ "fwd_iat_mean": 1.0,
+ "bwd_iat_mean": 1.0,
+ "flow_iat_mean": 1.0,
+ "pkt_len_mean": 1.0,
+ "dst_port": 1.0,
+ "protocol": 1.0,
+ "psh_flag_cnt": 2.0,
+ "ack_flag_cnt": 1.0,
+ "syn_flag_cnt": 2.0,
+ "fin_flag_cnt": 2.0,
+ "rst_flag_cnt": 2.0,
+ "pkt_len_std": 1.0,
+ "fwd_pkt_len_std": 1.0,
+ "bwd_pkt_len_std": 1.0,
+ "fwd_seg_size_min": 1.0,
+ "fwd_act_data_pkts": 1.0,
+ "fwd_iat_std": 1.5,
+ "bwd_iat_std": 1.5,
+ "fwd_bwd_bytes_ratio": 2.0,
+ "iat_cv": 2.0
+ },
+ "class_min_confidence": 0.4
}
\ No newline at end of file
diff --git a/models/manifest.yaml b/models/manifest.yaml
new file mode 100644
index 0000000..0ff4a93
--- /dev/null
+++ b/models/manifest.yaml
@@ -0,0 +1,74 @@
+# NetGuardia model manifest. Structural/semantic fields live here;
+# preprocessing arrays (scaler mean/std, clip params, feature weights) stay
+# in the JSON sidecar referenced by `preprocessing.scaler_sidecar`.
+
+name: netguardia-v10
+adapter: multi_task
+
+models:
+ autoencoder: deep_autoencoder.onnx
+ classifier: classifier.onnx
+
+# 31 AE-input features. Order matters — must match ONNX input column order
+# and inference_config.json `ae_feature_names`. The classifier takes these
+# plus `ae_anomaly_score` appended as the 32nd input (handled in code).
+features:
+ - 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
+ - fwd_iat_std
+ - bwd_iat_std
+ - fwd_bwd_bytes_ratio
+ - iat_cv
+
+# `confirmations` sets the per-class aggregator firing threshold. Classes
+# with single-shot semantics (C2 / Bot / DNS tunneling / exploit) use 1 so
+# the aggregator alerts on the first detection; noisier classes can raise
+# it (DoS/DDoS: 2). Absent entries fall back to the engine default.
+labels:
+ "0": { name: Bot, confirmations: 1 }
+ "1": { name: Brute Force }
+ "2": { name: C2 Communication, confirmations: 1 }
+ "3": { name: DNS Tunneling, confirmations: 1 }
+ "4": { name: DoS/DDoS, confirmations: 2 }
+ "5": { name: Exploitation, confirmations: 1 }
+ "6": { name: Malware }
+ "7": { name: Normal }
+ "8": { name: Reconnaissance }
+ "9": { name: Web Attack }
+
+thresholds:
+ anomaly: 0.9179317355155945
+ c2: 0.9085615873336792
+ class_min_confidence: 0.4
+ ae: 0.23011694848537445
+ # Average score must exceed `class_min_confidence * alert_multiplier`
+ # before the aggregator fires. Raising this suppresses borderline hits.
+ alert_multiplier: 1.2
+
+preprocessing:
+ scaler_sidecar: inference_config.json
diff --git a/net-guardia-frontend b/net-guardia-frontend
index 71d2d7f..00d347c 160000
--- a/net-guardia-frontend
+++ b/net-guardia-frontend
@@ -1 +1 @@
-Subproject commit 71d2d7f2d53f4afe6510b3018227aa5e28d97476
+Subproject commit 00d347c5eae3ed32f595b0a3553601f16bedfa7e
diff --git a/net-guardia-trainer b/net-guardia-trainer
index 1f5cbb8..dea59f2 160000
--- a/net-guardia-trainer
+++ b/net-guardia-trainer
@@ -1 +1 @@
-Subproject commit 1f5cbb8b9ba69a5bd16cc15055c230715d6bb9ae
+Subproject commit dea59f289635445fe63e69db8345df2f444fb9f1
diff --git a/net-guardia/Cargo.toml b/net-guardia/Cargo.toml
index 992f18a..33cef24 100644
--- a/net-guardia/Cargo.toml
+++ b/net-guardia/Cargo.toml
@@ -20,6 +20,9 @@ actix = { workspace = true }
actix-web = { workspace = true }
actix-cors = { workspace = true }
actix-ws = { workspace = true }
+actix-multipart = "0.7"
+actix-files = "0.6"
+uuid = { version = "1", features = ["v4"] }
rust-embed = "8.11.0"
mime_guess = "2.0.5"
url = "2.5.8"
@@ -28,6 +31,7 @@ tokio-tungstenite = "0.28.0"
# Serialization
serde = { workspace = true }
serde_json = { workspace = true }
+serde_yaml_ng = { workspace = true }
toml = "1.0.7"
# Async
@@ -53,6 +57,9 @@ reqwest = { version = "0.12", default-features = false, features = ["json", "rus
# Architecture
async-trait = "0.1"
dashmap = "6"
+arc-swap = "1"
+moka = { version = "0.12", features = ["sync"] }
+notify = "7"
# Utilities
parking_lot = { workspace = true }
@@ -67,6 +74,7 @@ r2d2_sqlite = "0.27"
jsonwebtoken = { workspace = true }
argon2 = { workspace = true }
sha2 = "0.10"
+hmac = "0.12"
aes-gcm = "0.10"
hkdf = "0.12"
base64 = { workspace = true }
diff --git a/net-guardia/src/adapter/access_control_adapter.rs b/net-guardia/src/adapter/access_control_adapter.rs
index 4614a67..7bc7968 100644
--- a/net-guardia/src/adapter/access_control_adapter.rs
+++ b/net-guardia/src/adapter/access_control_adapter.rs
@@ -1,13 +1,12 @@
use std::net::{IpAddr, SocketAddrV4, SocketAddrV6};
use std::sync::Arc;
-use async_trait::async_trait;
-
-use crate::core::ebpf::access_control::AccessControl;
+use crate::adapter::ebpf::access_control::AccessControl;
use crate::interface::port::access_control::AccessControlPort;
-use crate::model::direction::FlowDirection;
+use crate::model::access_control::list_type::ListType;
use crate::model::error::Error;
-use crate::model::list_type::ListType;
+use crate::model::error::ebpf::EbpfError;
+use crate::model::monitoring::direction::FlowDirection;
/// Adapter that implements AccessControlPort by delegating to the eBPF AccessControl.
pub struct EbpfAccessControlAdapter {
@@ -20,44 +19,39 @@ impl EbpfAccessControlAdapter {
}
}
-#[async_trait]
impl AccessControlPort for EbpfAccessControlAdapter {
- async fn block_ip(&self, ip: &str) -> Result<(), Error> {
+ fn block_ip(&self, ip: &str) -> Result<(), Error> {
let addr: IpAddr = ip
.parse()
- .map_err(|_| Error::from(crate::model::error::ebpf::EbpfError::InvalidIpAddress { ip: ip.to_string() }))?;
+ .map_err(|_| Error::from(EbpfError::InvalidIpAddress(ip.to_string())))?;
match addr {
IpAddr::V4(v4) => {
let socket = SocketAddrV4::new(v4, 0);
self.access_control
.add_ipv4_list(FlowDirection::Source, ListType::Black, socket)
- .await
}
IpAddr::V6(v6) => {
let socket = SocketAddrV6::new(v6, 0, 0, 0);
self.access_control
.add_ipv6_list(FlowDirection::Source, ListType::Black, socket)
- .await
}
}
}
- async fn unblock_ip(&self, ip: &str) -> Result<(), Error> {
+ fn unblock_ip(&self, ip: &str) -> Result<(), Error> {
let addr: IpAddr = ip
.parse()
- .map_err(|_| Error::from(crate::model::error::ebpf::EbpfError::InvalidIpAddress { ip: ip.to_string() }))?;
+ .map_err(|_| Error::from(EbpfError::InvalidIpAddress(ip.to_string())))?;
match addr {
IpAddr::V4(v4) => {
let socket = SocketAddrV4::new(v4, 0);
self.access_control
.remove_ipv4_list(FlowDirection::Source, ListType::Black, socket)
- .await
}
IpAddr::V6(v6) => {
let socket = SocketAddrV6::new(v6, 0, 0, 0);
self.access_control
.remove_ipv6_list(FlowDirection::Source, ListType::Black, socket)
- .await
}
}
}
diff --git a/net-guardia/src/core/ebpf/access_control.rs b/net-guardia/src/adapter/ebpf/access_control.rs
similarity index 57%
rename from net-guardia/src/core/ebpf/access_control.rs
rename to net-guardia/src/adapter/ebpf/access_control.rs
index f9f9844..0cc07c1 100644
--- a/net-guardia/src/core/ebpf/access_control.rs
+++ b/net-guardia/src/adapter/ebpf/access_control.rs
@@ -5,13 +5,14 @@ use aya::maps::{HashMap as AyaHashMap, MapData};
use aya::{Ebpf, Pod};
use common::model::ip_address::{IPv4, IPv6, Port};
use common::model::port_rule::PortRule;
-use tokio::sync::RwLock;
+use parking_lot::RwLock;
-use crate::model::direction::FlowDirection;
+use crate::interface::port::access_control_admin::AccessControlAdminPort;
+use crate::model::access_control::ip_address::NativeConvert;
+use crate::model::access_control::list_type::ListType;
use crate::model::error::Error;
use crate::model::error::ebpf::EbpfError;
-use crate::model::ip_address::NativeConvert;
-use crate::model::list_type::ListType;
+use crate::model::monitoring::direction::FlowDirection;
pub struct AccessControl {
ipv4_src_whitelist: RwLock>,
@@ -39,27 +40,43 @@ impl AccessControl {
Ok(access_control)
}
- pub async fn get_ipv4_list(&self, direction: FlowDirection, list_type: ListType) -> HashMap> {
+ /// Construct an AccessControl backed by no eBPF maps. Used when eBPF
+ /// failed to load at startup; every mutating call returns `EbpfError::NotLoaded`,
+ /// and list queries return empty maps.
+ pub fn unavailable() -> Self {
+ Self {
+ ipv4_src_whitelist: RwLock::new(MapWrapper::unavailable()),
+ ipv4_src_blacklist: RwLock::new(MapWrapper::unavailable()),
+ ipv4_dst_whitelist: RwLock::new(MapWrapper::unavailable()),
+ ipv4_dst_blacklist: RwLock::new(MapWrapper::unavailable()),
+ ipv6_src_whitelist: RwLock::new(MapWrapper::unavailable()),
+ ipv6_src_blacklist: RwLock::new(MapWrapper::unavailable()),
+ ipv6_dst_whitelist: RwLock::new(MapWrapper::unavailable()),
+ ipv6_dst_blacklist: RwLock::new(MapWrapper::unavailable()),
+ }
+ }
+
+ pub fn get_ipv4_list(&self, direction: FlowDirection, list_type: ListType) -> HashMap> {
let map_wrapper = match (direction, list_type) {
- (FlowDirection::Source, ListType::White) => self.ipv4_src_whitelist.read().await,
- (FlowDirection::Source, ListType::Black) => self.ipv4_src_blacklist.read().await,
- (FlowDirection::Destination, ListType::White) => self.ipv4_dst_whitelist.read().await,
- (FlowDirection::Destination, ListType::Black) => self.ipv4_dst_blacklist.read().await,
+ (FlowDirection::Source, ListType::White) => self.ipv4_src_whitelist.read(),
+ (FlowDirection::Source, ListType::Black) => self.ipv4_src_blacklist.read(),
+ (FlowDirection::Destination, ListType::White) => self.ipv4_dst_whitelist.read(),
+ (FlowDirection::Destination, ListType::Black) => self.ipv4_dst_blacklist.read(),
};
map_wrapper.get_list()
}
- pub async fn get_ipv6_list(&self, direction: FlowDirection, list_type: ListType) -> HashMap> {
+ pub fn get_ipv6_list(&self, direction: FlowDirection, list_type: ListType) -> HashMap> {
let map_wrapper = match (direction, list_type) {
- (FlowDirection::Source, ListType::White) => self.ipv6_src_whitelist.read().await,
- (FlowDirection::Source, ListType::Black) => self.ipv6_src_blacklist.read().await,
- (FlowDirection::Destination, ListType::White) => self.ipv6_dst_whitelist.read().await,
- (FlowDirection::Destination, ListType::Black) => self.ipv6_dst_blacklist.read().await,
+ (FlowDirection::Source, ListType::White) => self.ipv6_src_whitelist.read(),
+ (FlowDirection::Source, ListType::Black) => self.ipv6_src_blacklist.read(),
+ (FlowDirection::Destination, ListType::White) => self.ipv6_dst_whitelist.read(),
+ (FlowDirection::Destination, ListType::Black) => self.ipv6_dst_blacklist.read(),
};
map_wrapper.get_list()
}
- pub async fn add_ipv4_list(
+ pub fn add_ipv4_list(
&self,
direction: FlowDirection,
list_type: ListType,
@@ -68,15 +85,15 @@ impl AccessControl {
let ip: u32 = (*address.ip()).to_bits().to_be();
let port = address.port();
let mut map_wrapper = match (direction, list_type) {
- (FlowDirection::Source, ListType::White) => self.ipv4_src_whitelist.write().await,
- (FlowDirection::Source, ListType::Black) => self.ipv4_src_blacklist.write().await,
- (FlowDirection::Destination, ListType::White) => self.ipv4_dst_whitelist.write().await,
- (FlowDirection::Destination, ListType::Black) => self.ipv4_dst_blacklist.write().await,
+ (FlowDirection::Source, ListType::White) => self.ipv4_src_whitelist.write(),
+ (FlowDirection::Source, ListType::Black) => self.ipv4_src_blacklist.write(),
+ (FlowDirection::Destination, ListType::White) => self.ipv4_dst_whitelist.write(),
+ (FlowDirection::Destination, ListType::Black) => self.ipv4_dst_blacklist.write(),
};
map_wrapper.add(ip, port)
}
- pub async fn add_ipv6_list(
+ pub fn add_ipv6_list(
&self,
direction: FlowDirection,
list_type: ListType,
@@ -85,15 +102,15 @@ impl AccessControl {
let ip: u128 = (*address.ip()).to_bits().to_be();
let port = address.port();
let mut map_wrapper = match (direction, list_type) {
- (FlowDirection::Source, ListType::White) => self.ipv6_src_whitelist.write().await,
- (FlowDirection::Source, ListType::Black) => self.ipv6_src_blacklist.write().await,
- (FlowDirection::Destination, ListType::White) => self.ipv6_dst_whitelist.write().await,
- (FlowDirection::Destination, ListType::Black) => self.ipv6_dst_blacklist.write().await,
+ (FlowDirection::Source, ListType::White) => self.ipv6_src_whitelist.write(),
+ (FlowDirection::Source, ListType::Black) => self.ipv6_src_blacklist.write(),
+ (FlowDirection::Destination, ListType::White) => self.ipv6_dst_whitelist.write(),
+ (FlowDirection::Destination, ListType::Black) => self.ipv6_dst_blacklist.write(),
};
map_wrapper.add(ip, port)
}
- pub async fn remove_ipv4_list(
+ pub fn remove_ipv4_list(
&self,
direction: FlowDirection,
list_type: ListType,
@@ -102,15 +119,15 @@ impl AccessControl {
let ip: u32 = (*address.ip()).to_bits().to_be();
let port = address.port();
let mut map_wrapper = match (direction, list_type) {
- (FlowDirection::Source, ListType::White) => self.ipv4_src_whitelist.write().await,
- (FlowDirection::Source, ListType::Black) => self.ipv4_src_blacklist.write().await,
- (FlowDirection::Destination, ListType::White) => self.ipv4_dst_whitelist.write().await,
- (FlowDirection::Destination, ListType::Black) => self.ipv4_dst_blacklist.write().await,
+ (FlowDirection::Source, ListType::White) => self.ipv4_src_whitelist.write(),
+ (FlowDirection::Source, ListType::Black) => self.ipv4_src_blacklist.write(),
+ (FlowDirection::Destination, ListType::White) => self.ipv4_dst_whitelist.write(),
+ (FlowDirection::Destination, ListType::Black) => self.ipv4_dst_blacklist.write(),
};
map_wrapper.remove(ip, port)
}
- pub async fn remove_ipv6_list(
+ pub fn remove_ipv6_list(
&self,
direction: FlowDirection,
list_type: ListType,
@@ -119,43 +136,82 @@ impl AccessControl {
let ip: u128 = (*address.ip()).to_bits().to_be();
let port = address.port();
let mut map_wrapper = match (direction, list_type) {
- (FlowDirection::Source, ListType::White) => self.ipv6_src_whitelist.write().await,
- (FlowDirection::Source, ListType::Black) => self.ipv6_src_blacklist.write().await,
- (FlowDirection::Destination, ListType::White) => self.ipv6_dst_whitelist.write().await,
- (FlowDirection::Destination, ListType::Black) => self.ipv6_dst_blacklist.write().await,
+ (FlowDirection::Source, ListType::White) => self.ipv6_src_whitelist.write(),
+ (FlowDirection::Source, ListType::Black) => self.ipv6_src_blacklist.write(),
+ (FlowDirection::Destination, ListType::White) => self.ipv6_dst_whitelist.write(),
+ (FlowDirection::Destination, ListType::Black) => self.ipv6_dst_blacklist.write(),
};
map_wrapper.remove(ip, port)
}
}
+impl AccessControlAdminPort for AccessControl {
+ fn add_ipv4_list(&self, direction: FlowDirection, list_type: ListType, address: SocketAddrV4) -> Result<(), Error> {
+ self.add_ipv4_list(direction, list_type, address)
+ }
+ fn add_ipv6_list(&self, direction: FlowDirection, list_type: ListType, address: SocketAddrV6) -> Result<(), Error> {
+ self.add_ipv6_list(direction, list_type, address)
+ }
+ fn remove_ipv4_list(
+ &self,
+ direction: FlowDirection,
+ list_type: ListType,
+ address: SocketAddrV4,
+ ) -> Result<(), Error> {
+ self.remove_ipv4_list(direction, list_type, address)
+ }
+ fn remove_ipv6_list(
+ &self,
+ direction: FlowDirection,
+ list_type: ListType,
+ address: SocketAddrV6,
+ ) -> Result<(), Error> {
+ self.remove_ipv6_list(direction, list_type, address)
+ }
+ fn get_ipv4_list(&self, direction: FlowDirection, list_type: ListType) -> HashMap> {
+ self.get_ipv4_list(direction, list_type)
+ }
+ fn get_ipv6_list(&self, direction: FlowDirection, list_type: ListType) -> HashMap> {
+ self.get_ipv6_list(direction, list_type)
+ }
+}
+
struct MapWrapper {
- map: AyaHashMap,
+ map: Option>,
}
impl MapWrapper {
fn new(ebpf: &mut Ebpf, map_name: &str) -> Result {
let map = ebpf.take_map(map_name).ok_or(EbpfError::MapNotFound)?;
let map = AyaHashMap::try_from(map).map_err(EbpfError::MapOperationError)?;
- Ok(Self { map })
+ Ok(Self { map: Some(map) })
+ }
+
+ fn unavailable() -> Self {
+ Self { map: None }
}
fn get_list(&self) -> HashMap> {
- self.map
- .iter()
+ let Some(map) = self.map.as_ref() else {
+ return HashMap::new();
+ };
+ map.iter()
.filter_map(Result::ok)
.map(|(key, rule)| (key.into_native(), rule.to_port_vec()))
.collect()
}
fn add(&mut self, ip: T, port: Port) -> Result<(), Error> {
+ let Some(map) = self.map.as_mut() else {
+ return Err(EbpfError::NotLoaded.into());
+ };
if port == 0 {
- self.map
- .insert(ip, PortRule::new_match_all(), 0)
+ map.insert(ip, PortRule::new_match_all(), 0)
.map_err(EbpfError::MapOperationError)?;
return Ok(());
}
- let mut rule = self.map.get(&ip, 0).unwrap_or_else(|_| PortRule::new_empty());
+ let mut rule = map.get(&ip, 0).unwrap_or_else(|_| PortRule::new_empty());
if rule.is_match_all() {
return Ok(());
@@ -165,29 +221,32 @@ impl MapWrapper {
Err(EbpfError::RuleReachLimit)?;
}
- self.map.insert(ip, rule, 0).map_err(EbpfError::MapOperationError)?;
+ map.insert(ip, rule, 0).map_err(EbpfError::MapOperationError)?;
Ok(())
}
fn remove(&mut self, ip: T, port: Port) -> Result<(), Error> {
+ let Some(map) = self.map.as_mut() else {
+ return Err(EbpfError::NotLoaded.into());
+ };
if port == 0 {
- self.map.remove(&ip).map_err(EbpfError::MapOperationError)?;
+ map.remove(&ip).map_err(EbpfError::MapOperationError)?;
return Ok(());
}
- let mut rule = self.map.get(&ip, 0).map_err(|_| EbpfError::IpDoesNotExist)?;
+ let mut rule = map.get(&ip, 0).map_err(|_| EbpfError::IpDoesNotExist)?;
if rule.is_match_all() {
- self.map.remove(&ip).map_err(EbpfError::MapOperationError)?;
+ map.remove(&ip).map_err(EbpfError::MapOperationError)?;
return Ok(());
}
rule.remove_port(port);
if rule.is_empty() {
- self.map.remove(&ip).map_err(EbpfError::MapOperationError)?;
+ map.remove(&ip).map_err(EbpfError::MapOperationError)?;
} else {
- self.map.insert(ip, rule, 0).map_err(EbpfError::MapOperationError)?;
+ map.insert(ip, rule, 0).map_err(EbpfError::MapOperationError)?;
}
Ok(())
}
diff --git a/net-guardia/src/core/ebpf/dns_filter.rs b/net-guardia/src/adapter/ebpf/dns_filter.rs
similarity index 79%
rename from net-guardia/src/core/ebpf/dns_filter.rs
rename to net-guardia/src/adapter/ebpf/dns_filter.rs
index 0567aaa..80f3245 100644
--- a/net-guardia/src/core/ebpf/dns_filter.rs
+++ b/net-guardia/src/adapter/ebpf/dns_filter.rs
@@ -1,46 +1,59 @@
-use std::collections::HashSet;
+use core::str;
use common::model::dns_name::DnsName;
-use parking_lot::RwLock;
+use dashmap::DashSet;
+use crate::interface::port::dns_filter_api::DnsFilterPort;
+use crate::interface::port::dns_query_filter::DnsQueryFilter;
use crate::model::error::Error;
use crate::model::error::misc::MiscError;
pub struct DnsFilter {
- blacklist: RwLock>,
+ blacklist: DashSet,
}
impl DnsFilter {
pub fn new() -> Self {
Self {
- blacklist: RwLock::new(HashSet::new()),
+ blacklist: DashSet::new(),
}
}
pub fn add_domain(&self, domain: &str) -> Result<(), Error> {
let name = domain_to_wire_format(domain)?;
- self.blacklist.write().insert(name);
+ self.blacklist.insert(name);
Ok(())
}
pub fn remove_domain(&self, domain: &str) -> Result<(), Error> {
let name = domain_to_wire_format(domain)?;
- self.blacklist.write().remove(&name);
+ self.blacklist.remove(&name);
Ok(())
}
pub fn list_domains(&self) -> Vec {
- self.blacklist.read().iter().filter_map(wire_format_to_domain).collect()
+ self.blacklist
+ .iter()
+ .filter_map(|entry| wire_format_to_domain(&entry))
+ .collect()
+ }
+
+ /// Fast-path helper combining `parse_query_name` + `is_blacklisted` — used
+ /// by the AF_XDP RX loop.
+ pub fn is_query_blacklisted(&self, raw: &[u8]) -> bool {
+ match Self::parse_query_name(raw) {
+ Some((name, name_len)) => self.is_blacklisted(&name, name_len),
+ None => false,
+ }
}
/// Check if a DNS query name (in wire format) or any of its parent domains is blacklisted.
pub fn is_blacklisted(&self, name: &DnsName, name_len: usize) -> bool {
- let bl = self.blacklist.read();
- if bl.is_empty() {
+ if self.blacklist.is_empty() {
return false;
}
// Check exact match
- if bl.contains(name) {
+ if self.blacklist.contains(name) {
return true;
}
// Check parent domains
@@ -64,7 +77,7 @@ impl DnsFilter {
let mut parent = DnsName::zeroed();
let remaining = name_len - offset;
parent.data[..remaining.min(128)].copy_from_slice(&name.data[offset..offset + remaining.min(128)]);
- if bl.contains(&parent) {
+ if self.blacklist.contains(&parent) {
return true;
}
}
@@ -176,6 +189,24 @@ impl DnsFilter {
}
}
+impl DnsFilterPort for DnsFilter {
+ fn add_domain(&self, domain: &str) -> Result<(), Error> {
+ self.add_domain(domain)
+ }
+ fn remove_domain(&self, domain: &str) -> Result<(), Error> {
+ self.remove_domain(domain)
+ }
+ fn list_domains(&self) -> Vec {
+ self.list_domains()
+ }
+}
+
+impl DnsQueryFilter for DnsFilter {
+ fn is_query_blacklisted(&self, raw: &[u8]) -> bool {
+ self.is_query_blacklisted(raw)
+ }
+}
+
/// Convert a human-readable domain name (e.g., "example.com") to DNS wire format.
/// The result is a DnsName with lowercase, length-prefixed labels, zero-terminated and zero-padded.
fn domain_to_wire_format(domain: &str) -> Result {
@@ -187,16 +218,10 @@ fn domain_to_wire_format(domain: &str) -> Result {
let label_bytes = label.as_bytes();
let label_len = label_bytes.len();
if label_len == 0 || label_len >= 64 {
- return Err(MiscError::InvalidDnsName {
- reason: format!("invalid label length: {}", label_len),
- }
- .into());
+ return Err(MiscError::DnsLabelOutOfRange(label_len).into());
}
if pos + 1 + label_len >= 128 {
- return Err(MiscError::InvalidDnsName {
- reason: format!("domain name too long: {}", domain),
- }
- .into());
+ return Err(MiscError::DnsDomainTooLong(domain).into());
}
name.data[pos] = label_len as u8;
pos += 1;
@@ -229,7 +254,7 @@ fn wire_format_to_domain(name: &DnsName) -> Option {
return None;
}
pos += 1;
- let label = core::str::from_utf8(&name.data[pos..pos + label_len]).ok()?;
+ let label = str::from_utf8(&name.data[pos..pos + label_len]).ok()?;
labels.push(label.to_string());
pos += label_len;
}
diff --git a/net-guardia/src/core/ebpf/drop_monitor.rs b/net-guardia/src/adapter/ebpf/drop_monitor.rs
similarity index 57%
rename from net-guardia/src/core/ebpf/drop_monitor.rs
rename to net-guardia/src/adapter/ebpf/drop_monitor.rs
index f6644ef..4277c5c 100644
--- a/net-guardia/src/core/ebpf/drop_monitor.rs
+++ b/net-guardia/src/adapter/ebpf/drop_monitor.rs
@@ -1,20 +1,22 @@
use std::mem;
+use std::net::Ipv6Addr;
use std::sync::Arc;
+use std::sync::atomic::Ordering;
use std::time::Duration;
use aya::maps::{MapData, RingBuf};
use tokio::sync::{broadcast, oneshot};
+use tokio::time::interval;
use common::define::drop_reason::*;
use common::model::drop_event::DropEvent as RawDropEvent;
-use parking_lot::Mutex;
use crate::model::config::constants::DROP_CHANNEL_CAPACITY;
-use crate::model::drop_event::{DropCounters, DropEventMessage};
+use crate::model::monitoring::drop_event::{DropCounters, DropCountersAtomic, DropEventMessage};
pub struct DropMonitor {
broadcast_tx: broadcast::Sender,
- counters: Mutex,
+ counters: DropCountersAtomic,
}
impl DropMonitor {
@@ -22,7 +24,7 @@ impl DropMonitor {
let (tx, _) = broadcast::channel(DROP_CHANNEL_CAPACITY);
Self {
broadcast_tx: tx,
- counters: Mutex::new(DropCounters::default()),
+ counters: DropCountersAtomic::default(),
}
}
@@ -31,25 +33,47 @@ impl DropMonitor {
}
pub fn get_counters(&self) -> DropCounters {
- self.counters.lock().clone()
+ self.counters.snapshot()
+ }
+
+ /// Record a userspace drop decision (XSK worker's DNS filter) by the
+ /// per-reason counter. Callers at this layer haven't parsed src/dst yet,
+ /// so no broadcast event is emitted — `/api/stats/drops` stays correct,
+ /// `/ws/drops` simply does not surface the individual packet. Parse the
+ /// packet upstream if you need a structured event.
+ pub fn record_userspace_drop_count_only(&self, reason: u8) {
+ self.counters.total.fetch_add(1, Ordering::Relaxed);
+ let bucket = match reason {
+ DROP_REASON_ACL_BLACKLIST => Some(&self.counters.acl_blacklist),
+ DROP_REASON_RATE_LIMIT_PKT => Some(&self.counters.rate_limit_pkt),
+ DROP_REASON_RATE_LIMIT_SYN => Some(&self.counters.rate_limit_syn),
+ DROP_REASON_RATE_LIMIT_UDP => Some(&self.counters.rate_limit_udp),
+ DROP_REASON_RATE_LIMIT_DNS => Some(&self.counters.rate_limit_dns),
+ DROP_REASON_PROTOCOL_FILTER => Some(&self.counters.protocol_filter),
+ DROP_REASON_DNS_BLACKLIST => Some(&self.counters.dns_blacklist),
+ DROP_REASON_GEO_BLOCK => Some(&self.counters.geo_block),
+ _ => None,
+ };
+ if let Some(counter) = bucket {
+ counter.fetch_add(1, Ordering::Relaxed);
+ }
}
fn process_event(&self, raw: &RawDropEvent) {
- // Update counters
- {
- let mut c = self.counters.lock();
- c.total += 1;
- match raw.reason {
- DROP_REASON_ACL_BLACKLIST => c.acl_blacklist += 1,
- DROP_REASON_RATE_LIMIT_PKT => c.rate_limit_pkt += 1,
- DROP_REASON_RATE_LIMIT_SYN => c.rate_limit_syn += 1,
- DROP_REASON_RATE_LIMIT_UDP => c.rate_limit_udp += 1,
- DROP_REASON_RATE_LIMIT_DNS => c.rate_limit_dns += 1,
- DROP_REASON_PROTOCOL_FILTER => c.protocol_filter += 1,
- DROP_REASON_DNS_BLACKLIST => c.dns_blacklist += 1,
- DROP_REASON_GEO_BLOCK => c.geo_block += 1,
- _ => {}
- }
+ self.counters.total.fetch_add(1, Ordering::Relaxed);
+ let bucket = match raw.reason {
+ DROP_REASON_ACL_BLACKLIST => Some(&self.counters.acl_blacklist),
+ DROP_REASON_RATE_LIMIT_PKT => Some(&self.counters.rate_limit_pkt),
+ DROP_REASON_RATE_LIMIT_SYN => Some(&self.counters.rate_limit_syn),
+ DROP_REASON_RATE_LIMIT_UDP => Some(&self.counters.rate_limit_udp),
+ DROP_REASON_RATE_LIMIT_DNS => Some(&self.counters.rate_limit_dns),
+ DROP_REASON_PROTOCOL_FILTER => Some(&self.counters.protocol_filter),
+ DROP_REASON_DNS_BLACKLIST => Some(&self.counters.dns_blacklist),
+ DROP_REASON_GEO_BLOCK => Some(&self.counters.geo_block),
+ _ => None,
+ };
+ if let Some(counter) = bucket {
+ counter.fetch_add(1, Ordering::Relaxed);
}
let reason_str = reason_to_str(raw.reason);
@@ -101,7 +125,7 @@ fn format_ips(raw: &RawDropEvent) -> (String, String) {
}
fn format_ipv6(bytes: &[u8; 16]) -> String {
- std::net::Ipv6Addr::from(*bytes).to_string()
+ Ipv6Addr::from(*bytes).to_string()
}
fn reason_to_str(reason: u8) -> &'static str {
@@ -124,7 +148,7 @@ pub async fn start_consumer(ring_buf: RingBuf, monitor: Arc>,
- geo_block_v6: RwLock>,
- blocked_countries: RwLock>,
+ geo_block_v4: RwLock