diff --git a/traffic_core/src/firewall.rs b/traffic_core/src/firewall.rs index 6c671c7..c1f3306 100644 --- a/traffic_core/src/firewall.rs +++ b/traffic_core/src/firewall.rs @@ -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#" diff --git a/traffic_core/src/swap.rs b/traffic_core/src/swap.rs index a47b655..e1f638f 100644 --- a/traffic_core/src/swap.rs +++ b/traffic_core/src/swap.rs @@ -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::>().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"); diff --git a/traffic_core/src/wg.rs b/traffic_core/src/wg.rs index abbf9b9..02565ba 100644 --- a/traffic_core/src/wg.rs +++ b/traffic_core/src/wg.rs @@ -21,15 +21,25 @@ pub struct PeerRuntime { } pub fn discover_peers(pid: u32, container: &str, peer_cidr: &str) -> anyhow::Result> { - let boxed: Box = if nsenter_available() { - Box::new(NsenterTransport { pid }) - } else { - Box::new(DockerExecTransport { - container: container.to_string(), - }) + 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> { - let boxed: Box = if nsenter_available() { - Box::new(NsenterTransport { pid }) - } else { - Box::new(DockerExecTransport { - container: container.to_string(), - }) + 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(