platform: modularize api/gui, add docs-tests-web foundation, and refresh root config
This commit is contained in:
142
selective-vpn-api/app/traffic_mode_evaluate.go
Normal file
142
selective-vpn-api/app/traffic_mode_evaluate.go
Normal file
@@ -0,0 +1,142 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user