package app import ( "encoding/json" "os" trafficmodepkg "selective-vpn-api/app/trafficmode" "strings" "time" ) func normalizeTrafficMode(raw TrafficMode) TrafficMode { switch strings.ToLower(strings.TrimSpace(string(raw))) { case string(TrafficModeFullTunnel): return TrafficModeFullTunnel case string(TrafficModeDirect): return TrafficModeDirect case string(TrafficModeSelective): return TrafficModeSelective default: return TrafficModeSelective } } func normalizePreferredIface(raw string) string { return trafficmodepkg.NormalizePreferredIface(raw) } func tokenizeList(raw []string) []string { return trafficmodepkg.TokenizeList(raw) } func normalizeSubnetList(raw []string) []string { return trafficmodepkg.NormalizeSubnetList(raw) } func normalizeUIDToken(tok string) (string, bool) { return trafficmodepkg.NormalizeUIDToken(tok) } func normalizeUIDList(raw []string) []string { return trafficmodepkg.NormalizeUIDList(raw) } func normalizeCgroupList(raw []string) []string { return trafficmodepkg.NormalizeCgroupList(raw) } func normalizeTrafficModeState(st TrafficModeState) TrafficModeState { st.Mode = normalizeTrafficMode(st.Mode) st.PreferredIface = normalizePreferredIface(st.PreferredIface) st.ForceVPNSubnets = normalizeSubnetList(st.ForceVPNSubnets) st.ForceVPNUIDs = normalizeUIDList(st.ForceVPNUIDs) st.ForceVPNCGroups = normalizeCgroupList(st.ForceVPNCGroups) st.ForceDirectSubnets = normalizeSubnetList(st.ForceDirectSubnets) st.ForceDirectUIDs = normalizeUIDList(st.ForceDirectUIDs) st.ForceDirectCGroups = normalizeCgroupList(st.ForceDirectCGroups) return st } func loadTrafficModeState() TrafficModeState { data, err := os.ReadFile(trafficModePath) if err != nil { return inferTrafficModeState() } type diskState struct { Mode TrafficMode `json:"mode"` PreferredIface string `json:"preferred_iface,omitempty"` AutoLocalBypass *bool `json:"auto_local_bypass,omitempty"` IngressReplyBypass *bool `json:"ingress_reply_bypass,omitempty"` ForceVPNSubnets []string `json:"force_vpn_subnets,omitempty"` ForceVPNUIDs []string `json:"force_vpn_uids,omitempty"` ForceVPNCGroups []string `json:"force_vpn_cgroups,omitempty"` ForceDirectSubnets []string `json:"force_direct_subnets,omitempty"` ForceDirectUIDs []string `json:"force_direct_uids,omitempty"` ForceDirectCGroups []string `json:"force_direct_cgroups,omitempty"` } var raw diskState if err := json.Unmarshal(data, &raw); err != nil { return inferTrafficModeState() } st := TrafficModeState{ Mode: raw.Mode, PreferredIface: raw.PreferredIface, AutoLocalBypass: trafficAutoLocalDefault, IngressReplyBypass: trafficIngressReplyDefault, ForceVPNSubnets: append([]string(nil), raw.ForceVPNSubnets...), ForceVPNUIDs: append([]string(nil), raw.ForceVPNUIDs...), ForceVPNCGroups: append([]string(nil), raw.ForceVPNCGroups...), ForceDirectSubnets: append([]string(nil), raw.ForceDirectSubnets...), ForceDirectUIDs: append([]string(nil), raw.ForceDirectUIDs...), ForceDirectCGroups: append([]string(nil), raw.ForceDirectCGroups...), } if raw.AutoLocalBypass != nil { st.AutoLocalBypass = *raw.AutoLocalBypass } if raw.IngressReplyBypass != nil { st.IngressReplyBypass = *raw.IngressReplyBypass } return normalizeTrafficModeState(st) } func saveTrafficModeState(st TrafficModeState) error { st = normalizeTrafficModeState(st) st.UpdatedAt = time.Now().UTC().Format(time.RFC3339) data, err := json.MarshalIndent(st, "", " ") if err != nil { return err } if err := os.MkdirAll(stateDir, 0o755); err != nil { return err } tmp := trafficModePath + ".tmp" if err := os.WriteFile(tmp, data, 0o644); err != nil { return err } return os.Rename(tmp, trafficModePath) } func inferTrafficModeState() TrafficModeState { rules := readTrafficRules() mode := detectAppliedTrafficMode(rules) iface, _ := resolveTrafficIface("") return normalizeTrafficModeState(TrafficModeState{ Mode: mode, PreferredIface: iface, AutoLocalBypass: trafficAutoLocalDefault, IngressReplyBypass: trafficIngressReplyDefault, ForceVPNSubnets: nil, ForceVPNUIDs: nil, ForceVPNCGroups: nil, ForceDirectSubnets: nil, ForceDirectUIDs: nil, ForceDirectCGroups: nil, }) }