Полный production-рефакторинг билдера, drift-reconcile и документации по up5
This commit is contained in:
@@ -65,27 +65,21 @@ pub fn apply_firewall(cfg: &Config) -> anyhow::Result<()> {
|
||||
}
|
||||
|
||||
fn inspect_firewall(transport: &dyn NsTransport, cfg: &Config) -> anyhow::Result<FirewallActual> {
|
||||
let table = transport
|
||||
.run(&["nft", "list", "table", "inet", "awg2_harness"])
|
||||
.unwrap_or_default();
|
||||
let table = inspect_cmd(
|
||||
transport,
|
||||
&["nft", "list", "table", "inet", "awg2_harness"],
|
||||
"firewall table",
|
||||
)?;
|
||||
|
||||
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 needle_cidr = format!("ip saddr {}", cfg.anti_torrent.peer_ipv4_cidr);
|
||||
let needle_max = format!(
|
||||
"ct count over {}",
|
||||
cfg.anti_torrent.max_connections_per_peer
|
||||
let rule_matches = firewall_rule_contract_matches(
|
||||
&table,
|
||||
&cfg.anti_torrent.peer_ipv4_cidr,
|
||||
cfg.anti_torrent.max_connections_per_peer,
|
||||
);
|
||||
let rule_matches = table.contains(&needle_cidr)
|
||||
&& table.contains(&needle_max)
|
||||
&& table.contains("ct state new")
|
||||
&& table.contains("meter awg2_peer_connlimit_v4")
|
||||
&& table.contains("counter name antitorrent_dropped_v4")
|
||||
&& table.contains("drop")
|
||||
&& table.contains("hook forward")
|
||||
&& table.contains("policy accept");
|
||||
|
||||
Ok(FirewallActual {
|
||||
table_exists,
|
||||
@@ -185,6 +179,27 @@ fn run_allow_notfound(transport: &dyn NsTransport, args: &[&str]) -> anyhow::Res
|
||||
}
|
||||
}
|
||||
|
||||
fn inspect_cmd(transport: &dyn NsTransport, args: &[&str], what: &str) -> anyhow::Result<String> {
|
||||
transport
|
||||
.run(args)
|
||||
.map_err(|e| anyhow::anyhow!("{what} inspect failed: {e}"))
|
||||
}
|
||||
|
||||
fn firewall_rule_contract_matches(table: &str, cidr: &str, max_conn: u32) -> bool {
|
||||
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(&needle_cidr)
|
||||
&& table.contains("ct state new")
|
||||
&& table.contains("meter awg2_peer_connlimit_v4")
|
||||
&& table.contains(&needle_max)
|
||||
&& table.contains("counter name antitorrent_dropped_v4")
|
||||
&& table.contains("drop")
|
||||
}
|
||||
|
||||
pub fn antitorrent_drop_counter(cfg: &Config) -> anyhow::Result<Option<(u64, u64)>> {
|
||||
if !cfg.anti_torrent.enabled {
|
||||
return Ok(None);
|
||||
@@ -201,38 +216,25 @@ pub fn antitorrent_drop_counter(cfg: &Config) -> anyhow::Result<Option<(u64, u64
|
||||
"antitorrent_dropped_v4",
|
||||
]);
|
||||
|
||||
if let Ok(raw) = json {
|
||||
if let Some(v) = parse_counter_json(&raw)? {
|
||||
return Ok(Some(v));
|
||||
match json {
|
||||
Ok(raw) => {
|
||||
if let Some(v) = parse_counter_json(&raw)? {
|
||||
return Ok(Some(v));
|
||||
}
|
||||
return Ok(None);
|
||||
}
|
||||
Err(e) => {
|
||||
let msg = e.to_string();
|
||||
if msg.contains("No such file")
|
||||
|| msg.contains("No such")
|
||||
|| msg.contains("not found")
|
||||
|| msg.contains("not exist")
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
return Err(anyhow::anyhow!("nft counter inspection failed: {e}"));
|
||||
}
|
||||
}
|
||||
|
||||
let table = transport
|
||||
.run(&["nft", "list", "table", "inet", "awg2_harness"])
|
||||
.unwrap_or_default();
|
||||
for line in table.lines() {
|
||||
let t = line.trim();
|
||||
if !t.contains("packets") {
|
||||
continue;
|
||||
}
|
||||
let toks: Vec<&str> = t.split_whitespace().collect();
|
||||
let packets = toks
|
||||
.iter()
|
||||
.position(|x| *x == "packets")
|
||||
.and_then(|i| toks.get(i + 1))
|
||||
.and_then(|s| s.parse::<u64>().ok());
|
||||
let bytes = toks
|
||||
.iter()
|
||||
.position(|x| *x == "bytes")
|
||||
.and_then(|i| toks.get(i + 1))
|
||||
.and_then(|s| s.parse::<u64>().ok())
|
||||
.unwrap_or(0);
|
||||
if let Some(p) = packets {
|
||||
return Ok(Some((p, bytes)));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn parse_counter_json(raw: &str) -> anyhow::Result<Option<(u64, u64)>> {
|
||||
@@ -262,4 +264,46 @@ mod tests {
|
||||
let got = parse_counter_json(raw).expect("json parse");
|
||||
assert_eq!(got, Some((7, 448)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nft_rule_contract_parser_accepts_expected_rule() {
|
||||
let table = r#"
|
||||
table inet awg2_harness {
|
||||
counter antitorrent_dropped_v4
|
||||
chain forward_antitorrent {
|
||||
type filter hook forward priority filter; 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));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_counter_name_causes_drift() {
|
||||
let table = r#"
|
||||
table inet awg2_harness {
|
||||
counter antitorrent_dropped_v4
|
||||
chain forward_antitorrent {
|
||||
type filter hook forward priority filter; policy accept;
|
||||
ip saddr 10.8.1.0/24 ct state new meter awg2_peer_connlimit_v4 { ip saddr ct count over 100 } drop
|
||||
}
|
||||
}
|
||||
"#;
|
||||
assert!(!firewall_rule_contract_matches(table, "10.8.1.0/24", 100));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrong_max_connection_threshold_causes_drift() {
|
||||
let table = r#"
|
||||
table inet awg2_harness {
|
||||
counter antitorrent_dropped_v4
|
||||
chain forward_antitorrent {
|
||||
type filter hook forward priority filter; policy accept;
|
||||
ip saddr 10.8.1.0/24 ct state new meter awg2_peer_connlimit_v4 { ip saddr ct count over 250 } 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