platform: modularize api/gui, add docs-tests-web foundation, and refresh root config
This commit is contained in:
288
selective-vpn-api/app/transport_client_runtime_netns.go
Normal file
288
selective-vpn-api/app/transport_client_runtime_netns.go
Normal file
@@ -0,0 +1,288 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user