fix7: продакшен-реализация полного reconcile swap и интеграций
- Полностью переписан swap-модуль в режиме exclusive ownership\n- Добавлены verify/checks, статус фактического swap-state и drift\n- Интегрирован ensure-swap в daemon и systemd ExecStartPre\n- Усилен lock API (таймауты) и применение в traffic/firewall/swap\n- Усилен parser firewall (JSON-first + устойчивый text fallback)\n- Обновлены config validation, runtime tools и default priority\n- Синхронизированы docs и build/package hygiene\n- Удалены member Cargo.lock в пользу корневого lockfile
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
use crate::{
|
||||
config::Config,
|
||||
docker,
|
||||
lock::FileLock,
|
||||
netns::{NsTransport, build_host_transport, build_transport},
|
||||
};
|
||||
use serde_json::Value;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct FirewallActual {
|
||||
@@ -20,6 +22,9 @@ struct FirewallActual {
|
||||
}
|
||||
|
||||
pub fn apply_firewall(cfg: &Config) -> anyhow::Result<()> {
|
||||
let _lock =
|
||||
FileLock::acquire_timeout("/run/awg2-harness/firewall.lock", Duration::from_secs(10))?;
|
||||
|
||||
if !cfg.anti_torrent.enabled {
|
||||
apply_firewall_disabled(cfg)?;
|
||||
return Ok(());
|
||||
@@ -94,6 +99,13 @@ fn apply_firewall_disabled(cfg: &Config) -> anyhow::Result<()> {
|
||||
}
|
||||
|
||||
fn inspect_firewall(transport: &dyn NsTransport, cfg: &Config) -> anyhow::Result<FirewallActual> {
|
||||
let json = transport.run(&["nft", "-j", "list", "table", "inet", "awg2_harness"]);
|
||||
if let Ok(raw) = json {
|
||||
if let Some(actual) = inspect_firewall_json(&raw, cfg)? {
|
||||
return Ok(actual);
|
||||
}
|
||||
}
|
||||
|
||||
let table = match transport.run(&["nft", "list", "table", "inet", "awg2_harness"]) {
|
||||
Ok(out) => out,
|
||||
Err(e) if is_nft_not_found(&e.to_string()) => {
|
||||
@@ -113,15 +125,16 @@ fn inspect_firewall(transport: &dyn NsTransport, cfg: &Config) -> anyhow::Result
|
||||
Err(e) => return Err(anyhow::anyhow!("nft table inspect failed: {e}")),
|
||||
};
|
||||
|
||||
let table_exists = !table.trim().is_empty();
|
||||
let counter_exists = table.contains("counter antitorrent_dropped_v4");
|
||||
let chain_exists = table.contains("chain forward_antitorrent");
|
||||
let hook_forward_present = table.contains("hook forward");
|
||||
let policy_accept_present = table.contains("policy accept");
|
||||
let ct_state_new_present = table.contains("ct state new");
|
||||
let meter_present = table.contains("meter awg2_peer_connlimit_v4");
|
||||
let counter_name_used = table.contains("counter name antitorrent_dropped_v4");
|
||||
let drop_present = table.contains(" drop") || table.contains("\ndrop");
|
||||
let nt = normalize_nft_text(&table);
|
||||
let table_exists = !nt.trim().is_empty();
|
||||
let counter_exists = nt.contains("counter antitorrent_dropped_v4");
|
||||
let chain_exists = nt.contains("chain forward_antitorrent");
|
||||
let hook_forward_present = nt.contains("hook forward");
|
||||
let policy_accept_present = nt.contains("policy accept");
|
||||
let ct_state_new_present = nt.contains("ct state new");
|
||||
let meter_present = nt.contains("meter awg2_peer_connlimit_v4");
|
||||
let counter_name_used = nt.contains("counter name antitorrent_dropped_v4");
|
||||
let drop_present = nt.contains(" drop");
|
||||
|
||||
let rule_matches = firewall_rule_contract_matches(
|
||||
&table,
|
||||
@@ -292,12 +305,15 @@ fn firewall_drift_reason(actual: &FirewallActual) -> String {
|
||||
}
|
||||
|
||||
fn firewall_rule_contract_matches(table: &str, cidr: &str, max_conn: u32) -> bool {
|
||||
let table = normalize_nft_text(table);
|
||||
let needle_cidr = format!("ip saddr {cidr}");
|
||||
let needle_max = format!("ct count over {max_conn}");
|
||||
table.contains("table inet awg2_harness")
|
||||
&& table.contains("counter antitorrent_dropped_v4")
|
||||
&& table.contains("chain forward_antitorrent")
|
||||
&& table.contains("type filter hook forward priority filter; policy accept")
|
||||
&& table.contains("type filter hook forward")
|
||||
&& (table.contains("priority filter") || table.contains("priority 0"))
|
||||
&& table.contains("policy accept")
|
||||
&& table.contains(&needle_cidr)
|
||||
&& table.contains("ct state new")
|
||||
&& table.contains("meter awg2_peer_connlimit_v4")
|
||||
@@ -306,6 +322,48 @@ fn firewall_rule_contract_matches(table: &str, cidr: &str, max_conn: u32) -> boo
|
||||
&& table.contains("drop")
|
||||
}
|
||||
|
||||
fn normalize_nft_text(input: &str) -> String {
|
||||
input
|
||||
.replace(['\n', '\t', '{', '}', ';', ','], " ")
|
||||
.split_whitespace()
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
}
|
||||
|
||||
fn inspect_firewall_json(raw: &str, cfg: &Config) -> anyhow::Result<Option<FirewallActual>> {
|
||||
let v: Value = serde_json::from_str(raw)?;
|
||||
let Some(arr) = v.get("nftables").and_then(|x| x.as_array()) else {
|
||||
return Ok(None);
|
||||
};
|
||||
let mut text = String::new();
|
||||
for item in arr {
|
||||
text.push_str(&item.to_string());
|
||||
text.push(' ');
|
||||
}
|
||||
let nt = normalize_nft_text(&text);
|
||||
let table_exists = nt.contains("awg2_harness");
|
||||
if !table_exists {
|
||||
return Ok(None);
|
||||
}
|
||||
let actual = FirewallActual {
|
||||
table_exists,
|
||||
counter_exists: nt.contains("antitorrent_dropped_v4"),
|
||||
chain_exists: nt.contains("forward_antitorrent"),
|
||||
rule_matches: firewall_rule_contract_matches(
|
||||
&nt,
|
||||
&cfg.anti_torrent.peer_ipv4_cidr,
|
||||
cfg.anti_torrent.max_connections_per_peer,
|
||||
),
|
||||
hook_forward_present: nt.contains("hook") && nt.contains("forward"),
|
||||
policy_accept_present: nt.contains("policy") && nt.contains("accept"),
|
||||
ct_state_new_present: nt.contains("ct") && nt.contains("state") && nt.contains("new"),
|
||||
meter_present: nt.contains("awg2_peer_connlimit_v4"),
|
||||
counter_name_used: nt.contains("antitorrent_dropped_v4"),
|
||||
drop_present: nt.contains("drop"),
|
||||
};
|
||||
Ok(Some(actual))
|
||||
}
|
||||
|
||||
pub fn antitorrent_drop_counter(cfg: &Config) -> anyhow::Result<Option<(u64, u64)>> {
|
||||
if !cfg.anti_torrent.enabled {
|
||||
return Ok(None);
|
||||
@@ -412,4 +470,18 @@ table inet awg2_harness {
|
||||
"#;
|
||||
assert!(!firewall_rule_contract_matches(table, "10.8.1.0/24", 100));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nft_text_parser_tolerates_priority_0_and_spacing() {
|
||||
let table = r#"
|
||||
table inet awg2_harness {
|
||||
counter antitorrent_dropped_v4
|
||||
chain forward_antitorrent {
|
||||
type filter hook forward priority 0; policy accept;
|
||||
ip saddr 10.8.1.0/24 ct state new meter awg2_peer_connlimit_v4 { ip saddr ct count over 100 } counter name antitorrent_dropped_v4 drop
|
||||
}
|
||||
}
|
||||
"#;
|
||||
assert!(firewall_rule_contract_matches(table, "10.8.1.0/24", 100));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user