mirror of
https://github.com/safing/portmaster
synced 2025-04-24 04:49:10 +00:00
221 lines
5.3 KiB
Go
221 lines
5.3 KiB
Go
package navigator
|
|
|
|
import (
|
|
"fmt"
|
|
mrand "math/rand"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Routes holds a collection of Routes.
|
|
type Routes struct {
|
|
All []*Route
|
|
randomizeTopPercent float32
|
|
maxCost float32 // automatic
|
|
maxRoutes int // manual setting
|
|
}
|
|
|
|
// Len is the number of elements in the collection.
|
|
func (r *Routes) Len() int {
|
|
return len(r.All)
|
|
}
|
|
|
|
// Less reports whether the element with index i should sort before the element
|
|
// with index j.
|
|
func (r *Routes) Less(i, j int) bool {
|
|
return r.All[i].TotalCost < r.All[j].TotalCost
|
|
}
|
|
|
|
// Swap swaps the elements with indexes i and j.
|
|
func (r *Routes) Swap(i, j int) {
|
|
r.All[i], r.All[j] = r.All[j], r.All[i]
|
|
}
|
|
|
|
// isGoodEnough reports whether the route would survive a clean process.
|
|
func (r *Routes) isGoodEnough(route *Route) bool {
|
|
if r.maxCost > 0 && route.TotalCost > r.maxCost {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// add adds a Route if it is good enough.
|
|
func (r *Routes) add(route *Route) {
|
|
if !r.isGoodEnough(route) {
|
|
return
|
|
}
|
|
r.All = append(r.All, route.CopyUpTo(0))
|
|
r.clean()
|
|
}
|
|
|
|
// clean sort and shortens the list to the configured maximum.
|
|
func (r *Routes) clean() {
|
|
// Sort Routes so that the best ones are on top.
|
|
sort.Sort(r)
|
|
// Remove all remaining from the list.
|
|
if len(r.All) > r.maxRoutes {
|
|
r.All = r.All[:r.maxRoutes]
|
|
}
|
|
// Set new maximum total cost.
|
|
if len(r.All) >= r.maxRoutes {
|
|
r.maxCost = r.All[len(r.All)-1].TotalCost
|
|
}
|
|
}
|
|
|
|
// randomizeTop randomized to the top nearest pins for balancing the network.
|
|
func (r *Routes) randomizeTop() {
|
|
switch {
|
|
case r.randomizeTopPercent == 0:
|
|
// Check if randomization is enabled.
|
|
return
|
|
case len(r.All) < 2:
|
|
// Check if we have enough pins to work with.
|
|
return
|
|
}
|
|
|
|
// Find randomization set.
|
|
randomizeUpTo := len(r.All)
|
|
threshold := r.All[0].TotalCost * (1 + r.randomizeTopPercent)
|
|
for i, r := range r.All {
|
|
// Find first value above the threshold to stop.
|
|
if r.TotalCost > threshold {
|
|
randomizeUpTo = i
|
|
break
|
|
}
|
|
}
|
|
|
|
// Shuffle top set.
|
|
if randomizeUpTo >= 2 {
|
|
mr := mrand.New(mrand.NewSource(time.Now().UnixNano())) //nolint:gosec
|
|
mr.Shuffle(randomizeUpTo, r.Swap)
|
|
}
|
|
}
|
|
|
|
// Route is a path through the map.
|
|
type Route struct {
|
|
// Path is a list of Transit Hubs and the Destination Hub, including the Cost
|
|
// for each Hop.
|
|
Path []*Hop
|
|
|
|
// DstCost is the calculated cost between the Destination Hub and the destination IP.
|
|
DstCost float32
|
|
|
|
// TotalCost is the sum of all costs of this Route.
|
|
TotalCost float32
|
|
|
|
// Algorithm is the ID of the algorithm used to calculate the route.
|
|
Algorithm string
|
|
}
|
|
|
|
// Hop is one hop of a route's path.
|
|
type Hop struct {
|
|
pin *Pin
|
|
|
|
// HubID is the Hub ID.
|
|
HubID string
|
|
|
|
// Cost is the cost for both Lane to this Hub and the Hub itself.
|
|
Cost float32
|
|
}
|
|
|
|
// addHop adds a hop to the route.
|
|
func (r *Route) addHop(pin *Pin, cost float32) {
|
|
r.Path = append(r.Path, &Hop{
|
|
pin: pin,
|
|
Cost: cost,
|
|
})
|
|
r.recalculateTotalCost()
|
|
}
|
|
|
|
// completeRoute completes the route by adding the destination cost of the
|
|
// connection between the last hop and the destination IP.
|
|
func (r *Route) completeRoute(dstCost float32) {
|
|
r.DstCost = dstCost
|
|
r.recalculateTotalCost()
|
|
}
|
|
|
|
// removeHop removes the last hop from the Route.
|
|
func (r *Route) removeHop() {
|
|
// Reset DstCost, as the route might have been completed.
|
|
r.DstCost = 0
|
|
|
|
if len(r.Path) >= 1 {
|
|
r.Path = r.Path[:len(r.Path)-1]
|
|
}
|
|
r.recalculateTotalCost()
|
|
}
|
|
|
|
// recalculateTotalCost recalculates to total cost of this route.
|
|
func (r *Route) recalculateTotalCost() {
|
|
r.TotalCost = r.DstCost
|
|
for _, hop := range r.Path {
|
|
if hop.pin.HasActiveTerminal() {
|
|
// If we have an active connection, only take 80% of the cost.
|
|
r.TotalCost += hop.Cost * 0.8
|
|
} else {
|
|
r.TotalCost += hop.Cost
|
|
}
|
|
}
|
|
}
|
|
|
|
// CopyUpTo makes a somewhat deep copy of the Route up to the specified amount
|
|
// and returns it. Hops themselves are not copied, because their data does not
|
|
// change. Therefore, returned Hops may not be edited.
|
|
// Specify an amount of 0 to copy all.
|
|
func (r *Route) CopyUpTo(n int) *Route {
|
|
// Check amount.
|
|
if n == 0 || n > len(r.Path) {
|
|
n = len(r.Path)
|
|
}
|
|
|
|
newRoute := &Route{
|
|
Path: make([]*Hop, n),
|
|
DstCost: r.DstCost,
|
|
TotalCost: r.TotalCost,
|
|
}
|
|
copy(newRoute.Path, r.Path)
|
|
return newRoute
|
|
}
|
|
|
|
// makeExportReady fills in all the missing data fields which are meant for
|
|
// exporting only.
|
|
func (r *Routes) makeExportReady(algorithm string) {
|
|
for _, route := range r.All {
|
|
route.makeExportReady(algorithm)
|
|
}
|
|
}
|
|
|
|
// makeExportReady fills in all the missing data fields which are meant for
|
|
// exporting only.
|
|
func (r *Route) makeExportReady(algorithm string) {
|
|
r.Algorithm = algorithm
|
|
for _, hop := range r.Path {
|
|
hop.makeExportReady()
|
|
}
|
|
}
|
|
|
|
// makeExportReady fills in all the missing data fields which are meant for
|
|
// exporting only.
|
|
func (hop *Hop) makeExportReady() {
|
|
hop.HubID = hop.pin.Hub.ID
|
|
}
|
|
|
|
// Pin returns the Pin of the Hop.
|
|
func (hop *Hop) Pin() *Pin {
|
|
return hop.pin
|
|
}
|
|
|
|
func (r *Route) String() string {
|
|
s := make([]string, 0, len(r.Path)+2)
|
|
s = append(s, fmt.Sprintf("route with %.2fc:", r.TotalCost))
|
|
for i, hop := range r.Path {
|
|
if i == 0 {
|
|
s = append(s, hop.pin.String())
|
|
} else {
|
|
s = append(s, fmt.Sprintf("--> %.2fc %s", hop.Cost, hop.pin))
|
|
}
|
|
}
|
|
s = append(s, fmt.Sprintf("--> %.2fc", r.DstCost))
|
|
return strings.Join(s, " ")
|
|
}
|