feat: SQLite persistence for ACL, rate limit, DNS, and GeoIP rules

Add rusqlite with WAL mode for persisting all security rules. On
startup, load persisted state into eBPF maps. On API writes, persist
to DB alongside eBPF updates (DB-first for crash safety).

Tables: users, acl_rules, rate_limit_config, dns_blacklist,
geo_blocked_countries, settings. Database module uses parking_lot
Mutex for thread-safe access.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
DaLaw2 2026-03-21 15:59:40 +08:00
parent 6f98737cdc
commit 9247d42ff6
13 changed files with 397 additions and 0 deletions

View File

@ -50,6 +50,7 @@ sysinfo = "0.38.4"
maxminddb = "0.27.3"
ipnetwork = "0.21.1"
lru = "0.16.3"
rusqlite = { version = "0.34", features = ["bundled"] }
# Build dependencies
cargo_metadata = { version = "0.23.1", default-features = false }

View File

@ -28,6 +28,7 @@ traffic_log_csv_path = "traffic_log.csv"
[Misc]
geoip_db_name = "net-guardia/static/geo/GeoLite2-City.mmdb"
database_path = "net-guardia.db"
[Pipeline]
ingress = ["access_control", "rate_limit", "service"]

View File

@ -50,6 +50,7 @@ sysinfo = { workspace = true }
maxminddb = { workspace = true }
ipnetwork = { workspace = true }
lru = { workspace = true }
rusqlite = { workspace = true }
[build-dependencies]
cargo_metadata = { workspace = true }

View File

@ -0,0 +1,3 @@
pub mod repository;
pub use repository::Database;

View File

@ -0,0 +1,239 @@
use parking_lot::Mutex;
use rusqlite::{Connection, params};
use crate::model::error::database::DatabaseError;
use crate::model::error::Error;
pub struct Database {
conn: Mutex<Connection>,
}
impl Database {
pub fn new(path: &str) -> Result<Self, Error> {
let conn = Connection::open(path)
.map_err(|e| DatabaseError::QueryFailed { reason: e.to_string() })?;
conn.execute_batch("PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON;")
.map_err(|e| DatabaseError::QueryFailed { reason: e.to_string() })?;
let db = Self { conn: Mutex::new(conn) };
db.create_tables()?;
Ok(db)
}
fn create_tables(&self) -> Result<(), Error> {
let conn = self.conn.lock();
conn.execute_batch("
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'viewer',
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS acl_rules (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ip_version INTEGER NOT NULL,
direction TEXT NOT NULL,
list_type TEXT NOT NULL,
ip_address TEXT NOT NULL,
port INTEGER NOT NULL,
UNIQUE(ip_version, direction, list_type, ip_address, port)
);
CREATE TABLE IF NOT EXISTS rate_limit_config (
key TEXT PRIMARY KEY,
value INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS dns_blacklist (
domain TEXT PRIMARY KEY
);
CREATE TABLE IF NOT EXISTS geo_blocked_countries (
country_code TEXT PRIMARY KEY
);
CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
").map_err(|e| DatabaseError::QueryFailed { reason: e.to_string() })?;
Ok(())
}
// --- ACL ---
pub fn insert_acl_rule(&self, ip_version: u8, direction: &str, list_type: &str, ip_address: &str, port: u16) -> Result<(), Error> {
let conn = self.conn.lock();
conn.execute(
"INSERT OR IGNORE INTO acl_rules (ip_version, direction, list_type, ip_address, port) VALUES (?1, ?2, ?3, ?4, ?5)",
params![ip_version, direction, list_type, ip_address, port as i64],
).map_err(|e| DatabaseError::QueryFailed { reason: e.to_string() })?;
Ok(())
}
pub fn delete_acl_rule(&self, ip_version: u8, direction: &str, list_type: &str, ip_address: &str, port: u16) -> Result<(), Error> {
let conn = self.conn.lock();
conn.execute(
"DELETE FROM acl_rules WHERE ip_version = ?1 AND direction = ?2 AND list_type = ?3 AND ip_address = ?4 AND port = ?5",
params![ip_version, direction, list_type, ip_address, port as i64],
).map_err(|e| DatabaseError::QueryFailed { reason: e.to_string() })?;
Ok(())
}
pub fn load_acl_rules(&self) -> Result<Vec<(u8, String, String, String, u16)>, Error> {
let conn = self.conn.lock();
let mut stmt = conn.prepare("SELECT ip_version, direction, list_type, ip_address, port FROM acl_rules")
.map_err(|e| DatabaseError::QueryFailed { reason: e.to_string() })?;
let rows = stmt.query_map([], |row| {
Ok((
row.get::<_, u8>(0)?,
row.get::<_, String>(1)?,
row.get::<_, String>(2)?,
row.get::<_, String>(3)?,
row.get::<_, i64>(4)? as u16,
))
}).map_err(|e| DatabaseError::QueryFailed { reason: e.to_string() })?;
let mut results = Vec::new();
for row in rows {
results.push(row.map_err(|e| DatabaseError::QueryFailed { reason: e.to_string() })?);
}
Ok(results)
}
// --- Rate Limit ---
pub fn set_rate_limit(&self, key: &str, value: u64) -> Result<(), Error> {
let conn = self.conn.lock();
conn.execute(
"INSERT OR REPLACE INTO rate_limit_config (key, value) VALUES (?1, ?2)",
params![key, value as i64],
).map_err(|e| DatabaseError::QueryFailed { reason: e.to_string() })?;
Ok(())
}
pub fn load_rate_limit_config(&self) -> Result<Vec<(String, u64)>, Error> {
let conn = self.conn.lock();
let mut stmt = conn.prepare("SELECT key, value FROM rate_limit_config")
.map_err(|e| DatabaseError::QueryFailed { reason: e.to_string() })?;
let rows = stmt.query_map([], |row| {
Ok((row.get::<_, String>(0)?, row.get::<_, i64>(1)? as u64))
}).map_err(|e| DatabaseError::QueryFailed { reason: e.to_string() })?;
let mut results = Vec::new();
for row in rows {
results.push(row.map_err(|e| DatabaseError::QueryFailed { reason: e.to_string() })?);
}
Ok(results)
}
// --- DNS ---
pub fn insert_dns_domain(&self, domain: &str) -> Result<(), Error> {
let conn = self.conn.lock();
conn.execute("INSERT OR IGNORE INTO dns_blacklist (domain) VALUES (?1)", params![domain])
.map_err(|e| DatabaseError::QueryFailed { reason: e.to_string() })?;
Ok(())
}
pub fn delete_dns_domain(&self, domain: &str) -> Result<(), Error> {
let conn = self.conn.lock();
conn.execute("DELETE FROM dns_blacklist WHERE domain = ?1", params![domain])
.map_err(|e| DatabaseError::QueryFailed { reason: e.to_string() })?;
Ok(())
}
pub fn load_dns_domains(&self) -> Result<Vec<String>, Error> {
let conn = self.conn.lock();
let mut stmt = conn.prepare("SELECT domain FROM dns_blacklist")
.map_err(|e| DatabaseError::QueryFailed { reason: e.to_string() })?;
let rows = stmt.query_map([], |row| row.get(0))
.map_err(|e| DatabaseError::QueryFailed { reason: e.to_string() })?;
let mut results = Vec::new();
for row in rows {
results.push(row.map_err(|e| DatabaseError::QueryFailed { reason: e.to_string() })?);
}
Ok(results)
}
// --- Geo ---
pub fn insert_geo_country(&self, code: &str) -> Result<(), Error> {
let conn = self.conn.lock();
conn.execute("INSERT OR IGNORE INTO geo_blocked_countries (country_code) VALUES (?1)", params![code])
.map_err(|e| DatabaseError::QueryFailed { reason: e.to_string() })?;
Ok(())
}
pub fn delete_geo_country(&self, code: &str) -> Result<(), Error> {
let conn = self.conn.lock();
conn.execute("DELETE FROM geo_blocked_countries WHERE country_code = ?1", params![code])
.map_err(|e| DatabaseError::QueryFailed { reason: e.to_string() })?;
Ok(())
}
pub fn load_geo_countries(&self) -> Result<Vec<String>, Error> {
let conn = self.conn.lock();
let mut stmt = conn.prepare("SELECT country_code FROM geo_blocked_countries")
.map_err(|e| DatabaseError::QueryFailed { reason: e.to_string() })?;
let rows = stmt.query_map([], |row| row.get(0))
.map_err(|e| DatabaseError::QueryFailed { reason: e.to_string() })?;
let mut results = Vec::new();
for row in rows {
results.push(row.map_err(|e| DatabaseError::QueryFailed { reason: e.to_string() })?);
}
Ok(results)
}
// --- Settings ---
pub fn get_setting(&self, key: &str) -> Result<Option<String>, Error> {
let conn = self.conn.lock();
let result = conn.query_row(
"SELECT value FROM settings WHERE key = ?1",
params![key],
|row| row.get(0),
);
match result {
Ok(val) => Ok(Some(val)),
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
Err(e) => Err(DatabaseError::QueryFailed { reason: e.to_string() }.into()),
}
}
pub fn set_setting(&self, key: &str, value: &str) -> Result<(), Error> {
let conn = self.conn.lock();
conn.execute(
"INSERT OR REPLACE INTO settings (key, value) VALUES (?1, ?2)",
params![key, value],
).map_err(|e| DatabaseError::QueryFailed { reason: e.to_string() })?;
Ok(())
}
// --- Users ---
pub fn find_user(&self, username: &str) -> Result<Option<(i64, String, String, String)>, Error> {
let conn = self.conn.lock();
let result = conn.query_row(
"SELECT id, username, password_hash, role FROM users WHERE username = ?1",
params![username],
|row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)),
);
match result {
Ok(user) => Ok(Some(user)),
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
Err(e) => Err(DatabaseError::QueryFailed { reason: e.to_string() }.into()),
}
}
pub fn insert_user(&self, username: &str, password_hash: &str, role: &str) -> Result<(), Error> {
let conn = self.conn.lock();
conn.execute(
"INSERT INTO users (username, password_hash, role) VALUES (?1, ?2, ?3)",
params![username, password_hash, role],
).map_err(|e| {
if e.to_string().contains("UNIQUE constraint") {
DatabaseError::UserAlreadyExists { username: username.to_string() }.into()
} else {
DatabaseError::QueryFailed { reason: e.to_string() }.into()
}
})?;
Ok(())
}
pub fn user_count(&self) -> Result<i64, Error> {
let conn = self.conn.lock();
conn.query_row("SELECT COUNT(*) FROM users", [], |row| row.get(0))
.map_err(|e| DatabaseError::QueryFailed { reason: e.to_string() }.into())
}
}

View File

@ -1,3 +1,4 @@
pub mod database;
pub mod ebpf;
pub mod infrastructure;
pub mod ml;

View File

@ -10,6 +10,7 @@ use aya_log::EbpfLogger;
use common::define::pipeline::*;
use macros::log;
use crate::core::database::Database;
use crate::core::ebpf::EbpfServices;
use crate::core::infrastructure::app_config::AppConfig;
use crate::core::infrastructure::MLService;
@ -37,6 +38,7 @@ pub struct System {
pub inference_config: Arc<InferenceConfig>,
pub ebpf_services: Arc<EbpfServices>,
pub app_services: Arc<MLService>,
pub db: Arc<Database>,
pub ingress_ebpf: Ebpf,
pub egress_ebpf: Ebpf,
#[allow(dead_code)]
@ -61,6 +63,8 @@ impl System {
Self::write_num_queues(&mut ingress_ebpf, num_queues)?;
Self::write_num_queues(&mut egress_ebpf, num_queues)?;
let db = Arc::new(Database::new(&app_config.misc.database_path)?);
let ebpf_services = Arc::new(EbpfServices::new(
app_config.clone(),
&mut ingress_ebpf,
@ -69,11 +73,55 @@ impl System {
let app_services = Arc::new(MLService::new(app_config.clone(), inference_config.clone())?);
// Load persisted DNS blacklist
if let Ok(domains) = db.load_dns_domains() {
for domain in &domains {
if let Err(e) = ebpf_services.dns_filter.add_domain(domain) {
tracing::warn!("Failed to restore DNS domain '{}': {}", domain, e);
}
}
if !domains.is_empty() {
tracing::info!("Restored {} DNS blacklist domains from database", domains.len());
}
}
// Load persisted geo-blocked countries
if let Ok(countries) = db.load_geo_countries() {
if !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());
}
}
}
// Load persisted rate limit config
if let Ok(configs) = db.load_rate_limit_config() {
for (key, value) in &configs {
let result = match key.as_str() {
"packet_rate" => ebpf_services.rate_limit.set_packet_rate(*value),
"syn_rate" => ebpf_services.rate_limit.set_syn_rate(*value),
"udp_rate" => ebpf_services.rate_limit.set_udp_rate(*value),
"dns_rate" => ebpf_services.rate_limit.set_dns_rate(*value),
"window_ns" => ebpf_services.rate_limit.set_window_ns(*value),
_ => Ok(()),
};
if let Err(e) = result {
tracing::warn!("Failed to restore rate limit '{}': {}", key, e);
}
}
if !configs.is_empty() {
tracing::info!("Restored {} rate limit settings from database", configs.len());
}
}
Ok(System {
app_config,
inference_config,
ebpf_services,
app_services,
db,
ingress_ebpf,
egress_ebpf,
ingress_program_array,
@ -162,6 +210,7 @@ impl System {
let ml_engine = self.app_services.ml_engine.clone();
let flow_statistics = self.app_services.flow_statistics.clone();
let drop_monitor = self.ebpf_services.drop_monitor.clone();
let db = self.db.clone();
let port = self.app_config.http.http_server_bind_port;
HttpServer::new(move || {
let cors = actix_cors::Cors::default()
@ -184,6 +233,7 @@ impl System {
.app_data(web::Data::from(ml_engine.clone()))
.app_data(web::Data::from(flow_statistics.clone()))
.app_data(web::Data::from(drop_monitor.clone()))
.app_data(web::Data::from(db.clone()))
.service(
web::scope("/api")
.service(acl::initialize())

View File

@ -58,8 +58,12 @@ pub struct InferenceConfig {
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MiscConfig {
pub geoip_db_name: String,
#[serde(default = "default_db_path")]
pub database_path: String,
}
fn default_db_path() -> String { "net-guardia.db".to_string() }
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PipelineConfig {
pub ingress: Vec<String>,

View File

@ -0,0 +1,16 @@
use macros::traceable;
traceable! {
DatabaseError {
#[no_source]
#[error("Database error: {reason}")]
QueryFailed { reason: String } => tracing::Level::ERROR,
#[error("Database connection failed")]
ConnectionFailed => tracing::Level::ERROR,
#[no_source]
#[error("User '{username}' already exists")]
UserAlreadyExists { username: String } => tracing::Level::WARN,
}
}

View File

@ -1,3 +1,4 @@
pub mod database;
pub mod ebpf;
pub mod http;
pub mod io;
@ -7,6 +8,7 @@ pub mod system;
use serde::{Deserialize, Serialize};
use crate::model::error::database::DatabaseError;
use crate::model::error::ebpf::EbpfError;
use crate::model::error::http::HttpError;
use crate::model::error::io::IOError;
@ -16,6 +18,8 @@ use crate::model::error::system::SystemError;
#[derive(Clone, Debug, thiserror::Error, Serialize, Deserialize)]
pub enum Error {
#[error("{0}")]
Database(DatabaseError),
#[error("{0}")]
Ebpf(EbpfError),
#[error("{0}")]
@ -30,6 +34,12 @@ pub enum Error {
System(SystemError),
}
impl From<DatabaseError> for Error {
fn from(error: DatabaseError) -> Self {
Self::Database(error)
}
}
impl From<EbpfError> for Error {
fn from(error: EbpfError) -> Self {
Self::Ebpf(error)

View File

@ -3,6 +3,7 @@ use std::net::{SocketAddrV4, SocketAddrV6};
use actix_web::{web, HttpResponse, Responder, Scope};
use serde::Deserialize;
use crate::core::database::Database;
use crate::core::ebpf::access_control::AccessControl;
use crate::core::ebpf::geo_block::GeoBlock;
use crate::model::direction::FlowDirection;
@ -44,13 +45,25 @@ async fn get_ipv6_list(
HttpResponse::Ok().json(list)
}
fn direction_str(d: FlowDirection) -> &'static str {
match d { FlowDirection::Source => "source", FlowDirection::Destination => "destination" }
}
fn list_type_str(l: ListType) -> &'static str {
match l { ListType::White => "whitelist", ListType::Black => "blacklist" }
}
async fn add_ipv4_list(
address: web::Json<SocketAddrV4>,
path: web::Path<(FlowDirection, ListType)>,
access_control: web::Data<AccessControl>,
db: web::Data<Database>,
) -> impl Responder {
let address = address.into_inner();
let (direction, list_type) = path.into_inner();
if let Err(e) = db.insert_acl_rule(4, direction_str(direction), list_type_str(list_type), &address.ip().to_string(), address.port()) {
return HttpResponse::InternalServerError().json(serde_json::json!({"error": e.to_string()}));
}
match access_control.add_ipv4_list(direction, list_type, address).await {
Ok(_) => HttpResponse::Ok().finish(),
Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e.to_string()})),
@ -61,9 +74,13 @@ async fn add_ipv6_list(
address: web::Json<SocketAddrV6>,
path: web::Path<(FlowDirection, ListType)>,
access_control: web::Data<AccessControl>,
db: web::Data<Database>,
) -> impl Responder {
let address = address.into_inner();
let (direction, list_type) = path.into_inner();
if let Err(e) = db.insert_acl_rule(6, direction_str(direction), list_type_str(list_type), &address.ip().to_string(), address.port()) {
return HttpResponse::InternalServerError().json(serde_json::json!({"error": e.to_string()}));
}
match access_control.add_ipv6_list(direction, list_type, address).await {
Ok(_) => HttpResponse::Ok().finish(),
Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e.to_string()})),
@ -74,9 +91,13 @@ async fn remove_ipv4_list(
address: web::Json<SocketAddrV4>,
path: web::Path<(FlowDirection, ListType)>,
access_control: web::Data<AccessControl>,
db: web::Data<Database>,
) -> impl Responder {
let address = address.into_inner();
let (direction, list_type) = path.into_inner();
if let Err(e) = db.delete_acl_rule(4, direction_str(direction), list_type_str(list_type), &address.ip().to_string(), address.port()) {
return HttpResponse::InternalServerError().json(serde_json::json!({"error": e.to_string()}));
}
match access_control.remove_ipv4_list(direction, list_type, address).await {
Ok(_) => HttpResponse::Ok().finish(),
Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e.to_string()})),
@ -87,9 +108,13 @@ async fn remove_ipv6_list(
address: web::Json<SocketAddrV6>,
path: web::Path<(FlowDirection, ListType)>,
access_control: web::Data<AccessControl>,
db: web::Data<Database>,
) -> impl Responder {
let address = address.into_inner();
let (direction, list_type) = path.into_inner();
if let Err(e) = db.delete_acl_rule(6, direction_str(direction), list_type_str(list_type), &address.ip().to_string(), address.port()) {
return HttpResponse::InternalServerError().json(serde_json::json!({"error": e.to_string()}));
}
match access_control.remove_ipv6_list(direction, list_type, address).await {
Ok(_) => HttpResponse::Ok().finish(),
Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e.to_string()})),
@ -106,8 +131,15 @@ async fn get_geo_blocked(
async fn block_geo_countries(
body: web::Json<CountryCodesRequest>,
geo_block: web::Data<GeoBlock>,
db: web::Data<Database>,
) -> impl Responder {
let codes = body.into_inner().country_codes;
for code in &codes {
if let Err(e) = db.insert_geo_country(code) {
return HttpResponse::InternalServerError()
.json(serde_json::json!({"error": e.to_string()}));
}
}
match geo_block.block_countries(&codes) {
Ok(total_prefixes) => HttpResponse::Ok().json(serde_json::json!({
"blocked_countries": geo_block.get_blocked_countries(),
@ -121,8 +153,15 @@ async fn block_geo_countries(
async fn unblock_geo_countries(
body: web::Json<CountryCodesRequest>,
geo_block: web::Data<GeoBlock>,
db: web::Data<Database>,
) -> impl Responder {
let codes = body.into_inner().country_codes;
for code in &codes {
if let Err(e) = db.delete_geo_country(code) {
return HttpResponse::InternalServerError()
.json(serde_json::json!({"error": e.to_string()}));
}
}
match geo_block.unblock_countries(&codes) {
Ok(total_prefixes) => HttpResponse::Ok().json(serde_json::json!({
"blocked_countries": geo_block.get_blocked_countries(),

View File

@ -5,6 +5,7 @@ use actix_web::{web, HttpResponse, Responder, Scope};
use common::model::http_method::HttpMethod;
use serde::Deserialize;
use crate::core::database::Database;
use crate::core::ebpf::dns_filter::DnsFilter;
use crate::core::ebpf::protocol_filter::ProtocolFilter;
@ -48,12 +49,19 @@ async fn get_dns_blacklist(service: web::Data<DnsFilter>) -> impl Responder {
async fn add_dns_blacklist(
payload: web::Json<DnsDomainsPayload>,
service: web::Data<DnsFilter>,
db: web::Data<Database>,
) -> impl Responder {
let domains = payload.into_inner().domains;
if domains.len() > MAX_DNS_DOMAINS_PER_REQUEST {
return HttpResponse::BadRequest()
.json(serde_json::json!({"error": format!("too many domains (max {})", MAX_DNS_DOMAINS_PER_REQUEST)}));
}
for domain in &domains {
if let Err(e) = db.insert_dns_domain(domain) {
return HttpResponse::InternalServerError()
.json(serde_json::json!({"error": e.to_string()}));
}
}
for domain in &domains {
if let Err(e) = service.add_domain(domain) {
return HttpResponse::InternalServerError()
@ -66,8 +74,15 @@ async fn add_dns_blacklist(
async fn remove_dns_blacklist(
payload: web::Json<DnsDomainsPayload>,
service: web::Data<DnsFilter>,
db: web::Data<Database>,
) -> impl Responder {
let domains = payload.into_inner().domains;
for domain in &domains {
if let Err(e) = db.delete_dns_domain(domain) {
return HttpResponse::InternalServerError()
.json(serde_json::json!({"error": e.to_string()}));
}
}
for domain in &domains {
if let Err(e) = service.remove_domain(domain) {
return HttpResponse::InternalServerError()

View File

@ -2,6 +2,7 @@ use actix_web::{web, HttpResponse, Responder, Scope};
use serde::{Deserialize, Serialize};
use common::define::setting::*;
use crate::core::database::Database;
use crate::core::ebpf::rate_limit::RateLimitConfig;
#[derive(Serialize, Deserialize)]
@ -34,29 +35,45 @@ async fn get_config(
async fn set_config(
settings: web::Json<RateLimitSettings>,
config: web::Data<RateLimitConfig>,
db: web::Data<Database>,
) -> impl Responder {
let s = settings.into_inner();
if let Some(v) = s.packet_rate {
if let Err(e) = db.set_rate_limit("packet_rate", v) {
return HttpResponse::InternalServerError().json(serde_json::json!({"error": e.to_string()}));
}
if let Err(e) = config.set_packet_rate(v) {
return HttpResponse::InternalServerError().json(serde_json::json!({"error": e.to_string()}));
}
}
if let Some(v) = s.syn_rate {
if let Err(e) = db.set_rate_limit("syn_rate", v) {
return HttpResponse::InternalServerError().json(serde_json::json!({"error": e.to_string()}));
}
if let Err(e) = config.set_syn_rate(v) {
return HttpResponse::InternalServerError().json(serde_json::json!({"error": e.to_string()}));
}
}
if let Some(v) = s.udp_rate {
if let Err(e) = db.set_rate_limit("udp_rate", v) {
return HttpResponse::InternalServerError().json(serde_json::json!({"error": e.to_string()}));
}
if let Err(e) = config.set_udp_rate(v) {
return HttpResponse::InternalServerError().json(serde_json::json!({"error": e.to_string()}));
}
}
if let Some(v) = s.dns_rate {
if let Err(e) = db.set_rate_limit("dns_rate", v) {
return HttpResponse::InternalServerError().json(serde_json::json!({"error": e.to_string()}));
}
if let Err(e) = config.set_dns_rate(v) {
return HttpResponse::InternalServerError().json(serde_json::json!({"error": e.to_string()}));
}
}
if let Some(v) = s.window_ns {
if let Err(e) = db.set_rate_limit("window_ns", v) {
return HttpResponse::InternalServerError().json(serde_json::json!({"error": e.to_string()}));
}
if let Err(e) = config.set_window_ns(v) {
return HttpResponse::InternalServerError().json(serde_json::json!({"error": e.to_string()}));
}