Files
rdp-proxy/agents/rap-node-agent/internal/mesh/peer_connection_intent.go
T
m 20d361a886
build / backend (push) Has been cancelled
build / node-agent (push) Has been cancelled
build / worker (push) Has been cancelled
рабочий вариант, но скороть 10 МБит
2026-05-22 21:46:49 +03:00

337 lines
12 KiB
Go

package mesh
import (
"net"
"net/netip"
"net/url"
"sort"
"strings"
"time"
)
const (
PeerConnectionIntentMaintain = "maintain"
PeerConnectionIntentProbe = "probe"
PeerConnectionIntentRecover = "recover"
)
const (
PeerTransportModeDirect = "direct"
PeerTransportModePrivateLAN = "private_lan"
PeerTransportModeCorporateLAN = "corporate_lan"
PeerTransportModeOutboundOnly = "outbound_only"
PeerTransportModeRelayRequired = "relay_required"
PeerTransportModeRelayQUIC = "relay_quic"
PeerTransportModeUnknown = "unknown"
)
type PeerConnectionIntentPlanConfig struct {
PeerCache PeerCacheSnapshot
RecoveryPlan PeerRecoveryPlan
RendezvousLeases []PeerRendezvousLease
PreferredRegion string
Now time.Time
}
type PeerConnectionIntentPlan struct {
Mode string `json:"mode"`
IntentCount int `json:"intent_count"`
MaintainCount int `json:"maintain_count"`
ProbeCount int `json:"probe_count"`
RecoverCount int `json:"recover_count"`
DirectCount int `json:"direct_count"`
PrivateLANCount int `json:"private_lan_count"`
CorporateLANCount int `json:"corporate_lan_count"`
OutboundOnlyCount int `json:"outbound_only_count"`
RelayRequiredCount int `json:"relay_required_count"`
RelayQUICCount int `json:"relay_quic_count"`
RendezvousRequiredCount int `json:"rendezvous_required_count"`
RendezvousResolvedCount int `json:"rendezvous_resolved_count"`
RendezvousLeaseCount int `json:"rendezvous_lease_count"`
GeneratedAt time.Time `json:"generated_at"`
Intents []PeerConnectionIntent `json:"intents,omitempty"`
}
type PeerConnectionIntent struct {
NodeID string `json:"node_id"`
Action string `json:"action"`
Reason string `json:"reason"`
Endpoint string `json:"endpoint,omitempty"`
ConnectionState string `json:"connection_state"`
Transport string `json:"transport,omitempty"`
TransportMode string `json:"transport_mode"`
Reachability string `json:"reachability,omitempty"`
ConnectivityMode string `json:"connectivity_mode,omitempty"`
NATType string `json:"nat_type,omitempty"`
Region string `json:"region,omitempty"`
PolicyTags []string `json:"policy_tags,omitempty"`
RequiresRendezvous bool `json:"requires_rendezvous"`
RendezvousResolved bool `json:"rendezvous_resolved"`
DirectCandidate bool `json:"direct_candidate"`
RelayCandidate bool `json:"relay_candidate"`
BestCandidateID string `json:"best_candidate_id,omitempty"`
BestPeerCertSHA256 string `json:"best_peer_cert_sha256,omitempty"`
RendezvousLeaseID string `json:"rendezvous_lease_id,omitempty"`
RelayNodeID string `json:"relay_node_id,omitempty"`
RelayEndpoint string `json:"relay_endpoint,omitempty"`
ControlPlaneOnly bool `json:"control_plane_only"`
RecoverySeed bool `json:"recovery_seed"`
Priority int `json:"priority"`
GeneratedAt time.Time `json:"generated_at"`
}
func PlanPeerConnectionIntents(cfg PeerConnectionIntentPlanConfig) PeerConnectionIntentPlan {
now := normalizedNow(cfg.Now)
entryByNode := map[string]PeerCacheEntry{}
for _, entry := range cfg.PeerCache.Entries {
if strings.TrimSpace(entry.NodeID) == "" {
continue
}
entryByNode[entry.NodeID] = entry
}
intents := make([]PeerConnectionIntent, 0, len(cfg.RecoveryPlan.Candidates))
for _, candidate := range cfg.RecoveryPlan.Candidates {
if strings.TrimSpace(candidate.NodeID) == "" {
continue
}
entry := entryByNode[candidate.NodeID]
intent := PeerConnectionIntent{
NodeID: candidate.NodeID,
Action: connectionIntentAction(candidate),
Reason: candidate.Reason,
Endpoint: candidate.Endpoint,
ConnectionState: candidate.ConnectionState,
Transport: firstNonEmpty(candidate.BestTransport, entry.BestTransport),
Reachability: entry.BestReachability,
ConnectivityMode: entry.BestConnectivity,
NATType: entry.BestNATType,
Region: entry.BestRegion,
PolicyTags: append([]string{}, entry.BestPolicyTags...),
BestCandidateID: firstNonEmpty(candidate.BestCandidateID, entry.BestCandidateID),
BestPeerCertSHA256: entry.BestPeerCertSHA256,
RendezvousLeaseID: entry.RendezvousLeaseID,
RelayNodeID: entry.RelayNodeID,
RelayEndpoint: entry.RelayEndpoint,
RelayCandidate: entry.RelayQUIC,
ControlPlaneOnly: entry.RelayQUIC,
RecoverySeed: candidate.RecoverySeed || entry.RecoverySeed,
Priority: candidate.Priority,
GeneratedAt: now,
}
mode, requiresRendezvous, directCandidate := classifyPeerTransport(intent, cfg.PreferredRegion)
intent.TransportMode = mode
intent.RequiresRendezvous = requiresRendezvous
intent.DirectCandidate = directCandidate
if intent.RequiresRendezvous {
if lease, ok := rendezvousLeaseForPeer(cfg.RendezvousLeases, intent.NodeID, now); ok {
applyRendezvousLease(&intent, lease, cfg.PeerCache.LocalNodeID)
}
}
intents = append(intents, intent)
}
sort.SliceStable(intents, func(i, j int) bool {
if intents[i].Priority != intents[j].Priority {
return intents[i].Priority > intents[j].Priority
}
return intents[i].NodeID < intents[j].NodeID
})
plan := PeerConnectionIntentPlan{
Mode: cfg.RecoveryPlan.Mode,
IntentCount: len(intents),
GeneratedAt: now,
Intents: intents,
}
for _, intent := range intents {
switch intent.Action {
case PeerConnectionIntentMaintain:
plan.MaintainCount++
case PeerConnectionIntentProbe:
plan.ProbeCount++
case PeerConnectionIntentRecover:
plan.RecoverCount++
}
switch intent.TransportMode {
case PeerTransportModeDirect:
plan.DirectCount++
case PeerTransportModePrivateLAN:
plan.PrivateLANCount++
case PeerTransportModeCorporateLAN:
plan.CorporateLANCount++
case PeerTransportModeOutboundOnly:
plan.OutboundOnlyCount++
case PeerTransportModeRelayRequired:
plan.RelayRequiredCount++
case PeerTransportModeRelayQUIC:
plan.RelayQUICCount++
}
if intent.RequiresRendezvous {
plan.RendezvousRequiredCount++
}
if intent.RendezvousResolved {
plan.RendezvousResolvedCount++
}
if intent.RendezvousLeaseID != "" {
plan.RendezvousLeaseCount++
}
}
return plan
}
func connectionIntentAction(candidate PeerRecoveryCandidate) string {
switch candidate.Reason {
case "maintain_ready":
return PeerConnectionIntentMaintain
case "recover_degraded", "recover_seed", "recover_warm", "recover_peer":
return PeerConnectionIntentRecover
default:
return PeerConnectionIntentProbe
}
}
func classifyPeerTransport(intent PeerConnectionIntent, preferredRegion string) (string, bool, bool) {
transport := strings.ToLower(strings.TrimSpace(intent.Transport))
connectivity := strings.ToLower(strings.TrimSpace(intent.ConnectivityMode))
reachability := strings.ToLower(strings.TrimSpace(intent.Reachability))
region := strings.TrimSpace(intent.Region)
preferredRegion = strings.TrimSpace(preferredRegion)
tags := lowerStringSet(intent.PolicyTags)
if strings.Contains(transport, "relay") || connectivity == "relay_required" || reachability == "relay" {
return PeerTransportModeRelayRequired, true, false
}
if connectivity == "outbound_only" || reachability == "outbound_only" {
return PeerTransportModeOutboundOnly, true, false
}
if tags["corp-lan"] || tags["same-site"] {
return PeerTransportModeCorporateLAN, false, true
}
if tags["private-lan"] || reachability == "private" || endpointHasPrivateHost(intent.Endpoint) {
if preferredRegion != "" && region != "" && !strings.EqualFold(region, preferredRegion) {
return PeerTransportModeRelayRequired, true, false
}
return PeerTransportModePrivateLAN, false, true
}
if strings.Contains(transport, "direct") || reachability == "public" || connectivity == "direct" {
return PeerTransportModeDirect, false, true
}
return PeerTransportModeUnknown, false, false
}
func rendezvousLeaseForPeer(leases []PeerRendezvousLease, peerNodeID string, now time.Time) (PeerRendezvousLease, bool) {
now = normalizedNow(now)
candidates := make([]PeerRendezvousLease, 0, len(leases))
for _, lease := range leases {
if strings.TrimSpace(lease.PeerNodeID) != peerNodeID ||
strings.TrimSpace(lease.RelayEndpoint) == "" ||
strings.TrimSpace(lease.RelayNodeID) == "" ||
!lease.ControlPlaneOnly ||
lease.ExpiresAt.IsZero() ||
!lease.ExpiresAt.After(now) {
continue
}
candidates = append(candidates, lease)
}
if len(candidates) == 0 {
return PeerRendezvousLease{}, false
}
sort.SliceStable(candidates, func(i, j int) bool {
leftPriority := candidates[i].Priority
rightPriority := candidates[j].Priority
if leftPriority <= 0 {
leftPriority = 100
}
if rightPriority <= 0 {
rightPriority = 100
}
if leftPriority != rightPriority {
return leftPriority < rightPriority
}
if !candidates[i].ExpiresAt.Equal(candidates[j].ExpiresAt) {
return candidates[i].ExpiresAt.After(candidates[j].ExpiresAt)
}
return candidates[i].LeaseID < candidates[j].LeaseID
})
return candidates[0], true
}
func applyRendezvousLease(intent *PeerConnectionIntent, lease PeerRendezvousLease, localNodeID string) {
localRelay := strings.TrimSpace(lease.RelayNodeID) == strings.TrimSpace(localNodeID)
if !localRelay {
intent.Endpoint = strings.TrimRight(strings.TrimSpace(lease.RelayEndpoint), "/")
}
if localRelay {
intent.Transport = "reverse_quic"
} else {
intent.Transport = firstNonEmpty(lease.Transport, "relay_quic")
}
intent.TransportMode = PeerTransportModeRelayQUIC
intent.RequiresRendezvous = false
intent.RendezvousResolved = true
intent.DirectCandidate = false
intent.RelayCandidate = true
intent.RendezvousLeaseID = lease.LeaseID
intent.RelayNodeID = lease.RelayNodeID
intent.RelayEndpoint = strings.TrimRight(strings.TrimSpace(lease.RelayEndpoint), "/")
intent.ControlPlaneOnly = true
if certSHA256 := rendezvousLeasePeerCertSHA256(lease); certSHA256 != "" && !localRelay {
intent.BestPeerCertSHA256 = certSHA256
}
if lease.ConnectivityMode != "" {
intent.ConnectivityMode = lease.ConnectivityMode
}
}
func endpointHasPrivateHost(rawEndpoint string) bool {
addr, ok := endpointHostAddr(rawEndpoint)
if !ok {
return false
}
return addr.IsPrivate() || addr.IsLoopback() || addr.IsLinkLocalUnicast()
}
func endpointHasUnspecifiedHost(rawEndpoint string) bool {
addr, ok := endpointHostAddr(rawEndpoint)
return ok && addr.IsUnspecified()
}
func endpointHostAddr(rawEndpoint string) (netip.Addr, bool) {
rawEndpoint = strings.TrimSpace(rawEndpoint)
if rawEndpoint == "" {
return netip.Addr{}, false
}
host := rawEndpoint
if parsed, err := url.Parse(rawEndpoint); err == nil && parsed.Host != "" {
host = parsed.Host
}
if splitHost, _, err := net.SplitHostPort(host); err == nil {
host = splitHost
}
addr, err := netip.ParseAddr(strings.Trim(host, "[]"))
if err != nil {
return netip.Addr{}, false
}
return addr, true
}
func lowerStringSet(values []string) map[string]bool {
out := map[string]bool{}
for _, value := range values {
value = strings.ToLower(strings.TrimSpace(value))
if value != "" {
out[value] = true
}
}
return out
}
func firstNonEmpty(values ...string) string {
for _, value := range values {
if strings.TrimSpace(value) != "" {
return strings.TrimSpace(value)
}
}
return ""
}