| Mike Yu | bab3daa | 2018-10-19 22:11:43 +0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2017 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| Ken Chen | 5471dca | 2019-04-15 15:25:35 +0800 | [diff] [blame] | 17 | #define LOG_TAG "resolv" |
| Mike Yu | bab3daa | 2018-10-19 22:11:43 +0800 | [diff] [blame] | 18 | |
| Bernie Innocenti | ec4219b | 2019-01-30 11:16:36 +0900 | [diff] [blame] | 19 | #include "DnsTlsDispatcher.h" |
| Mike Yu | e655b1d | 2019-08-28 17:49:59 +0800 | [diff] [blame] | 20 | |
| lifr | 9498178 | 2019-05-17 21:15:19 +0800 | [diff] [blame] | 21 | #include <netdutils/Stopwatch.h> |
| Mike Yu | e655b1d | 2019-08-28 17:49:59 +0800 | [diff] [blame] | 22 | |
| Bernie Innocenti | ec4219b | 2019-01-30 11:16:36 +0900 | [diff] [blame] | 23 | #include "DnsTlsSocketFactory.h" |
| Mike Yu | 82ae84b | 2020-12-02 21:04:40 +0800 | [diff] [blame] | 24 | #include "Experiments.h" |
| 25 | #include "PrivateDnsConfiguration.h" |
| Mike Yu | e655b1d | 2019-08-28 17:49:59 +0800 | [diff] [blame] | 26 | #include "resolv_cache.h" |
| lifr | 9498178 | 2019-05-17 21:15:19 +0800 | [diff] [blame] | 27 | #include "resolv_private.h" |
| 28 | #include "stats.pb.h" |
| Mike Yu | bab3daa | 2018-10-19 22:11:43 +0800 | [diff] [blame] | 29 | |
| chenbruce | aff8584 | 2019-05-31 15:46:42 +0800 | [diff] [blame] | 30 | #include <android-base/logging.h> |
| Mike Yu | bab3daa | 2018-10-19 22:11:43 +0800 | [diff] [blame] | 31 | |
| 32 | namespace android { |
| 33 | namespace net { |
| 34 | |
| Mike Yu | e655b1d | 2019-08-28 17:49:59 +0800 | [diff] [blame] | 35 | using android::netdutils::IPSockAddr; |
| lifr | 9498178 | 2019-05-17 21:15:19 +0800 | [diff] [blame] | 36 | using android::netdutils::Stopwatch; |
| Mike Yu | bab3daa | 2018-10-19 22:11:43 +0800 | [diff] [blame] | 37 | using netdutils::Slice; |
| 38 | |
| 39 | // static |
| 40 | std::mutex DnsTlsDispatcher::sLock; |
| 41 | |
| 42 | DnsTlsDispatcher::DnsTlsDispatcher() { |
| 43 | mFactory.reset(new DnsTlsSocketFactory()); |
| 44 | } |
| 45 | |
| Mike Yu | 9e8cf8d | 2020-10-26 19:04:33 +0800 | [diff] [blame] | 46 | DnsTlsDispatcher& DnsTlsDispatcher::getInstance() { |
| 47 | static DnsTlsDispatcher instance; |
| 48 | return instance; |
| 49 | } |
| 50 | |
| Mike Yu | 82ae84b | 2020-12-02 21:04:40 +0800 | [diff] [blame] | 51 | std::list<DnsTlsServer> DnsTlsDispatcher::getOrderedAndUsableServerList( |
| 52 | const std::list<DnsTlsServer>& tlsServers, unsigned netId, unsigned mark) { |
| Mike Yu | bab3daa | 2018-10-19 22:11:43 +0800 | [diff] [blame] | 53 | // Our preferred DnsTlsServer order is: |
| 54 | // 1) reuse existing IPv6 connections |
| 55 | // 2) reuse existing IPv4 connections |
| 56 | // 3) establish new IPv6 connections |
| 57 | // 4) establish new IPv4 connections |
| 58 | std::list<DnsTlsServer> existing6; |
| 59 | std::list<DnsTlsServer> existing4; |
| 60 | std::list<DnsTlsServer> new6; |
| 61 | std::list<DnsTlsServer> new4; |
| 62 | |
| 63 | // Pull out any servers for which we might have existing connections and |
| 64 | // place them at the from the list of servers to try. |
| 65 | { |
| 66 | std::lock_guard guard(sLock); |
| 67 | |
| 68 | for (const auto& tlsServer : tlsServers) { |
| 69 | const Key key = std::make_pair(mark, tlsServer); |
| Mike Yu | 82ae84b | 2020-12-02 21:04:40 +0800 | [diff] [blame] | 70 | if (const Transport* xport = getTransport(key); xport != nullptr) { |
| 71 | // DoT revalidation specific feature. |
| 72 | if (!xport->usable()) { |
| 73 | // Don't use this xport. It will be removed after timeout |
| 74 | // (IDLE_TIMEOUT minutes). |
| 75 | LOG(DEBUG) << "Skip using DoT server " << tlsServer.toIpString() << " on " |
| 76 | << netId; |
| 77 | continue; |
| 78 | } |
| 79 | |
| Mike Yu | bab3daa | 2018-10-19 22:11:43 +0800 | [diff] [blame] | 80 | switch (tlsServer.ss.ss_family) { |
| 81 | case AF_INET: |
| 82 | existing4.push_back(tlsServer); |
| 83 | break; |
| 84 | case AF_INET6: |
| 85 | existing6.push_back(tlsServer); |
| 86 | break; |
| 87 | } |
| 88 | } else { |
| 89 | switch (tlsServer.ss.ss_family) { |
| 90 | case AF_INET: |
| 91 | new4.push_back(tlsServer); |
| 92 | break; |
| 93 | case AF_INET6: |
| 94 | new6.push_back(tlsServer); |
| 95 | break; |
| 96 | } |
| 97 | } |
| 98 | } |
| 99 | } |
| 100 | |
| 101 | auto& out = existing6; |
| 102 | out.splice(out.cend(), existing4); |
| 103 | out.splice(out.cend(), new6); |
| 104 | out.splice(out.cend(), new4); |
| 105 | return out; |
| 106 | } |
| 107 | |
| lifr | 9498178 | 2019-05-17 21:15:19 +0800 | [diff] [blame] | 108 | DnsTlsTransport::Response DnsTlsDispatcher::query(const std::list<DnsTlsServer>& tlsServers, |
| 109 | res_state statp, const Slice query, |
| 110 | const Slice ans, int* resplen) { |
| Mike Yu | 82ae84b | 2020-12-02 21:04:40 +0800 | [diff] [blame] | 111 | const std::list<DnsTlsServer> servers( |
| 112 | getOrderedAndUsableServerList(tlsServers, statp->netid, statp->_mark)); |
| Mike Yu | bab3daa | 2018-10-19 22:11:43 +0800 | [diff] [blame] | 113 | |
| Mike Yu | 82ae84b | 2020-12-02 21:04:40 +0800 | [diff] [blame] | 114 | if (servers.empty()) LOG(WARNING) << "No usable DnsTlsServers"; |
| Mike Yu | bab3daa | 2018-10-19 22:11:43 +0800 | [diff] [blame] | 115 | |
| 116 | DnsTlsTransport::Response code = DnsTlsTransport::Response::internal_error; |
| lifr | 9498178 | 2019-05-17 21:15:19 +0800 | [diff] [blame] | 117 | int serverCount = 0; |
| Mike Yu | 82ae84b | 2020-12-02 21:04:40 +0800 | [diff] [blame] | 118 | for (const auto& server : servers) { |
| lifr | 9498178 | 2019-05-17 21:15:19 +0800 | [diff] [blame] | 119 | DnsQueryEvent* dnsQueryEvent = |
| 120 | statp->event->mutable_dns_query_events()->add_dns_query_event(); |
| Mike Yu | cb2bb7c | 2019-11-22 20:42:13 +0800 | [diff] [blame] | 121 | |
| 122 | bool connectTriggered = false; |
| lifr | d4d9fbb | 2019-07-31 20:18:35 +0800 | [diff] [blame] | 123 | Stopwatch queryStopwatch; |
| Mike Yu | 82ae84b | 2020-12-02 21:04:40 +0800 | [diff] [blame] | 124 | code = this->query(server, statp->netid, statp->_mark, query, ans, resplen, |
| 125 | &connectTriggered); |
| lifr | 9498178 | 2019-05-17 21:15:19 +0800 | [diff] [blame] | 126 | |
| lifr | d4d9fbb | 2019-07-31 20:18:35 +0800 | [diff] [blame] | 127 | dnsQueryEvent->set_latency_micros(saturate_cast<int32_t>(queryStopwatch.timeTakenUs())); |
| lifr | 9498178 | 2019-05-17 21:15:19 +0800 | [diff] [blame] | 128 | dnsQueryEvent->set_dns_server_index(serverCount++); |
| 129 | dnsQueryEvent->set_ip_version(ipFamilyToIPVersion(server.ss.ss_family)); |
| 130 | dnsQueryEvent->set_protocol(PROTO_DOT); |
| 131 | dnsQueryEvent->set_type(getQueryType(query.base(), query.size())); |
| Mike Yu | cb2bb7c | 2019-11-22 20:42:13 +0800 | [diff] [blame] | 132 | dnsQueryEvent->set_connected(connectTriggered); |
| lifr | 9498178 | 2019-05-17 21:15:19 +0800 | [diff] [blame] | 133 | |
| Mike Yu | bab3daa | 2018-10-19 22:11:43 +0800 | [diff] [blame] | 134 | switch (code) { |
| 135 | // These response codes are valid responses and not expected to |
| 136 | // change if another server is queried. |
| 137 | case DnsTlsTransport::Response::success: |
| lifr | 9498178 | 2019-05-17 21:15:19 +0800 | [diff] [blame] | 138 | dnsQueryEvent->set_rcode( |
| 139 | static_cast<NsRcode>(reinterpret_cast<HEADER*>(ans.base())->rcode)); |
| Mike Yu | e655b1d | 2019-08-28 17:49:59 +0800 | [diff] [blame] | 140 | resolv_stats_add(statp->netid, IPSockAddr::toIPSockAddr(server.ss), dnsQueryEvent); |
| lifr | d4d9fbb | 2019-07-31 20:18:35 +0800 | [diff] [blame] | 141 | return code; |
| Mike Yu | bab3daa | 2018-10-19 22:11:43 +0800 | [diff] [blame] | 142 | case DnsTlsTransport::Response::limit_error: |
| lifr | d4d9fbb | 2019-07-31 20:18:35 +0800 | [diff] [blame] | 143 | dnsQueryEvent->set_rcode(NS_R_INTERNAL_ERROR); |
| Mike Yu | e655b1d | 2019-08-28 17:49:59 +0800 | [diff] [blame] | 144 | resolv_stats_add(statp->netid, IPSockAddr::toIPSockAddr(server.ss), dnsQueryEvent); |
| Mike Yu | bab3daa | 2018-10-19 22:11:43 +0800 | [diff] [blame] | 145 | return code; |
| Mike Yu | bab3daa | 2018-10-19 22:11:43 +0800 | [diff] [blame] | 146 | // These response codes might differ when trying other servers, so |
| 147 | // keep iterating to see if we can get a different (better) result. |
| 148 | case DnsTlsTransport::Response::network_error: |
| lifr | 9498178 | 2019-05-17 21:15:19 +0800 | [diff] [blame] | 149 | // Sync from res_tls_send in res_send.cpp |
| 150 | dnsQueryEvent->set_rcode(NS_R_TIMEOUT); |
| Mike Yu | e655b1d | 2019-08-28 17:49:59 +0800 | [diff] [blame] | 151 | resolv_stats_add(statp->netid, IPSockAddr::toIPSockAddr(server.ss), dnsQueryEvent); |
| 152 | break; |
| Mike Yu | bab3daa | 2018-10-19 22:11:43 +0800 | [diff] [blame] | 153 | case DnsTlsTransport::Response::internal_error: |
| lifr | d4d9fbb | 2019-07-31 20:18:35 +0800 | [diff] [blame] | 154 | dnsQueryEvent->set_rcode(NS_R_INTERNAL_ERROR); |
| Mike Yu | e655b1d | 2019-08-28 17:49:59 +0800 | [diff] [blame] | 155 | resolv_stats_add(statp->netid, IPSockAddr::toIPSockAddr(server.ss), dnsQueryEvent); |
| 156 | break; |
| Mike Yu | bab3daa | 2018-10-19 22:11:43 +0800 | [diff] [blame] | 157 | // No "default" statement. |
| 158 | } |
| 159 | } |
| 160 | |
| 161 | return code; |
| 162 | } |
| 163 | |
| Mike Yu | 82ae84b | 2020-12-02 21:04:40 +0800 | [diff] [blame] | 164 | DnsTlsTransport::Response DnsTlsDispatcher::query(const DnsTlsServer& server, unsigned netId, |
| 165 | unsigned mark, const Slice query, const Slice ans, |
| 166 | int* resplen, bool* connectTriggered) { |
| Mike Yu | e9b78d8 | 2020-05-20 20:58:49 +0800 | [diff] [blame] | 167 | // TODO: This can cause the resolver to create multiple connections to the same DoT server |
| 168 | // merely due to different mark, such as the bit explicitlySelected unset. |
| 169 | // See if we can save them and just create one connection for one DoT server. |
| Mike Yu | bab3daa | 2018-10-19 22:11:43 +0800 | [diff] [blame] | 170 | const Key key = std::make_pair(mark, server); |
| 171 | Transport* xport; |
| 172 | { |
| 173 | std::lock_guard guard(sLock); |
| Mike Yu | 82ae84b | 2020-12-02 21:04:40 +0800 | [diff] [blame] | 174 | if (xport = getTransport(key); xport == nullptr) { |
| 175 | xport = addTransport(server, mark); |
| Mike Yu | bab3daa | 2018-10-19 22:11:43 +0800 | [diff] [blame] | 176 | } |
| 177 | ++xport->useCount; |
| 178 | } |
| 179 | |
| Mike Yu | 568ed6c | 2020-07-01 12:02:14 +0800 | [diff] [blame] | 180 | // Don't call this function and hold sLock at the same time because of the following reason: |
| 181 | // TLS handshake requires a lock which is also needed by this function, if the handshake gets |
| 182 | // stuck, this function also gets blocked. |
| 183 | const int connectCounter = xport->transport.getConnectCounter(); |
| 184 | |
| Mike Yu | 08b2f2b | 2020-12-16 11:45:36 +0800 | [diff] [blame] | 185 | const auto& result = queryInternal(*xport, query); |
| Mike Yu | 568ed6c | 2020-07-01 12:02:14 +0800 | [diff] [blame] | 186 | *connectTriggered = (xport->transport.getConnectCounter() > connectCounter); |
| 187 | |
| Mike Yu | bab3daa | 2018-10-19 22:11:43 +0800 | [diff] [blame] | 188 | DnsTlsTransport::Response code = result.code; |
| 189 | if (code == DnsTlsTransport::Response::success) { |
| 190 | if (result.response.size() > ans.size()) { |
| chenbruce | aff8584 | 2019-05-31 15:46:42 +0800 | [diff] [blame] | 191 | LOG(DEBUG) << "Response too large: " << result.response.size() << " > " << ans.size(); |
| Mike Yu | bab3daa | 2018-10-19 22:11:43 +0800 | [diff] [blame] | 192 | code = DnsTlsTransport::Response::limit_error; |
| 193 | } else { |
| chenbruce | aff8584 | 2019-05-31 15:46:42 +0800 | [diff] [blame] | 194 | LOG(DEBUG) << "Got response successfully"; |
| Mike Yu | bab3daa | 2018-10-19 22:11:43 +0800 | [diff] [blame] | 195 | *resplen = result.response.size(); |
| 196 | netdutils::copy(ans, netdutils::makeSlice(result.response)); |
| 197 | } |
| 198 | } else { |
| chenbruce | aff8584 | 2019-05-31 15:46:42 +0800 | [diff] [blame] | 199 | LOG(DEBUG) << "Query failed: " << (unsigned int)code; |
| Mike Yu | bab3daa | 2018-10-19 22:11:43 +0800 | [diff] [blame] | 200 | } |
| 201 | |
| 202 | auto now = std::chrono::steady_clock::now(); |
| 203 | { |
| 204 | std::lock_guard guard(sLock); |
| Mike Yu | bab3daa | 2018-10-19 22:11:43 +0800 | [diff] [blame] | 205 | --xport->useCount; |
| 206 | xport->lastUsed = now; |
| Mike Yu | 82ae84b | 2020-12-02 21:04:40 +0800 | [diff] [blame] | 207 | |
| 208 | // DoT revalidation specific feature. |
| 209 | if (xport->checkRevalidationNecessary(code)) { |
| 210 | // Even if the revalidation passes, it doesn't guarantee that DoT queries |
| 211 | // to the xport can stop failing because revalidation creates a new connection |
| 212 | // to probe while the xport still uses an existing connection. So far, there isn't |
| 213 | // a feasible way to force the xport to disconnect the connection. If the case |
| 214 | // happens, the xport will be marked as unusable and DoT queries won't be sent to |
| 215 | // it anymore. Eventually, after IDLE_TIMEOUT, the xport will be destroyed, and |
| 216 | // a new xport will be created. |
| 217 | const auto result = |
| 218 | PrivateDnsConfiguration::getInstance().requestValidation(netId, server, mark); |
| 219 | LOG(WARNING) << "Requested validation for " << server.toIpString() << " with mark 0x" |
| 220 | << std::hex << mark << ", " |
| 221 | << (result.ok() ? "succeeded" : "failed: " + result.error().message()); |
| 222 | } |
| 223 | |
| Mike Yu | bab3daa | 2018-10-19 22:11:43 +0800 | [diff] [blame] | 224 | cleanup(now); |
| 225 | } |
| 226 | return code; |
| 227 | } |
| 228 | |
| Mike Yu | 08b2f2b | 2020-12-16 11:45:36 +0800 | [diff] [blame] | 229 | DnsTlsTransport::Result DnsTlsDispatcher::queryInternal(Transport& xport, |
| 230 | const netdutils::Slice query) { |
| 231 | LOG(DEBUG) << "Sending query of length " << query.size(); |
| 232 | |
| 233 | // If dot_async_handshake is not set, the call might block in some cases; otherwise, |
| 234 | // the call should return very soon. |
| 235 | auto res = xport.transport.query(query); |
| 236 | LOG(DEBUG) << "Awaiting response"; |
| 237 | |
| 238 | if (xport.timeout().count() == -1) { |
| 239 | // Infinite timeout. |
| 240 | return res.get(); |
| 241 | } |
| 242 | |
| 243 | const auto status = res.wait_for(xport.timeout()); |
| 244 | if (status == std::future_status::timeout) { |
| Mike Yu | 1f663fc | 2021-02-20 17:22:24 +0800 | [diff] [blame] | 245 | // TODO(b/186613628): notify the Transport to remove this query. |
| Mike Yu | 08b2f2b | 2020-12-16 11:45:36 +0800 | [diff] [blame] | 246 | LOG(WARNING) << "DoT query timed out after " << xport.timeout().count() << " ms"; |
| 247 | return DnsTlsTransport::Result{ |
| 248 | .code = DnsTlsTransport::Response::network_error, |
| 249 | .response = {}, |
| 250 | }; |
| 251 | } |
| 252 | |
| 253 | return res.get(); |
| 254 | } |
| 255 | |
| Mike Yu | bab3daa | 2018-10-19 22:11:43 +0800 | [diff] [blame] | 256 | // This timeout effectively controls how long to keep SSL session tickets. |
| 257 | static constexpr std::chrono::minutes IDLE_TIMEOUT(5); |
| 258 | void DnsTlsDispatcher::cleanup(std::chrono::time_point<std::chrono::steady_clock> now) { |
| 259 | // To avoid scanning mStore after every query, return early if a cleanup has been |
| 260 | // performed recently. |
| 261 | if (now - mLastCleanup < IDLE_TIMEOUT) { |
| 262 | return; |
| 263 | } |
| 264 | for (auto it = mStore.begin(); it != mStore.end();) { |
| 265 | auto& s = it->second; |
| 266 | if (s->useCount == 0 && now - s->lastUsed > IDLE_TIMEOUT) { |
| 267 | it = mStore.erase(it); |
| 268 | } else { |
| 269 | ++it; |
| 270 | } |
| 271 | } |
| 272 | mLastCleanup = now; |
| 273 | } |
| 274 | |
| Mike Yu | 82ae84b | 2020-12-02 21:04:40 +0800 | [diff] [blame] | 275 | DnsTlsDispatcher::Transport* DnsTlsDispatcher::addTransport(const DnsTlsServer& server, |
| 276 | unsigned mark) { |
| 277 | const Key key = std::make_pair(mark, server); |
| 278 | Transport* ret = getTransport(key); |
| 279 | if (ret != nullptr) return ret; |
| 280 | |
| 281 | const Experiments* const instance = Experiments::getInstance(); |
| 282 | int triggerThr = |
| 283 | instance->getFlag("dot_revalidation_threshold", Transport::kDotRevalidationThreshold); |
| 284 | int unusableThr = instance->getFlag("dot_xport_unusable_threshold", |
| 285 | Transport::kDotXportUnusableThreshold); |
| Mike Yu | 08b2f2b | 2020-12-16 11:45:36 +0800 | [diff] [blame] | 286 | int queryTimeout = instance->getFlag("dot_query_timeout_ms", Transport::kDotQueryTimeoutMs); |
| Mike Yu | 82ae84b | 2020-12-02 21:04:40 +0800 | [diff] [blame] | 287 | |
| 288 | // Check and adjust the parameters if they are improperly set. |
| 289 | bool revalidationEnabled = false; |
| 290 | const bool isForOpportunisticMode = server.name.empty(); |
| 291 | if (triggerThr > 0 && unusableThr > 0 && isForOpportunisticMode) { |
| 292 | revalidationEnabled = true; |
| 293 | } else { |
| 294 | triggerThr = -1; |
| 295 | unusableThr = -1; |
| 296 | } |
| Mike Yu | 08b2f2b | 2020-12-16 11:45:36 +0800 | [diff] [blame] | 297 | if (queryTimeout < 0) { |
| 298 | queryTimeout = -1; |
| 299 | } else if (queryTimeout < 1000) { |
| 300 | queryTimeout = 1000; |
| 301 | } |
| Mike Yu | 82ae84b | 2020-12-02 21:04:40 +0800 | [diff] [blame] | 302 | |
| Mike Yu | 08b2f2b | 2020-12-16 11:45:36 +0800 | [diff] [blame] | 303 | ret = new Transport(server, mark, mFactory.get(), revalidationEnabled, triggerThr, unusableThr, |
| 304 | queryTimeout); |
| 305 | LOG(DEBUG) << "Transport is initialized with { " << triggerThr << ", " << unusableThr << ", " |
| 306 | << queryTimeout << "ms }" |
| Mike Yu | 82ae84b | 2020-12-02 21:04:40 +0800 | [diff] [blame] | 307 | << " for server { " << server.toIpString() << "/" << server.name << " }"; |
| 308 | |
| 309 | mStore[key].reset(ret); |
| 310 | |
| 311 | return ret; |
| 312 | } |
| 313 | |
| 314 | DnsTlsDispatcher::Transport* DnsTlsDispatcher::getTransport(const Key& key) { |
| 315 | auto it = mStore.find(key); |
| 316 | return (it == mStore.end() ? nullptr : it->second.get()); |
| 317 | } |
| 318 | |
| 319 | bool DnsTlsDispatcher::Transport::checkRevalidationNecessary(DnsTlsTransport::Response code) { |
| 320 | if (!revalidationEnabled) return false; |
| 321 | |
| 322 | if (code == DnsTlsTransport::Response::network_error) { |
| 323 | continuousfailureCount++; |
| 324 | } else { |
| 325 | continuousfailureCount = 0; |
| 326 | } |
| 327 | |
| 328 | // triggerThreshold must be greater than 0 because the value of revalidationEnabled is true. |
| 329 | if (usable() && continuousfailureCount == triggerThreshold) { |
| 330 | return true; |
| 331 | } |
| 332 | return false; |
| 333 | } |
| 334 | |
| 335 | bool DnsTlsDispatcher::Transport::usable() const { |
| 336 | if (!revalidationEnabled) return true; |
| 337 | |
| 338 | return continuousfailureCount < unusableThreshold; |
| 339 | } |
| 340 | |
| Mike Yu | bab3daa | 2018-10-19 22:11:43 +0800 | [diff] [blame] | 341 | } // end of namespace net |
| 342 | } // end of namespace android |