package app import ( "fmt" trafficmodepkg "selective-vpn-api/app/trafficmode" "strings" ) type trafficRulesState = trafficmodepkg.RulesState func readTrafficRules() trafficRulesState { return trafficmodepkg.ReadRules(trafficModeRulesConfig(), runCommand) } func detectAppliedTrafficMode(rules trafficRulesState) TrafficMode { return TrafficMode(trafficmodepkg.DetectAppliedMode(trafficModeRulesConfig(), rules)) } func probeTrafficMode(mode TrafficMode, iface string) (bool, string) { return trafficmodepkg.ProbeMode( trafficModeRulesConfig(), string(normalizeTrafficMode(mode)), strings.TrimSpace(iface), runCommand, ) } func evaluateTrafficMode(st TrafficModeState) TrafficModeStatusResponse { st = normalizeTrafficModeState(st) eff := buildEffectiveOverrides(st) advancedActive := st.Mode == TrafficModeFullTunnel autoLocalActive := advancedActive && st.AutoLocalBypass ingressDesired := st.IngressReplyBypass ingressExpected := advancedActive && ingressDesired hasVPN := len(eff.VPNSubnets) > 0 || len(eff.VPNUIDs) > 0 iface, reason := resolveTrafficIface(st.PreferredIface) rules := readTrafficRules() applied := detectAppliedTrafficMode(rules) ingressNft := false if rules.IngressReply || st.Mode == TrafficModeFullTunnel || st.IngressReplyBypass { ingressNft = ingressReplyNftActive() } bypassCandidates := 0 if autoLocalActive && (st.Mode != TrafficModeDirect || hasVPN) { bypassCandidates = len(detectAutoLocalBypassRoutes(iface)) } overridesApplied := len(eff.VPNSubnets) + len(eff.VPNUIDs) + len(eff.DirectSubnets) + len(eff.DirectUIDs) tableDefault := false if iface != "" && (st.Mode != TrafficModeDirect || hasVPN) { ok, _ := checkPolicyRoute(iface, routesTableName()) tableDefault = ok } res := TrafficModeStatusResponse{ Mode: st.Mode, DesiredMode: st.Mode, AppliedMode: applied, PreferredIface: st.PreferredIface, AdvancedActive: advancedActive, AutoLocalBypass: st.AutoLocalBypass, AutoLocalActive: autoLocalActive, IngressReplyBypass: ingressDesired, IngressReplyActive: rules.IngressReply && ingressNft, BypassCandidates: bypassCandidates, ForceVPNSubnets: append([]string(nil), st.ForceVPNSubnets...), ForceVPNUIDs: append([]string(nil), st.ForceVPNUIDs...), ForceVPNCGroups: append([]string(nil), st.ForceVPNCGroups...), ForceDirectSubnets: append([]string(nil), st.ForceDirectSubnets...), ForceDirectUIDs: append([]string(nil), st.ForceDirectUIDs...), ForceDirectCGroups: append([]string(nil), st.ForceDirectCGroups...), OverridesApplied: overridesApplied, CgroupResolvedUIDs: eff.CgroupResolvedUIDs, CgroupWarning: eff.CgroupWarning, ActiveIface: iface, IfaceReason: reason, RuleMark: rules.Mark, RuleFull: rules.Full, IngressRulePresent: rules.IngressReply, IngressNftActive: ingressNft, TableDefault: tableDefault, } res.ProbeOK, res.ProbeMessage = probeTrafficMode(st.Mode, iface) switch st.Mode { case TrafficModeDirect: // direct mode can still be healthy when vpn overrides exist // (base full/selective rules must be absent). if hasVPN { res.Healthy = !rules.Mark && !rules.Full && !rules.IngressReply && !ingressNft && tableDefault && iface != "" && res.ProbeOK } else { res.Healthy = !rules.Mark && !rules.Full && !rules.IngressReply && !ingressNft && res.ProbeOK } case TrafficModeFullTunnel: if ingressExpected { res.Healthy = rules.Full && !rules.Mark && rules.IngressReply && ingressNft && tableDefault && iface != "" && res.ProbeOK } else { res.Healthy = rules.Full && !rules.Mark && !rules.IngressReply && !ingressNft && tableDefault && iface != "" && res.ProbeOK } case TrafficModeSelective: res.Healthy = rules.Mark && !rules.Full && !rules.IngressReply && !ingressNft && tableDefault && iface != "" && res.ProbeOK default: res.Healthy = false } if res.Healthy { res.Message = "traffic mode applied" return res } if iface == "" && (st.Mode != TrafficModeDirect || hasVPN) { res.Message = "vpn interface not found" return res } if st.Mode != applied { res.Message = fmt.Sprintf("desired=%s applied=%s mismatch", st.Mode, applied) return res } if (st.Mode != TrafficModeDirect || hasVPN) && !tableDefault { res.Message = "policy table default route is missing" return res } if !res.ProbeOK { res.Message = res.ProbeMessage return res } if rules.Mark && rules.Full { res.Message = "conflicting traffic rules detected" return res } if ingressExpected && (!rules.IngressReply || !ingressNft) { res.Message = "ingress-reply bypass rule is not active" return res } if !ingressExpected && (rules.IngressReply || ingressNft) { res.Message = "stale ingress-reply bypass rule is active" return res } res.Message = "traffic mode check failed" return res }