199 lines
4.9 KiB
Go
199 lines
4.9 KiB
Go
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
|
|
}
|