Fix21: прод-фиксы swap/firewall/wg без легаси
- Swap: допуск небольшой дельты usable size при verify/compare - Firewall: поддержка Debian 13 dynamic set/add @set вместо только literal meter - WG: приоритет docker exec для peer/runtime discovery с безопасным fallback
This commit is contained in:
@@ -234,7 +234,7 @@ fn inspect_firewall(transport: &dyn NsTransport, cfg: &Config) -> anyhow::Result
|
||||
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 meter_present = connlimit_state_present(&nt);
|
||||
let counter_name_used = nt.contains("counter name antitorrent_dropped_v4");
|
||||
let drop_present = nt.contains(" drop");
|
||||
|
||||
@@ -470,7 +470,7 @@ fn firewall_drift_reason(actual: &FirewallActual) -> String {
|
||||
return "missing ct state new".to_string();
|
||||
}
|
||||
if !actual.meter_present {
|
||||
return "missing nft meter".to_string();
|
||||
return "missing nft meter/dynamic set".to_string();
|
||||
}
|
||||
if !actual.counter_name_used {
|
||||
return "missing nft counter name usage".to_string();
|
||||
@@ -496,12 +496,19 @@ fn firewall_rule_contract_matches(table: &str, cidr: &str, max_conn: u32) -> boo
|
||||
&& table.contains("policy accept")
|
||||
&& table.contains(&needle_cidr)
|
||||
&& table.contains("ct state new")
|
||||
&& table.contains("meter awg2_peer_connlimit_v4")
|
||||
&& connlimit_state_present(&table)
|
||||
&& table.contains(&needle_max)
|
||||
&& table.contains("counter name antitorrent_dropped_v4")
|
||||
&& table.contains("drop")
|
||||
}
|
||||
|
||||
fn connlimit_state_present(table: &str) -> bool {
|
||||
table.contains("meter awg2_peer_connlimit_v4")
|
||||
|| (table.contains("set awg2_peer_connlimit_v4")
|
||||
&& table.contains("flags dynamic")
|
||||
&& table.contains("add @awg2_peer_connlimit_v4"))
|
||||
}
|
||||
|
||||
fn normalize_nft_text(input: &str) -> String {
|
||||
input
|
||||
.replace(['\n', '\t', '{', '}', ';', ',', '"'], " ")
|
||||
@@ -633,6 +640,27 @@ table inet awg2_harness {
|
||||
assert!(firewall_rule_contract_matches(table, "10.8.1.0/24", 100));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nft_rule_contract_accepts_debian13_dynamic_set_rendering() {
|
||||
let table = r#"
|
||||
table inet awg2_harness {
|
||||
counter antitorrent_dropped_v4 {
|
||||
packets 0 bytes 0
|
||||
}
|
||||
set awg2_peer_connlimit_v4 {
|
||||
type ipv4_addr
|
||||
size 65535
|
||||
flags dynamic
|
||||
}
|
||||
chain forward_antitorrent {
|
||||
type filter hook forward priority filter; policy accept;
|
||||
ip saddr 10.8.1.0/24 ct state new add @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 host_firewall_parser_tolerates_quoted_lo_and_ipv6_icmp_alias() {
|
||||
let table = r#"
|
||||
|
||||
@@ -195,7 +195,9 @@ fn disable_foreign_swaps(desired: &DesiredSwap, active: &[ActiveSwap]) -> anyhow
|
||||
if entry.name != desired.path {
|
||||
run("swapoff", &[entry.name.to_string_lossy().as_ref()])?;
|
||||
maybe_delete_foreign_swapfile(entry, &desired.path)?;
|
||||
} else if entry.size_bytes != desired.size_bytes || entry.priority != desired.priority {
|
||||
} else if !active_swap_size_matches(entry.size_bytes, desired.size_bytes)
|
||||
|| entry.priority != desired.priority
|
||||
{
|
||||
run("swapoff", &[desired.path.to_string_lossy().as_ref()])?;
|
||||
}
|
||||
}
|
||||
@@ -428,7 +430,7 @@ fn verify_exclusive_swap_state(desired: &DesiredSwap) -> anyhow::Result<()> {
|
||||
desired.path.display()
|
||||
);
|
||||
}
|
||||
if only.size_bytes != desired.size_bytes {
|
||||
if !active_swap_size_matches(only.size_bytes, desired.size_bytes) {
|
||||
anyhow::bail!(
|
||||
"swap verify failed: size mismatch {} != {}",
|
||||
only.size_bytes,
|
||||
@@ -490,6 +492,13 @@ fn normalize_ws(s: &str) -> String {
|
||||
s.split_whitespace().collect::<Vec<_>>().join(" ")
|
||||
}
|
||||
|
||||
fn active_swap_size_matches(active_bytes: u64, desired_file_bytes: u64) -> bool {
|
||||
if active_bytes == desired_file_bytes {
|
||||
return true;
|
||||
}
|
||||
active_bytes < desired_file_bytes && desired_file_bytes - active_bytes <= 1024 * 1024
|
||||
}
|
||||
|
||||
fn run(program: &str, args: &[&str]) -> anyhow::Result<()> {
|
||||
let out = Command::new(program).args(args).output()?;
|
||||
if !out.status.success() {
|
||||
@@ -542,6 +551,12 @@ mod tests {
|
||||
assert_eq!(got[0].priority, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn active_swap_size_accepts_mkswap_usable_size_delta() {
|
||||
assert!(active_swap_size_matches(2147479552, 2147483648));
|
||||
assert!(!active_swap_size_matches(2146435072, 2147483648));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_swapon_rejects_malformed_size() {
|
||||
let err = parse_swapon_output("/swapfile file abc 1\n");
|
||||
|
||||
+33
-12
@@ -21,15 +21,25 @@ pub struct PeerRuntime {
|
||||
}
|
||||
|
||||
pub fn discover_peers(pid: u32, container: &str, peer_cidr: &str) -> anyhow::Result<Vec<Peer>> {
|
||||
let boxed: Box<dyn NsTransport> = if nsenter_available() {
|
||||
Box::new(NsenterTransport { pid })
|
||||
} else {
|
||||
Box::new(DockerExecTransport {
|
||||
let docker = DockerExecTransport {
|
||||
container: container.to_string(),
|
||||
})
|
||||
};
|
||||
let docker_result = discover_peers_with_transport(&docker, peer_cidr);
|
||||
if let Ok(peers) = &docker_result
|
||||
&& (!peers.is_empty() || !nsenter_available())
|
||||
{
|
||||
return docker_result;
|
||||
}
|
||||
|
||||
discover_peers_with_transport(boxed.as_ref(), peer_cidr)
|
||||
if nsenter_available() {
|
||||
let nsenter = NsenterTransport { pid };
|
||||
match discover_peers_with_transport(&nsenter, peer_cidr) {
|
||||
Ok(peers) if !peers.is_empty() => Ok(peers),
|
||||
_ => docker_result,
|
||||
}
|
||||
} else {
|
||||
docker_result
|
||||
}
|
||||
}
|
||||
|
||||
pub fn discover_peers_with_transport(
|
||||
@@ -57,14 +67,25 @@ pub fn discover_peer_runtime(
|
||||
container: &str,
|
||||
peer_cidr: &str,
|
||||
) -> anyhow::Result<Vec<PeerRuntime>> {
|
||||
let boxed: Box<dyn NsTransport> = if nsenter_available() {
|
||||
Box::new(NsenterTransport { pid })
|
||||
} else {
|
||||
Box::new(DockerExecTransport {
|
||||
let docker = DockerExecTransport {
|
||||
container: container.to_string(),
|
||||
})
|
||||
};
|
||||
discover_peer_runtime_with_transport(boxed.as_ref(), peer_cidr)
|
||||
let docker_result = discover_peer_runtime_with_transport(&docker, peer_cidr);
|
||||
if let Ok(peers) = &docker_result
|
||||
&& (!peers.is_empty() || !nsenter_available())
|
||||
{
|
||||
return docker_result;
|
||||
}
|
||||
|
||||
if nsenter_available() {
|
||||
let nsenter = NsenterTransport { pid };
|
||||
match discover_peer_runtime_with_transport(&nsenter, peer_cidr) {
|
||||
Ok(peers) if !peers.is_empty() => Ok(peers),
|
||||
_ => docker_result,
|
||||
}
|
||||
} else {
|
||||
docker_result
|
||||
}
|
||||
}
|
||||
|
||||
pub fn discover_peer_runtime_with_transport(
|
||||
|
||||
Reference in New Issue
Block a user