package app import ( "fmt" "strings" "time" ) type transportNetnsToggleClientPlan struct { Item TransportNetnsToggleItem NeedsProvision bool NeedsRestart bool } func prepareTransportNetnsToggleClientLocked( it TransportClient, ifaces transportInterfacesState, targetEnabled bool, now time.Time, ) (TransportNetnsToggleItem, TransportClient) { item := TransportNetnsToggleItem{ OK: true, ClientID: it.ID, Kind: it.Kind, StatusBefore: it.Status, StatusAfter: it.Status, NetnsEnabled: targetEnabled, } if it.Kind != TransportClientSingBox { item.OK = false item.Code = "TRANSPORT_NETNS_KIND_UNSUPPORTED" item.Message = "netns toggle is supported only for singbox clients" return item, it } cfg := cloneMap(it.Config) if cfg == nil { cfg = map[string]any{} } changed := false prevEnabled := transportConfigBool(cfg, "netns_enabled") if prevEnabled != targetEnabled { changed = true } cfg["netns_enabled"] = targetEnabled if targetEnabled && strings.TrimSpace(transportConfigString(cfg, "netns_name")) == "" { probe := it probe.Config = cfg binding := resolveTransportIfaceBinding(probe, ifaces) if strings.TrimSpace(binding.NetnsName) != "" { cfg["netns_name"] = binding.NetnsName changed = true } } item.ConfigUpdated = changed if changed { it.Config = cfg it.UpdatedAt = now.Format(time.RFC3339) } if rebound, reboundChanged := applyTransportIfaceBinding(it, ifaces, now); reboundChanged { it = rebound item.ConfigUpdated = true } return item, it } func applyTransportNetnsToggleClientPlanLocked(plan transportNetnsToggleClientPlan) TransportNetnsToggleItem { item := plan.Item if !item.OK { return item } if plan.NeedsProvision { _, provisionResp := executeTransportClientProvisionActionLocked(item.ClientID) mergeTransportNetnsToggleProvisionResponse(&item, provisionResp) if !item.OK { return item } } if plan.NeedsRestart { _, restartResp := executeTransportLifecycleActionWithPreflightLocked(item.ClientID, "restart") mergeTransportNetnsToggleRestartResponse(&item, restartResp) } if strings.TrimSpace(item.Message) == "" { item.Message = fmt.Sprintf("netns=%t", item.NetnsEnabled) } return item } func mergeTransportNetnsToggleProvisionResponse(item *TransportNetnsToggleItem, resp TransportClientLifecycleResponse) { if item == nil { return } item.Provisioned = resp.OK item.Stdout = joinNonEmptyLines(item.Stdout, resp.Stdout) item.Stderr = joinNonEmptyLines(item.Stderr, resp.Stderr) item.StatusAfter = resp.StatusAfter if resp.OK { return } item.OK = false item.Code = strings.TrimSpace(resp.Code) item.Message = strings.TrimSpace(resp.Message) if item.Message == "" { item.Message = "provision failed" } } func mergeTransportNetnsToggleRestartResponse(item *TransportNetnsToggleItem, resp TransportClientLifecycleResponse) { if item == nil { return } item.Restarted = resp.OK item.Stdout = joinNonEmptyLines(item.Stdout, resp.Stdout) item.Stderr = joinNonEmptyLines(item.Stderr, resp.Stderr) item.StatusAfter = resp.StatusAfter if resp.OK { msg := strings.TrimSpace(resp.Message) if msg == "" { msg = "restart done" } item.Message = msg return } item.OK = false item.Code = strings.TrimSpace(resp.Code) item.Message = strings.TrimSpace(resp.Message) if item.Message == "" { item.Message = "restart failed" } } func markTransportNetnsTogglePlansSaveFailed( plans []transportNetnsToggleClientPlan, msg string, ) []TransportNetnsToggleItem { items := make([]TransportNetnsToggleItem, 0, len(plans)) for _, plan := range plans { item := plan.Item if item.OK { item.OK = false item.Code = "TRANSPORT_CLIENT_SAVE_FAILED" item.Message = msg item.Provisioned = false item.Restarted = false } items = append(items, item) } return items } func refreshTransportNetnsToggleFinalStatuses(resp *TransportNetnsToggleResponse) { if resp == nil || len(resp.Items) == 0 { return } transportMu.Lock() st := loadTransportClientsState() transportMu.Unlock() for i := range resp.Items { idx := findTransportClientIndex(st.Items, resp.Items[i].ClientID) if idx < 0 { continue } resp.Items[i].StatusAfter = st.Items[idx].Status resp.Items[i].NetnsEnabled = transportNetnsEnabled(st.Items[idx]) } } func finalizeTransportNetnsToggleResponse(resp TransportNetnsToggleResponse) TransportNetnsToggleResponse { resp.Count = len(resp.Items) for _, item := range resp.Items { if item.OK { resp.SuccessCount++ } else { resp.FailureCount++ } } if resp.Count == 0 { resp.OK = false resp.Code = "TRANSPORT_NETNS_NO_TARGETS" resp.Message = "no singbox clients found" return resp } if resp.FailureCount > 0 { resp.OK = false } if !resp.OK && resp.Code == "" { resp.Code = "TRANSPORT_NETNS_TOGGLE_PARTIAL_FAILED" } resp.Message = fmt.Sprintf( "netns=%t applied: success=%d failed=%d", resp.Enabled, resp.SuccessCount, resp.FailureCount, ) return resp }