mirror of
https://github.com/safing/portmaster
synced 2025-04-23 04:19:10 +00:00
* Move portbase into monorepo * Add new simple module mgr * [WIP] Switch to new simple module mgr * Add StateMgr and more worker variants * [WIP] Switch more modules * [WIP] Switch more modules * [WIP] swtich more modules * [WIP] switch all SPN modules * [WIP] switch all service modules * [WIP] Convert all workers to the new module system * [WIP] add new task system to module manager * [WIP] Add second take for scheduling workers * [WIP] Add FIXME for bugs in new scheduler * [WIP] Add minor improvements to scheduler * [WIP] Add new worker scheduler * [WIP] Fix more bug related to new module system * [WIP] Fix start handing of the new module system * [WIP] Improve startup process * [WIP] Fix minor issues * [WIP] Fix missing subsystem in settings * [WIP] Initialize managers in constructor * [WIP] Move module event initialization to constrictors * [WIP] Fix setting for enabling and disabling the SPN module * [WIP] Move API registeration into module construction * [WIP] Update states mgr for all modules * [WIP] Add CmdLine operation support * Add state helper methods to module group and instance * Add notification and module status handling to status package * Fix starting issues * Remove pilot widget and update security lock to new status data * Remove debug logs * Improve http server shutdown * Add workaround for cleanly shutting down firewall+netquery * Improve logging * Add syncing states with notifications for new module system * Improve starting, stopping, shutdown; resolve FIXMEs/TODOs * [WIP] Fix most unit tests * Review new module system and fix minor issues * Push shutdown and restart events again via API * Set sleep mode via interface * Update example/template module * [WIP] Fix spn/cabin unit test * Remove deprecated UI elements * Make log output more similar for the logging transition phase * Switch spn hub and observer cmds to new module system * Fix log sources * Make worker mgr less error prone * Fix tests and minor issues * Fix observation hub * Improve shutdown and restart handling * Split up big connection.go source file * Move varint and dsd packages to structures repo * Improve expansion test * Fix linter warnings * Fix interception module on windows * Fix linter errors --------- Co-authored-by: Vladimir Stoilov <vladimir@safing.io>
776 lines
21 KiB
Go
776 lines
21 KiB
Go
package navigator
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"path"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/tevino/abool"
|
|
"golang.org/x/exp/slices"
|
|
|
|
"github.com/safing/portmaster/base/config"
|
|
"github.com/safing/portmaster/base/database"
|
|
"github.com/safing/portmaster/base/database/query"
|
|
"github.com/safing/portmaster/base/database/record"
|
|
"github.com/safing/portmaster/base/log"
|
|
"github.com/safing/portmaster/base/utils"
|
|
"github.com/safing/portmaster/service/intel/geoip"
|
|
"github.com/safing/portmaster/service/mgr"
|
|
"github.com/safing/portmaster/service/netenv"
|
|
"github.com/safing/portmaster/service/profile"
|
|
"github.com/safing/portmaster/spn/hub"
|
|
)
|
|
|
|
var db = database.NewInterface(&database.Options{
|
|
Local: true,
|
|
Internal: true,
|
|
})
|
|
|
|
// InitializeFromDatabase loads all Hubs from the given database prefix and adds them to the Map.
|
|
func (m *Map) InitializeFromDatabase() error {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
// start query for Hubs
|
|
iter, err := db.Query(query.New(hub.MakeHubDBKey(m.Name, "")))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to start query for initialization feed of %s map: %w", m.Name, err)
|
|
}
|
|
|
|
// update navigator
|
|
var hubCount int
|
|
log.Tracef("spn/navigator: starting to initialize %s map from database", m.Name)
|
|
for r := range iter.Next {
|
|
h, err := hub.EnsureHub(r)
|
|
if err != nil {
|
|
log.Warningf("spn/navigator: could not parse hub %q while initializing %s map: %s", r.Key(), m.Name, err)
|
|
continue
|
|
}
|
|
|
|
hubCount++
|
|
m.updateHub(h, false, true)
|
|
}
|
|
switch {
|
|
case iter.Err() != nil:
|
|
return fmt.Errorf("failed to (fully) initialize %s map: %w", m.Name, iter.Err())
|
|
case hubCount == 0:
|
|
log.Warningf("spn/navigator: no hubs available for %s map - this is normal on first start", m.Name)
|
|
default:
|
|
log.Infof("spn/navigator: added %d hubs from database to %s map", hubCount, m.Name)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// UpdateHook updates the a map from database changes.
|
|
type UpdateHook struct {
|
|
database.HookBase
|
|
m *Map
|
|
}
|
|
|
|
// UsesPrePut implements the Hook interface.
|
|
func (hook *UpdateHook) UsesPrePut() bool {
|
|
return true
|
|
}
|
|
|
|
// PrePut implements the Hook interface.
|
|
func (hook *UpdateHook) PrePut(r record.Record) (record.Record, error) {
|
|
// Remove deleted hubs from the map.
|
|
if r.Meta().IsDeleted() {
|
|
hook.m.RemoveHub(path.Base(r.Key()))
|
|
return r, nil
|
|
}
|
|
|
|
// Ensure we have a hub and update it in navigation map.
|
|
h, err := hub.EnsureHub(r)
|
|
if err != nil {
|
|
log.Debugf("spn/navigator: record %s is not a hub", r.Key())
|
|
} else {
|
|
hook.m.updateHub(h, true, false)
|
|
}
|
|
|
|
return r, nil
|
|
}
|
|
|
|
// RegisterHubUpdateHook registers a database pre-put hook that updates all
|
|
// Hubs saved at the given database prefix.
|
|
func (m *Map) RegisterHubUpdateHook() (err error) {
|
|
m.hubUpdateHook, err = database.RegisterHook(
|
|
query.New(hub.MakeHubDBKey(m.Name, "")),
|
|
&UpdateHook{m: m},
|
|
)
|
|
return err
|
|
}
|
|
|
|
// CancelHubUpdateHook cancels the map's update hook.
|
|
func (m *Map) CancelHubUpdateHook() {
|
|
if m.hubUpdateHook != nil {
|
|
if err := m.hubUpdateHook.Cancel(); err != nil {
|
|
log.Warningf("spn/navigator: failed to cancel update hook for map %s: %s", m.Name, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// RemoveHub removes a Hub from the Map.
|
|
func (m *Map) RemoveHub(id string) {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
// Get pin and remove it from the map, if it exists.
|
|
pin, ok := m.all[id]
|
|
if !ok {
|
|
return
|
|
}
|
|
delete(m.all, id)
|
|
|
|
// Remove lanes from removed Pin.
|
|
for id := range pin.ConnectedTo {
|
|
// Remove Lane from peer.
|
|
peer, ok := m.all[id]
|
|
if ok {
|
|
delete(peer.ConnectedTo, pin.Hub.ID)
|
|
peer.pushChanges.Set()
|
|
}
|
|
}
|
|
|
|
// Push update to subscriptions.
|
|
export := pin.Export()
|
|
export.Meta().Delete()
|
|
mapDBController.PushUpdate(export)
|
|
// Push lane changes.
|
|
m.PushPinChanges()
|
|
}
|
|
|
|
// UpdateHub updates a Hub on the Map.
|
|
func (m *Map) UpdateHub(h *hub.Hub) {
|
|
m.updateHub(h, true, true)
|
|
}
|
|
|
|
func (m *Map) updateHub(h *hub.Hub, lockMap, lockHub bool) {
|
|
if lockMap {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
}
|
|
if lockHub {
|
|
h.Lock()
|
|
defer h.Unlock()
|
|
}
|
|
|
|
// Hub requires both Info and Status to be added to the Map.
|
|
if h.Info == nil || h.Status == nil {
|
|
return
|
|
}
|
|
|
|
// Create or update Pin.
|
|
pin, ok := m.all[h.ID]
|
|
if ok {
|
|
pin.Hub = h
|
|
} else {
|
|
pin = &Pin{
|
|
Hub: h,
|
|
ConnectedTo: make(map[string]*Lane),
|
|
pushChanges: abool.New(),
|
|
}
|
|
m.all[h.ID] = pin
|
|
}
|
|
pin.pushChanges.Set()
|
|
|
|
// 1. Update Pin Data.
|
|
|
|
// Add/Update location data from IP addresses.
|
|
pin.updateLocationData()
|
|
|
|
// Override Pin Data.
|
|
m.updateInfoOverrides(pin)
|
|
|
|
// Update Hub cost.
|
|
pin.Cost = CalculateHubCost(pin.Hub.Status.Load)
|
|
|
|
// Ensure measurements are set when enabled.
|
|
if m.measuringEnabled && pin.measurements == nil {
|
|
// Get shared measurements.
|
|
pin.measurements = pin.Hub.GetMeasurementsWithLockedHub()
|
|
|
|
// Update cost calculation.
|
|
latency, _ := pin.measurements.GetLatency()
|
|
capacity, _ := pin.measurements.GetCapacity()
|
|
pin.measurements.SetCalculatedCost(CalculateLaneCost(latency, capacity))
|
|
|
|
// Update geo proximity.
|
|
// Get own location.
|
|
var myLocation *geoip.Location
|
|
switch {
|
|
case m.home != nil && m.home.LocationV4 != nil:
|
|
myLocation = m.home.LocationV4
|
|
case m.home != nil && m.home.LocationV6 != nil:
|
|
myLocation = m.home.LocationV6
|
|
default:
|
|
locations, ok := netenv.GetInternetLocation()
|
|
if ok {
|
|
myLocation = locations.Best().LocationOrNil()
|
|
}
|
|
}
|
|
// Calculate proximity with available location.
|
|
if myLocation != nil {
|
|
switch {
|
|
case pin.LocationV4 != nil:
|
|
pin.measurements.SetGeoProximity(
|
|
myLocation.EstimateNetworkProximity(pin.LocationV4),
|
|
)
|
|
case pin.LocationV6 != nil:
|
|
pin.measurements.SetGeoProximity(
|
|
myLocation.EstimateNetworkProximity(pin.LocationV6),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 2. Update Pin States.
|
|
|
|
// Update the invalid status of the Pin.
|
|
if pin.Hub.InvalidInfo || pin.Hub.InvalidStatus {
|
|
pin.addStates(StateInvalid)
|
|
} else {
|
|
pin.removeStates(StateInvalid)
|
|
}
|
|
|
|
// Update online status of the Pin.
|
|
if pin.Hub.HasFlag(hub.FlagOffline) || pin.Hub.Status.Version == hub.VersionOffline {
|
|
pin.addStates(StateOffline)
|
|
} else {
|
|
pin.removeStates(StateOffline)
|
|
}
|
|
|
|
// Update online status of the Pin.
|
|
if pin.Hub.HasFlag(hub.FlagAllowUnencrypted) {
|
|
pin.addStates(StateAllowUnencrypted)
|
|
} else {
|
|
pin.removeStates(StateAllowUnencrypted)
|
|
}
|
|
|
|
// Update from status flags.
|
|
if pin.Hub.HasFlag(hub.FlagNetError) {
|
|
pin.addStates(StateConnectivityIssues)
|
|
} else {
|
|
pin.removeStates(StateConnectivityIssues)
|
|
}
|
|
|
|
// Update Trust and Advisory Statuses.
|
|
m.updateIntelStatuses(pin, cfgOptionTrustNodeNodes())
|
|
|
|
// Update Statuses derived from Hub.
|
|
pin.updateStateHasRequiredInfo()
|
|
pin.updateStateActive(time.Now().Unix())
|
|
|
|
// 3. Update Lanes.
|
|
|
|
// Mark all existing Lanes as inactive.
|
|
for _, lane := range pin.ConnectedTo {
|
|
lane.active = false
|
|
}
|
|
|
|
// Update Lanes (connections to other Hubs) from the Status.
|
|
for _, lane := range pin.Hub.Status.Lanes {
|
|
// Check if this is a Lane to itself.
|
|
if lane.ID == pin.Hub.ID {
|
|
continue
|
|
}
|
|
|
|
// First, get the Lane peer.
|
|
peer, ok := m.all[lane.ID]
|
|
if !ok {
|
|
// We need to wait for peer to be added to the Map.
|
|
continue
|
|
}
|
|
|
|
m.updateHubLane(pin, lane, peer)
|
|
}
|
|
|
|
// Remove all inactive/abandoned Lanes from both Pins.
|
|
var removedLanes bool
|
|
for id, lane := range pin.ConnectedTo {
|
|
if !lane.active {
|
|
// Remove Lane from this Pin.
|
|
delete(pin.ConnectedTo, id)
|
|
pin.pushChanges.Set()
|
|
removedLanes = true
|
|
// Remove Lane from peer.
|
|
peer, ok := m.all[id]
|
|
if ok {
|
|
delete(peer.ConnectedTo, pin.Hub.ID)
|
|
peer.pushChanges.Set()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fully recalculate reachability if any Lanes were removed.
|
|
if removedLanes {
|
|
err := m.recalculateReachableHubs()
|
|
if err != nil {
|
|
log.Warningf("spn/navigator: failed to recalculate reachable Hubs: %s", err)
|
|
}
|
|
}
|
|
|
|
// 4. Update states that depend on other information.
|
|
|
|
// Check if hub is superseded or if it supersedes another hub.
|
|
m.updateStateSuperseded(pin)
|
|
|
|
// Push updates.
|
|
m.PushPinChanges()
|
|
}
|
|
|
|
const (
|
|
minUnconfirmedLatency = 10 * time.Millisecond
|
|
maxUnconfirmedCapacity = 100000000 // 100Mbit/s
|
|
|
|
cap1Mbit float32 = 1000000
|
|
cap10Mbit float32 = 10000000
|
|
cap100Mbit float32 = 100000000
|
|
cap1Gbit float32 = 1000000000
|
|
cap10Gbit float32 = 10000000000
|
|
)
|
|
|
|
// updateHubLane updates a lane between two Hubs on the Map.
|
|
// pin must already be locked, lane belongs to pin.
|
|
// peer will be locked by this function.
|
|
func (m *Map) updateHubLane(pin *Pin, lane *hub.Lane, peer *Pin) {
|
|
peer.Hub.Lock()
|
|
defer peer.Hub.Unlock()
|
|
|
|
// Then get the corresponding Lane from that peer, if it exists.
|
|
var peerLane *hub.Lane
|
|
for _, possiblePeerLane := range peer.Hub.Status.Lanes {
|
|
if possiblePeerLane.ID == pin.Hub.ID {
|
|
peerLane = possiblePeerLane
|
|
// We have found the corresponding peerLane, break the loop.
|
|
break
|
|
}
|
|
}
|
|
if peerLane == nil {
|
|
// The peer obviously does not advertise a Lane to this Hub.
|
|
// Maybe this is a fresh Lane, and the message has not yet reached us.
|
|
// Alternatively, the Lane could have been recently removed.
|
|
|
|
// Abandon this Lane for now.
|
|
delete(pin.ConnectedTo, peer.Hub.ID)
|
|
return
|
|
}
|
|
|
|
// Calculate combined latency, use the greater value.
|
|
combinedLatency := lane.Latency
|
|
if peerLane.Latency > combinedLatency {
|
|
combinedLatency = peerLane.Latency
|
|
}
|
|
// Enforce minimum value if at least one side has no data.
|
|
if (lane.Latency == 0 || peerLane.Latency == 0) && combinedLatency < minUnconfirmedLatency {
|
|
combinedLatency = minUnconfirmedLatency
|
|
}
|
|
|
|
// Calculate combined capacity, use the lesser existing value.
|
|
combinedCapacity := lane.Capacity
|
|
if combinedCapacity == 0 || (peerLane.Capacity > 0 && peerLane.Capacity < combinedCapacity) {
|
|
combinedCapacity = peerLane.Capacity
|
|
}
|
|
// Enforce maximum value if at least one side has no data.
|
|
if (lane.Capacity == 0 || peerLane.Capacity == 0) && combinedCapacity > maxUnconfirmedCapacity {
|
|
combinedCapacity = maxUnconfirmedCapacity
|
|
}
|
|
|
|
// Calculate lane cost.
|
|
laneCost := CalculateLaneCost(combinedLatency, combinedCapacity)
|
|
|
|
// Add Lane to both Pins and override old values in the process.
|
|
pin.ConnectedTo[peer.Hub.ID] = &Lane{
|
|
Pin: peer,
|
|
Capacity: combinedCapacity,
|
|
Latency: combinedLatency,
|
|
Cost: laneCost,
|
|
active: true,
|
|
}
|
|
peer.ConnectedTo[pin.Hub.ID] = &Lane{
|
|
Pin: pin,
|
|
Capacity: combinedCapacity,
|
|
Latency: combinedLatency,
|
|
Cost: laneCost,
|
|
active: true,
|
|
}
|
|
peer.pushChanges.Set()
|
|
|
|
// Check for reachability.
|
|
|
|
if pin.State.Has(StateReachable) {
|
|
peer.markReachable(pin.HopDistance + 1)
|
|
}
|
|
if peer.State.Has(StateReachable) {
|
|
pin.markReachable(peer.HopDistance + 1)
|
|
}
|
|
}
|
|
|
|
// ResetFailingStates resets the failing state on all pins.
|
|
func (m *Map) ResetFailingStates() {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
for _, pin := range m.all {
|
|
pin.ResetFailingState()
|
|
}
|
|
|
|
m.PushPinChanges()
|
|
}
|
|
|
|
func (m *Map) updateFailingStates(ctx *mgr.WorkerCtx) error {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
for _, pin := range m.all {
|
|
if pin.State.Has(StateFailing) && !pin.IsFailing() {
|
|
pin.removeStates(StateFailing)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *Map) updateStates(ctx *mgr.WorkerCtx) error {
|
|
var toDelete []string
|
|
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
pinLoop:
|
|
for _, pin := range m.all {
|
|
// Check for discontinued Hubs.
|
|
if m.intel != nil {
|
|
hubIntel, ok := m.intel.Hubs[pin.Hub.ID]
|
|
if ok && hubIntel.Discontinued {
|
|
toDelete = append(toDelete, pin.Hub.ID)
|
|
log.Infof("spn/navigator: deleting discontinued %s", pin.Hub)
|
|
continue pinLoop
|
|
}
|
|
}
|
|
// Check for obsoleted Hubs.
|
|
if pin.State.HasNoneOf(StateActive) && pin.Hub.Obsolete() {
|
|
toDelete = append(toDelete, pin.Hub.ID)
|
|
log.Infof("spn/navigator: deleting obsolete %s", pin.Hub)
|
|
}
|
|
|
|
// Delete hubs async, as deleting triggers a couple hooks that lock the map.
|
|
if len(toDelete) > 0 {
|
|
module.mgr.Go("delete hubs", func(_ *mgr.WorkerCtx) error {
|
|
for _, idToDelete := range toDelete {
|
|
err := hub.RemoveHubAndMsgs(m.Name, idToDelete)
|
|
if err != nil {
|
|
log.Warningf("spn/navigator: failed to delete Hub %s: %s", idToDelete, err)
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
}
|
|
|
|
// Update StateActive.
|
|
m.updateActiveHubs()
|
|
|
|
// Update StateReachable.
|
|
return m.recalculateReachableHubs()
|
|
}
|
|
|
|
// AddBootstrapHubs adds the given bootstrap hubs to the map.
|
|
func (m *Map) AddBootstrapHubs(bootstrapTransports []string) error {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
return m.addBootstrapHubs(bootstrapTransports)
|
|
}
|
|
|
|
func (m *Map) addBootstrapHubs(bootstrapTransports []string) error {
|
|
var anyAdded bool
|
|
var lastErr error
|
|
var failed int
|
|
for _, bootstrapTransport := range bootstrapTransports {
|
|
err := m.addBootstrapHub(bootstrapTransport)
|
|
if err != nil {
|
|
log.Warningf("spn/navigator: failed to add bootstrap hub %q to map %s: %s", bootstrapTransport, m.Name, err)
|
|
lastErr = err
|
|
failed++
|
|
} else {
|
|
anyAdded = true
|
|
}
|
|
}
|
|
|
|
if lastErr != nil && !anyAdded {
|
|
return lastErr
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *Map) addBootstrapHub(bootstrapTransport string) error {
|
|
// Parse bootstrap hub.
|
|
transport, hubID, hubIP, err := hub.ParseBootstrapHub(bootstrapTransport)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid bootstrap hub: %w", err)
|
|
}
|
|
|
|
// Check if hub already exists.
|
|
var h *hub.Hub
|
|
pin, ok := m.all[hubID]
|
|
if ok {
|
|
h = pin.Hub
|
|
} else {
|
|
h = &hub.Hub{
|
|
ID: hubID,
|
|
Map: m.Name,
|
|
Info: &hub.Announcement{
|
|
ID: hubID,
|
|
},
|
|
Status: &hub.Status{},
|
|
FirstSeen: time.Now(), // Do not garbage collect bootstrap hubs.
|
|
}
|
|
}
|
|
|
|
// Add IP if it does not yet exist.
|
|
if hubIP4 := hubIP.To4(); hubIP4 != nil {
|
|
if h.Info.IPv4 == nil {
|
|
h.Info.IPv4 = hubIP4
|
|
} else if !h.Info.IPv4.Equal(hubIP4) {
|
|
return fmt.Errorf("additional bootstrap entry with same ID but mismatching IP address: %s", hubIP)
|
|
}
|
|
} else {
|
|
if h.Info.IPv6 == nil {
|
|
h.Info.IPv6 = hubIP
|
|
} else if !h.Info.IPv6.Equal(hubIP) {
|
|
return fmt.Errorf("additional bootstrap entry with same ID but mismatching IP address: %s", hubIP)
|
|
}
|
|
}
|
|
|
|
// Add transport if it does not yet exist.
|
|
t := transport.String()
|
|
if !utils.StringInSlice(h.Info.Transports, t) {
|
|
h.Info.Transports = append(h.Info.Transports, t)
|
|
}
|
|
|
|
// Add/update to map for bootstrapping.
|
|
m.updateHub(h, false, false)
|
|
log.Infof("spn/navigator: added/updated bootstrap %s to map %s", h, m.Name)
|
|
return nil
|
|
}
|
|
|
|
// UpdateConfigQuickSettings updates config quick settings with available countries.
|
|
func (m *Map) UpdateConfigQuickSettings(wc *mgr.WorkerCtx) error {
|
|
ctx, tracer := log.AddTracer(wc.Ctx())
|
|
tracer.Trace("navigator: updating SPN rules country quick settings")
|
|
defer tracer.Submit()
|
|
|
|
opts := m.DefaultOptions()
|
|
opts.Home = &HomeHubOptions{
|
|
Regard: StateTrusted,
|
|
}
|
|
opts.Destination = &DestinationHubOptions{
|
|
Regard: StateTrusted,
|
|
Disregard: StateIsHomeHub,
|
|
}
|
|
|
|
// Home Policy.
|
|
if err := m.updateQuickSettingExcludeCountryList(ctx, "spn/homePolicy", opts, HomeHub); err != nil {
|
|
return err
|
|
}
|
|
// Transit Policy.
|
|
if err := m.updateQuickSettingExcludeCountryList(ctx, profile.CfgOptionTransitHubPolicyKey, opts, TransitHub); err != nil {
|
|
return err
|
|
}
|
|
// Exit Policy.
|
|
if err := m.updateSelectRuleCountryList(ctx, profile.CfgOptionExitHubPolicyKey, opts, DestinationHub); err != nil {
|
|
return err
|
|
}
|
|
// DNS Exit Policy.
|
|
if err := m.updateSelectRuleCountryList(ctx, "spn/dnsExitPolicy", opts, DestinationHub); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Trust Nodes.
|
|
if err := m.updateQuickSettingVerifiedOwnerList(ctx, "spn/trustNodes"); err != nil {
|
|
return err
|
|
}
|
|
|
|
tracer.Trace("navigator: finished updating SPN rules country quick settings")
|
|
return nil
|
|
}
|
|
|
|
func (m *Map) updateQuickSettingExcludeCountryList(ctx context.Context, configKey string, opts *Options, matchFor HubType) error {
|
|
// Get config option.
|
|
cfgOption, err := config.GetOption(configKey)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get config option %s: %w", configKey, err)
|
|
}
|
|
|
|
// Get list of countries for this config option.
|
|
countries := m.GetAvailableCountries(opts, matchFor)
|
|
// Convert to list.
|
|
countryList := make([]*geoip.CountryInfo, 0, len(countries))
|
|
for _, country := range countries {
|
|
countryList = append(countryList, country)
|
|
}
|
|
// Sort list.
|
|
slices.SortFunc[[]*geoip.CountryInfo, *geoip.CountryInfo](countryList, func(a, b *geoip.CountryInfo) int {
|
|
return strings.Compare(a.Name, b.Name)
|
|
})
|
|
|
|
// Compile list of quick settings.
|
|
quickSettings := make([]config.QuickSetting, 0, len(countries))
|
|
for _, country := range countryList {
|
|
quickSettings = append(quickSettings, config.QuickSetting{
|
|
Name: fmt.Sprintf("Exclude %s (%s)", country.Name, country.Code),
|
|
Value: []string{"- " + country.Code},
|
|
Action: config.QuickMergeTop,
|
|
})
|
|
}
|
|
|
|
// Lock config option and set new quick settings.
|
|
cfgOption.Lock()
|
|
defer cfgOption.Unlock()
|
|
cfgOption.Annotations[config.QuickSettingsAnnotation] = quickSettings
|
|
|
|
log.Tracer(ctx).Debugf("navigator: updated %d countries in quick settings for %s", len(quickSettings), configKey)
|
|
return nil
|
|
}
|
|
|
|
type selectCountry struct {
|
|
config.QuickSetting
|
|
FlagID string
|
|
}
|
|
|
|
func (m *Map) updateSelectRuleCountryList(ctx context.Context, configKey string, opts *Options, matchFor HubType) error {
|
|
// Get config option.
|
|
cfgOption, err := config.GetOption(configKey)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get config option %s: %w", configKey, err)
|
|
}
|
|
|
|
// Get list of countries for this config option.
|
|
countries := m.GetAvailableCountries(opts, matchFor)
|
|
// Convert to list.
|
|
countryList := make([]*geoip.CountryInfo, 0, len(countries))
|
|
for _, country := range countries {
|
|
countryList = append(countryList, country)
|
|
}
|
|
// Sort list.
|
|
slices.SortFunc[[]*geoip.CountryInfo, *geoip.CountryInfo](countryList, func(a, b *geoip.CountryInfo) int {
|
|
return strings.Compare(a.Name, b.Name)
|
|
})
|
|
|
|
// Get continents from countries.
|
|
continents := make(map[string]*geoip.ContinentInfo)
|
|
for _, country := range countryList {
|
|
continents[country.Continent.Code] = &country.Continent
|
|
}
|
|
// Convert to list.
|
|
continentList := make([]*geoip.ContinentInfo, 0, len(continents))
|
|
for _, continent := range continents {
|
|
continentList = append(continentList, continent)
|
|
}
|
|
// Sort list.
|
|
slices.SortFunc[[]*geoip.ContinentInfo, *geoip.ContinentInfo](continentList, func(a, b *geoip.ContinentInfo) int {
|
|
return strings.Compare(a.Name, b.Name)
|
|
})
|
|
|
|
// Start compiling all options.
|
|
selections := make([]selectCountry, 0, len(continents)+len(countries)+2)
|
|
|
|
// Add EU as special region.
|
|
selections = append(selections, selectCountry{
|
|
QuickSetting: config.QuickSetting{
|
|
Name: "European Union",
|
|
Value: []string{"+ AT", "+ BE", "+ BG", "+ CY", "+ CZ", "+ DE", "+ DK", "+ EE", "+ ES", "+ FI", "+ FR", "+ GR", "+ HR", "+ HU", "+ IE", "+ IT", "+ LT", "+ LU", "+ LV", "+ MT", "+ NL", "+ PL", "+ PT", "+ RO", "+ SE", "+ SI", "+ SK", "- *"},
|
|
Action: config.QuickReplace,
|
|
},
|
|
FlagID: "EU",
|
|
})
|
|
selections = append(selections, selectCountry{
|
|
QuickSetting: config.QuickSetting{
|
|
Name: "US and Canada",
|
|
Value: []string{"+ US", "+ CA", "- *"},
|
|
Action: config.QuickReplace,
|
|
},
|
|
})
|
|
|
|
// Add countries to quick settings.
|
|
for _, country := range countryList {
|
|
selections = append(selections, selectCountry{
|
|
QuickSetting: config.QuickSetting{
|
|
Name: fmt.Sprintf("%s (%s)", country.Name, country.Code),
|
|
Value: []string{"+ " + country.Code, "- *"},
|
|
Action: config.QuickReplace,
|
|
},
|
|
FlagID: country.Code,
|
|
})
|
|
}
|
|
|
|
// Add continents to quick settings.
|
|
for _, continent := range continentList {
|
|
selections = append(selections, selectCountry{
|
|
QuickSetting: config.QuickSetting{
|
|
Name: fmt.Sprintf("%s (C:%s)", continent.Name, continent.Code),
|
|
Value: []string{"+ C:" + continent.Code, "- *"},
|
|
Action: config.QuickReplace,
|
|
},
|
|
})
|
|
}
|
|
|
|
// Lock config option and set new quick settings.
|
|
cfgOption.Lock()
|
|
defer cfgOption.Unlock()
|
|
cfgOption.Annotations[config.QuickSettingsAnnotation] = selections
|
|
|
|
log.Tracer(ctx).Debugf("navigator: updated %d countries in quick settings for %s", len(selections), configKey)
|
|
return nil
|
|
}
|
|
|
|
func (m *Map) updateQuickSettingVerifiedOwnerList(ctx context.Context, configKey string) error {
|
|
// Get config option.
|
|
cfgOption, err := config.GetOption(configKey)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get config option %s: %w", configKey, err)
|
|
}
|
|
|
|
pins := m.pinList(true)
|
|
verifiedOwners := make([]string, 0, len(pins)/5) // Capacity is an estimation.
|
|
for _, pin := range pins {
|
|
pin.Lock()
|
|
vo := pin.VerifiedOwner
|
|
pin.Unlock()
|
|
|
|
// Skip invalid/unneeded values.
|
|
switch vo {
|
|
case "", "Safing":
|
|
continue
|
|
}
|
|
|
|
// Add to list, if not yet in there.
|
|
if !slices.Contains[[]string, string](verifiedOwners, vo) {
|
|
verifiedOwners = append(verifiedOwners, vo)
|
|
}
|
|
}
|
|
|
|
// Sort list.
|
|
slices.Sort[[]string](verifiedOwners)
|
|
|
|
// Compile list of quick settings.
|
|
quickSettings := make([]config.QuickSetting, 0, len(verifiedOwners))
|
|
for _, vo := range verifiedOwners {
|
|
quickSettings = append(quickSettings, config.QuickSetting{
|
|
Name: fmt.Sprintf("Trust %s", vo),
|
|
Value: []string{vo},
|
|
Action: config.QuickMergeBottom,
|
|
})
|
|
}
|
|
|
|
// Lock config option and set new quick settings.
|
|
cfgOption.Lock()
|
|
defer cfgOption.Unlock()
|
|
cfgOption.Annotations[config.QuickSettingsAnnotation] = quickSettings
|
|
|
|
log.Tracer(ctx).Debugf("navigator: updated %d verified owners in quick settings for %s", len(quickSettings), configKey)
|
|
return nil
|
|
}
|