289 lines
7.4 KiB
Go
289 lines
7.4 KiB
Go
package app
|
|
|
|
import (
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type transportNetnsPeerMatch struct {
|
|
Index int
|
|
Binding transportIfaceBinding
|
|
}
|
|
|
|
type transportLifecyclePeerStopPlan struct {
|
|
ClientID string
|
|
Client TransportClient
|
|
}
|
|
|
|
type transportLifecyclePeerStopExecution struct {
|
|
ClientID string
|
|
BackendID string
|
|
Result transportBackendActionResult
|
|
}
|
|
|
|
type transportLifecycleStateChange struct {
|
|
ClientID string
|
|
From TransportClientStatus
|
|
To TransportClientStatus
|
|
}
|
|
|
|
func matchTransportSingBoxPeersInSameNetns(
|
|
items []TransportClient,
|
|
targetIdx int,
|
|
ifaces transportInterfacesState,
|
|
) (transportIfaceBinding, []transportNetnsPeerMatch) {
|
|
if targetIdx < 0 || targetIdx >= len(items) {
|
|
return transportIfaceBinding{}, nil
|
|
}
|
|
target := items[targetIdx]
|
|
targetBinding := resolveTransportIfaceBinding(target, ifaces)
|
|
if target.Kind != TransportClientSingBox || !transportNetnsEnabled(target) {
|
|
return targetBinding, nil
|
|
}
|
|
targetNS := strings.TrimSpace(targetBinding.NetnsName)
|
|
if targetNS == "" {
|
|
return targetBinding, nil
|
|
}
|
|
|
|
peers := make([]transportNetnsPeerMatch, 0, 2)
|
|
for i := range items {
|
|
if i == targetIdx {
|
|
continue
|
|
}
|
|
peer := items[i]
|
|
if peer.Kind != TransportClientSingBox {
|
|
continue
|
|
}
|
|
if !transportNetnsEnabled(peer) {
|
|
continue
|
|
}
|
|
if normalizeTransportStatus(peer.Status) == TransportClientDown {
|
|
continue
|
|
}
|
|
peerBinding := resolveTransportIfaceBinding(peer, ifaces)
|
|
if strings.TrimSpace(peerBinding.NetnsName) != targetNS {
|
|
continue
|
|
}
|
|
peers = append(peers, transportNetnsPeerMatch{
|
|
Index: i,
|
|
Binding: peerBinding,
|
|
})
|
|
}
|
|
return targetBinding, peers
|
|
}
|
|
|
|
func matchTransportSingBoxPeersInSameNetnsForLock(
|
|
items []TransportClient,
|
|
targetIdx int,
|
|
ifaces transportInterfacesState,
|
|
) (transportIfaceBinding, []transportNetnsPeerMatch) {
|
|
if targetIdx < 0 || targetIdx >= len(items) {
|
|
return transportIfaceBinding{}, nil
|
|
}
|
|
target := items[targetIdx]
|
|
targetBinding := resolveTransportIfaceBinding(target, ifaces)
|
|
if target.Kind != TransportClientSingBox || !transportNetnsEnabled(target) {
|
|
return targetBinding, nil
|
|
}
|
|
targetNS := strings.TrimSpace(targetBinding.NetnsName)
|
|
if targetNS == "" {
|
|
return targetBinding, nil
|
|
}
|
|
|
|
peers := make([]transportNetnsPeerMatch, 0, 2)
|
|
for i := range items {
|
|
if i == targetIdx {
|
|
continue
|
|
}
|
|
peer := items[i]
|
|
if peer.Kind != TransportClientSingBox {
|
|
continue
|
|
}
|
|
if !transportNetnsEnabled(peer) {
|
|
continue
|
|
}
|
|
peerBinding := resolveTransportIfaceBinding(peer, ifaces)
|
|
if strings.TrimSpace(peerBinding.NetnsName) != targetNS {
|
|
continue
|
|
}
|
|
peers = append(peers, transportNetnsPeerMatch{
|
|
Index: i,
|
|
Binding: peerBinding,
|
|
})
|
|
}
|
|
return targetBinding, peers
|
|
}
|
|
|
|
func planTransportSingBoxPeerStops(
|
|
items []TransportClient,
|
|
targetIdx int,
|
|
ifaces transportInterfacesState,
|
|
now time.Time,
|
|
) []transportLifecyclePeerStopPlan {
|
|
_, peers := matchTransportSingBoxPeersInSameNetns(items, targetIdx, ifaces)
|
|
if len(peers) == 0 {
|
|
return nil
|
|
}
|
|
plans := make([]transportLifecyclePeerStopPlan, 0, len(peers))
|
|
for _, peerMatch := range peers {
|
|
if peerMatch.Index < 0 || peerMatch.Index >= len(items) {
|
|
continue
|
|
}
|
|
peer := items[peerMatch.Index]
|
|
if bound, changed := applyTransportIfaceBinding(peer, ifaces, now); changed {
|
|
peer = bound
|
|
}
|
|
plans = append(plans, transportLifecyclePeerStopPlan{
|
|
ClientID: peer.ID,
|
|
Client: peer,
|
|
})
|
|
}
|
|
return plans
|
|
}
|
|
|
|
func executeTransportLifecyclePeerStops(
|
|
plans []transportLifecyclePeerStopPlan,
|
|
) []transportLifecyclePeerStopExecution {
|
|
if len(plans) == 0 {
|
|
return nil
|
|
}
|
|
results := make([]transportLifecyclePeerStopExecution, 0, len(plans))
|
|
for _, plan := range plans {
|
|
backend := selectTransportBackend(plan.Client)
|
|
stopRes := backend.Action(plan.Client, "stop")
|
|
results = append(results, transportLifecyclePeerStopExecution{
|
|
ClientID: plan.ClientID,
|
|
BackendID: backend.ID(),
|
|
Result: stopRes,
|
|
})
|
|
if !stopRes.OK {
|
|
break
|
|
}
|
|
}
|
|
return results
|
|
}
|
|
|
|
func summarizeTransportLifecyclePeerStops(
|
|
results []transportLifecyclePeerStopExecution,
|
|
) transportBackendActionResult {
|
|
if len(results) == 0 {
|
|
return transportBackendActionResult{OK: true, ExitCode: 0}
|
|
}
|
|
|
|
stopped := make([]string, 0, len(results))
|
|
aggOut := make([]string, 0, len(results))
|
|
aggErr := make([]string, 0, len(results))
|
|
for _, res := range results {
|
|
if s := strings.TrimSpace(res.Result.Stdout); s != "" {
|
|
aggOut = append(aggOut, res.ClientID+": "+s)
|
|
}
|
|
if s := strings.TrimSpace(res.Result.Stderr); s != "" {
|
|
aggErr = append(aggErr, res.ClientID+": "+s)
|
|
}
|
|
if !res.Result.OK {
|
|
msg := strings.TrimSpace(res.Result.Message)
|
|
if msg == "" {
|
|
msg = "stop conflicting peer failed: " + res.ClientID
|
|
}
|
|
return transportBackendActionResult{
|
|
OK: false,
|
|
Code: res.Result.Code,
|
|
Message: msg,
|
|
ExitCode: res.Result.ExitCode,
|
|
Stdout: strings.Join(aggOut, "\n"),
|
|
Stderr: strings.Join(aggErr, "\n"),
|
|
}
|
|
}
|
|
stopped = append(stopped, res.ClientID)
|
|
}
|
|
if len(stopped) == 0 {
|
|
return transportBackendActionResult{OK: true, ExitCode: 0}
|
|
}
|
|
return transportBackendActionResult{
|
|
OK: true,
|
|
ExitCode: 0,
|
|
Message: "stopped conflicting peers: " + strings.Join(stopped, ", "),
|
|
Stdout: strings.Join(aggOut, "\n"),
|
|
Stderr: strings.Join(aggErr, "\n"),
|
|
}
|
|
}
|
|
|
|
func applyTransportLifecyclePeerStopExecutionsLocked(
|
|
st *transportClientsState,
|
|
ifaces transportInterfacesState,
|
|
now time.Time,
|
|
results []transportLifecyclePeerStopExecution,
|
|
) []transportLifecycleStateChange {
|
|
if st == nil || len(results) == 0 {
|
|
return nil
|
|
}
|
|
changes := make([]transportLifecycleStateChange, 0, len(results))
|
|
for _, res := range results {
|
|
idx := findTransportClientIndex(st.Items, res.ClientID)
|
|
if idx < 0 {
|
|
continue
|
|
}
|
|
peer := st.Items[idx]
|
|
if bound, changed := applyTransportIfaceBinding(peer, ifaces, now); changed {
|
|
peer = bound
|
|
}
|
|
prev := peer.Status
|
|
if res.Result.OK {
|
|
applyTransportLifecycleAction(&peer, "stop", now)
|
|
peer.Runtime.Backend = res.BackendID
|
|
} else {
|
|
applyTransportLifecycleFailure(&peer, "stop", now, res.BackendID, res.Result)
|
|
}
|
|
st.Items[idx] = peer
|
|
if prev != peer.Status {
|
|
changes = append(changes, transportLifecycleStateChange{
|
|
ClientID: peer.ID,
|
|
From: prev,
|
|
To: peer.Status,
|
|
})
|
|
}
|
|
}
|
|
return changes
|
|
}
|
|
|
|
func joinNonEmptyLines(parts ...string) string {
|
|
lines := make([]string, 0, len(parts))
|
|
for _, part := range parts {
|
|
v := strings.TrimSpace(part)
|
|
if v == "" {
|
|
continue
|
|
}
|
|
lines = append(lines, v)
|
|
}
|
|
return strings.Join(lines, "\n")
|
|
}
|
|
|
|
func stopTransportSingBoxPeersInSameNetnsLocked(
|
|
st *transportClientsState,
|
|
targetIdx int,
|
|
now time.Time,
|
|
) transportBackendActionResult {
|
|
if st == nil || targetIdx < 0 || targetIdx >= len(st.Items) {
|
|
return transportBackendActionResult{
|
|
OK: false,
|
|
Code: "TRANSPORT_CLIENT_NOT_FOUND",
|
|
Message: "target client not found",
|
|
ExitCode: -1,
|
|
}
|
|
}
|
|
ifaces, err := syncTransportInterfacesWithClientsLocked(st.Items)
|
|
if err != nil {
|
|
return transportBackendActionResult{
|
|
OK: false,
|
|
Code: "TRANSPORT_INTERFACES_SAVE_FAILED",
|
|
Message: "interfaces sync failed: " + err.Error(),
|
|
ExitCode: -1,
|
|
}
|
|
}
|
|
plans := planTransportSingBoxPeerStops(st.Items, targetIdx, ifaces, now)
|
|
results := executeTransportLifecyclePeerStops(plans)
|
|
summary := summarizeTransportLifecyclePeerStops(results)
|
|
applyTransportLifecyclePeerStopExecutionsLocked(st, ifaces, now, results)
|
|
return summary
|
|
}
|