package app import ( "encoding/json" "io" "net/http" "strings" "syscall" ) func handleTrafficModePost(w http.ResponseWriter, r *http.Request) { lock, lockMsg := acquireTrafficApplyLock() if lockMsg != nil { writeJSON(w, http.StatusOK, *lockMsg) return } defer func() { _ = syscall.Flock(int(lock.Fd()), syscall.LOCK_UN) _ = lock.Close() }() prev := loadTrafficModeState() next := prev var body TrafficModeRequest if r.Body != nil { defer r.Body.Close() if err := json.NewDecoder(io.LimitReader(r.Body, 1<<20)).Decode(&body); err != nil { http.Error(w, "bad json", http.StatusBadRequest) return } } if strings.TrimSpace(string(body.Mode)) != "" { next.Mode = normalizeTrafficMode(body.Mode) } if body.PreferredIface != nil { next.PreferredIface = normalizePreferredIface(*body.PreferredIface) } if body.AutoLocalBypass != nil { next.AutoLocalBypass = *body.AutoLocalBypass } if body.IngressReplyBypass != nil { next.IngressReplyBypass = *body.IngressReplyBypass } if body.ForceVPNSubnets != nil { next.ForceVPNSubnets = append([]string(nil), (*body.ForceVPNSubnets)...) } if body.ForceVPNUIDs != nil { next.ForceVPNUIDs = append([]string(nil), (*body.ForceVPNUIDs)...) } if body.ForceVPNCGroups != nil { next.ForceVPNCGroups = append([]string(nil), (*body.ForceVPNCGroups)...) } if body.ForceDirectSubnets != nil { next.ForceDirectSubnets = append([]string(nil), (*body.ForceDirectSubnets)...) } if body.ForceDirectUIDs != nil { next.ForceDirectUIDs = append([]string(nil), (*body.ForceDirectUIDs)...) } if body.ForceDirectCGroups != nil { next.ForceDirectCGroups = append([]string(nil), (*body.ForceDirectCGroups)...) } next = normalizeTrafficModeState(next) prev = normalizeTrafficModeState(prev) nextIface, _ := resolveTrafficIface(next.PreferredIface) if err := applyTrafficMode(next, nextIface); err != nil { prevIface, _ := resolveTrafficIface(prev.PreferredIface) _ = applyTrafficMode(prev, prevIface) msg := evaluateTrafficMode(prev) msg.Message = "apply failed, rolled back: " + err.Error() writeJSON(w, http.StatusOK, msg) return } if err := saveTrafficModeState(next); err != nil { prevIface, _ := resolveTrafficIface(prev.PreferredIface) _ = applyTrafficMode(prev, prevIface) _ = saveTrafficModeState(prev) rolled := evaluateTrafficMode(prev) rolled.Message = "state save failed, rolled back: " + err.Error() writeJSON(w, http.StatusOK, rolled) return } res := evaluateTrafficMode(next) if !res.Healthy { prevIface, _ := resolveTrafficIface(prev.PreferredIface) _ = applyTrafficMode(prev, prevIface) _ = saveTrafficModeState(prev) rolled := evaluateTrafficMode(prev) rolled.Message = "verification failed, rolled back: " + res.Message writeJSON(w, http.StatusOK, rolled) return } events.push("traffic_mode_changed", map[string]any{ "mode": res.Mode, "applied": res.AppliedMode, "active_iface": res.ActiveIface, "healthy": res.Healthy, "advanced_active": res.AdvancedActive, "auto_local_bypass": res.AutoLocalBypass, "auto_local_active": res.AutoLocalActive, "ingress_reply": res.IngressReplyBypass, "ingress_active": res.IngressReplyActive, "overrides_applied": res.OverridesApplied, }) writeJSON(w, http.StatusOK, res) }