blob: 206cd4548615caf9d0b712305de3f21b2ca72a6f [file] [log] [blame]
[email protected]d0d49dd82012-01-26 00:03:591// Copyright (c) 2012 The Chromium Authors. All rights reserved.
[email protected]7556ea22011-12-08 19:29:152// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "net/dns/dns_session.h"
6
avi65168052015-12-01 19:27:077#include <stdint.h>
danakj22f90e72016-04-16 01:55:408
dalyk6ab72db2019-09-04 23:12:119#include <cstdlib>
avi65168052015-12-01 19:27:0710#include <limits>
dalyk6ab72db2019-09-04 23:12:1111#include <string>
dchengc7eeda422015-12-26 03:56:4812#include <utility>
avi65168052015-12-01 19:27:0713
[email protected]7556ea22011-12-08 19:29:1514#include "base/bind.h"
[email protected]a6c84f42013-06-07 20:39:3815#include "base/lazy_instance.h"
Avi Drissman13fc8932015-12-20 04:40:4616#include "base/macros.h"
dalyk6ab72db2019-09-04 23:12:1117#include "base/metrics/histogram_functions.h"
asvitkinec3c93722015-06-17 14:48:3718#include "base/metrics/histogram_macros.h"
[email protected]ae1b30b2013-05-23 23:06:0319#include "base/metrics/sample_vector.h"
[email protected]120d38d2012-12-14 01:42:3220#include "base/rand_util.h"
21#include "base/stl_util.h"
dalyk6ab72db2019-09-04 23:12:1122#include "base/strings/stringprintf.h"
[email protected]66e96c42013-06-28 15:20:3123#include "base/time/time.h"
[email protected]7556ea22011-12-08 19:29:1524#include "net/base/ip_endpoint.h"
[email protected]120d38d2012-12-14 01:42:3225#include "net/base/net_errors.h"
dalykc2adf182019-09-02 14:31:2226#include "net/dns/dns_config.h"
[email protected]120d38d2012-12-14 01:42:3227#include "net/dns/dns_socket_pool.h"
ttuttlecf1158bf2016-03-18 16:37:4428#include "net/dns/dns_util.h"
mikecirone8b85c432016-09-08 19:11:0029#include "net/log/net_log_event_type.h"
mikecironef22f9812016-10-04 03:40:1930#include "net/log/net_log_source.h"
31#include "net/log/net_log_with_source.h"
tfarina5dd13c22016-11-16 12:08:2632#include "net/socket/datagram_client_socket.h"
[email protected]bdb65982012-12-20 20:44:5933#include "net/socket/stream_socket.h"
[email protected]7556ea22011-12-08 19:29:1534
35namespace net {
36
[email protected]ae1b30b2013-05-23 23:06:0337namespace {
ttuttlecf1158bf2016-03-18 16:37:4438
[email protected]ae1b30b2013-05-23 23:06:0339// Set min timeout, in case we are talking to a local DNS proxy.
40const unsigned kMinTimeoutMs = 10;
41
ttuttlecf1158bf2016-03-18 16:37:4442// Default maximum timeout between queries, even with exponential backoff.
43// (Can be overridden by field trial.)
44const unsigned kDefaultMaxTimeoutMs = 5000;
45
46// Maximum RTT that will fit in the RTT histograms.
47const int32_t kRTTMaxMs = 30000;
[email protected]ae1b30b2013-05-23 23:06:0348// Number of buckets in the histogram of observed RTTs.
ttuttlecf1158bf2016-03-18 16:37:4449const size_t kRTTBucketCount = 350;
[email protected]ae1b30b2013-05-23 23:06:0350// Target percentile in the RTT histogram used for retransmission timeout.
51const unsigned kRTOPercentile = 99;
dalykc2adf182019-09-02 14:31:2252// Number of samples to seed the histogram with.
53const unsigned kNumSeeds = 2;
ttuttlecf1158bf2016-03-18 16:37:4454
[email protected]ae1b30b2013-05-23 23:06:0355} // namespace
56
[email protected]a6c84f42013-06-07 20:39:3857// Runtime statistics of DNS server.
58struct DnsSession::ServerStats {
59 ServerStats(base::TimeDelta rtt_estimate_param, RttBuckets* buckets)
60 : last_failure_count(0), rtt_estimate(rtt_estimate_param) {
61 rtt_histogram.reset(new base::SampleVector(buckets));
[email protected]a144bd22013-07-29 21:53:1062 // Seed histogram with 2 samples at |rtt_estimate| timeout.
pkasting6b68a162014-12-01 22:10:2963 rtt_histogram->Accumulate(
64 static_cast<base::HistogramBase::Sample>(rtt_estimate.InMilliseconds()),
dalykc2adf182019-09-02 14:31:2265 kNumSeeds);
[email protected]a6c84f42013-06-07 20:39:3866 }
67
68 // Count of consecutive failures after last success.
69 int last_failure_count;
70
71 // Last time when server returned failure or timeout.
72 base::Time last_failure;
73 // Last time when server returned success.
74 base::Time last_success;
75
76 // Estimated RTT using moving average.
77 base::TimeDelta rtt_estimate;
78 // Estimated error in the above.
79 base::TimeDelta rtt_deviation;
80
81 // A histogram of observed RTT .
danakj22f90e72016-04-16 01:55:4082 std::unique_ptr<base::SampleVector> rtt_histogram;
[email protected]a6c84f42013-06-07 20:39:3883
84 DISALLOW_COPY_AND_ASSIGN(ServerStats);
85};
86
87// static
88base::LazyInstance<DnsSession::RttBuckets>::Leaky DnsSession::rtt_buckets_ =
89 LAZY_INSTANCE_INITIALIZER;
90
91DnsSession::RttBuckets::RttBuckets() : base::BucketRanges(kRTTBucketCount + 1) {
ttuttlecf1158bf2016-03-18 16:37:4492 base::Histogram::InitializeBucketRanges(1, kRTTMaxMs, this);
[email protected]a6c84f42013-06-07 20:39:3893}
94
danakj22f90e72016-04-16 01:55:4095DnsSession::SocketLease::SocketLease(
96 scoped_refptr<DnsSession> session,
97 unsigned server_index,
98 std::unique_ptr<DatagramClientSocket> socket)
dchengc7eeda422015-12-26 03:56:4899 : session_(session),
100 server_index_(server_index),
101 socket_(std::move(socket)) {}
[email protected]120d38d2012-12-14 01:42:32102
103DnsSession::SocketLease::~SocketLease() {
dchengc7eeda422015-12-26 03:56:48104 session_->FreeSocket(server_index_, std::move(socket_));
[email protected]120d38d2012-12-14 01:42:32105}
106
[email protected]7556ea22011-12-08 19:29:15107DnsSession::DnsSession(const DnsConfig& config,
danakj22f90e72016-04-16 01:55:40108 std::unique_ptr<DnsSocketPool> socket_pool,
[email protected]7556ea22011-12-08 19:29:15109 const RandIntCallback& rand_int_callback,
110 NetLog* net_log)
111 : config_(config),
dchengc7eeda422015-12-26 03:56:48112 socket_pool_(std::move(socket_pool)),
avi65168052015-12-01 19:27:07113 rand_callback_(base::Bind(rand_int_callback,
114 0,
115 std::numeric_limits<uint16_t>::max())),
[email protected]7556ea22011-12-08 19:29:15116 net_log_(net_log),
[email protected]a6c84f42013-06-07 20:39:38117 server_index_(0) {
[email protected]120d38d2012-12-14 01:42:32118 socket_pool_->Initialize(&config_.nameservers, net_log);
drbasicf0d1b262016-08-23 06:10:42119 UMA_HISTOGRAM_CUSTOM_COUNTS("AsyncDNS.ServerCount",
120 config_.nameservers.size(), 1, 10, 11);
ttuttlecf1158bf2016-03-18 16:37:44121 UpdateTimeouts(NetworkChangeNotifier::GetConnectionType());
122 InitializeServerStats();
[email protected]120d38d2012-12-14 01:42:32123}
124
dalykc2adf182019-09-02 14:31:22125DnsSession::~DnsSession() = default;
ttuttlecf1158bf2016-03-18 16:37:44126
127void DnsSession::UpdateTimeouts(NetworkChangeNotifier::ConnectionType type) {
128 initial_timeout_ = GetTimeDeltaForConnectionTypeFromFieldTrialOrDefault(
129 "AsyncDnsInitialTimeoutMsByConnectionType", config_.timeout, type);
130 max_timeout_ = GetTimeDeltaForConnectionTypeFromFieldTrialOrDefault(
131 "AsyncDnsMaxTimeoutMsByConnectionType",
132 base::TimeDelta::FromMilliseconds(kDefaultMaxTimeoutMs), type);
133}
134
135void DnsSession::InitializeServerStats() {
136 server_stats_.clear();
dalykc2adf182019-09-02 14:31:22137 for (size_t i = 0; i < config_.nameservers.size(); ++i) {
Jeremy Roman0579ed62017-08-29 15:56:19138 server_stats_.push_back(std::make_unique<ServerStats>(
ricea2deef682016-09-09 08:04:07139 initial_timeout_, rtt_buckets_.Pointer()));
ttuttlecf1158bf2016-03-18 16:37:44140 }
ttuttlecf1158bf2016-03-18 16:37:44141
dalykc2adf182019-09-02 14:31:22142 doh_server_stats_.clear();
143 for (size_t i = 0; i < config_.dns_over_https_servers.size(); ++i) {
144 doh_server_stats_.push_back(std::make_pair(
145 std::make_unique<ServerStats>(initial_timeout_, rtt_buckets_.Pointer()),
146 false));
ttuttlecf1158bf2016-03-18 16:37:44147 }
[email protected]a6c84f42013-06-07 20:39:38148}
[email protected]7556ea22011-12-08 19:29:15149
avi65168052015-12-01 19:27:07150uint16_t DnsSession::NextQueryId() const {
151 return static_cast<uint16_t>(rand_callback_.Run());
pkasting6b68a162014-12-01 22:10:29152}
[email protected]7556ea22011-12-08 19:29:15153
[email protected]a6c84f42013-06-07 20:39:38154unsigned DnsSession::NextFirstServerIndex() {
155 unsigned index = NextGoodServerIndex(server_index_);
[email protected]7556ea22011-12-08 19:29:15156 if (config_.rotate)
157 server_index_ = (server_index_ + 1) % config_.nameservers.size();
[email protected]d0d49dd82012-01-26 00:03:59158 return index;
[email protected]7556ea22011-12-08 19:29:15159}
160
[email protected]a6c84f42013-06-07 20:39:38161unsigned DnsSession::NextGoodServerIndex(unsigned server_index) {
Brad Lassey786929ad2018-02-21 20:54:27162 DCHECK_GE(server_index, 0u);
163 DCHECK_LT(server_index, config_.nameservers.size());
[email protected]a6c84f42013-06-07 20:39:38164 unsigned index = server_index;
165 base::Time oldest_server_failure(base::Time::Now());
166 unsigned oldest_server_failure_index = 0;
167
[email protected]a6c84f42013-06-07 20:39:38168 do {
[email protected]a6c84f42013-06-07 20:39:38169 // If number of failures on this server doesn't exceed number of allowed
170 // attempts, return its index.
171 if (server_stats_[server_index]->last_failure_count < config_.attempts) {
172 return index;
173 }
174 // Track oldest failed server.
dalykc2adf182019-09-02 14:31:22175 base::Time cur_server_failure = server_stats_[index]->last_failure;
[email protected]a6c84f42013-06-07 20:39:38176 if (cur_server_failure < oldest_server_failure) {
177 oldest_server_failure = cur_server_failure;
178 oldest_server_failure_index = index;
179 }
180 index = (index + 1) % config_.nameservers.size();
181 } while (index != server_index);
182
183 // If we are here it means that there are no successful servers, so we have
184 // to use one that has failed oldest.
185 return oldest_server_failure_index;
186}
187
dalykc2adf182019-09-02 14:31:22188int DnsSession::NextGoodDohServerIndex(
189 unsigned doh_server_index,
190 DnsConfig::SecureDnsMode secure_dns_mode) {
191 DCHECK_GE(doh_server_index, 0u);
192 DCHECK_LT(doh_server_index, config_.dns_over_https_servers.size());
193 unsigned index = doh_server_index;
Brad Lassey786929ad2018-02-21 20:54:27194 base::Time oldest_server_failure(base::Time::Now());
dalykc2adf182019-09-02 14:31:22195 int oldest_available_server_failure_index = -1;
Brad Lassey786929ad2018-02-21 20:54:27196
197 do {
dalykc2adf182019-09-02 14:31:22198 // For a server to be considered "available", the server must have a
199 // successful probe status if we are in AUTOMATIC mode.
200 if (secure_dns_mode == DnsConfig::SecureDnsMode::SECURE ||
201 doh_server_stats_[index].second) {
202 // If number of failures on this server doesn't exceed |config_.attempts|,
203 // return its index. |config_.attempts| will generally be more restrictive
204 // than |kAutomaticModeFailureLimit|, although this is not guaranteed.
205 const ServerStats* stats =
206 GetServerStats(index, true /* is_doh_server */);
207 if (stats->last_failure_count < config_.attempts) {
208 return index;
209 }
210 // Track oldest failed available server.
211 base::Time cur_server_failure = stats->last_failure;
212 if (cur_server_failure < oldest_server_failure) {
213 oldest_server_failure = cur_server_failure;
214 oldest_available_server_failure_index = index;
215 }
Brad Lassey786929ad2018-02-21 20:54:27216 }
dalykc2adf182019-09-02 14:31:22217 index = (index + 1) % config_.dns_over_https_servers.size();
218 } while (index != doh_server_index);
Brad Lassey786929ad2018-02-21 20:54:27219
dalykc2adf182019-09-02 14:31:22220 // If we are here it means that there are either no available DoH servers or
221 // that all available DoH servers have at least |config_.attempts| consecutive
222 // failures. In the latter case, we'll return the available DoH server that
223 // failed least recently. In the former case we return -1.
224 return oldest_available_server_failure_index;
Brad Lassey786929ad2018-02-21 20:54:27225}
226
dalykc2adf182019-09-02 14:31:22227bool DnsSession::HasAvailableDohServer() {
228 for (const auto& doh_stats_ : doh_server_stats_) {
229 if (doh_stats_.second)
230 return true;
231 }
232 return false;
[email protected]a6c84f42013-06-07 20:39:38233}
234
dalykc2adf182019-09-02 14:31:22235unsigned DnsSession::NumAvailableDohServers() {
236 unsigned count = 0;
237 for (const auto& doh_stats_ : doh_server_stats_) {
238 if (doh_stats_.second)
239 count++;
240 }
241 return count;
[email protected]a6c84f42013-06-07 20:39:38242}
243
dalykc2adf182019-09-02 14:31:22244DnsSession::ServerStats* DnsSession::GetServerStats(unsigned server_index,
245 bool is_doh_server) {
246 DCHECK_GE(server_index, 0u);
247 if (!is_doh_server) {
248 DCHECK_LT(server_index, config_.nameservers.size());
249 return server_stats_[server_index].get();
250 } else {
251 DCHECK_LT(server_index, config_.dns_over_https_servers.size());
252 return doh_server_stats_[server_index].first.get();
253 }
254}
255
256void DnsSession::RecordServerFailure(unsigned server_index,
257 bool is_doh_server) {
258 ServerStats* stats = GetServerStats(server_index, is_doh_server);
259 ++(stats->last_failure_count);
260 stats->last_failure = base::Time::Now();
261
262 if (is_doh_server &&
263 stats->last_failure_count >= kAutomaticModeFailureLimit) {
264 SetProbeSuccess(server_index, false /* success */);
265 }
266}
267
268void DnsSession::RecordServerSuccess(unsigned server_index,
269 bool is_doh_server) {
270 ServerStats* stats = GetServerStats(server_index, is_doh_server);
271
272 // DoH queries can be sent using more than one URLRequestContext. A success
273 // from one URLRequestContext shouldn't zero out failures that may be
274 // consistently occurring for another URLRequestContext.
275 if (!is_doh_server)
276 stats->last_failure_count = 0;
277 stats->last_failure = base::Time();
278 stats->last_success = base::Time::Now();
279}
280
281void DnsSession::SetProbeSuccess(unsigned doh_server_index, bool success) {
282 DCHECK_GE(doh_server_index, 0u);
283 DCHECK_LT(doh_server_index, config_.dns_over_https_servers.size());
Eric Ortha3559ca2019-09-05 23:40:54284
285 bool doh_available_before = HasAvailableDohServer();
dalykc2adf182019-09-02 14:31:22286 doh_server_stats_[doh_server_index].second = success;
Eric Ortha3559ca2019-09-05 23:40:54287
288 if (doh_available_before != HasAvailableDohServer())
289 NetworkChangeNotifier::TriggerNonSystemDnsChange();
dalykc2adf182019-09-02 14:31:22290}
291
292void DnsSession::RecordRTT(unsigned server_index,
293 bool is_doh_server,
dalyk6ab72db2019-09-04 23:12:11294 base::TimeDelta rtt,
295 int rv) {
296 RecordRTTForHistogram(server_index, is_doh_server, rtt, rv);
297
dalykc2adf182019-09-02 14:31:22298 ServerStats* stats = GetServerStats(server_index, is_doh_server);
[email protected]ae1b30b2013-05-23 23:06:03299
[email protected]ae1b30b2013-05-23 23:06:03300 // Jacobson/Karels algorithm for TCP.
301 // Using parameters: alpha = 1/8, delta = 1/4, beta = 4
dalykc2adf182019-09-02 14:31:22302 base::TimeDelta& estimate = stats->rtt_estimate;
303 base::TimeDelta& deviation = stats->rtt_deviation;
[email protected]ae1b30b2013-05-23 23:06:03304 base::TimeDelta current_error = rtt - estimate;
305 estimate += current_error / 8; // * alpha
306 base::TimeDelta abs_error = base::TimeDelta::FromInternalValue(
307 std::abs(current_error.ToInternalValue()));
308 deviation += (abs_error - deviation) / 4; // * delta
309
dalykc2adf182019-09-02 14:31:22310 // RTT values shouldn't be less than 0, but it shouldn't cause a crash if
311 // they are anyway, so clip to 0. See https://crbug.com/753568.
Miriam Gershenson68378c62017-08-10 22:26:25312 int32_t rtt_ms = rtt.InMilliseconds();
313 if (rtt_ms < 0)
314 rtt_ms = 0;
315
[email protected]ae1b30b2013-05-23 23:06:03316 // Histogram-based method.
dalykc2adf182019-09-02 14:31:22317 stats->rtt_histogram->Accumulate(
Miriam Gershenson68378c62017-08-10 22:26:25318 static_cast<base::HistogramBase::Sample>(rtt_ms), 1);
[email protected]ae1b30b2013-05-23 23:06:03319}
320
[email protected]ae1b30b2013-05-23 23:06:03321base::TimeDelta DnsSession::NextTimeout(unsigned server_index, int attempt) {
dalykc2adf182019-09-02 14:31:22322 return NextTimeoutHelper(
323 GetServerStats(server_index, false /* is _doh_server */),
324 attempt / config_.nameservers.size());
325}
326
327base::TimeDelta DnsSession::NextDohTimeout(unsigned doh_server_index) {
328 return NextTimeoutHelper(
329 GetServerStats(doh_server_index, true /* is _doh_server */),
330 0 /* num_backoffs */);
331}
332
333base::TimeDelta DnsSession::NextTimeoutHelper(ServerStats* server_stats,
334 int num_backoffs) {
ttuttlecf1158bf2016-03-18 16:37:44335 // Respect initial timeout (from config or field trial) if it exceeds max.
336 if (initial_timeout_ > max_timeout_)
337 return initial_timeout_;
Paul Jensenf3593242018-12-04 19:15:18338
Paul Jensenf3593242018-12-04 19:15:18339 static_assert(std::numeric_limits<base::HistogramBase::Count>::is_signed,
340 "histogram base count assumed to be signed");
341
342 // Use fixed percentile of observed samples.
dalykc2adf182019-09-02 14:31:22343 const base::SampleVector& samples = *server_stats->rtt_histogram;
Paul Jensenf3593242018-12-04 19:15:18344
345 base::HistogramBase::Count total = samples.TotalCount();
346 base::HistogramBase::Count remaining_count = kRTOPercentile * total / 100;
347 size_t index = 0;
348 while (remaining_count > 0 && index < rtt_buckets_.Get().size()) {
349 remaining_count -= samples.GetCountAtIndex(index);
350 ++index;
351 }
352
353 base::TimeDelta timeout =
354 base::TimeDelta::FromMilliseconds(rtt_buckets_.Get().range(index));
355
356 timeout = std::max(timeout, base::TimeDelta::FromMilliseconds(kMinTimeoutMs));
357
Paul Jensenf3593242018-12-04 19:15:18358 return std::min(timeout * (1 << num_backoffs), max_timeout_);
[email protected]7556ea22011-12-08 19:29:15359}
360
[email protected]120d38d2012-12-14 01:42:32361// Allocate a socket, already connected to the server address.
danakj22f90e72016-04-16 01:55:40362std::unique_ptr<DnsSession::SocketLease> DnsSession::AllocateSocket(
363 unsigned server_index,
mikecironef22f9812016-10-04 03:40:19364 const NetLogSource& source) {
danakj22f90e72016-04-16 01:55:40365 std::unique_ptr<DatagramClientSocket> socket;
[email protected]120d38d2012-12-14 01:42:32366
367 socket = socket_pool_->AllocateSocket(server_index);
[email protected]dd946bb2013-06-12 22:53:01368 if (!socket.get())
danakj22f90e72016-04-16 01:55:40369 return std::unique_ptr<SocketLease>();
[email protected]120d38d2012-12-14 01:42:32370
Eric Roman06bd9742019-07-13 15:19:13371 socket->NetLog().BeginEventReferencingSource(NetLogEventType::SOCKET_IN_USE,
372 source);
[email protected]120d38d2012-12-14 01:42:32373
dchengc7eeda422015-12-26 03:56:48374 SocketLease* lease = new SocketLease(this, server_index, std::move(socket));
danakj22f90e72016-04-16 01:55:40375 return std::unique_ptr<SocketLease>(lease);
[email protected]120d38d2012-12-14 01:42:32376}
377
danakj22f90e72016-04-16 01:55:40378std::unique_ptr<StreamSocket> DnsSession::CreateTCPSocket(
379 unsigned server_index,
mikecironef22f9812016-10-04 03:40:19380 const NetLogSource& source) {
[email protected]bdb65982012-12-20 20:44:59381 return socket_pool_->CreateTCPSocket(server_index, source);
382}
383
[email protected]120d38d2012-12-14 01:42:32384// Release a socket.
[email protected]ae1b30b2013-05-23 23:06:03385void DnsSession::FreeSocket(unsigned server_index,
danakj22f90e72016-04-16 01:55:40386 std::unique_ptr<DatagramClientSocket> socket) {
[email protected]120d38d2012-12-14 01:42:32387 DCHECK(socket.get());
388
mikecirone8b85c432016-09-08 19:11:00389 socket->NetLog().EndEvent(NetLogEventType::SOCKET_IN_USE);
[email protected]120d38d2012-12-14 01:42:32390
dchengc7eeda422015-12-26 03:56:48391 socket_pool_->FreeSocket(server_index, std::move(socket));
[email protected]120d38d2012-12-14 01:42:32392}
[email protected]7556ea22011-12-08 19:29:15393
dalyk6ab72db2019-09-04 23:12:11394void DnsSession::RecordRTTForHistogram(unsigned server_index,
395 bool is_doh_server,
396 base::TimeDelta rtt,
397 int rv) {
398 std::string query_type;
399 std::string provider_id;
400 if (is_doh_server) {
401 // Secure queries are validated if the DoH server state is available.
402 if (doh_server_stats_[server_index].second)
403 query_type = "SecureValidated";
404 else
405 query_type = "SecureNotValidated";
406 provider_id = GetDohProviderIdForHistogramFromDohConfig(
407 config_.dns_over_https_servers[server_index]);
408 } else {
409 query_type = "Insecure";
410 provider_id = GetDohProviderIdForHistogramFromNameserver(
411 config_.nameservers[server_index]);
412 }
413 if (rv == OK || rv == ERR_NAME_NOT_RESOLVED) {
414 base::UmaHistogramMediumTimes(
415 base::StringPrintf("Net.DNS.DnsTransaction.%s.%s.SuccessTime",
416 query_type.c_str(), provider_id.c_str()),
417 rtt);
418 } else {
419 base::UmaHistogramMediumTimes(
420 base::StringPrintf("Net.DNS.DnsTransaction.%s.%s.FailureTime",
421 query_type.c_str(), provider_id.c_str()),
422 rtt);
423 if (is_doh_server) {
424 base::UmaHistogramSparse(
425 base::StringPrintf("Net.DNS.DnsTransaction.%s.%s.FailureError",
426 query_type.c_str(), provider_id.c_str()),
427 std::abs(rv));
428 }
429 }
430}
431
[email protected]7556ea22011-12-08 19:29:15432} // namespace net