mirror of
https://github.com/DaLaw2/NetGuardia.git
synced 2026-08-24 14:10:28 +09:00
feat: frontend overhaul, design system, CI fix
Frontend (submodule update): - JWT authentication with login page and route protection - WebSocket refactor: 26 connections → 4 with subscription filtering - All API paths migrated from /ebpf/ to /api/ with JWT headers - 6 new pages: drops, geo-block, dns-filter, rate-limit, protocol-filter, system settings - 3-group sidebar navigation (監控/安全/系統) - Updated all existing pages to new backend API Design system: - DESIGN.md: Industrial/Utilitarian aesthetic, Geist + JetBrains Mono, Slate palette, compact spacing, accessibility specs - CLAUDE.md: design system reference for future work - TODOS.md: implementation tracking CI fix: - Add Node.js 22 setup + npm install for frontend build in build.rs - Fix bpf-linker resolution: find_bpf_linker() in net-guardia/build.rs resolves path and passes via CARGO_TARGET_BPFEL_UNKNOWN_NONE_LINKER env var to eBPF subprocess (no more PATH guessing) - Add which crate to net-guardia build-dependencies - Force-install bpf-linker to avoid stale cache false positive - Set stable as default toolchain so clippy runs on stable - Make ingress/egress-ebpf build.rs non-fatal on which() failure Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
88b49d7493
commit
89cdbff542
36
.github/workflows/ci.yml
vendored
36
.github/workflows/ci.yml
vendored
@ -29,32 +29,47 @@ jobs:
|
||||
gcc m4 clang llvm \
|
||||
libelf-dev zlib1g-dev pkg-config
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: net-guardia-frontend/package-lock.json
|
||||
|
||||
- name: Install frontend dependencies
|
||||
run: npm install
|
||||
working-directory: net-guardia-frontend
|
||||
|
||||
- name: Install Rust stable toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: clippy
|
||||
|
||||
- name: Install Rust nightly toolchain (for bpf-linker)
|
||||
- name: Install Rust nightly toolchain (for eBPF)
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
components: rust-src
|
||||
|
||||
# Ensure stable is the default so cargo check/test/clippy use stable.
|
||||
# Nightly is only needed for the eBPF subprocess (which uses
|
||||
# rust-toolchain.toml in ingress-ebpf/egress-ebpf).
|
||||
- name: Set stable as default toolchain
|
||||
run: rustup default stable
|
||||
|
||||
- name: Cache cargo registry and build artifacts
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
|
||||
# bpf-linker is required by build.rs to compile eBPF programs.
|
||||
# Use cargo-binstall for a faster pre-built binary install when available,
|
||||
# otherwise fall back to building from source (can take ~30 min).
|
||||
- name: Install bpf-linker
|
||||
run: |
|
||||
cargo install cargo-binstall --locked 2>/dev/null || true
|
||||
if command -v cargo-binstall &>/dev/null; then
|
||||
cargo binstall bpf-linker --no-confirm --locked || cargo install bpf-linker --locked
|
||||
cargo binstall bpf-linker --no-confirm --force || cargo install bpf-linker --locked
|
||||
else
|
||||
cargo install bpf-linker --locked
|
||||
fi
|
||||
ls -la "$HOME/.cargo/bin/bpf-linker"
|
||||
timeout-minutes: 45
|
||||
|
||||
- name: cargo check
|
||||
@ -76,17 +91,6 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Integration tests require a podman-based multi-container network
|
||||
# environment (netguardia, router, external, internal containers) that
|
||||
# is not available in GitHub Actions runners.
|
||||
#
|
||||
# The real integration tests are run on the dev server via:
|
||||
# /home/dalaw2/test_netguardia.sh
|
||||
#
|
||||
# That script tests XDP packet forwarding, Web API endpoints, ACL
|
||||
# blocking, rate limiting, ML engine, WebSocket, GeoIP country
|
||||
# blocking, and DNS blacklist functionality across the container
|
||||
# network topology.
|
||||
- name: Integration test reminder
|
||||
run: |
|
||||
echo "============================================"
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -33,3 +33,6 @@ license-generator/target/
|
||||
license.key
|
||||
license_priv.key
|
||||
license_pub.key
|
||||
.gstack/
|
||||
interfaces.txt
|
||||
traffic_log.csv
|
||||
|
||||
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -2009,6 +2009,7 @@ dependencies = [
|
||||
"tracing-subscriber",
|
||||
"tract-onnx",
|
||||
"url",
|
||||
"which",
|
||||
"xsk-rs",
|
||||
]
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
#[cfg(all(feature = "user"))]
|
||||
#[cfg(feature = "user")]
|
||||
use std::vec::Vec;
|
||||
|
||||
#[cfg(feature = "user")]
|
||||
@ -23,7 +23,7 @@ pub enum HttpMethod {
|
||||
#[cfg(feature = "user")]
|
||||
impl HttpMethod {
|
||||
pub fn convert_from_bitmap(http_method_bitmap: HttpMethodBitmap) -> Vec<HttpMethod> {
|
||||
let value = http_method_bitmap as u16;
|
||||
let value = http_method_bitmap;
|
||||
let mut http_methods = Vec::new();
|
||||
|
||||
let all_methods = [
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
use which::which;
|
||||
|
||||
fn main() {
|
||||
let bpf_linker = which("bpf-linker").unwrap();
|
||||
println!("cargo:rerun-if-changed={}", bpf_linker.to_str().unwrap());
|
||||
// bpf-linker path is resolved and injected by net-guardia/build.rs
|
||||
// via CARGO_TARGET_BPFEB_UNKNOWN_NONE_LINKER env var.
|
||||
// This build.rs only needs to exist for cargo to run it.
|
||||
if let Ok(linker) = which::which("bpf-linker") {
|
||||
println!("cargo:rerun-if-changed={}", linker.display());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
use which::which;
|
||||
|
||||
fn main() {
|
||||
let bpf_linker = which("bpf-linker").unwrap();
|
||||
println!("cargo:rerun-if-changed={}", bpf_linker.to_str().unwrap());
|
||||
// bpf-linker path is resolved and injected by net-guardia/build.rs
|
||||
// via CARGO_TARGET_BPFEL_UNKNOWN_NONE_LINKER env var.
|
||||
// This build.rs only needs to exist for cargo to run it.
|
||||
if let Ok(linker) = which::which("bpf-linker") {
|
||||
println!("cargo:rerun-if-changed={}", linker.display());
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,7 +198,7 @@ pub fn generate_error_enum(input: TokenStream, force_no_source: bool) -> TokenSt
|
||||
});
|
||||
|
||||
let expanded = quote! {
|
||||
#[allow(dead_code)]
|
||||
#[allow(dead_code, clippy::enum_variant_names)]
|
||||
#[derive(Debug, Clone, thiserror::Error, serde::Serialize, serde::Deserialize)]
|
||||
pub enum #enum_name {
|
||||
#(#enum_variants,)*
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit 55da05e710cb00bbc9bef85804667624b0ddf377
|
||||
Subproject commit 599f170385f6c36071dbb7be4664db7edfdcdf3e
|
||||
@ -71,6 +71,7 @@ license = ["dep:ed25519-dalek", "dep:base64"]
|
||||
|
||||
[build-dependencies]
|
||||
cargo_metadata = { workspace = true }
|
||||
which = { workspace = true }
|
||||
dotenvy = "0.15.7"
|
||||
|
||||
[[bin]]
|
||||
|
||||
@ -15,7 +15,6 @@ fn main() {
|
||||
}
|
||||
|
||||
fn embed_license_public_key() {
|
||||
// Only needed when license feature is enabled
|
||||
let license_enabled = env::var("CARGO_FEATURE_LICENSE").is_ok();
|
||||
|
||||
let project_root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap())
|
||||
@ -40,11 +39,34 @@ fn embed_license_public_key() {
|
||||
key_path.display()
|
||||
);
|
||||
} else {
|
||||
// Feature not enabled, set clearly invalid placeholder (won't be used)
|
||||
println!("cargo:rustc-env=LICENSE_PUBLIC_KEY=DISABLED");
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve the absolute path of bpf-linker.
|
||||
/// Searches PATH first, then falls back to CARGO_HOME/bin.
|
||||
fn find_bpf_linker() -> PathBuf {
|
||||
// Try PATH via which
|
||||
if let Ok(path) = which::which("bpf-linker") {
|
||||
return path;
|
||||
}
|
||||
|
||||
// Fallback: CARGO_HOME/bin (handles CI cache + which v8 issues)
|
||||
let cargo_home = env::var("CARGO_HOME").unwrap_or_else(|_| {
|
||||
let home = env::var("HOME").unwrap_or_default();
|
||||
format!("{home}/.cargo")
|
||||
});
|
||||
let candidate = PathBuf::from(format!("{cargo_home}/bin/bpf-linker"));
|
||||
if candidate.exists() {
|
||||
return candidate;
|
||||
}
|
||||
|
||||
panic!(
|
||||
"bpf-linker not found in PATH or $CARGO_HOME/bin.\n\
|
||||
Install with: cargo install bpf-linker"
|
||||
);
|
||||
}
|
||||
|
||||
fn build_ebpf_package(package_name: &str, target_subdir: &str) {
|
||||
let Metadata { packages, .. } = MetadataCommand::new().no_deps().exec().unwrap();
|
||||
let ebpf_package = packages
|
||||
@ -67,9 +89,13 @@ fn build_ebpf_package(package_name: &str, target_subdir: &str) {
|
||||
let build_ebpf = true;
|
||||
if build_ebpf {
|
||||
let arch = env::var_os("CARGO_CFG_TARGET_ARCH").unwrap();
|
||||
|
||||
let target = format!("{target}-unknown-none");
|
||||
|
||||
// Find bpf-linker once, pass its path to the subprocess explicitly.
|
||||
let bpf_linker = find_bpf_linker();
|
||||
let bpf_linker_str = bpf_linker.to_str()
|
||||
.expect("bpf-linker path is not valid UTF-8");
|
||||
|
||||
let Package { manifest_path, .. } = ebpf_package;
|
||||
let ebpf_dir = manifest_path.parent().unwrap();
|
||||
|
||||
@ -90,6 +116,13 @@ fn build_ebpf_package(package_name: &str, target_subdir: &str) {
|
||||
|
||||
cmd.env("CARGO_CFG_BPF_TARGET_ARCH", arch);
|
||||
|
||||
// Tell cargo which linker to use for the BPF targets.
|
||||
// This avoids relying on PATH in the subprocess.
|
||||
let linker_env_bpfel = "CARGO_TARGET_BPFEL_UNKNOWN_NONE_LINKER";
|
||||
let linker_env_bpfeb = "CARGO_TARGET_BPFEB_UNKNOWN_NONE_LINKER";
|
||||
cmd.env(linker_env_bpfel, bpf_linker_str);
|
||||
cmd.env(linker_env_bpfeb, bpf_linker_str);
|
||||
|
||||
for key in ["RUSTUP_TOOLCHAIN", "RUSTC", "RUSTC_WORKSPACE_WRAPPER"] {
|
||||
cmd.env_remove(key);
|
||||
}
|
||||
@ -168,10 +201,6 @@ fn build_ebpf_package(package_name: &str, target_subdir: &str) {
|
||||
fn build_frontend() {
|
||||
let _ = dotenvy::dotenv();
|
||||
|
||||
// let Some(frontend_dir) = env::var_os("FRONTEND_DIR") else {
|
||||
// panic!("FRONTEND_DIR environment variable is required but not set");
|
||||
// };
|
||||
|
||||
let project_root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
|
||||
let static_dir = project_root.join("static").join("web");
|
||||
|
||||
@ -188,26 +217,11 @@ fn build_frontend() {
|
||||
println!("cargo:rerun-if-changed={}", frontend_dir.join("src").display());
|
||||
println!("cargo:rerun-if-changed={}", frontend_dir.join("public").display());
|
||||
println!("cargo:rerun-if-changed={}", frontend_dir.join("package.json").display());
|
||||
println!(
|
||||
"cargo:rerun-if-changed={}",
|
||||
frontend_dir.join("package-lock.json").display()
|
||||
);
|
||||
println!(
|
||||
"cargo:rerun-if-changed={}",
|
||||
frontend_dir.join("next.config.js").display()
|
||||
);
|
||||
println!(
|
||||
"cargo:rerun-if-changed={}",
|
||||
frontend_dir.join("tailwind.config.js").display()
|
||||
);
|
||||
println!(
|
||||
"cargo:rerun-if-changed={}",
|
||||
frontend_dir.join("postcss.config.js").display()
|
||||
);
|
||||
println!(
|
||||
"cargo:rerun-if-changed={}",
|
||||
frontend_dir.join("tsconfig.json").display()
|
||||
);
|
||||
println!("cargo:rerun-if-changed={}", frontend_dir.join("package-lock.json").display());
|
||||
println!("cargo:rerun-if-changed={}", frontend_dir.join("next.config.js").display());
|
||||
println!("cargo:rerun-if-changed={}", frontend_dir.join("tailwind.config.js").display());
|
||||
println!("cargo:rerun-if-changed={}", frontend_dir.join("postcss.config.js").display());
|
||||
println!("cargo:rerun-if-changed={}", frontend_dir.join("tsconfig.json").display());
|
||||
|
||||
let out_dir = frontend_dir.join("out");
|
||||
let need_build = needs_frontend_rebuild(&frontend_dir, &out_dir, &static_dir);
|
||||
@ -215,22 +229,24 @@ fn build_frontend() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut cmd = Command::new("npm");
|
||||
cmd.arg("install")
|
||||
.current_dir(&frontend_dir);
|
||||
let npm = which::which("npm")
|
||||
.unwrap_or_else(|_| panic!("npm not found in PATH. Install Node.js first."));
|
||||
|
||||
let status = cmd
|
||||
let status = Command::new(&npm)
|
||||
.arg("install")
|
||||
.current_dir(&frontend_dir)
|
||||
.status()
|
||||
.unwrap_or_else(|err| panic!("failed to run npm install: {err}"));
|
||||
if !status.success() {
|
||||
panic!("npm install failed with exit code: {:?}", status.code());
|
||||
}
|
||||
|
||||
let mut cmd = Command::new("npx");
|
||||
cmd.args(["next", "build"])
|
||||
.current_dir(&frontend_dir);
|
||||
let npx = which::which("npx")
|
||||
.unwrap_or_else(|_| panic!("npx not found in PATH. Install Node.js first."));
|
||||
|
||||
let status = cmd
|
||||
let status = Command::new(&npx)
|
||||
.args(["next", "build"])
|
||||
.current_dir(&frontend_dir)
|
||||
.status()
|
||||
.unwrap_or_else(|err| panic!("failed to run next build: {err}"));
|
||||
if !status.success() {
|
||||
@ -245,38 +261,24 @@ fn build_frontend() {
|
||||
copy_dir_all(&out_dir, &static_dir).unwrap_or_else(|err| panic!("failed to copy frontend build: {err}"));
|
||||
}
|
||||
|
||||
fn needs_frontend_rebuild(frontend_dir: &PathBuf, out_dir: &PathBuf, static_dir: &PathBuf) -> bool {
|
||||
if !out_dir.exists() {
|
||||
return true;
|
||||
}
|
||||
|
||||
if !static_dir.exists() {
|
||||
fn needs_frontend_rebuild(frontend_dir: &std::path::Path, out_dir: &std::path::Path, static_dir: &std::path::Path) -> bool {
|
||||
if !out_dir.exists() || !static_dir.exists() {
|
||||
return true;
|
||||
}
|
||||
|
||||
let out_modified = match fs::metadata(out_dir).and_then(|m| m.modified()) {
|
||||
Ok(time) => time,
|
||||
Err(_) => {
|
||||
return true;
|
||||
}
|
||||
Err(_) => return true,
|
||||
};
|
||||
|
||||
let static_modified = match fs::metadata(static_dir).and_then(|m| m.modified()) {
|
||||
Ok(time) => time,
|
||||
Err(_) => {
|
||||
return true;
|
||||
}
|
||||
Err(_) => return true,
|
||||
};
|
||||
|
||||
let essential_items = [
|
||||
"src",
|
||||
"public",
|
||||
"package.json",
|
||||
"next.config.js",
|
||||
"tailwind.config.js",
|
||||
"postcss.config.js",
|
||||
"tsconfig.json",
|
||||
"package-lock.json",
|
||||
"src", "public", "package.json", "next.config.js",
|
||||
"tailwind.config.js", "postcss.config.js", "tsconfig.json", "package-lock.json",
|
||||
];
|
||||
|
||||
for item_name in essential_items {
|
||||
@ -284,49 +286,39 @@ fn needs_frontend_rebuild(frontend_dir: &PathBuf, out_dir: &PathBuf, static_dir:
|
||||
if !item_path.exists() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let item_modified = match get_dir_last_modified(&item_path) {
|
||||
Some(time) => time,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
if item_modified > out_modified {
|
||||
if let Some(item_modified) = get_dir_last_modified(&item_path)
|
||||
&& item_modified > out_modified
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if out_modified > static_modified {
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
out_modified > static_modified
|
||||
}
|
||||
|
||||
fn get_dir_last_modified(path: &PathBuf) -> Option<SystemTime> {
|
||||
fn get_dir_last_modified(path: &std::path::Path) -> Option<SystemTime> {
|
||||
if path.is_file() {
|
||||
return fs::metadata(path).and_then(|m| m.modified()).ok();
|
||||
}
|
||||
|
||||
if path.is_dir() {
|
||||
let mut latest = fs::metadata(path).and_then(|m| m.modified()).ok()?;
|
||||
|
||||
if let Ok(entries) = fs::read_dir(path) {
|
||||
for entry in entries.flatten() {
|
||||
if let Some(modified) = get_dir_last_modified(&entry.path()) {
|
||||
if modified > latest {
|
||||
latest = modified;
|
||||
}
|
||||
if let Some(modified) = get_dir_last_modified(&entry.path())
|
||||
&& modified > latest
|
||||
{
|
||||
latest = modified;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Some(latest);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn copy_dir_all(src: &PathBuf, dst: &PathBuf) -> std::io::Result<()> {
|
||||
fn copy_dir_all(src: &std::path::Path, dst: &std::path::Path) -> std::io::Result<()> {
|
||||
for entry in fs::read_dir(src)? {
|
||||
let entry = entry?;
|
||||
let file_type = entry.file_type()?;
|
||||
|
||||
@ -86,7 +86,7 @@ impl Database {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_acl_rules(&self) -> Result<Vec<(u8, String, String, String, u16)>, Error> {
|
||||
pub fn load_acl_rules(&self) -> Result<Vec<crate::interface::port::repository::AclRuleTuple>, Error> {
|
||||
let conn = self.conn.lock();
|
||||
let mut stmt = conn.prepare("SELECT ip_version, direction, list_type, ip_address, port FROM acl_rules")?;
|
||||
let rows = stmt.query_map([], |row| {
|
||||
@ -201,7 +201,7 @@ impl Database {
|
||||
}
|
||||
|
||||
// --- Users ---
|
||||
pub fn find_user(&self, username: &str) -> Result<Option<(i64, String, String, String, bool)>, Error> {
|
||||
pub fn find_user(&self, username: &str) -> Result<Option<crate::interface::port::repository::UserTuple>, Error> {
|
||||
let conn = self.conn.lock();
|
||||
let result = conn.query_row(
|
||||
"SELECT id, username, password_hash, role, force_password_change FROM users WHERE username = ?1",
|
||||
@ -270,8 +270,8 @@ impl Database {
|
||||
|
||||
pub fn check_login_locked(&self, username: &str) -> Result<Option<u64>, Error> {
|
||||
let key_locked = format!("login_locked_until:{}", username);
|
||||
if let Some(locked_str) = self.get_setting(&key_locked)? {
|
||||
if let Ok(locked_until) = locked_str.parse::<u64>() {
|
||||
if let Some(locked_str) = self.get_setting(&key_locked)?
|
||||
&& let Ok(locked_until) = locked_str.parse::<u64>() {
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
@ -281,7 +281,6 @@ impl Database {
|
||||
}
|
||||
// Lock expired, clear it
|
||||
self.clear_login_failures(username)?;
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
@ -299,7 +298,7 @@ impl Database {
|
||||
impl crate::interface::port::repository::RepositoryPort for Database {
|
||||
fn insert_acl_rule(&self, ip_version: u8, direction: &str, list_type: &str, ip_address: &str, port: u16) -> Result<(), Error> { self.insert_acl_rule(ip_version, direction, list_type, ip_address, port) }
|
||||
fn delete_acl_rule(&self, ip_version: u8, direction: &str, list_type: &str, ip_address: &str, port: u16) -> Result<(), Error> { self.delete_acl_rule(ip_version, direction, list_type, ip_address, port) }
|
||||
fn load_acl_rules(&self) -> Result<Vec<(u8, String, String, String, u16)>, Error> { self.load_acl_rules() }
|
||||
fn load_acl_rules(&self) -> Result<Vec<crate::interface::port::repository::AclRuleTuple>, Error> { self.load_acl_rules() }
|
||||
fn set_rate_limit(&self, key: &str, value: u64) -> Result<(), Error> { self.set_rate_limit(key, value) }
|
||||
fn load_rate_limit_config(&self) -> Result<Vec<(String, u64)>, Error> { self.load_rate_limit_config() }
|
||||
fn insert_dns_domain(&self, domain: &str) -> Result<(), Error> { self.insert_dns_domain(domain) }
|
||||
@ -310,7 +309,7 @@ impl crate::interface::port::repository::RepositoryPort for Database {
|
||||
fn load_geo_countries(&self) -> Result<Vec<String>, Error> { self.load_geo_countries() }
|
||||
fn get_setting(&self, key: &str) -> Result<Option<String>, Error> { self.get_setting(key) }
|
||||
fn set_setting(&self, key: &str, value: &str) -> Result<(), Error> { self.set_setting(key, value) }
|
||||
fn find_user(&self, username: &str) -> Result<Option<(i64, String, String, String, bool)>, Error> { self.find_user(username) }
|
||||
fn find_user(&self, username: &str) -> Result<Option<crate::interface::port::repository::UserTuple>, Error> { self.find_user(username) }
|
||||
fn insert_user(&self, username: &str, password_hash: &str, role: &str, force_password_change: bool) -> Result<(), Error> { self.insert_user(username, password_hash, role, force_password_change) }
|
||||
fn update_user_password(&self, user_id: i64, password_hash: &str) -> Result<(), Error> { self.update_user_password(user_id, password_hash) }
|
||||
fn user_count(&self) -> Result<i64, Error> { self.user_count() }
|
||||
|
||||
@ -35,10 +35,9 @@ pub async fn flow_stats_ws(
|
||||
tokio::select! {
|
||||
_ = ticker.tick() => {
|
||||
let flows = stats.get_filtered_flows(&subscription);
|
||||
if let Ok(json) = serde_json::to_string(&flows) {
|
||||
if session.text(json).await.is_err() {
|
||||
if let Ok(json) = serde_json::to_string(&flows)
|
||||
&& session.text(json).await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
msg = msg_stream.next() => {
|
||||
@ -52,10 +51,9 @@ pub async fn flow_stats_ws(
|
||||
ticker = interval(Duration::from_secs(new_interval));
|
||||
|
||||
let flows = stats.get_filtered_flows(&subscription);
|
||||
if let Ok(json) = serde_json::to_string(&flows) {
|
||||
if session.text(json).await.is_err() {
|
||||
if let Ok(json) = serde_json::to_string(&flows)
|
||||
&& session.text(json).await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
|
||||
@ -75,8 +75,8 @@ where
|
||||
let token = match auth_header {
|
||||
Some(val) => {
|
||||
let val_str = val.to_str().unwrap_or("");
|
||||
if val_str.starts_with("Bearer ") {
|
||||
&val_str[7..]
|
||||
if let Some(token_str) = val_str.strip_prefix("Bearer ") {
|
||||
token_str
|
||||
} else {
|
||||
let resp = HttpResponse::Unauthorized()
|
||||
.json(serde_json::json!({"error": "Invalid authorization header"}));
|
||||
|
||||
@ -33,7 +33,7 @@ impl DnsFilter {
|
||||
self.blacklist
|
||||
.read()
|
||||
.iter()
|
||||
.filter_map(|name| wire_format_to_domain(name))
|
||||
.filter_map(wire_format_to_domain)
|
||||
.collect()
|
||||
}
|
||||
|
||||
@ -169,7 +169,7 @@ impl DnsFilter {
|
||||
out += 1;
|
||||
for j in 0..ll {
|
||||
let mut b = raw[pos + 1 + j];
|
||||
if b >= b'A' && b <= b'Z' {
|
||||
if b.is_ascii_uppercase() {
|
||||
b += 32;
|
||||
}
|
||||
name.data[out] = b;
|
||||
|
||||
@ -70,7 +70,8 @@ impl EbpfServices {
|
||||
let xsk_manager = self.xsk_manager.clone();
|
||||
xsk_manager.run(Some(ml_engine), Some(self.dns_filter.clone()), &self.shutdowns)?;
|
||||
|
||||
if let Some(ring_buf) = self.drop_ring_buf.lock().take() {
|
||||
let ring_buf = self.drop_ring_buf.lock().take();
|
||||
if let Some(ring_buf) = ring_buf {
|
||||
let shutdown = drop_monitor::start_consumer(ring_buf, self.drop_monitor.clone()).await;
|
||||
self.shutdowns.push(shutdown);
|
||||
}
|
||||
|
||||
@ -191,11 +191,7 @@ impl WhiteListControl {
|
||||
fn is_white_list_enable(&self) -> bool {
|
||||
match self.map.get(&0, 0) {
|
||||
Ok(status) => {
|
||||
if status == 0 {
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
status != 0
|
||||
}
|
||||
Err(_) => false,
|
||||
}
|
||||
@ -253,7 +249,7 @@ impl<T: NativeConvert + Pod> HttpServiceWrapper<T> {
|
||||
} else {
|
||||
let new_http_method = HttpMethod::convert_to_bitmap(http_method);
|
||||
self.map
|
||||
.insert(&address, new_http_method, 0)
|
||||
.insert(address, new_http_method, 0)
|
||||
.map_err(EbpfError::MapOperationError)?;
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@ -314,7 +314,7 @@ impl XskPair {
|
||||
|
||||
for rx_desc in rx_descs.iter().take(rx_count) {
|
||||
let lengths = rx_desc.lengths();
|
||||
let packet_len = lengths.data() as usize;
|
||||
let packet_len = lengths.data();
|
||||
|
||||
let data = unsafe { self.umem.data(rx_desc) };
|
||||
let contents = data.contents();
|
||||
@ -326,20 +326,17 @@ impl XskPair {
|
||||
let raw = &contents[..packet_len];
|
||||
|
||||
// DNS blacklist check — drop blacklisted DNS queries before forwarding
|
||||
if let Some(ref dns) = self.dns_filter {
|
||||
if let Some((dns_name, name_len)) = DnsFilter::parse_query_name(raw) {
|
||||
if dns.is_blacklisted(&dns_name, name_len) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if let Some(ref dns) = self.dns_filter
|
||||
&& let Some((dns_name, name_len)) = DnsFilter::parse_query_name(raw)
|
||||
&& dns.is_blacklisted(&dns_name, name_len) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse directly from UMEM (zero-copy for ML path).
|
||||
// Only clone for the forwarding path afterwards.
|
||||
if let Some(ref tracker) = self.tracker {
|
||||
if let Some((packet_info, _)) = parse_packet(raw) {
|
||||
if let Some(ref tracker) = self.tracker
|
||||
&& let Some((packet_info, _)) = parse_packet(raw) {
|
||||
tracker.lock().process_packet(packet_info, is_ingress);
|
||||
}
|
||||
}
|
||||
|
||||
// Clone into pooled buffer for forwarding
|
||||
@ -428,10 +425,9 @@ impl XskPair {
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(e) = self.tx.wakeup() {
|
||||
if e.kind() != std::io::ErrorKind::WouldBlock {
|
||||
if let Err(e) = self.tx.wakeup()
|
||||
&& e.kind() != std::io::ErrorKind::WouldBlock {
|
||||
log!(EbpfLog::TXWakeupFailed(e.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
// Log dropped packets when frames < packets
|
||||
|
||||
@ -1,5 +1,2 @@
|
||||
pub mod report;
|
||||
pub mod scheduler;
|
||||
|
||||
pub use report::generate_weekly_report;
|
||||
pub use scheduler::ReportScheduler;
|
||||
|
||||
@ -43,10 +43,9 @@ impl FlowFeatures {
|
||||
|
||||
pub fn winsorize(&mut self, clip_params: &HashMap<String, ClipParams>, feature_names: &[String]) {
|
||||
for (i, feature_name) in feature_names.iter().enumerate() {
|
||||
if i < self.feature_num {
|
||||
if let Some(params) = clip_params.get(feature_name) {
|
||||
if i < self.feature_num
|
||||
&& let Some(params) = clip_params.get(feature_name) {
|
||||
self.features[i] = self.features[i].clamp(params.lower, params.upper);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,9 +95,8 @@ impl FlowData {
|
||||
|
||||
if iat > IDLE_THRESHOLD_US {
|
||||
if self.idle_periods.len() < MAX_PERIODS { self.idle_periods.push(iat); }
|
||||
} else if iat > 0 {
|
||||
if self.active_periods.len() < MAX_PERIODS { self.active_periods.push(iat); }
|
||||
}
|
||||
} else if iat > 0
|
||||
&& self.active_periods.len() < MAX_PERIODS { self.active_periods.push(iat); }
|
||||
|
||||
self.last_packet_time = packet.timestamp_us;
|
||||
self.last_time_us = packet.timestamp_us;
|
||||
@ -239,13 +238,12 @@ impl FlowTracker {
|
||||
|
||||
flow.add_packet(&packet);
|
||||
|
||||
if self.active.len() > self.max_flows {
|
||||
if let Some(oldest_key) = self.active.iter()
|
||||
if self.active.len() > self.max_flows
|
||||
&& let Some(oldest_key) = self.active.iter()
|
||||
.min_by_key(|(_, flow)| flow.last_time_us)
|
||||
.map(|(k, _)| k.clone())
|
||||
{
|
||||
self.active.remove(&oldest_key);
|
||||
}
|
||||
{
|
||||
self.active.remove(&oldest_key);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -24,7 +24,7 @@ impl MLModels {
|
||||
|
||||
let load = || -> Result<RunnableModel, Box<dyn std::error::Error>> {
|
||||
let mut model = onnx().model_for_path(&model_path)?;
|
||||
model.set_input_fact(0, f32::fact(&[1, features]).into())?;
|
||||
model.set_input_fact(0, f32::fact([1, features]).into())?;
|
||||
Ok(model.into_optimized()?.into_runnable()?)
|
||||
};
|
||||
|
||||
|
||||
@ -90,10 +90,9 @@ impl SystemHealth {
|
||||
drop(networks);
|
||||
drop(components);
|
||||
|
||||
if self.broadcast_tx.receiver_count() > 0 {
|
||||
if let Err(e) = self.broadcast_tx.send(metrics) {
|
||||
if self.broadcast_tx.receiver_count() > 0
|
||||
&& let Err(e) = self.broadcast_tx.send(metrics) {
|
||||
log!(Health::BroadcastFailed(e.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -174,7 +174,7 @@ impl ServiceFactory {
|
||||
|
||||
if stages.is_empty() {
|
||||
next_stage
|
||||
.set(STAGE_ENTRY as u32, STAGE_TRANSMISSION, 0)
|
||||
.set(STAGE_ENTRY, STAGE_TRANSMISSION, 0)
|
||||
.map_err(EbpfError::MapOperationError)?;
|
||||
return Ok(program_array);
|
||||
}
|
||||
@ -190,7 +190,7 @@ impl ServiceFactory {
|
||||
}
|
||||
|
||||
next_stage
|
||||
.set(STAGE_ENTRY as u32, slots[0].1, 0)
|
||||
.set(STAGE_ENTRY, slots[0].1, 0)
|
||||
.map_err(EbpfError::MapOperationError)?;
|
||||
|
||||
for i in 0..slots.len() {
|
||||
@ -201,7 +201,7 @@ impl ServiceFactory {
|
||||
STAGE_TRANSMISSION
|
||||
};
|
||||
next_stage
|
||||
.set(stage_id as u32, next_slot, 0)
|
||||
.set(stage_id, next_slot, 0)
|
||||
.map_err(EbpfError::MapOperationError)?;
|
||||
}
|
||||
|
||||
@ -315,14 +315,13 @@ impl ServiceFactory {
|
||||
}
|
||||
|
||||
fn restore_geo_countries(db: &Database, ebpf_services: &EbpfServices) {
|
||||
if let Ok(countries) = db.load_geo_countries() {
|
||||
if !countries.is_empty() {
|
||||
if let Ok(countries) = db.load_geo_countries()
|
||||
&& !countries.is_empty() {
|
||||
if let Err(e) = ebpf_services.geo_block.block_countries(&countries) {
|
||||
tracing::warn!("Failed to restore geo-blocked countries: {}", e);
|
||||
} else {
|
||||
tracing::info!("Restored {} geo-blocked countries from database", countries.len());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
use crate::model::error::Error;
|
||||
use crate::model::health::{SystemHealthMetrics, SystemHealthStatus};
|
||||
use async_trait::async_trait;
|
||||
|
||||
|
||||
@ -1,12 +1,18 @@
|
||||
use crate::model::error::Error;
|
||||
|
||||
/// Type alias for ACL rule tuples: (ip_version, direction, list_type, ip_address, port)
|
||||
pub type AclRuleTuple = (u8, String, String, String, u16);
|
||||
|
||||
/// Type alias for user record tuples: (id, username, password_hash, role, force_password_change)
|
||||
pub type UserTuple = (i64, String, String, String, bool);
|
||||
|
||||
/// Port for persistent storage operations.
|
||||
/// Adapters: SQLite (current), could be Postgres, etc.
|
||||
pub trait RepositoryPort: Send + Sync {
|
||||
// --- ACL ---
|
||||
fn insert_acl_rule(&self, ip_version: u8, direction: &str, list_type: &str, ip_address: &str, port: u16) -> Result<(), Error>;
|
||||
fn delete_acl_rule(&self, ip_version: u8, direction: &str, list_type: &str, ip_address: &str, port: u16) -> Result<(), Error>;
|
||||
fn load_acl_rules(&self) -> Result<Vec<(u8, String, String, String, u16)>, Error>;
|
||||
fn load_acl_rules(&self) -> Result<Vec<AclRuleTuple>, Error>;
|
||||
|
||||
// --- Rate Limit ---
|
||||
fn set_rate_limit(&self, key: &str, value: u64) -> Result<(), Error>;
|
||||
@ -27,7 +33,7 @@ pub trait RepositoryPort: Send + Sync {
|
||||
fn set_setting(&self, key: &str, value: &str) -> Result<(), Error>;
|
||||
|
||||
// --- Users ---
|
||||
fn find_user(&self, username: &str) -> Result<Option<(i64, String, String, String, bool)>, Error>;
|
||||
fn find_user(&self, username: &str) -> Result<Option<UserTuple>, Error>;
|
||||
fn insert_user(&self, username: &str, password_hash: &str, role: &str, force_password_change: bool) -> Result<(), Error>;
|
||||
fn update_user_password(&self, user_id: i64, password_hash: &str) -> Result<(), Error>;
|
||||
fn user_count(&self) -> Result<i64, Error>;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user