Files
elmprodvpn/selective-vpn-api/app/transport_handlers_netns_apply.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
}