package app import ( "fmt" "sort" "strings" "time" ) func auditNftAppMarks(state []appMarkItem) (issues []string, summary map[string]any) { summary = map[string]any{ "output_jump_ok": false, "output_apps_ok": false, "state_items": len(state), "nft_rules": 0, "missing_rules": 0, "orphan_rules": 0, "missing_rule_ids": []string{}, "orphan_rule_ids": []string{}, } // Check output -> jump output_apps. outOutput, _, codeOut, errOut := runCommandTimeout(3*time.Second, "nft", "list", "chain", "inet", appMarksTable, "output") if errOut != nil || codeOut != 0 { issues = append(issues, "nft_error: failed to read chain output") } else { ok := strings.Contains(outOutput, "jump "+appMarksChain) summary["output_jump_ok"] = ok if !ok { issues = append(issues, "nft_missing_jump: output -> output_apps") } } outApps, _, codeApps, errApps := runCommandTimeout(3*time.Second, "nft", "-a", "list", "chain", "inet", appMarksTable, appMarksChain) if errApps != nil || codeApps != 0 { issues = append(issues, "nft_error: failed to read chain output_apps") return issues, summary } summary["output_apps_ok"] = true rules := parseAppMarkRules(outApps) summary["nft_rules"] = len(rules) stateIDs := map[string]struct{}{} for _, it := range state { tgt := strings.ToLower(strings.TrimSpace(it.Target)) if tgt != "vpn" && tgt != "direct" { continue } if it.ID == 0 { continue } stateIDs[fmt.Sprintf("%s:%d", tgt, it.ID)] = struct{}{} } ruleIDs := map[string]struct{}{} for _, k := range rules { ruleIDs[k] = struct{}{} } missing := []string{} for k := range stateIDs { if _, ok := ruleIDs[k]; !ok { missing = append(missing, k) } } orphan := []string{} for k := range ruleIDs { if _, ok := stateIDs[k]; !ok { orphan = append(orphan, k) } } sort.Strings(missing) sort.Strings(orphan) summary["missing_rules"] = len(missing) summary["orphan_rules"] = len(orphan) summary["missing_rule_ids"] = missing summary["orphan_rule_ids"] = orphan for _, k := range missing { issues = append(issues, "nft_missing_rule: "+k) } for _, k := range orphan { issues = append(issues, "nft_orphan_rule: "+k) } return issues, summary }