diff --git a/traffic_core/src/firewall.rs b/traffic_core/src/firewall.rs index dfe3fb2..08ab2df 100644 --- a/traffic_core/src/firewall.rs +++ b/traffic_core/src/firewall.rs @@ -320,9 +320,7 @@ fn host_firewall_drift_reason_from_text(nt: &str, cfg: &Config) -> Option Option bool { + nt.contains("ct state established related accept") + || nt.contains("ct state related established accept") +} + fn apply_rule(transport: &dyn NsTransport, cidr: &str, max_conn: u32) -> anyhow::Result<()> { let _ = transport.run(&[ "nft", @@ -686,6 +689,70 @@ table inet awg2_host_firewall { assert_eq!(host_firewall_drift_reason_from_text(&nt, &cfg), None); } + #[test] + fn normalized_established_related_rule_matches() { + let nt = normalize_nft_text("ct state established,related accept"); + assert!(nt.contains("ct state established related accept")); + assert!(has_established_related_rule(&nt)); + } + + #[test] + fn host_firewall_drift_detects_missing_established_related_rule() { + let table = r#" +table inet awg2_host_firewall { + chain input { + type filter hook input priority filter; policy drop; + iif "lo" accept + tcp dport 2323 accept + udp dport 443 accept + ip protocol icmp accept + ip6 nexthdr icmpv6 accept + } +} +"#; + let cfg = Config { + amnezia: crate::config::AmneziaConfig { + container_names: vec!["amnezia-awg2".into()], + interface_candidates: vec!["awg0".into()], + interface_patterns: vec!["awg*".into()], + peer_ipv4_cidr: "10.8.1.0/24".into(), + }, + traffic: crate::config::TrafficConfig { + enabled: true, + peer_limit: "70mbit".into(), + root_rate: "1000mbit".into(), + reconcile_interval_secs: 15, + container_ready_timeout_secs: 30, + class_start_id: 10, + ifb_name: "ifb0".into(), + }, + host_firewall: crate::config::HostFirewallConfig::default(), + anti_torrent: crate::config::AntiTorrentConfig { + enabled: false, + max_connections_per_peer: 100, + peer_ipv4_cidr: "10.8.1.0/24".into(), + apply_in_amnezia_netns: false, + }, + reboot: crate::config::RebootConfig { + enabled: false, + window_start_utc: "01:00".into(), + window_end_utc: "02:00".into(), + }, + swap: crate::config::SwapConfig { + enabled: false, + path: "/swapfile".into(), + size_mb: 2048, + priority: 1, + swappiness: 1, + }, + }; + let nt = normalize_nft_text(table); + assert_eq!( + host_firewall_drift_reason_from_text(&nt, &cfg), + Some("missing established/related allow".into()) + ); + } + #[test] fn host_firewall_batch_script_does_not_include_delete_table() { let cfg = Config { diff --git a/traffic_core/src/status.rs b/traffic_core/src/status.rs index 8076ecb..a8248b1 100644 --- a/traffic_core/src/status.rs +++ b/traffic_core/src/status.rs @@ -175,7 +175,10 @@ pub fn print_status(cfg: &Config) -> anyhow::Result<()> { ); println!( " input_policy: {}", - cfg.host_firewall.default_input_policy.to_ascii_lowercase() + cfg.host_firewall + .default_input_policy + .trim() + .to_ascii_lowercase() ); if cfg.host_firewall.enabled { match firewall::host_firewall_drift_reason(cfg) { @@ -284,7 +287,10 @@ fn print_degraded_sections(cfg: &Config) -> anyhow::Result<()> { ); println!( " input_policy: {}", - cfg.host_firewall.default_input_policy.to_ascii_lowercase() + cfg.host_firewall + .default_input_policy + .trim() + .to_ascii_lowercase() ); if cfg.host_firewall.enabled { match firewall::host_firewall_drift_reason(cfg) {