143 lines
5.2 KiB
Go
143 lines
5.2 KiB
Go
|
|
package poindexter
|
||
|
|
|
||
|
|
import (
|
||
|
|
"math"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
// NATRoutingMetrics provides metrics specifically for NAT traversal routing decisions.
|
||
|
|
type NATRoutingMetrics struct {
|
||
|
|
// Connectivity score (0-1): higher means better reachability
|
||
|
|
ConnectivityScore float64 `json:"connectivityScore"`
|
||
|
|
// Symmetry score (0-1): higher means more symmetric NAT (easier to traverse)
|
||
|
|
SymmetryScore float64 `json:"symmetryScore"`
|
||
|
|
// Relay requirement probability (0-1): likelihood peer needs relay
|
||
|
|
RelayProbability float64 `json:"relayProbability"`
|
||
|
|
// Direct connection success rate (historical)
|
||
|
|
DirectSuccessRate float64 `json:"directSuccessRate"`
|
||
|
|
// Average RTT in milliseconds
|
||
|
|
AvgRTTMs float64 `json:"avgRttMs"`
|
||
|
|
// Jitter (RTT variance) in milliseconds
|
||
|
|
JitterMs float64 `json:"jitterMs"`
|
||
|
|
// Packet loss rate (0-1)
|
||
|
|
PacketLossRate float64 `json:"packetLossRate"`
|
||
|
|
// Bandwidth estimate in Mbps
|
||
|
|
BandwidthMbps float64 `json:"bandwidthMbps"`
|
||
|
|
// NAT type classification
|
||
|
|
NATType string `json:"natType"`
|
||
|
|
// Last probe timestamp
|
||
|
|
LastProbeAt time.Time `json:"lastProbeAt"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// NATTypeClassification enumerates common NAT types for routing decisions.
|
||
|
|
type NATTypeClassification string
|
||
|
|
|
||
|
|
const (
|
||
|
|
NATTypeOpen NATTypeClassification = "open" // No NAT / Public IP
|
||
|
|
NATTypeFullCone NATTypeClassification = "full_cone" // Easy to traverse
|
||
|
|
NATTypeRestrictedCone NATTypeClassification = "restricted_cone" // Moderate difficulty
|
||
|
|
NATTypePortRestricted NATTypeClassification = "port_restricted" // Harder to traverse
|
||
|
|
NATTypeSymmetric NATTypeClassification = "symmetric" // Hardest to traverse
|
||
|
|
NATTypeSymmetricUDP NATTypeClassification = "symmetric_udp" // UDP-only symmetric
|
||
|
|
NATTypeUnknown NATTypeClassification = "unknown" // Not yet classified
|
||
|
|
NATTypeBehindCGNAT NATTypeClassification = "cgnat" // Carrier-grade NAT
|
||
|
|
NATTypeFirewalled NATTypeClassification = "firewalled" // Blocked by firewall
|
||
|
|
NATTypeRelayRequired NATTypeClassification = "relay_required" // Must use relay
|
||
|
|
)
|
||
|
|
|
||
|
|
// PeerQualityScore computes a composite quality score for peer selection.
|
||
|
|
// Higher scores indicate better peers for routing.
|
||
|
|
// Weights can be customized; default weights emphasize latency and reliability.
|
||
|
|
func PeerQualityScore(metrics NATRoutingMetrics, weights *QualityWeights) float64 {
|
||
|
|
w := DefaultQualityWeights()
|
||
|
|
if weights != nil {
|
||
|
|
w = *weights
|
||
|
|
}
|
||
|
|
|
||
|
|
// Normalize metrics to 0-1 scale (higher is better)
|
||
|
|
latencyScore := 1.0 - math.Min(metrics.AvgRTTMs/1000.0, 1.0) // <1000ms is acceptable
|
||
|
|
jitterScore := 1.0 - math.Min(metrics.JitterMs/100.0, 1.0) // <100ms jitter
|
||
|
|
lossScore := 1.0 - metrics.PacketLossRate // 0 loss is best
|
||
|
|
bandwidthScore := math.Min(metrics.BandwidthMbps/100.0, 1.0) // 100Mbps is excellent
|
||
|
|
connectivityScore := metrics.ConnectivityScore // Already 0-1
|
||
|
|
symmetryScore := metrics.SymmetryScore // Already 0-1
|
||
|
|
directScore := metrics.DirectSuccessRate // Already 0-1
|
||
|
|
relayPenalty := 1.0 - metrics.RelayProbability // Prefer non-relay
|
||
|
|
|
||
|
|
// NAT type bonus/penalty
|
||
|
|
natScore := natTypeScore(metrics.NATType)
|
||
|
|
|
||
|
|
// Weighted combination
|
||
|
|
score := (w.Latency*latencyScore +
|
||
|
|
w.Jitter*jitterScore +
|
||
|
|
w.PacketLoss*lossScore +
|
||
|
|
w.Bandwidth*bandwidthScore +
|
||
|
|
w.Connectivity*connectivityScore +
|
||
|
|
w.Symmetry*symmetryScore +
|
||
|
|
w.DirectSuccess*directScore +
|
||
|
|
w.RelayPenalty*relayPenalty +
|
||
|
|
w.NATType*natScore) / w.Total()
|
||
|
|
|
||
|
|
return math.Max(0, math.Min(1, score))
|
||
|
|
}
|
||
|
|
|
||
|
|
// QualityWeights configures the importance of each metric in peer selection.
|
||
|
|
type QualityWeights struct {
|
||
|
|
Latency float64 `json:"latency"`
|
||
|
|
Jitter float64 `json:"jitter"`
|
||
|
|
PacketLoss float64 `json:"packetLoss"`
|
||
|
|
Bandwidth float64 `json:"bandwidth"`
|
||
|
|
Connectivity float64 `json:"connectivity"`
|
||
|
|
Symmetry float64 `json:"symmetry"`
|
||
|
|
DirectSuccess float64 `json:"directSuccess"`
|
||
|
|
RelayPenalty float64 `json:"relayPenalty"`
|
||
|
|
NATType float64 `json:"natType"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// Total returns the sum of all weights for normalization.
|
||
|
|
func (w QualityWeights) Total() float64 {
|
||
|
|
return w.Latency + w.Jitter + w.PacketLoss + w.Bandwidth +
|
||
|
|
w.Connectivity + w.Symmetry + w.DirectSuccess + w.RelayPenalty + w.NATType
|
||
|
|
}
|
||
|
|
|
||
|
|
// DefaultQualityWeights returns sensible defaults for peer selection.
|
||
|
|
func DefaultQualityWeights() QualityWeights {
|
||
|
|
return QualityWeights{
|
||
|
|
Latency: 3.0, // Most important
|
||
|
|
Jitter: 1.5,
|
||
|
|
PacketLoss: 2.0,
|
||
|
|
Bandwidth: 1.0,
|
||
|
|
Connectivity: 2.0,
|
||
|
|
Symmetry: 1.0,
|
||
|
|
DirectSuccess: 2.0,
|
||
|
|
RelayPenalty: 1.5,
|
||
|
|
NATType: 1.0,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// natTypeScore returns a 0-1 score based on NAT type (higher is better for routing).
|
||
|
|
func natTypeScore(natType string) float64 {
|
||
|
|
switch NATTypeClassification(natType) {
|
||
|
|
case NATTypeOpen:
|
||
|
|
return 1.0
|
||
|
|
case NATTypeFullCone:
|
||
|
|
return 0.9
|
||
|
|
case NATTypeRestrictedCone:
|
||
|
|
return 0.7
|
||
|
|
case NATTypePortRestricted:
|
||
|
|
return 0.5
|
||
|
|
case NATTypeSymmetric:
|
||
|
|
return 0.3
|
||
|
|
case NATTypeSymmetricUDP:
|
||
|
|
return 0.25
|
||
|
|
case NATTypeBehindCGNAT:
|
||
|
|
return 0.2
|
||
|
|
case NATTypeFirewalled:
|
||
|
|
return 0.1
|
||
|
|
case NATTypeRelayRequired:
|
||
|
|
return 0.05
|
||
|
|
default:
|
||
|
|
return 0.4 // Unknown gets middle score
|
||
|
|
}
|
||
|
|
}
|