1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef NET_DNS_RESOLVE_CONTEXT_H_
6 #define NET_DNS_RESOLVE_CONTEXT_H_
7 
8 #include <memory>
9 #include <string>
10 #include <vector>
11 
12 #include "base/memory/weak_ptr.h"
13 #include "base/metrics/sample_vector.h"
14 #include "base/observer_list.h"
15 #include "base/observer_list_types.h"
16 #include "base/optional.h"
17 #include "base/time/time.h"
18 #include "net/base/isolation_info.h"
19 #include "net/base/net_export.h"
20 #include "net/dns/dns_config.h"
21 #include "net/dns/public/secure_dns_mode.h"
22 
23 namespace net {
24 
25 class ClassicDnsServerIterator;
26 class DnsSession;
27 class DnsServerIterator;
28 class DohDnsServerIterator;
29 class HostCache;
30 class URLRequestContext;
31 
32 // Per-URLRequestContext data used by HostResolver. Expected to be owned by the
33 // ContextHostResolver, and all usage/references are expected to be cleaned up
34 // or cancelled before the URLRequestContext goes out of service.
35 class NET_EXPORT_PRIVATE ResolveContext : public base::CheckedObserver {
36  public:
37   // Number of failures allowed before a DoH server is designated 'unavailable'.
38   // In AUTOMATIC mode, non-probe DoH queries should not be sent to DoH servers
39   // that have reached this limit.
40   //
41   // This limit is different from the failure limit that governs insecure async
42   // resolver bypass in multiple ways: NXDOMAIN responses are never counted as
43   // failures, and the outcome of fallback queries is not taken into account.
44   static const int kAutomaticModeFailureLimit = 10;
45 
46   class DohStatusObserver : public base::CheckedObserver {
47    public:
48     // Notification indicating that the current session for which DoH servers
49     // are being tracked has changed.
50     virtual void OnSessionChanged() = 0;
51 
52     // Notification indicating that a DoH server has been marked unavailable,
53     // but is ready for usage such as availability probes.
54     //
55     // |network_change| true if the invalidation was triggered by a network
56     // connection change.
57     virtual void OnDohServerUnavailable(bool network_change) = 0;
58 
59    protected:
60     DohStatusObserver() = default;
61     ~DohStatusObserver() override = default;
62   };
63 
64   ResolveContext(URLRequestContext* url_request_context, bool enable_caching);
65 
66   ResolveContext(const ResolveContext&) = delete;
67   ResolveContext& operator=(const ResolveContext&) = delete;
68 
69   ~ResolveContext() override;
70 
71   // Returns an iterator for DoH DNS servers.
72   std::unique_ptr<DnsServerIterator> GetDohIterator(const DnsConfig& config,
73                                                     const SecureDnsMode& mode,
74                                                     const DnsSession* session);
75 
76   // Returns an iterator for classic DNS servers.
77   std::unique_ptr<DnsServerIterator> GetClassicDnsIterator(
78       const DnsConfig& config,
79       const DnsSession* session);
80 
81   // Returns whether |doh_server_index| is eligible for use in AUTOMATIC mode,
82   // that is that consecutive failures are less than kAutomaticModeFailureLimit
83   // and the server has had at least one successful query or probe. Always
84   // |false| if |session| is not the current session.
85   bool GetDohServerAvailability(size_t doh_server_index,
86                                 const DnsSession* session) const;
87 
88   // Returns the number of DoH servers available for use in AUTOMATIC mode (see
89   // GetDohServerAvailability()). Always 0 if |session| is not the current
90   // session.
91   size_t NumAvailableDohServers(const DnsSession* session) const;
92 
93   // Record failure to get a response from the server (e.g. SERVFAIL, connection
94   // failures, or that the server failed to respond before the fallback period
95   // elapsed. If |is_doh_server| and the number of failures has surpassed a
96   // threshold, sets the DoH probe state to unavailable. Noop if |session| is
97   // not the current session. Should only be called with with server failure
98   // |rv|s, not e.g. OK, ERR_NAME_NOT_RESOLVED (which at the transaction level
99   // is expected to be nxdomain), or ERR_IO_PENDING.
100   void RecordServerFailure(size_t server_index,
101                            bool is_doh_server,
102                            int rv,
103                            const DnsSession* session);
104 
105   // Record that server responded successfully. Noop if |session| is not the
106   // current session.
107   void RecordServerSuccess(size_t server_index,
108                            bool is_doh_server,
109                            const DnsSession* session);
110 
111   // Record how long it took to receive a response from the server. Noop if
112   // |session| is not the current session.
113   void RecordRtt(size_t server_index,
114                  bool is_doh_server,
115                  base::TimeDelta rtt,
116                  int rv,
117                  const DnsSession* session);
118 
119   // Return the period the next query should run before fallback to next
120   // attempt. (Not actually a "timeout" because queries are not typically
121   // cancelled as additional attempts are made.) |attempt| counts from 0 and is
122   // used for exponential backoff.
123   base::TimeDelta NextClassicFallbackPeriod(size_t classic_server_index,
124                                             int attempt,
125                                             const DnsSession* session);
126 
127   // Return the period the next DoH query should run before fallback to next
128   // attempt.
129   base::TimeDelta NextDohFallbackPeriod(size_t doh_server_index,
130                                         const DnsSession* session);
131 
132   // Return a timeout for an insecure transaction (from Transaction::Start()).
133   // Expected that the transaction will skip waiting for this timeout if it is
134   // using fast timeouts, and also expected that transactions will always wait
135   // for all attempts to run for at least their fallback period before dying
136   // with timeout.
137   base::TimeDelta ClassicTransactionTimeout(const DnsSession* session);
138 
139   // Return a timeout for a secure transaction (from Transaction::Start()).
140   // Expected that the transaction will skip waiting for this timeout if it is
141   // using fast timeouts, and also expected that transactions will always wait
142   // for all attempts to run for at least their fallback period before dying
143   // with timeout.
144   base::TimeDelta SecureTransactionTimeout(SecureDnsMode secure_dns_mode,
145                                            const DnsSession* session);
146 
147   void RegisterDohStatusObserver(DohStatusObserver* observer);
148   void UnregisterDohStatusObserver(const DohStatusObserver* observer);
149 
url_request_context()150   URLRequestContext* url_request_context() { return url_request_context_; }
set_url_request_context(URLRequestContext * url_request_context)151   void set_url_request_context(URLRequestContext* url_request_context) {
152     DCHECK(!url_request_context_);
153     DCHECK(url_request_context);
154     url_request_context_ = url_request_context;
155   }
156 
host_cache()157   HostCache* host_cache() { return host_cache_.get(); }
158 
159   // Invalidate or clear saved per-context cached data that is not expected to
160   // stay valid between connections or sessions (eg the HostCache and DNS server
161   // stats). |new_session|, if non-null, will be the new "current" session for
162   // which per-session data will be kept.
163   void InvalidateCachesAndPerSessionData(const DnsSession* new_session,
164                                          bool network_change);
165 
current_session_for_testing()166   const DnsSession* current_session_for_testing() const {
167     return current_session_.get();
168   }
169 
170   // Returns IsolationInfo that should be used for DoH requests. Using a single
171   // transient IsolationInfo ensures that DNS requests aren't pooled with normal
172   // web requests, but still allows them to be pooled with each other, to allow
173   // reusing connections to the DoH server across different third party
174   // contexts. One downside of a transient IsolationInfo is that it means
175   // metadata about the DoH server itself will not be cached across restarts
176   // (alternative service info if it supports QUIC, for instance).
isolation_info()177   const IsolationInfo& isolation_info() const { return isolation_info_; }
178 
179  private:
180   friend DohDnsServerIterator;
181   friend ClassicDnsServerIterator;
182   // Runtime statistics of DNS server.
183   struct ServerStats {
184     explicit ServerStats(std::unique_ptr<base::SampleVector> rtt_histogram);
185 
186     ServerStats(ServerStats&&);
187 
188     ~ServerStats();
189 
190     // Count of consecutive failures after last success.
191     int last_failure_count;
192 
193     // True if any success has ever been recorded for this server for the
194     // current connection.
195     bool current_connection_success = false;
196 
197     // Last time when server returned failure or exceeded fallback period.
198     base::TimeTicks last_failure;
199     // Last time when server returned success.
200     base::TimeTicks last_success;
201 
202     // A histogram of observed RTT .
203     std::unique_ptr<base::SampleVector> rtt_histogram;
204   };
205 
206   // Return the (potentially rotating) index of the first configured server (to
207   // be passed to [Doh]ServerIndexToUse()). Always returns 0 if |session| is not
208   // the current session.
209   size_t FirstServerIndex(bool doh_server, const DnsSession* session);
210 
211   bool IsCurrentSession(const DnsSession* session) const;
212 
213   // Returns the ServerStats for the designated server. Returns nullptr if no
214   // ServerStats found.
215   ServerStats* GetServerStats(size_t server_index, bool is_doh_server);
216 
217   // Return the fallback period for the next query.
218   base::TimeDelta NextFallbackPeriodHelper(const ServerStats* server_stats,
219                                            int attempt);
220 
221   template <typename Iterator>
222   base::TimeDelta TransactionTimeoutHelper(Iterator server_stats_begin,
223                                            Iterator server_stats_end);
224 
225   // Record the time to perform a query.
226   void RecordRttForUma(size_t server_index,
227                        bool is_doh_server,
228                        base::TimeDelta rtt,
229                        int rv,
230                        base::TimeDelta base_fallback_period,
231                        const DnsSession* session);
232   std::string GetQueryTypeForUma(size_t server_index,
233                                  bool is_doh_server,
234                                  const DnsSession* session);
235   std::string GetDohProviderIdForUma(size_t server_index,
236                                      bool is_doh_server,
237                                      const DnsSession* session);
238 
239   void NotifyDohStatusObserversOfSessionChanged();
240   void NotifyDohStatusObserversOfUnavailable(bool network_change);
241 
242   static bool ServerStatsToDohAvailability(const ServerStats& stats);
243 
244   URLRequestContext* url_request_context_;
245 
246   std::unique_ptr<HostCache> host_cache_;
247 
248   // Current maximum server fallback period. Updated on connection change.
249   base::TimeDelta max_fallback_period_;
250 
251   base::ObserverList<DohStatusObserver,
252                      true /* check_empty */,
253                      false /* allow_reentrancy */>
254       doh_status_observers_;
255 
256   // Per-session data is only stored and valid for the latest session. Before
257   // accessing, should check that |current_session_| is valid and matches a
258   // passed in DnsSession.
259   //
260   // Using a WeakPtr, so even if a new session has the same pointer as an old
261   // invalidated session, it can be recognized as a different session.
262   //
263   // TODO(crbug.com/1022059): Make const DnsSession once server stats have been
264   // moved and no longer need to be read from DnsSession for availability logic.
265   base::WeakPtr<const DnsSession> current_session_;
266   // Current index into |config_.nameservers| to begin resolution with.
267   int classic_server_index_ = 0;
268   base::TimeDelta initial_fallback_period_;
269   // Track runtime statistics of each classic (insecure) DNS server.
270   std::vector<ServerStats> classic_server_stats_;
271   // Track runtime statistics of each DoH server.
272   std::vector<ServerStats> doh_server_stats_;
273 
274   const IsolationInfo isolation_info_;
275 };
276 
277 }  // namespace net
278 
279 #endif  // NET_DNS_RESOLVE_CONTEXT_H_
280