Добавлен fallback диагностики FORWARD через nft и расширен вывод status

This commit is contained in:
2026-05-12 11:48:18 +05:00
parent ed786f165f
commit 1e2a3b5b67
4 changed files with 134 additions and 1 deletions
+120 -1
View File
@@ -118,7 +118,7 @@ pub fn collect_port_path_diagnostics(
let docker_userland_proxy = detect_userland_proxy().unwrap_or(TriState::Unknown);
let published_udp_port = discover_published_udp_port(container_name, expected_udp_port).ok();
let (host_forward_policy_drop, docker_forward_rules_present) =
inspect_iptables_forward_state().unwrap_or((TriState::Unknown, TriState::Unknown));
inspect_forward_state().unwrap_or((TriState::Unknown, TriState::Unknown));
PortPathDiagnostics {
docker_userland_proxy,
@@ -146,6 +146,42 @@ fn inspect_iptables_forward_state() -> anyhow::Result<(TriState, TriState)> {
Ok(parse_forward_policy_and_docker_rules(&fwd))
}
fn inspect_forward_state() -> anyhow::Result<(TriState, TriState)> {
let iptables_state =
inspect_iptables_forward_state().unwrap_or((TriState::Unknown, TriState::Unknown));
let nft_state = inspect_nft_forward_state().unwrap_or((TriState::Unknown, TriState::Unknown));
Ok(merge_forward_states(iptables_state, nft_state))
}
fn inspect_nft_forward_state() -> anyhow::Result<(TriState, TriState)> {
let ruleset = run_capture(&["nft", "list", "ruleset"])?;
Ok(parse_nft_forward_policy_and_docker_rules(&ruleset))
}
fn merge_forward_states(a: (TriState, TriState), b: (TriState, TriState)) -> (TriState, TriState) {
(merge_policy_drop(a.0, b.0), merge_docker_rules(a.1, b.1))
}
fn merge_policy_drop(a: TriState, b: TriState) -> TriState {
if a == TriState::True || b == TriState::True {
TriState::True
} else if a == TriState::False || b == TriState::False {
TriState::False
} else {
TriState::Unknown
}
}
fn merge_docker_rules(a: TriState, b: TriState) -> TriState {
if a == TriState::True || b == TriState::True {
TriState::True
} else if a == TriState::False && b == TriState::False {
TriState::False
} else {
TriState::Unknown
}
}
fn parse_userland_proxy_from_docker_info(raw: &str) -> TriState {
let Ok(v) = serde_json::from_str::<Value>(raw) else {
return TriState::Unknown;
@@ -279,6 +315,34 @@ fn parse_forward_policy_and_docker_rules(raw: &str) -> (TriState, TriState) {
(policy_drop, docker_rules)
}
fn parse_nft_forward_policy_and_docker_rules(raw: &str) -> (TriState, TriState) {
let nt = normalize_nft_forward_text(raw);
let policy_drop = if nt.contains("hook forward") && nt.contains("policy drop") {
TriState::True
} else if nt.contains("hook forward") && nt.contains("policy accept") {
TriState::False
} else {
TriState::Unknown
};
let docker_rules = if nt.contains("docker") && nt.contains("forward") {
TriState::True
} else {
TriState::Unknown
};
(policy_drop, docker_rules)
}
fn normalize_nft_forward_text(raw: &str) -> String {
raw.to_ascii_lowercase()
.replace(['\n', '\t', '{', '}', ';', '"'], " ")
.split_whitespace()
.collect::<Vec<_>>()
.join(" ")
}
#[cfg(test)]
mod tests {
use super::*;
@@ -432,6 +496,61 @@ mod tests {
);
}
#[test]
fn parse_nft_forward_policy_and_docker_rules_works() {
let raw = r#"
table inet docker_filter {
chain forward {
type filter hook forward priority filter; policy accept;
jump DOCKER-USER
}
}
"#;
assert_eq!(
parse_nft_forward_policy_and_docker_rules(raw),
(TriState::False, TriState::True)
);
}
#[test]
fn parse_nft_forward_policy_drop_detects_drop_forward_chain() {
let raw = r#"
table inet filter {
chain forward {
type filter hook forward priority filter; policy drop;
}
}
"#;
assert_eq!(
parse_nft_forward_policy_and_docker_rules(raw),
(TriState::True, TriState::Unknown)
);
}
#[test]
fn merge_forward_states_preserves_nft_docker_rules() {
assert_eq!(
merge_forward_states(
(TriState::Unknown, TriState::Unknown),
(TriState::False, TriState::True)
),
(TriState::False, TriState::True)
);
}
#[test]
fn merge_forward_states_is_conservative_on_drop_policy() {
assert_eq!(
merge_forward_states(
(TriState::True, TriState::Unknown),
(TriState::False, TriState::True)
),
(TriState::True, TriState::True)
);
}
#[test]
fn warning_is_emitted_for_risky_forward_state() {
let diag = PortPathDiagnostics {
+10
View File
@@ -313,6 +313,8 @@ fn print_degraded_sections(cfg: &Config) -> anyhow::Result<()> {
println!("Docker/Amnezia port path:");
println!(" docker_userland_proxy: unknown");
println!(" published_udp_port: unknown");
println!(" host_forward_policy_drop: unknown");
println!(" docker_forward_rules_present: unknown");
println!(" note: FORWARD is Docker-managed; awg2-harness does not modify host FORWARD");
println!("Anti-torrent:");
@@ -465,6 +467,14 @@ fn print_docker_amnezia_port_path(diag: &docker::PortPathDiagnostics) {
),
None => println!(" published_udp_port: unknown"),
}
println!(
" host_forward_policy_drop: {}",
diag.host_forward_policy_drop.as_str()
);
println!(
" docker_forward_rules_present: {}",
diag.docker_forward_rules_present.as_str()
);
println!(" note: FORWARD is Docker-managed; awg2-harness does not modify host FORWARD");
if let Some(w) = diag.warning() {
println!(" warning: {w}");