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 hook_forward_present = nt.contains("hook forward");
|
||||||
let policy_accept_present = nt.contains("policy accept");
|
let policy_accept_present = nt.contains("policy accept");
|
||||||
let ct_state_new_present = nt.contains("ct state new");
|
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 counter_name_used = nt.contains("counter name antitorrent_dropped_v4");
|
||||||
let drop_present = nt.contains(" drop");
|
let drop_present = nt.contains(" drop");
|
||||||
|
|
||||||
@@ -470,7 +470,7 @@ fn firewall_drift_reason(actual: &FirewallActual) -> String {
|
|||||||
return "missing ct state new".to_string();
|
return "missing ct state new".to_string();
|
||||||
}
|
}
|
||||||
if !actual.meter_present {
|
if !actual.meter_present {
|
||||||
return "missing nft meter".to_string();
|
return "missing nft meter/dynamic set".to_string();
|
||||||
}
|
}
|
||||||
if !actual.counter_name_used {
|
if !actual.counter_name_used {
|
||||||
return "missing nft counter name usage".to_string();
|
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("policy accept")
|
||||||
&& table.contains(&needle_cidr)
|
&& table.contains(&needle_cidr)
|
||||||
&& table.contains("ct state new")
|
&& table.contains("ct state new")
|
||||||
&& table.contains("meter awg2_peer_connlimit_v4")
|
&& connlimit_state_present(&table)
|
||||||
&& table.contains(&needle_max)
|
&& table.contains(&needle_max)
|
||||||
&& table.contains("counter name antitorrent_dropped_v4")
|
&& table.contains("counter name antitorrent_dropped_v4")
|
||||||
&& table.contains("drop")
|
&& 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 {
|
fn normalize_nft_text(input: &str) -> String {
|
||||||
input
|
input
|
||||||
.replace(['\n', '\t', '{', '}', ';', ',', '"'], " ")
|
.replace(['\n', '\t', '{', '}', ';', ',', '"'], " ")
|
||||||
@@ -633,6 +640,27 @@ table inet awg2_harness {
|
|||||||
assert!(firewall_rule_contract_matches(table, "10.8.1.0/24", 100));
|
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]
|
#[test]
|
||||||
fn host_firewall_parser_tolerates_quoted_lo_and_ipv6_icmp_alias() {
|
fn host_firewall_parser_tolerates_quoted_lo_and_ipv6_icmp_alias() {
|
||||||
let table = r#"
|
let table = r#"
|
||||||
|
|||||||
@@ -195,7 +195,9 @@ fn disable_foreign_swaps(desired: &DesiredSwap, active: &[ActiveSwap]) -> anyhow
|
|||||||
if entry.name != desired.path {
|
if entry.name != desired.path {
|
||||||
run("swapoff", &[entry.name.to_string_lossy().as_ref()])?;
|
run("swapoff", &[entry.name.to_string_lossy().as_ref()])?;
|
||||||
maybe_delete_foreign_swapfile(entry, &desired.path)?;
|
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()])?;
|
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()
|
desired.path.display()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if only.size_bytes != desired.size_bytes {
|
if !active_swap_size_matches(only.size_bytes, desired.size_bytes) {
|
||||||
anyhow::bail!(
|
anyhow::bail!(
|
||||||
"swap verify failed: size mismatch {} != {}",
|
"swap verify failed: size mismatch {} != {}",
|
||||||
only.size_bytes,
|
only.size_bytes,
|
||||||
@@ -490,6 +492,13 @@ fn normalize_ws(s: &str) -> String {
|
|||||||
s.split_whitespace().collect::<Vec<_>>().join(" ")
|
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<()> {
|
fn run(program: &str, args: &[&str]) -> anyhow::Result<()> {
|
||||||
let out = Command::new(program).args(args).output()?;
|
let out = Command::new(program).args(args).output()?;
|
||||||
if !out.status.success() {
|
if !out.status.success() {
|
||||||
@@ -542,6 +551,12 @@ mod tests {
|
|||||||
assert_eq!(got[0].priority, 1);
|
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]
|
#[test]
|
||||||
fn parse_swapon_rejects_malformed_size() {
|
fn parse_swapon_rejects_malformed_size() {
|
||||||
let err = parse_swapon_output("/swapfile file abc 1\n");
|
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>> {
|
pub fn discover_peers(pid: u32, container: &str, peer_cidr: &str) -> anyhow::Result<Vec<Peer>> {
|
||||||
let boxed: Box<dyn NsTransport> = if nsenter_available() {
|
let docker = DockerExecTransport {
|
||||||
Box::new(NsenterTransport { pid })
|
|
||||||
} else {
|
|
||||||
Box::new(DockerExecTransport {
|
|
||||||
container: container.to_string(),
|
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(
|
pub fn discover_peers_with_transport(
|
||||||
@@ -57,14 +67,25 @@ pub fn discover_peer_runtime(
|
|||||||
container: &str,
|
container: &str,
|
||||||
peer_cidr: &str,
|
peer_cidr: &str,
|
||||||
) -> anyhow::Result<Vec<PeerRuntime>> {
|
) -> anyhow::Result<Vec<PeerRuntime>> {
|
||||||
let boxed: Box<dyn NsTransport> = if nsenter_available() {
|
let docker = DockerExecTransport {
|
||||||
Box::new(NsenterTransport { pid })
|
|
||||||
} else {
|
|
||||||
Box::new(DockerExecTransport {
|
|
||||||
container: container.to_string(),
|
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(
|
pub fn discover_peer_runtime_with_transport(
|
||||||
|
|||||||
Reference in New Issue
Block a user