diff --git a/internal/enginenetx/network.go b/internal/enginenetx/network.go index b4bb2b3092..bd7a99ab49 100644 --- a/internal/enginenetx/network.go +++ b/internal/enginenetx/network.go @@ -96,7 +96,7 @@ func NewNetwork( httpsDialer := newHTTPSDialer( logger, &netxlite.Netx{Underlying: nil}, // nil means using netxlite's singleton - newHTTPSDialerPolicy(kvStore, logger, resolver), + newHTTPSDialerPolicy(kvStore, logger, resolver, stats), stats, ) @@ -139,10 +139,16 @@ func NewNetwork( } // newHTTPSDialerPolicy contains the logic to select the [HTTPSDialerPolicy] to use. -func newHTTPSDialerPolicy(kvStore model.KeyValueStore, logger model.Logger, resolver model.Resolver) httpsDialerPolicy { +func newHTTPSDialerPolicy( + kvStore model.KeyValueStore, + logger model.Logger, + resolver model.Resolver, + stats *statsManager, +) httpsDialerPolicy { // create a composed fallback TLS dialer policy - fallback := &beaconsPolicy{ - Fallback: &dnsPolicy{logger, resolver}, + fallback := &statsPolicy{ + Fallback: &beaconsPolicy{Fallback: &dnsPolicy{logger, resolver}}, + Stats: stats, } // make sure we honor a user-provided policy diff --git a/internal/enginenetx/statsmanager.go b/internal/enginenetx/statsmanager.go index f05539fd7b..fba166cbb4 100644 --- a/internal/enginenetx/statsmanager.go +++ b/internal/enginenetx/statsmanager.go @@ -403,17 +403,22 @@ func (mt *statsManager) Close() error { // LookupTacticsStats returns stats about tactics for a given domain and port. The returned // list is a clone of the one stored by [*statsManager] so, it can easily be modified. -func (mt *statsManager) LookupTactics(domain string, port string) []*statsTactic { +func (mt *statsManager) LookupTactics(domain string, port string) ([]*statsTactic, bool) { out := []*statsTactic{} // get exclusive access defer mt.mu.Unlock() mt.mu.Lock() + // check whether we have information on this endpoint + domainEpnts, good := mt.container.DomainEndpoints[net.JoinHostPort(domain, port)] + if !good { + return out, len(out) > 0 + } + // return a copy of each entry - domainEpnts := mt.container.DomainEndpoints[net.JoinHostPort(domain, port)] for _, entry := range domainEpnts.Tactics { out = append(out, entry.Clone()) } - return out + return out, len(out) > 0 } diff --git a/internal/enginenetx/statsmanager_test.go b/internal/enginenetx/statsmanager_test.go index e3c017f844..9c8d50e4ee 100644 --- a/internal/enginenetx/statsmanager_test.go +++ b/internal/enginenetx/statsmanager_test.go @@ -848,24 +848,40 @@ func TestStatsManagerLookupTacticsStats(t *testing.T) { // create the stats manager stats := newStatsManager(kvStore, log.Log) - // obtain tactics - tactics := stats.LookupTactics("api.ooni.io", "443") - if len(tactics) != 3 { - t.Fatal("unexpected tactics length") - } + t.Run("when we're searching for a domain endpoint we know about", func(t *testing.T) { + // obtain tactics + tactics, good := stats.LookupTactics("api.ooni.io", "443") + if !good { + t.Fatal("expected good") + } + if len(tactics) != 3 { + t.Fatal("unexpected tactics length") + } + + // sort obtained tactics lexicographically + sort.SliceStable(tactics, func(i, j int) bool { + return tactics[i].Tactic.tacticSummaryKey() < tactics[j].Tactic.tacticSummaryKey() + }) - // sort obtained tactics lexicographically - sort.SliceStable(tactics, func(i, j int) bool { - return tactics[i].Tactic.tacticSummaryKey() < tactics[j].Tactic.tacticSummaryKey() - }) + // sort the initial tactics as well + sort.SliceStable(expectTactics, func(i, j int) bool { + return expectTactics[i].Tactic.tacticSummaryKey() < expectTactics[j].Tactic.tacticSummaryKey() + }) - // sort the initial tactics as well - sort.SliceStable(expectTactics, func(i, j int) bool { - return expectTactics[i].Tactic.tacticSummaryKey() < expectTactics[j].Tactic.tacticSummaryKey() + // compare once we have sorted + if diff := cmp.Diff(expectTactics, tactics); diff != "" { + t.Fatal(diff) + } }) - // compare once we have sorted - if diff := cmp.Diff(expectTactics, tactics); diff != "" { - t.Fatal(diff) - } + t.Run("when we don't have information about a domain endpoint", func(t *testing.T) { + // obtain tactics + tactics, good := stats.LookupTactics("api.ooni.io", "444") // note: different port! + if good { + t.Fatal("expected !good") + } + if len(tactics) != 0 { + t.Fatal("unexpected tactics length") + } + }) } diff --git a/internal/enginenetx/statspolicy.go b/internal/enginenetx/statspolicy.go index c57f74c29e..ff0e43cb7a 100644 --- a/internal/enginenetx/statspolicy.go +++ b/internal/enginenetx/statspolicy.go @@ -65,7 +65,10 @@ func (p *statsPolicy) LookupTactics(ctx context.Context, domain string, port str } func (p *statsPolicy) statsLookupTactics(domain string, port string) (out []*httpsDialerTactic) { - tactics := p.Stats.LookupTactics(domain, port) + tactics, good := p.Stats.LookupTactics(domain, port) + if !good { + return + } successRate := func(t *statsTactic) (rate float64) { if t.CountStarted > 0 {