From d67c8e9a04003751dbfcaf3b77f48c77811e05ce Mon Sep 17 00:00:00 2001 From: Alexandr Stelnykovych Date: Tue, 2 Sep 2025 18:24:50 +0300 Subject: [PATCH 1/3] fix: Ensure intel data for the main SPN map is initialized before Portmaster starts an SPN connection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related Issue Details: In some situations, SPN intel data was not fully applied. This lead to Portmaster making connections that don’t align with the intended intel preferences. https://github.com/safing/portmaster/issues/1999 https://github.com/safing/portmaster-shadow/issues/35 --- spn/captain/module.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spn/captain/module.go b/spn/captain/module.go index c5279c4d..c2d698ee 100644 --- a/spn/captain/module.go +++ b/spn/captain/module.go @@ -135,8 +135,9 @@ func start() error { crew.EnableConnecting(publicIdentity.Hub) } - // Initialize intel. - module.mgr.Go("start", func(wc *mgr.WorkerCtx) error { + // Initialize SPN intel. + // Do this synchronously to ensure everything is up to date before + module.mgr.Do("start", func(wc *mgr.WorkerCtx) error { if err := registerIntelUpdateHook(); err != nil { return err } From 7a95b021a5bb7a9b173d2acf0868b6f58e5bf210 Mon Sep 17 00:00:00 2001 From: Alexandr Stelnykovych Date: Tue, 2 Sep 2025 18:26:34 +0300 Subject: [PATCH 2/3] fix: Prevent unintended use of SPN nodes when intel data is not yet applied MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related Issue Details: In some situations, SPN intel data was not fully applied. This lead to Portmaster making connections that don’t align with the intended intel preferences. https://github.com/safing/portmaster/issues/1999 https://github.com/safing/portmaster-shadow/issues/35 --- spn/navigator/intel.go | 8 ++++++-- spn/navigator/options.go | 8 ++++++++ spn/navigator/pin.go | 4 ++++ spn/navigator/state.go | 6 ++++++ spn/navigator/update.go | 7 ++++--- 5 files changed, 28 insertions(+), 5 deletions(-) diff --git a/spn/navigator/intel.go b/spn/navigator/intel.go index cd4501b4..43fff54a 100644 --- a/spn/navigator/intel.go +++ b/spn/navigator/intel.go @@ -67,14 +67,18 @@ func (m *Map) GetIntel() *hub.Intel { } func (m *Map) updateIntelStatuses(pin *Pin, trustNodes []string) { - // Reset all related states. - pin.removeStates(StateTrusted | StateUsageDiscouraged | StateUsageAsHomeDiscouraged | StateUsageAsDestinationDiscouraged) + // Reset all related states (StateSummaryStatusesAppliedFromIntel). + pin.stateIntelApplied.UnSet() + pin.removeStates(StateTrusted | StateUsageDiscouraged | StateUsageAsHomeDiscouraged | StateUsageAsDestinationDiscouraged) // same as: StateSummaryStatusesAppliedFromIntel // Check if Intel data is loaded. if m.intel == nil { return } + // Indicate that intel statuses have been applied to the pin + defer pin.stateIntelApplied.Set() + // Check Hub Intel hubIntel, ok := m.intel.Hubs[pin.Hub.ID] if ok { diff --git a/spn/navigator/options.go b/spn/navigator/options.go index 3e2407fa..708d7f7e 100644 --- a/spn/navigator/options.go +++ b/spn/navigator/options.go @@ -246,6 +246,14 @@ func (o *HubOptions) Matcher(hubType HubType, hubIntel *hub.Intel) PinMatcher { return false } + // Check if all required states from intel were applied. + if regard.HasAnyOf(StateSummaryStatusesAppliedFromIntel) || disregard.HasAnyOf(StateSummaryStatusesAppliedFromIntel) { + if pin.stateIntelApplied.IsNotSet() { + log.Warningf("spn/navigator: pin %s skipped as intel statuses were not applied", pin.Hub.ID) + return false + } + } + // Check verified owners. if len(o.RequireVerifiedOwners) > 0 { // Check if Pin has a verified owner at all. diff --git a/spn/navigator/pin.go b/spn/navigator/pin.go index 3c2b99bf..54571b6b 100644 --- a/spn/navigator/pin.go +++ b/spn/navigator/pin.go @@ -26,6 +26,10 @@ type Pin struct { //nolint:maligned // Hub Status State PinState + + // stateIntelApplied signifies that states from intel file were applied to the pin. + stateIntelApplied *abool.AtomicBool + // VerifiedOwner holds the name of the verified owner / operator of the Hub. VerifiedOwner string // HopDistance signifies the needed hops to reach this Hub. diff --git a/spn/navigator/state.go b/spn/navigator/state.go index 755e2895..dd260754 100644 --- a/spn/navigator/state.go +++ b/spn/navigator/state.go @@ -91,6 +91,12 @@ const ( StateOffline | StateUsageDiscouraged | StateIsHomeHub + + // StateSummaryStatusesAppliedFromIntel summarizes all states that are applied from the intel file. + StateSummaryStatusesAppliedFromIntel = StateTrusted | + StateUsageDiscouraged | + StateUsageAsHomeDiscouraged | + StateUsageAsDestinationDiscouraged ) var allStates = []PinState{ diff --git a/spn/navigator/update.go b/spn/navigator/update.go index f0b1a339..17b73ae3 100644 --- a/spn/navigator/update.go +++ b/spn/navigator/update.go @@ -168,9 +168,10 @@ func (m *Map) updateHub(h *hub.Hub, lockMap, lockHub bool) { pin.Hub = h } else { pin = &Pin{ - Hub: h, - ConnectedTo: make(map[string]*Lane), - pushChanges: abool.New(), + Hub: h, + ConnectedTo: make(map[string]*Lane), + stateIntelApplied: abool.New(), + pushChanges: abool.New(), } m.all[h.ID] = pin } From 2c6a1a993cfd74bb74eae2b444589b8dc78c6e9e Mon Sep 17 00:00:00 2001 From: Alexandr Stelnykovych Date: Wed, 3 Sep 2025 12:31:25 +0300 Subject: [PATCH 3/3] fix: Ensure error handling during SPN intel initialization --- spn/captain/module.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spn/captain/module.go b/spn/captain/module.go index c2d698ee..6ebee1a1 100644 --- a/spn/captain/module.go +++ b/spn/captain/module.go @@ -137,7 +137,7 @@ func start() error { // Initialize SPN intel. // Do this synchronously to ensure everything is up to date before - module.mgr.Do("start", func(wc *mgr.WorkerCtx) error { + err = module.mgr.Do("start", func(wc *mgr.WorkerCtx) error { if err := registerIntelUpdateHook(); err != nil { return err } @@ -146,6 +146,9 @@ func start() error { } return nil }) + if err != nil { + return err + } // Subscribe to updates of cranes. startDockHooks()