Implement DNS over HTTPS (DoH)
https://tools.ietf.org/id/draft-ietf-doh-dns-over-https-02.txt

R=mkwst@chromium.org, mmenke@chromium.org

Bug: 799753
Cq-Include-Trybots: master.tryserver.chromium.android:android_cronet_tester;master.tryserver.chromium.mac:ios-simulator-cronet
Change-Id: I8a5e2aa914e075d12fe805764bf4878524ea0993
Reviewed-on: https://chromium-review.googlesource.com/710554
Reviewed-by: Mike West <mkwst@chromium.org>
Reviewed-by: Matt Menke <mmenke@chromium.org>
Reviewed-by: Ryan Hamilton <rch@chromium.org>
Commit-Queue: Brad Lassey <lassey@chromium.org>
Cr-Commit-Position: refs/heads/master@{#538204}
diff --git a/chrome/browser/io_thread.cc b/chrome/browser/io_thread.cc
index ba07741..fd02fe3 100644
--- a/chrome/browser/io_thread.cc
+++ b/chrome/browser/io_thread.cc
@@ -51,6 +51,7 @@
 #include "components/data_use_measurement/core/data_use_ascriber.h"
 #include "components/metrics/metrics_service.h"
 #include "components/net_log/chrome_net_log.h"
+#include "components/network_session_configurator/common/network_features.h"
 #include "components/policy/core/common/policy_service.h"
 #include "components/policy/policy_constants.h"
 #include "components/prefs/pref_registry_simple.h"
@@ -86,6 +87,7 @@
 #include "net/net_features.h"
 #include "net/nqe/external_estimate_provider.h"
 #include "net/nqe/network_quality_estimator_params.h"
+//#include "net/proxy/proxy_script_fetcher_impl.h"
 #include "net/proxy_resolution/pac_file_fetcher_impl.h"
 #include "net/proxy_resolution/proxy_config_service.h"
 #include "net/proxy_resolution/proxy_service.h"
@@ -373,6 +375,31 @@
                                       base::Unretained(this)));
   dns_client_enabled_.MoveToThread(io_thread_proxy);
 
+  if (base::FeatureList::IsEnabled(features::kDnsOverHttps)) {
+    base::Value specs(base::Value::Type::LIST);
+    base::Value methods(base::Value::Type::LIST);
+    base::Value spec(variations::GetVariationParamValueByFeature(
+        features::kDnsOverHttps, "server"));
+    base::Value method(variations::GetVariationParamValueByFeature(
+        features::kDnsOverHttps, "method"));
+    if (spec.GetString().size() > 0) {
+      specs.GetList().push_back(std::move(spec));
+      methods.GetList().push_back(std::move(method));
+      local_state->SetDefaultPrefValue(prefs::kDnsOverHttpsServers,
+                                       std::move(specs));
+      local_state->SetDefaultPrefValue(prefs::kDnsOverHttpsServerMethods,
+                                       std::move(methods));
+    }
+  }
+  dns_over_https_servers_.Init(
+      prefs::kDnsOverHttpsServers, local_state,
+      base::Bind(&IOThread::UpdateDnsClientEnabled, base::Unretained(this)));
+  dns_over_https_server_methods_.Init(
+      prefs::kDnsOverHttpsServerMethods, local_state,
+      base::Bind(&IOThread::UpdateDnsClientEnabled, base::Unretained(this)));
+  dns_over_https_servers_.MoveToThread(io_thread_proxy);
+  dns_over_https_server_methods_.MoveToThread(io_thread_proxy);
+
 #if defined(OS_POSIX)
   local_state->SetDefaultPrefValue(
       prefs::kNtlmV2Enabled,
@@ -626,6 +653,8 @@
   registry->RegisterBooleanPref(prefs::kEnableReferrers, true);
   data_reduction_proxy::RegisterPrefs(registry);
   registry->RegisterBooleanPref(prefs::kBuiltInDnsClientEnabled, true);
+  registry->RegisterListPref(prefs::kDnsOverHttpsServers);
+  registry->RegisterListPref(prefs::kDnsOverHttpsServerMethods);
   registry->RegisterBooleanPref(prefs::kQuickCheckEnabled, true);
   registry->RegisterBooleanPref(prefs::kPacHttpsUrlStrippingEnabled, true);
 #if defined(OS_POSIX)
@@ -724,7 +753,21 @@
 
 void IOThread::UpdateDnsClientEnabled() {
   globals()->system_request_context->host_resolver()->SetDnsClientEnabled(
-      *dns_client_enabled_);
+      *dns_client_enabled_ || (*dns_over_https_servers_).size() > 0);
+
+  net::HostResolver* resolver =
+      globals()->system_request_context->host_resolver();
+  if ((*dns_over_https_servers_).size()) {
+    resolver->SetRequestContext(globals_->system_request_context);
+  }
+  DCHECK((*dns_over_https_servers_).size() ==
+         (*dns_over_https_server_methods_).size());
+  resolver->ClearDnsOverHttpsServers();
+  for (unsigned int i = 0; i < (*dns_over_https_servers_).size(); i++) {
+    resolver->AddDnsOverHttpsServer(
+        (*dns_over_https_servers_)[i],
+        (*dns_over_https_server_methods_)[i].compare("POST") == 0);
+  }
 }
 
 void IOThread::RegisterSTHObserver(net::ct::STHObserver* observer) {
diff --git a/chrome/browser/io_thread.h b/chrome/browser/io_thread.h
index 21d23c9..2c58c61 100644
--- a/chrome/browser/io_thread.h
+++ b/chrome/browser/io_thread.h
@@ -280,6 +280,10 @@
 
   BooleanPrefMember pac_https_url_stripping_enabled_;
 
+  StringListPrefMember dns_over_https_servers_;
+
+  StringListPrefMember dns_over_https_server_methods_;
+
   // Store HTTP Auth-related policies in this thread.
   // TODO(aberent) Make the list of auth schemes a PrefMember, so that the
   // policy can change after startup (https://crbug/549273).
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index 85f943e..ceb04ee 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -1674,6 +1674,12 @@
 // Boolean that specifies whether the built-in asynchronous DNS client is used.
 const char kBuiltInDnsClientEnabled[] = "async_dns.enabled";
 
+// String containing list of DNS over HTTPS servers to be used.
+const char kDnsOverHttpsServers[] = "dns_over_https.servers";
+// String contianing list of methods (GET or POST) to use with DNS over HTTPS
+// servers, in the same order of the above pref.
+const char kDnsOverHttpsServerMethods[] = "dns_over_https.methods";
+
 // A pref holding the value of the policy used to explicitly allow or deny
 // access to audio capture devices.  When enabled or not set, the user is
 // prompted for device access.  When disabled, access to audio capture devices
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index a71c273..705e971d 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -685,6 +685,8 @@
 #endif  // defined(OS_POSIX)
 
 extern const char kBuiltInDnsClientEnabled[];
+extern const char kDnsOverHttpsServers[];
+extern const char kDnsOverHttpsServerMethods[];
 
 extern const char kRegisteredProtocolHandlers[];
 extern const char kIgnoredProtocolHandlers[];
diff --git a/components/network_session_configurator/common/network_features.cc b/components/network_session_configurator/common/network_features.cc
index abf8b98..87dbb7d 100644
--- a/components/network_session_configurator/common/network_features.cc
+++ b/components/network_session_configurator/common/network_features.cc
@@ -9,4 +9,7 @@
 const base::Feature kTokenBinding{"token-binding",
                                   base::FEATURE_DISABLED_BY_DEFAULT};
 
+const base::Feature kDnsOverHttps{"dns-over-https",
+                                  base::FEATURE_DISABLED_BY_DEFAULT};
+
 }  // namespace features
diff --git a/components/network_session_configurator/common/network_features.h b/components/network_session_configurator/common/network_features.h
index dbcc98c..6d92c7a 100644
--- a/components/network_session_configurator/common/network_features.h
+++ b/components/network_session_configurator/common/network_features.h
@@ -14,6 +14,10 @@
 // (https://www.ietf.org/id/draft-ietf-tokbind-protocol-04.txt).
 NETWORK_SESSION_CONFIGURATOR_EXPORT extern const base::Feature kTokenBinding;
 
+// Enabled DNS over HTTPS
+// (https://tools.ietf.org/id/draft-ietf-doh-dns-over-https-02.txt).
+NETWORK_SESSION_CONFIGURATOR_EXPORT extern const base::Feature kDnsOverHttps;
+
 }  // namespace features
 
 #endif  // COMPONENTS_NETWORK_SESSION_CONFIGURATOR_COMMON_NETWORK_FEATURES_H_
diff --git a/net/base/net_error_list.h b/net/base/net_error_list.h
index d1857c2..d3932f0 100644
--- a/net/base/net_error_list.h
+++ b/net/base/net_error_list.h
@@ -901,3 +901,6 @@
 
 // Failed to sort addresses according to RFC3484.
 NET_ERROR(DNS_SORT_ERROR, -806)
+
+// Failed to resolve over HTTP, fallback to legacy
+NET_ERROR(DNS_HTTP_FAILED, -807)
diff --git a/net/dns/dns_config_service.cc b/net/dns/dns_config_service.cc
index 1f19a7b..cf7de9c 100644
--- a/net/dns/dns_config_service.cc
+++ b/net/dns/dns_config_service.cc
@@ -83,10 +83,25 @@
   dict->SetBoolean("edns0", edns0);
   dict->SetBoolean("use_local_ipv6", use_local_ipv6);
   dict->SetInteger("num_hosts", hosts.size());
+  list = std::make_unique<base::ListValue>();
+  for (auto& server : dns_over_https_servers) {
+    base::Value val(base::Value::Type::DICTIONARY);
+    base::DictionaryValue* dict;
+    val.GetAsDictionary(&dict);
+    dict->SetString("server", server.server.spec());
+    dict->SetBoolean("use_post", server.use_post);
+    list->GetList().push_back(std::move(val));
+  }
+  dict->Set("doh_servers", std::move(list));
 
   return std::move(dict);
 }
 
+DnsConfig::DnsOverHttpsServerConfig::DnsOverHttpsServerConfig(
+    const GURL& server,
+    bool use_post)
+    : server(server), use_post(use_post) {}
+
 DnsConfigService::DnsConfigService()
     : watch_failed_(false),
       have_config_(false),
diff --git a/net/dns/dns_config_service.h b/net/dns/dns_config_service.h
index 6ccd52f..231a569 100644
--- a/net/dns/dns_config_service.h
+++ b/net/dns/dns_config_service.h
@@ -20,6 +20,7 @@
 #include "net/base/ip_endpoint.h"  // win requires size of IPEndPoint
 #include "net/base/net_export.h"
 #include "net/dns/dns_hosts.h"
+#include "url/gurl.h"
 
 namespace base {
 class Value;
@@ -50,6 +51,13 @@
     return !nameservers.empty();
   }
 
+  struct NET_EXPORT_PRIVATE DnsOverHttpsServerConfig {
+    DnsOverHttpsServerConfig(const GURL& server, bool use_post);
+
+    GURL server;
+    bool use_post;
+  };
+
   // List of name server addresses.
   std::vector<IPEndPoint> nameservers;
   // Suffix search list; used on first lookup when number of dots in given name
@@ -87,6 +95,10 @@
   // DirectAccess. This is exposed for HostResolver to skip IPv6 probes,
   // as it may cause them to return incorrect results.
   bool use_local_ipv6;
+
+  // List of servers to query over HTTPS, queried in order
+  // (https://tools.ietf.org/id/draft-ietf-doh-dns-over-https-02.txt).
+  std::vector<DnsOverHttpsServerConfig> dns_over_https_servers;
 };
 
 // Service for reading system DNS settings, on demand or when signalled by
diff --git a/net/dns/dns_response.cc b/net/dns/dns_response.cc
index 79c1d5c..8d258e4 100644
--- a/net/dns/dns_response.cc
+++ b/net/dns/dns_response.cc
@@ -151,17 +151,18 @@
 }
 
 DnsResponse::DnsResponse()
-    : io_buffer_(new IOBufferWithSize(dns_protocol::kMaxUDPSize + 1)) {
-}
+    : io_buffer_(new IOBuffer(dns_protocol::kMaxUDPSize + 1)),
+      io_buffer_size_(dns_protocol::kMaxUDPSize + 1) {}
+
+DnsResponse::DnsResponse(IOBuffer* buffer, size_t size)
+    : io_buffer_(buffer), io_buffer_size_(size) {}
 
 DnsResponse::DnsResponse(size_t length)
-    : io_buffer_(new IOBufferWithSize(length)) {
-}
+    : io_buffer_(new IOBuffer(length)), io_buffer_size_(length) {}
 
-DnsResponse::DnsResponse(const void* data,
-                         size_t length,
-                         size_t answer_offset)
+DnsResponse::DnsResponse(const void* data, size_t length, size_t answer_offset)
     : io_buffer_(new IOBufferWithSize(length)),
+      io_buffer_size_(length),
       parser_(io_buffer_->data(), length, answer_offset) {
   DCHECK(data);
   memcpy(io_buffer_->data(), data, length);
@@ -169,11 +170,12 @@
 
 DnsResponse::~DnsResponse() = default;
 
-bool DnsResponse::InitParse(int nbytes, const DnsQuery& query) {
-  DCHECK_GE(nbytes, 0);
+bool DnsResponse::InitParse(size_t nbytes, const DnsQuery& query) {
   // Response includes query, it should be at least that size.
-  if (nbytes < query.io_buffer()->size() || nbytes >= io_buffer_->size())
+  if (nbytes < static_cast<size_t>(query.io_buffer()->size()) ||
+      nbytes >= io_buffer_size_) {
     return false;
+  }
 
   // Match the query id.
   if (base::NetToHost16(header()->id) != query.id())
@@ -196,11 +198,10 @@
   return true;
 }
 
-bool DnsResponse::InitParseWithoutQuery(int nbytes) {
-  DCHECK_GE(nbytes, 0);
-
-  if (nbytes < static_cast<int>(kHeaderSize) || nbytes >= io_buffer_->size())
+bool DnsResponse::InitParseWithoutQuery(size_t nbytes) {
+  if (nbytes < kHeaderSize || nbytes >= io_buffer_size_) {
     return false;
+  }
 
   parser_ = DnsRecordParser(io_buffer_->data(), nbytes, kHeaderSize);
 
diff --git a/net/dns/dns_response.h b/net/dns/dns_response.h
index e9231a5d..ee533c93 100644
--- a/net/dns/dns_response.h
+++ b/net/dns/dns_response.h
@@ -20,7 +20,7 @@
 
 class AddressList;
 class DnsQuery;
-class IOBufferWithSize;
+class IOBuffer;
 
 namespace dns_protocol {
 struct Header;
@@ -108,6 +108,9 @@
   // Constructs a response buffer of given length. Used for TCP transactions.
   explicit DnsResponse(size_t length);
 
+  // Constructs a response taking ownership of the passed buffer.
+  DnsResponse(IOBuffer* buffer, size_t size);
+
   // Constructs a response from |data|. Used for testing purposes only!
   DnsResponse(const void* data, size_t length, size_t answer_offset);
 
@@ -115,15 +118,18 @@
 
   // Internal buffer accessor into which actual bytes of response will be
   // read.
-  IOBufferWithSize* io_buffer() { return io_buffer_.get(); }
+  IOBuffer* io_buffer() { return io_buffer_.get(); }
+
+  // Size of the internal buffer.
+  size_t io_buffer_size() const { return io_buffer_size_; }
 
   // Assuming the internal buffer holds |nbytes| bytes, returns true iff the
   // packet matches the |query| id and question.
-  bool InitParse(int nbytes, const DnsQuery& query);
+  bool InitParse(size_t nbytes, const DnsQuery& query);
 
   // Assuming the internal buffer holds |nbytes| bytes, initialize the parser
   // without matching it against an existing query.
-  bool InitParseWithoutQuery(int nbytes);
+  bool InitParseWithoutQuery(size_t nbytes);
 
   // Returns true if response is valid, that is, after successful InitParse.
   bool IsValid() const;
@@ -158,7 +164,10 @@
   const dns_protocol::Header* header() const;
 
   // Buffer into which response bytes are read.
-  scoped_refptr<IOBufferWithSize> io_buffer_;
+  scoped_refptr<IOBuffer> io_buffer_;
+
+  // Size of the buffer.
+  size_t io_buffer_size_;
 
   // Iterator constructed after InitParse positioned at the answer section.
   // It is never updated afterwards, so can be used in accessors.
diff --git a/net/dns/dns_session.cc b/net/dns/dns_session.cc
index 007ecd2e..3e6efc1 100644
--- a/net/dns/dns_session.cc
+++ b/net/dns/dns_session.cc
@@ -134,7 +134,9 @@
 
 void DnsSession::InitializeServerStats() {
   server_stats_.clear();
-  for (size_t i = 0; i < config_.nameservers.size(); ++i) {
+  for (size_t i = 0;
+       i < config_.nameservers.size() + config_.dns_over_https_servers.size();
+       ++i) {
     server_stats_.push_back(std::make_unique<ServerStats>(
         initial_timeout_, rtt_buckets_.Pointer()));
   }
@@ -162,6 +164,8 @@
 }
 
 unsigned DnsSession::NextGoodServerIndex(unsigned server_index) {
+  DCHECK_GE(server_index, 0u);
+  DCHECK_LT(server_index, config_.nameservers.size());
   unsigned index = server_index;
   base::Time oldest_server_failure(base::Time::Now());
   unsigned oldest_server_failure_index = 0;
@@ -186,6 +190,37 @@
   return oldest_server_failure_index;
 }
 
+unsigned DnsSession::NextGoodDnsOverHttpsServerIndex(unsigned server_index) {
+  DCHECK_GE(server_index, config_.nameservers.size());
+  DCHECK_LT(server_index,
+            config_.nameservers.size() + config_.dns_over_https_servers.size());
+  unsigned index = server_index;
+  base::Time oldest_server_failure(base::Time::Now());
+  unsigned oldest_server_failure_index = config_.nameservers.size();
+
+  do {
+    base::Time cur_server_failure = server_stats_[index]->last_failure;
+    // If number of failures on this server doesn't exceed number of allowed
+    // attempts, return its index.
+    if (server_stats_[index]->last_failure_count < config_.attempts) {
+      return index;
+    }
+    // Track oldest failed server.
+    if (cur_server_failure < oldest_server_failure) {
+      oldest_server_failure = cur_server_failure;
+      oldest_server_failure_index = index;
+    }
+    // Index of dns over https servers begins at nameservers.size().
+    unsigned doh_index = index - config_.nameservers.size();
+    doh_index = ((doh_index + 1) % config_.dns_over_https_servers.size());
+    index = doh_index + config_.nameservers.size();
+  } while (index != server_index);
+
+  // If we are here it means that there are no successful servers, so we have
+  // to use one that has failed oldest.
+  return oldest_server_failure_index;
+}
+
 void DnsSession::RecordServerFailure(unsigned server_index) {
   UMA_HISTOGRAM_CUSTOM_COUNTS("AsyncDNS.ServerFailureIndex", server_index, 1,
                               10, 11);
diff --git a/net/dns/dns_session.h b/net/dns/dns_session.h
index 7b9b26f..3a09d33 100644
--- a/net/dns/dns_session.h
+++ b/net/dns/dns_session.h
@@ -74,12 +74,17 @@
   // Return the index of the first configured server to use on first attempt.
   unsigned NextFirstServerIndex();
 
-  // Start with |server_index| and find the index of the next known good server
-  // to use on this attempt. Returns |server_index| if this server has no
-  // recorded failures, or if there are no other servers that have not failed
-  // or have failed longer time ago.
+  // Start with |server_index| and find the index of the next known
+  // good non-dns-over-https server to use on this attempt. Returns
+  // |server_index| if this server has no recorded failures, or if
+  // there are no other servers that have not failed or have failed
+  // longer time ago.
   unsigned NextGoodServerIndex(unsigned server_index);
 
+  // Same as above, but for DNS over HTTPS servers and ignoring
+  // non-dns-over-https servers
+  unsigned NextGoodDnsOverHttpsServerIndex(unsigned server_index);
+
   // Record that server failed to respond (due to SRV_FAIL or timeout).
   void RecordServerFailure(unsigned server_index);
 
@@ -146,7 +151,10 @@
 
   struct ServerStats;
 
-  // Track runtime statistics of each DNS server.
+  // Track runtime statistics of each DNS server. This combines both
+  // dns-over-https servers and non-dns-over-https servers.
+  // non-dns-over-https servers come first and dns-over-https servers
+  // started at the index of nameservers.size().
   std::vector<std::unique_ptr<ServerStats>> server_stats_;
 
   // Buckets shared for all |ServerStats::rtt_histogram|.
diff --git a/net/dns/dns_test_util.cc b/net/dns/dns_test_util.cc
index d043a01..f18613c 100644
--- a/net/dns/dns_test_util.cc
+++ b/net/dns/dns_test_util.cc
@@ -148,6 +148,9 @@
     }
   }
 
+  void SetRequestContext(URLRequestContext*) override {}
+  void SetRequestPriority(RequestPriority priority) override {}
+
   MockDnsClientRule::Result result_;
   const std::string hostname_;
   const uint16_t qtype_;
@@ -213,6 +216,10 @@
 }
 
 const DnsConfig* MockDnsClient::GetConfig() const {
+  if (!config_.IsValid())
+    printf("invalid config\n");
+  else
+    printf("valid config\n");
   return config_.IsValid() ? &config_ : NULL;
 }
 
diff --git a/net/dns/dns_transaction.cc b/net/dns/dns_transaction.cc
index dc86ffb..f7a3e8d 100644
--- a/net/dns/dns_transaction.cc
+++ b/net/dns/dns_transaction.cc
@@ -9,6 +9,7 @@
 #include <utility>
 #include <vector>
 
+#include "base/base64url.h"
 #include "base/big_endian.h"
 #include "base/bind.h"
 #include "base/callback_helpers.h"
@@ -27,11 +28,14 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/timer/timer.h"
 #include "base/values.h"
+#include "build/build_config.h"
 #include "net/base/completion_callback.h"
+#include "net/base/elements_upload_data_stream.h"
 #include "net/base/io_buffer.h"
 #include "net/base/ip_address.h"
 #include "net/base/ip_endpoint.h"
 #include "net/base/net_errors.h"
+#include "net/base/upload_bytes_element_reader.h"
 #include "net/dns/dns_protocol.h"
 #include "net/dns/dns_query.h"
 #include "net/dns/dns_response.h"
@@ -45,6 +49,11 @@
 #include "net/socket/datagram_client_socket.h"
 #include "net/socket/stream_socket.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "net/url_request/url_fetcher_response_writer.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_builder.h"
 
 namespace net {
 
@@ -77,6 +86,8 @@
             "Essential for Chrome's navigation."
         })");
 
+const char kDnsOverHttpResponseContentType[] = "application/dns-udpwireformat";
+
 // Count labels in the fully-qualified name in DNS format.
 int CountLabels(const std::string& name) {
   size_t count = 0;
@@ -297,11 +308,10 @@
 
   int DoReadResponse() {
     next_state_ = STATE_READ_RESPONSE_COMPLETE;
-    response_.reset(new DnsResponse());
-    return socket()->Read(response_->io_buffer(),
-                          response_->io_buffer()->size(),
-                          base::Bind(&DnsUDPAttempt::OnIOComplete,
-                                     base::Unretained(this)));
+    response_ = std::make_unique<DnsResponse>();
+    return socket()->Read(
+        response_->io_buffer(), response_->io_buffer_size(),
+        base::Bind(&DnsUDPAttempt::OnIOComplete, base::Unretained(this)));
   }
 
   int DoReadResponseComplete(int rv) {
@@ -352,6 +362,224 @@
   DISALLOW_COPY_AND_ASSIGN(DnsUDPAttempt);
 };
 
+class DnsHTTPAttempt : public DnsAttempt, public URLRequest::Delegate {
+ public:
+  DnsHTTPAttempt(unsigned server_index,
+                 std::unique_ptr<DnsQuery> query,
+                 const GURL& server,
+                 bool use_post,
+                 URLRequestContext* url_request_context,
+                 RequestPriority request_priority_)
+      : DnsAttempt(server_index),
+        query_(std::move(query)),
+        weak_factory_(this) {
+    GURL url(server);
+    if (!use_post) {
+      std::string encoded_query;
+      base::Base64UrlEncode(base::StringPiece(query_->io_buffer()->data(),
+                                              query_->io_buffer()->size()),
+                            base::Base64UrlEncodePolicy::INCLUDE_PADDING,
+                            &encoded_query);
+      std::string query_str("content-type=application/dns-udpwireformat&body=" +
+                            encoded_query);
+      GURL::Replacements replacements;
+      replacements.SetQuery(query_str.c_str(),
+                            url::Component(0, query_str.length()));
+      url = server.ReplaceComponents(replacements);
+    }
+
+    HttpRequestHeaders extra_request_headers;
+    extra_request_headers.SetHeader("Accept", kDnsOverHttpResponseContentType);
+
+    request_ = url_request_context->CreateRequest(
+        url, request_priority_, this,
+        net::DefineNetworkTrafficAnnotation("dns_over_https", R"(
+        semantics {
+          sender: "DNS over HTTPS"
+          description: "Domain name resolution over HTTPS"
+          trigger: "User enters a navigates to a domain or Chrome otherwise "
+                   "makes a connection to a domain whose IP address isn't cached"
+          data: "The domain name that is being requested"
+          destination: OTHER
+          destination_other: "The user configured DNS over HTTPS server, which"
+                             "may be dns.google.com"
+        }
+        policy {
+          cookies_allowed: NO
+          setting:
+            "You can configure this feature via that 'dns_over_https_servers' and"
+            "'dns_over_https.method' prefs. Empty lists imply this feature is"
+            "disabled"
+          policy_exception_justification: "Experimental feature that"
+                                          "is disabled by default"
+        }
+      )"));
+
+    if (use_post) {
+      request_->set_method("POST");
+      std::unique_ptr<UploadElementReader> reader =
+          std::make_unique<UploadBytesElementReader>(
+              query_->io_buffer()->data(), query_->io_buffer()->size());
+      request_->set_upload(
+          ElementsUploadDataStream::CreateWithReader(std::move(reader), 0));
+      extra_request_headers.SetHeader(HttpRequestHeaders::kContentType,
+                                      kDnsOverHttpResponseContentType);
+    }
+
+    request_->SetExtraRequestHeaders(extra_request_headers);
+    request_->SetLoadFlags(request_->load_flags() | LOAD_DISABLE_CACHE |
+                           LOAD_BYPASS_PROXY | LOAD_DO_NOT_SEND_COOKIES |
+                           LOAD_DO_NOT_SAVE_COOKIES |
+                           LOAD_DO_NOT_SEND_AUTH_DATA);
+  }
+
+  // DnsAttempt overrides.
+
+  int Start(const CompletionCallback& callback) override {
+    if (DNSDomainToString(query_->qname()).compare(request_->url().host()) ==
+        0) {
+      // Fast failing looking up a server with itself.
+      return ERR_DNS_HTTP_FAILED;
+    }
+
+    callback_ = callback;
+    request_->Start();
+    return ERR_IO_PENDING;
+  }
+
+  void Cancel() { request_.reset(); }
+
+  const DnsQuery* GetQuery() const override { return query_.get(); }
+  const DnsResponse* GetResponse() const override {
+    const DnsResponse* resp = response_.get();
+    return (resp != NULL && resp->IsValid()) ? resp : NULL;
+  }
+  const NetLogWithSource& GetSocketNetLog() const override {
+    return request_->net_log();
+  }
+
+  // URLRequest::Delegate overrides
+
+  void OnResponseStarted(net::URLRequest* request, int net_error) override {
+    DCHECK_NE(net::ERR_IO_PENDING, net_error);
+    std::string content_type;
+    if (net_error != OK) {
+      ResponseCompleted(net_error);
+      return;
+    }
+
+    if (request_->GetResponseCode() != 200 ||
+        !request->response_headers()->GetMimeType(&content_type) ||
+        0 != content_type.compare(kDnsOverHttpResponseContentType)) {
+      ResponseCompleted(ERR_DNS_MALFORMED_RESPONSE);
+      return;
+    }
+
+    buffer_ = new GrowableIOBuffer();
+
+    if (request->response_headers()->HasHeader(
+            HttpRequestHeaders::kContentLength)) {
+      buffer_->SetCapacity(request_->response_headers()->GetContentLength() +
+                           1);
+    } else {
+      buffer_->SetCapacity(66560);  // 64kb.
+    }
+
+    DCHECK(buffer_->data());
+    DCHECK_GT(buffer_->capacity(), 0);
+
+    int bytes_read =
+        request_->Read(buffer_.get(), buffer_->RemainingCapacity());
+
+    // If IO is pending, wait for the URLRequest to call OnReadCompleted.
+    if (bytes_read == net::ERR_IO_PENDING)
+      return;
+
+    OnReadCompleted(request_.get(), bytes_read);
+  }
+
+  void OnReadCompleted(net::URLRequest* request, int bytes_read) override {
+    // bytes_read can be an error.
+    if (bytes_read < 0) {
+      ResponseCompleted(bytes_read);
+      return;
+    }
+
+    DCHECK_GE(bytes_read, 0);
+
+    if (bytes_read > 0) {
+      buffer_->set_offset(buffer_->offset() + bytes_read);
+
+      if (buffer_->RemainingCapacity() == 0) {
+        buffer_->SetCapacity(buffer_->capacity() + 16384);  // Grow by 16kb.
+      }
+
+      DCHECK(buffer_->data());
+      DCHECK_GT(buffer_->capacity(), 0);
+
+      int bytes_read =
+          request_->Read(buffer_.get(), buffer_->RemainingCapacity());
+
+      // If IO is pending, wait for the URLRequest to call OnReadCompleted.
+      if (bytes_read == net::ERR_IO_PENDING)
+        return;
+
+      if (bytes_read <= 0) {
+        OnReadCompleted(request_.get(), bytes_read);
+      } else {
+        // Else, trigger OnReadCompleted asynchronously to avoid starving the IO
+        // thread in case the URLRequest can provide data synchronously.
+        base::SequencedTaskRunnerHandle::Get()->PostTask(
+            FROM_HERE, base::BindOnce(&DnsHTTPAttempt::OnReadCompleted,
+                                      weak_factory_.GetWeakPtr(),
+                                      request_.get(), bytes_read));
+      }
+    } else {
+      // URLRequest reported an EOF. Call ResponseCompleted.
+      DCHECK_EQ(0, bytes_read);
+      ResponseCompleted(net::OK);
+    }
+  }
+
+ private:
+  void ResponseCompleted(int net_error) {
+    request_.reset();
+    callback_.Run(CompleteResponse(net_error));
+  }
+
+  int CompleteResponse(int net_error) {
+    DCHECK_NE(net::ERR_IO_PENDING, net_error);
+    if (net_error != OK) {
+      return net_error;
+    }
+    if (!buffer_.get() || 0 == buffer_->capacity())
+      return ERR_DNS_MALFORMED_RESPONSE;
+
+    size_t size = buffer_->offset();
+    buffer_->set_offset(0);
+    if (size == 0u)
+      return ERR_DNS_MALFORMED_RESPONSE;
+    response_ = std::make_unique<DnsResponse>(buffer_.get(), size + 1);
+    if (!response_->InitParse(size, *query_))
+      return ERR_DNS_MALFORMED_RESPONSE;
+    if (response_->rcode() == dns_protocol::kRcodeNXDOMAIN)
+      return ERR_NAME_NOT_RESOLVED;
+    if (response_->rcode() != dns_protocol::kRcodeNOERROR)
+      return ERR_DNS_SERVER_FAILED;
+    return OK;
+  }
+
+  scoped_refptr<GrowableIOBuffer> buffer_;
+  std::unique_ptr<DnsQuery> query_;
+  CompletionCallback callback_;
+  std::unique_ptr<DnsResponse> response_;
+  std::unique_ptr<URLRequest> request_;
+
+  base::WeakPtrFactory<DnsHTTPAttempt> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(DnsHTTPAttempt);
+};
+
 class DnsTCPAttempt : public DnsAttempt {
  public:
   DnsTCPAttempt(unsigned server_index,
@@ -370,8 +598,8 @@
     callback_ = callback;
     start_time_ = base::TimeTicks::Now();
     next_state_ = STATE_CONNECT_COMPLETE;
-    int rv = socket_->Connect(base::Bind(&DnsTCPAttempt::OnIOComplete,
-                                         base::Unretained(this)));
+    int rv = socket_->Connect(
+        base::Bind(&DnsTCPAttempt::OnIOComplete, base::Unretained(this)));
     if (rv == ERR_IO_PENDING) {
       set_result(rv);
       return rv;
@@ -476,8 +704,8 @@
           base::Bind(&DnsTCPAttempt::OnIOComplete, base::Unretained(this)),
           kTrafficAnnotation);
     }
-    buffer_ = new DrainableIOBuffer(query_->io_buffer(),
-                                    query_->io_buffer()->size());
+    buffer_ =
+        new DrainableIOBuffer(query_->io_buffer(), query_->io_buffer()->size());
     next_state_ = STATE_SEND_QUERY;
     return OK;
   }
@@ -551,7 +779,7 @@
       next_state_ = STATE_READ_RESPONSE;
       return OK;
     }
-
+    DCHECK_GT(buffer_->BytesConsumed(), 0);
     if (!response_->InitParse(buffer_->BytesConsumed(), *query_))
       return ERR_DNS_MALFORMED_RESPONSE;
     if (response_->flags() & dns_protocol::kFlagTC)
@@ -573,8 +801,7 @@
 
   int ReadIntoBuffer() {
     return socket_->Read(
-        buffer_.get(),
-        buffer_->BytesRemaining(),
+        buffer_.get(), buffer_->BytesRemaining(),
         base::Bind(&DnsTCPAttempt::OnIOComplete, base::Unretained(this)));
   }
 
@@ -619,8 +846,11 @@
         net_log_(net_log),
         qnames_initial_size_(0),
         attempts_count_(0),
+        doh_attempts_(0),
         had_tcp_attempt_(false),
-        first_server_index_(0) {
+        doh_attempt_(false),
+        first_server_index_(0),
+        request_priority_(DEFAULT_PRIORITY) {
     DCHECK(session_.get());
     DCHECK(!hostname_.empty());
     DCHECK(!callback_.is_null());
@@ -666,6 +896,14 @@
     }
   }
 
+  void SetRequestContext(URLRequestContext* context) override {
+    url_request_context_ = context;
+  }
+
+  void SetRequestPriority(RequestPriority priority) override {
+    request_priority_ = priority;
+  }
+
  private:
   // Wrapper for the result of a DnsUDPAttempt.
   struct AttemptResult {
@@ -732,8 +970,8 @@
     if (callback_.is_null())
       return;
 
-    const DnsResponse* response = result.attempt ?
-        result.attempt->GetResponse() : NULL;
+    const DnsResponse* response =
+        result.attempt ? result.attempt->GetResponse() : NULL;
     CHECK(result.rv != OK || response != NULL);
 
     timer_.Stop();
@@ -755,9 +993,29 @@
     base::ResetAndReturn(&callback_).Run(this, result.rv, response);
   }
 
+  bool IsHostInDnsOverHttpsServerList(const std::string& host) const {
+    for (const auto& server : session_->config().dns_over_https_servers) {
+      if (host == server.server.host_piece())
+        return true;
+    }
+    return false;
+  }
+
+  AttemptResult MakeAttempt() {
+    // Make an HTTP attempt unless we have already made more attempts
+    // than we have configured servers. Otherwise make a UDP attempt
+    // as long as we have configured nameservers.
+    DnsConfig config = session_->config();
+    if (doh_attempts_ < config.dns_over_https_servers.size())
+      return MakeHTTPAttempt(config.dns_over_https_servers);
+    DCHECK_GT(config.nameservers.size(), 0u);
+    return MakeUDPAttempt();
+  }
+
   // Makes another attempt at the current name, |qnames_.front()|, using the
   // next nameserver.
-  AttemptResult MakeAttempt() {
+  AttemptResult MakeUDPAttempt() {
+    doh_attempt_ = false;
     unsigned attempt_number = attempts_.size();
 
     uint16_t id = session_->NextQueryId();
@@ -770,18 +1028,19 @@
 
     const DnsConfig& config = session_->config();
 
-    unsigned server_index =
-        (first_server_index_ + attempt_number) % config.nameservers.size();
+    unsigned non_doh_server_index =
+        (first_server_index_ + attempt_number - doh_attempts_) %
+        config.nameservers.size();
     // Skip over known failed servers.
-    server_index = session_->NextGoodServerIndex(server_index);
+    non_doh_server_index = session_->NextGoodServerIndex(non_doh_server_index);
 
     std::unique_ptr<DnsSession::SocketLease> lease =
-        session_->AllocateSocket(server_index, net_log_.source());
+        session_->AllocateSocket(non_doh_server_index, net_log_.source());
 
     bool got_socket = !!lease.get();
 
-    DnsUDPAttempt* attempt =
-        new DnsUDPAttempt(server_index, std::move(lease), std::move(query));
+    DnsUDPAttempt* attempt = new DnsUDPAttempt(
+        non_doh_server_index, std::move(lease), std::move(query));
 
     attempts_.push_back(base::WrapUnique(attempt));
     ++attempts_count_;
@@ -793,18 +1052,55 @@
         NetLogEventType::DNS_TRANSACTION_ATTEMPT,
         attempt->GetSocketNetLog().source().ToEventParametersCallback());
 
-    int rv = attempt->Start(
-        base::Bind(&DnsTransactionImpl::OnUdpAttemptComplete,
-                   base::Unretained(this), attempt_number,
-                   base::TimeTicks::Now()));
+    int rv = attempt->Start(base::Bind(
+        &DnsTransactionImpl::OnUdpAttemptComplete, base::Unretained(this),
+        attempt_number, base::TimeTicks::Now()));
     if (rv == ERR_IO_PENDING) {
-      base::TimeDelta timeout = session_->NextTimeout(server_index,
-                                                      attempt_number);
+      base::TimeDelta timeout =
+          session_->NextTimeout(non_doh_server_index, attempt_number);
       timer_.Start(FROM_HERE, timeout, this, &DnsTransactionImpl::OnTimeout);
     }
     return AttemptResult(rv, attempt);
   }
 
+  AttemptResult MakeHTTPAttempt(
+      const std::vector<DnsConfig::DnsOverHttpsServerConfig>& servers) {
+    doh_attempt_ = true;
+    unsigned attempt_number = attempts_.size();
+    uint16_t id = session_->NextQueryId();
+    std::unique_ptr<DnsQuery> query;
+    if (attempts_.empty()) {
+      query.reset(new DnsQuery(id, qnames_.front(), qtype_, opt_rdata_));
+    } else {
+      query = attempts_[0]->GetQuery()->CloneWithNewId(id);
+    }
+    // doh_attempts_ counts the number of attempts made via HTTPS. To
+    // get a server index cap that by the number of DoH servers we
+    // have configured and then offset it by the number of non-doh
+    // servers we have configured since they come first in the server
+    // stats list.
+    unsigned server_index = session_->NextGoodDnsOverHttpsServerIndex(
+        (doh_attempts_ % session_->config().dns_over_https_servers.size()) +
+        session_->config().nameservers.size());
+
+    attempts_.push_back(std::make_unique<DnsHTTPAttempt>(
+        server_index, std::move(query),
+        servers[server_index - session_->config().nameservers.size()].server,
+        servers[server_index - session_->config().nameservers.size()].use_post,
+        url_request_context_, request_priority_));
+    ++doh_attempts_;
+    ++attempts_count_;
+    // Check that we're not looking one of the DNS over HTTPS servers.
+    if (IsHostInDnsOverHttpsServerList(DNSDomainToString(qnames_.front()))) {
+      static_cast<DnsHTTPAttempt*>(attempts_.back().get())->Cancel();
+      return AttemptResult(ERR_CONNECTION_REFUSED, attempts_.back().get());
+    }
+    int rv = attempts_.back()->Start(
+        base::Bind(&DnsTransactionImpl::OnAttemptComplete,
+                   base::Unretained(this), attempt_number));
+    return AttemptResult(rv, attempts_.back().get());
+  }
+
   AttemptResult MakeTCPAttempt(const DnsAttempt* previous_attempt) {
     DCHECK(previous_attempt);
     DCHECK(!had_tcp_attempt_);
@@ -844,8 +1140,7 @@
         attempt->GetSocketNetLog().source().ToEventParametersCallback());
 
     int rv = attempt->Start(base::Bind(&DnsTransactionImpl::OnAttemptComplete,
-                                       base::Unretained(this),
-                                       attempt_number));
+                                       base::Unretained(this), attempt_number));
     if (rv == ERR_IO_PENDING) {
       // Custom timeout for TCP attempt.
       base::TimeDelta timeout = timer_.GetCurrentDelay() * 2;
@@ -860,7 +1155,9 @@
     net_log_.BeginEvent(NetLogEventType::DNS_TRANSACTION_QUERY,
                         NetLog::StringCallback("qname", &dotted_qname));
 
-    first_server_index_ = session_->NextFirstServerIndex();
+    first_server_index_ = session_->config().nameservers.empty()
+                              ? 0
+                              : session_->NextFirstServerIndex();
     RecordLostPacketsIfAny();
     attempts_.clear();
     had_tcp_attempt_ = false;
@@ -894,7 +1191,7 @@
     // Loop through attempts until we find first that is completed
     size_t first_completed = 0;
     for (first_completed = 0; first_completed < attempts_.size();
-        ++first_completed) {
+         ++first_completed) {
       if (attempts_[first_completed]->is_completed())
         break;
     }
@@ -903,7 +1200,8 @@
     if (first_completed == attempts_.size())
       return;
 
-    size_t num_servers = session_->config().nameservers.size();
+    size_t num_servers = session_->config().nameservers.size() +
+                         session_->config().dns_over_https_servers.size();
     std::vector<int> server_attempts(num_servers);
     for (size_t i = 0; i < first_completed; ++i) {
       unsigned server_index = attempts_[i]->server_index();
@@ -927,7 +1225,8 @@
     if (had_tcp_attempt_)
       return false;
     const DnsConfig& config = session_->config();
-    return attempts_.size() < config.attempts * config.nameservers.size();
+    return attempts_.size() < config.attempts * config.nameservers.size() +
+                                  config.dns_over_https_servers.size();
   }
 
   // Resolves the result of a DnsAttempt until a terminal result is reached
@@ -938,7 +1237,8 @@
 
       switch (result.rv) {
         case OK:
-          session_->RecordServerSuccess(result.attempt->server_index());
+          if (!doh_attempt_)
+            session_->RecordServerSuccess(result.attempt->server_index());
           net_log_.EndEventWithNetErrorCode(
               NetLogEventType::DNS_TRANSACTION_QUERY, result.rv);
           DCHECK(result.attempt);
@@ -984,9 +1284,9 @@
           if (MoreAttemptsAllowed()) {
             result = MakeAttempt();
           } else if (result.rv == ERR_DNS_MALFORMED_RESPONSE &&
-                     !had_tcp_attempt_) {
-            // For UDP only, ignore the response and wait until the last attempt
-            // times out.
+                     !had_tcp_attempt_ && !doh_attempt_) {
+            // For UDP only, ignore the response and wait until the last
+            // attempt times out.
             return AttemptResult(ERR_IO_PENDING, NULL);
           } else {
             return AttemptResult(result.rv, NULL);
@@ -1023,14 +1323,19 @@
   // List of attempts for the current name.
   std::vector<std::unique_ptr<DnsAttempt>> attempts_;
   // Count of attempts, not reset when |attempts_| vector is cleared.
-  int  attempts_count_;
+  int attempts_count_;
+  uint16_t doh_attempts_;
   bool had_tcp_attempt_;
+  bool doh_attempt_;
 
   // Index of the first server to try on each search query.
   int first_server_index_;
 
   base::OneShotTimer timer_;
 
+  URLRequestContext* url_request_context_;
+  RequestPriority request_priority_;
+
   THREAD_CHECKER(thread_checker_);
 
   DISALLOW_COPY_AND_ASSIGN(DnsTransactionImpl);
diff --git a/net/dns/dns_transaction.h b/net/dns/dns_transaction.h
index 5f6cd0a..11d8531 100644
--- a/net/dns/dns_transaction.h
+++ b/net/dns/dns_transaction.h
@@ -10,16 +10,16 @@
 #include <memory>
 #include <string>
 
-#include "base/callback_forward.h"
-#include "base/compiler_specific.h"
-#include "net/base/net_export.h"
+#include "net/base/request_priority.h"
 #include "net/dns/record_rdata.h"
+#include "url/gurl.h"
 
 namespace net {
 
 class DnsResponse;
 class DnsSession;
 class NetLogWithSource;
+class URLRequestContext;
 
 // DnsTransaction implements a stub DNS resolver as defined in RFC 1034.
 // The DnsTransaction takes care of retransmissions, name server fallback (or
@@ -39,6 +39,10 @@
 
   // Starts the transaction.  Always completes asynchronously.
   virtual void Start() = 0;
+
+  virtual void SetRequestContext(URLRequestContext*) = 0;
+
+  virtual void SetRequestPriority(RequestPriority priority) = 0;
 };
 
 // Creates DnsTransaction which performs asynchronous DNS search.
diff --git a/net/dns/dns_transaction_unittest.cc b/net/dns/dns_transaction_unittest.cc
index ce7bea0..f61f0ff 100644
--- a/net/dns/dns_transaction_unittest.cc
+++ b/net/dns/dns_transaction_unittest.cc
@@ -9,15 +9,22 @@
 #include <limits>
 #include <utility>
 
+#include "base/base64url.h"
 #include "base/bind.h"
 #include "base/containers/circular_deque.h"
 #include "base/macros.h"
+#include "base/message_loop/message_loop.h"
 #include "base/rand_util.h"
 #include "base/run_loop.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
 #include "base/sys_byteorder.h"
 #include "base/test/scoped_task_environment.h"
 #include "base/time/time.h"
 #include "net/base/ip_address.h"
+#include "net/base/port_util.h"
+#include "net/base/upload_bytes_element_reader.h"
+#include "net/base/url_util.h"
 #include "net/dns/dns_protocol.h"
 #include "net/dns/dns_query.h"
 #include "net/dns/dns_response.h"
@@ -25,9 +32,14 @@
 #include "net/dns/dns_test_util.h"
 #include "net/dns/dns_util.h"
 #include "net/log/net_log_with_source.h"
+#include "net/proxy_resolution/proxy_config_service_fixed.h"
 #include "net/socket/socket_test_util.h"
 #include "net/test/gtest_util.h"
 #include "net/test/net_test_suite.h"
+#include "net/test/url_request/url_request_failed_job.h"
+#include "net/url_request/url_request_filter.h"
+#include "net/url_request/url_request_interceptor.h"
+#include "net/url_request/url_request_test_util.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -35,18 +47,20 @@
 
 namespace net {
 
-class NetLog;
-
 namespace {
 
 base::TimeDelta kTimeout = base::TimeDelta::FromSeconds(1);
 
+const char kMockHostname[] = "mock.http";
+
 std::string DomainFromDot(const base::StringPiece& dotted) {
   std::string out;
   EXPECT_TRUE(DNSDomainFromDot(dotted, &out));
   return out;
 }
 
+enum class Transport { UDP, TCP, HTTPS };
+
 // A SocketDataProvider builder.
 class DnsSocketData {
  public:
@@ -55,11 +69,11 @@
                 const char* dotted_name,
                 uint16_t qtype,
                 IoMode mode,
-                bool use_tcp,
+                Transport transport,
                 const OptRecordRdata* opt_rdata = nullptr)
       : query_(new DnsQuery(id, DomainFromDot(dotted_name), qtype, opt_rdata)),
-        use_tcp_(use_tcp) {
-    if (use_tcp_) {
+        transport_(transport) {
+    if (Transport::TCP == transport_) {
       std::unique_ptr<uint16_t> length(new uint16_t);
       *length = base::HostToNet16(query_->io_buffer()->size());
       writes_.push_back(MockWrite(mode,
@@ -80,7 +94,7 @@
                              IoMode mode,
                              uint16_t tcp_length) {
     CHECK(!provider_.get());
-    if (use_tcp_) {
+    if (Transport::TCP == transport_) {
       std::unique_ptr<uint16_t> length(new uint16_t);
       *length = base::HostToNet16(tcp_length);
       reads_.push_back(MockRead(mode,
@@ -89,14 +103,14 @@
       lengths_.push_back(std::move(length));
     }
     reads_.push_back(MockRead(mode, response->io_buffer()->data(),
-                              response->io_buffer()->size(),
+                              response->io_buffer_size(),
                               num_reads_and_writes()));
     responses_.push_back(std::move(response));
   }
 
   // Adds pre-built DnsResponse.
   void AddResponse(std::unique_ptr<DnsResponse> response, IoMode mode) {
-    uint16_t tcp_length = response->io_buffer()->size();
+    uint16_t tcp_length = response->io_buffer_size();
     AddResponseWithLength(std::move(response), mode, tcp_length);
   }
 
@@ -108,6 +122,18 @@
                 mode);
   }
 
+  // Adds pre-built response from |data| buffer.
+  void AddResponseData(const uint8_t* data,
+                       size_t length,
+                       int offset,
+                       IoMode mode) {
+    CHECK(!provider_.get());
+    AddResponse(
+        std::make_unique<DnsResponse>(reinterpret_cast<const char*>(data),
+                                      length - offset, offset),
+        mode);
+  }
+
   // Add no-answer (RCODE only) response matching the query.
   void AddRcode(int rcode, IoMode mode) {
     std::unique_ptr<DnsResponse> response(new DnsResponse(
@@ -130,11 +156,13 @@
       return provider_.get();
     // Terminate the reads with ERR_IO_PENDING to prevent overrun and default to
     // timeout.
-    reads_.push_back(
-        MockRead(SYNCHRONOUS, ERR_IO_PENDING, writes_.size() + reads_.size()));
+    if (transport_ != Transport::HTTPS) {
+      reads_.push_back(MockRead(SYNCHRONOUS, ERR_IO_PENDING,
+                                writes_.size() + reads_.size()));
+    }
     provider_.reset(new SequencedSocketData(&reads_[0], reads_.size(),
                                             &writes_[0], writes_.size()));
-    if (use_tcp_) {
+    if (Transport::TCP == transport_ || Transport::HTTPS == transport_) {
       provider_->set_connect_data(MockConnect(reads_[0].mode, OK));
     }
     return provider_.get();
@@ -142,11 +170,13 @@
 
   uint16_t query_id() const { return query_->id(); }
 
+  IOBufferWithSize* query_buffer() { return query_->io_buffer(); }
+
  private:
   size_t num_reads_and_writes() const { return reads_.size() + writes_.size(); }
 
   std::unique_ptr<DnsQuery> query_;
-  bool use_tcp_;
+  Transport transport_;
   std::vector<std::unique_ptr<uint16_t>> lengths_;
   std::vector<std::unique_ptr<DnsResponse>> responses_;
   std::vector<MockWrite> writes_;
@@ -255,6 +285,8 @@
         hostname_, qtype_, base::Bind(&TransactionHelper::OnTransactionComplete,
                                       base::Unretained(this)),
         NetLogWithSource());
+    transaction_->SetRequestContext(&request_context_);
+    transaction_->SetRequestPriority(DEFAULT_PRIORITY);
     EXPECT_EQ(hostname_, transaction_->GetHostname());
     EXPECT_EQ(qtype_, transaction_->GetType());
     transaction_->Start();
@@ -273,6 +305,9 @@
 
     completed_ = true;
 
+    if (transaction_complete_run_loop_)
+      transaction_complete_run_loop_->QuitWhenIdle();
+
     if (cancel_in_callback_) {
       Cancel();
       return;
@@ -307,6 +342,15 @@
     return has_completed();
   }
 
+  bool RunUntilDone(DnsTransactionFactory* factory) {
+    DCHECK(!transaction_complete_run_loop_);
+    transaction_complete_run_loop_ = std::make_unique<base::RunLoop>();
+    StartTransaction(factory);
+    transaction_complete_run_loop_->Run();
+    transaction_complete_run_loop_.reset();
+    return has_completed();
+  }
+
   bool FastForwardByTimeout(DnsSession* session,
                             unsigned server_index,
                             int attempt) {
@@ -320,16 +364,194 @@
     return has_completed();
   }
 
+  TestURLRequestContext* request_context() { return &request_context_; }
+
  private:
   std::string hostname_;
   uint16_t qtype_;
   std::unique_ptr<DnsTransaction> transaction_;
   int expected_answer_count_;
   bool cancel_in_callback_;
-
+  TestURLRequestContext request_context_;
+  std::unique_ptr<base::RunLoop> transaction_complete_run_loop_;
   bool completed_;
 };
 
+// Callback that allows a test to modify HttpResponseinfo
+// before the response is sent to the requester. This allows
+// response headers to be changed.
+typedef base::RepeatingCallback<void(URLRequest* request,
+                                     HttpResponseInfo* info)>
+    ResponseModifierCallback;
+
+// Callback that allows the test to substiture its own implementation
+// of URLRequestJob to handle the request.
+typedef base::RepeatingCallback<URLRequestJob*(
+    URLRequest* request,
+    NetworkDelegate* network_delegate,
+    SocketDataProvider* data_provider)>
+    DohJobMakerCallback;
+
+// Subclass of URLRequest which takes a SocketDataProvider with data
+// representing both a DNS over HTTPS query and response.
+class URLRequestMockDohJob : public URLRequestJob, public AsyncSocket {
+ public:
+  URLRequestMockDohJob(
+      URLRequest* request,
+      NetworkDelegate* network_delegate,
+      SocketDataProvider* data_provider,
+      ResponseModifierCallback response_modifier = ResponseModifierCallback())
+      : URLRequestJob(request, network_delegate),
+        content_length_(0),
+        leftover_data_len_(0),
+        data_provider_(data_provider),
+        response_modifier_(response_modifier),
+        weak_factory_(this) {
+    data_provider_->Initialize(this);
+    MatchQueryData(request, data_provider);
+  }
+
+  // Compare the query contained in either the POST body or the body
+  // parameter of the GET query to the write data of the SocketDataProvider.
+  static void MatchQueryData(URLRequest* request,
+                             SocketDataProvider* data_provider) {
+    std::string decoded_query;
+    if (request->method() == "GET") {
+      std::string encoded_query;
+      EXPECT_TRUE(
+          GetValueForKeyInQuery(request->url(), "body", &encoded_query));
+      EXPECT_GT(encoded_query.size(), 0ul);
+
+      EXPECT_TRUE(base::Base64UrlDecode(
+          encoded_query, base::Base64UrlDecodePolicy::IGNORE_PADDING,
+          &decoded_query));
+    } else if (request->method() == "POST") {
+      const UploadDataStream* stream = request->get_upload();
+      auto* readers = stream->GetElementReaders();
+      EXPECT_TRUE(readers);
+      EXPECT_FALSE(readers->empty());
+      for (auto& reader : *readers) {
+        const UploadBytesElementReader* byte_reader = reader->AsBytesReader();
+        decoded_query +=
+            std::string(byte_reader->bytes(), byte_reader->length());
+      }
+    }
+
+    std::string query(decoded_query);
+    MockWriteResult result(SYNCHRONOUS, 1);
+    while (result.result > 0 && query.length() > 0) {
+      result = data_provider->OnWrite(query);
+      if (result.result > 0)
+        query = query.substr(result.result);
+    }
+  }
+
+  static GURL GetMockHttpsUrl(const std::string& path) {
+    return GURL("https://" + (kMockHostname + ("/" + path)));
+  }
+
+  // URLRequestJob implementation:
+  void Start() override {
+    // Start reading asynchronously so that all error reporting and data
+    // callbacks happen as they would for network requests.
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::Bind(&URLRequestMockDohJob::StartAsync,
+                              weak_factory_.GetWeakPtr()));
+  }
+
+  ~URLRequestMockDohJob() override {
+    if (data_provider_)
+      data_provider_->DetachSocket();
+  }
+
+  int ReadRawData(IOBuffer* buf, int buf_size) override {
+    if (!data_provider_)
+      return ERR_FAILED;
+    if (leftover_data_len_ > 0) {
+      int rv = DoBufferCopy(leftover_data_, leftover_data_len_, buf, buf_size);
+      return rv;
+    }
+
+    if (data_provider_->AllReadDataConsumed())
+      return 0;
+
+    MockRead read = data_provider_->OnRead();
+
+    if (read.result < ERR_IO_PENDING)
+      return read.result;
+
+    if (read.result == ERR_IO_PENDING) {
+      pending_buf_ = buf;
+      pending_buf_size_ = buf_size;
+      return ERR_IO_PENDING;
+    }
+    return DoBufferCopy(read.data, read.data_len, buf, buf_size);
+  }
+
+  void GetResponseInfo(HttpResponseInfo* info) override {
+    // Send back mock headers.
+    std::string raw_headers;
+    raw_headers.append(
+        "HTTP/1.1 200 OK\n"
+        "Content-type: application/dns-udpwireformat\n");
+    if (content_length_ > 0) {
+      raw_headers.append(base::StringPrintf("Content-Length: %1d\n",
+                                            static_cast<int>(content_length_)));
+    }
+    info->headers =
+        base::MakeRefCounted<HttpResponseHeaders>(HttpUtil::AssembleRawHeaders(
+            raw_headers.c_str(), static_cast<int>(raw_headers.length())));
+    if (response_modifier_)
+      response_modifier_.Run(request(), info);
+  }
+
+  // AsyncSocket implementation:
+  void OnReadComplete(const MockRead& data) override {
+    EXPECT_NE(data.result, ERR_IO_PENDING);
+    if (data.result < 0)
+      return ReadRawDataComplete(data.result);
+    ReadRawDataComplete(DoBufferCopy(data.data, data.data_len, pending_buf_,
+                                     pending_buf_size_));
+  }
+  void OnWriteComplete(int rv) override {}
+  void OnConnectComplete(const MockConnect& data) override {}
+  void OnDataProviderDestroyed() override { data_provider_ = nullptr; }
+
+ private:
+  void StartAsync() {
+    if (!request_)
+      return;
+    if (content_length_)
+      set_expected_content_size(content_length_);
+    NotifyHeadersComplete();
+  }
+
+  int DoBufferCopy(const char* data,
+                   int data_len,
+                   IOBuffer* buf,
+                   int buf_size) {
+    if (data_len > buf_size) {
+      memcpy(buf->data(), data, buf_size);
+      leftover_data_ = data + buf_size;
+      leftover_data_len_ = data_len - buf_size;
+      return buf_size;
+    }
+    memcpy(buf->data(), data, data_len);
+    return data_len;
+  }
+
+  const int content_length_;
+  const char* leftover_data_;
+  int leftover_data_len_;
+  SocketDataProvider* data_provider_;
+  const ResponseModifierCallback response_modifier_;
+  IOBuffer* pending_buf_;
+  int pending_buf_size_;
+  DISALLOW_COPY_AND_ASSIGN(URLRequestMockDohJob);
+
+  base::WeakPtrFactory<URLRequestMockDohJob> weak_factory_;
+};
+
 class DnsTransactionTest : public testing::Test {
  public:
   DnsTransactionTest() = default;
@@ -344,6 +566,96 @@
     }
   }
 
+  // Generates |nameservers| for DnsConfig.
+  void ConfigureDohServers(unsigned num_servers, bool use_post) {
+    CHECK_LE(num_servers, 255u);
+    for (unsigned i = 0; i < num_servers; ++i) {
+      GURL url(URLRequestMockDohJob::GetMockHttpsUrl(
+          base::StringPrintf("doh_test_%d", i)));
+      config_.dns_over_https_servers.push_back(
+          DnsConfig::DnsOverHttpsServerConfig(url, use_post));
+    }
+  }
+
+  // Configures the DnsConfig with one dns over https server, which either
+  // accepts GET or POST requests based on use_post. If |clear_udp| is true,
+  // existing IP name servers are removed from the DnsConfig. If a
+  // ResponseModifierCallback is provided it will be called to contruct the
+  // HTTPResponse.
+  void ConfigDohServers(bool clear_udp,
+                        bool use_post,
+                        int num_doh_servers = 1) {
+    if (clear_udp)
+      ConfigureNumServers(0);
+    NetTestSuite::SetScopedTaskEnvironment(
+        base::test::ScopedTaskEnvironment::MainThreadType::IO);
+    GURL url(URLRequestMockDohJob::GetMockHttpsUrl("doh_test"));
+    URLRequestFilter* filter = URLRequestFilter::GetInstance();
+    filter->AddHostnameInterceptor(url.scheme(), url.host(),
+                                   std::make_unique<DohJobInterceptor>(this));
+    ConfigureDohServers(num_doh_servers, use_post);
+    ConfigureFactory();
+  }
+
+  URLRequestJob* MaybeInterceptRequest(URLRequest* request,
+                                       NetworkDelegate* network_delegate) {
+    // If the path indicates a redirct, skip checking the list of
+    // configured servers, because it won't be there and we still want
+    // to handle it.
+    bool server_found = request->url().path() == "/redirect-destination";
+    for (auto server : config_.dns_over_https_servers) {
+      if (server_found)
+        break;
+      GURL url(request->url());
+      GURL server_url = server.server;
+      if (url.has_query()) {
+        server_url = GURL(server_url.spec() + "?" + url.query());
+      }
+      if (server_url == url) {
+        EXPECT_TRUE((server.use_post ? "POST" : "GET") == request->method());
+        server_found = true;
+      }
+    }
+    EXPECT_TRUE(server_found);
+
+    HttpRequestHeaders* headers = nullptr;
+    if (request->GetFullRequestHeaders(headers)) {
+      EXPECT_FALSE(headers->HasHeader(HttpRequestHeaders::kCookie));
+    }
+    EXPECT_FALSE(request->extra_request_headers().HasHeader(
+        HttpRequestHeaders::kCookie));
+
+    std::string accept;
+    EXPECT_TRUE(request->extra_request_headers().GetHeader("Accept", &accept));
+    EXPECT_EQ(accept, "application/dns-udpwireformat");
+
+    SocketDataProvider* provider = socket_factory_->mock_data().GetNext();
+
+    if (doh_job_maker_)
+      return doh_job_maker_.Run(request, network_delegate, provider);
+
+    return new URLRequestMockDohJob(request, network_delegate, provider,
+                                    response_modifier_);
+  }
+
+  class DohJobInterceptor : public URLRequestInterceptor {
+   public:
+    explicit DohJobInterceptor(DnsTransactionTest* test) : test_(test) {}
+    ~DohJobInterceptor() override {}
+
+    // URLRequestInterceptor implementation:
+    URLRequestJob* MaybeInterceptRequest(
+        URLRequest* request,
+        NetworkDelegate* network_delegate) const override {
+      return test_->MaybeInterceptRequest(request, network_delegate);
+    }
+
+   private:
+    DnsTransactionTest* test_;
+
+    DISALLOW_COPY_AND_ASSIGN(DohJobInterceptor);
+  };
+
   // Called after fully configuring |config|.
   void ConfigureFactory() {
     socket_factory_.reset(new TestSocketFactory());
@@ -371,23 +683,36 @@
                            const uint8_t* response_data,
                            size_t response_length,
                            IoMode mode,
-                           bool use_tcp,
+                           Transport transport,
                            const OptRecordRdata* opt_rdata = nullptr) {
     CHECK(socket_factory_.get());
     std::unique_ptr<DnsSocketData> data(
-        new DnsSocketData(id, dotted_name, qtype, mode, use_tcp, opt_rdata));
+        new DnsSocketData(id, dotted_name, qtype, mode, transport, opt_rdata));
     data->AddResponseData(response_data, response_length, mode);
     AddSocketData(std::move(data));
   }
 
+  void AddQueryAndErrorResponse(uint16_t id,
+                                const char* dotted_name,
+                                uint16_t qtype,
+                                int error,
+                                IoMode mode,
+                                Transport transport) {
+    CHECK(socket_factory_.get());
+    std::unique_ptr<DnsSocketData> data(
+        new DnsSocketData(id, dotted_name, qtype, mode, transport));
+    data->AddReadError(error, mode);
+    AddSocketData(std::move(data));
+  }
+
   void AddAsyncQueryAndResponse(uint16_t id,
                                 const char* dotted_name,
                                 uint16_t qtype,
                                 const uint8_t* data,
                                 size_t data_length,
                                 const OptRecordRdata* opt_rdata = nullptr) {
-    AddQueryAndResponse(id, dotted_name, qtype, data, data_length, ASYNC, false,
-                        opt_rdata);
+    AddQueryAndResponse(id, dotted_name, qtype, data, data_length, ASYNC,
+                        Transport::UDP, opt_rdata);
   }
 
   void AddSyncQueryAndResponse(uint16_t id,
@@ -397,14 +722,14 @@
                                size_t data_length,
                                const OptRecordRdata* opt_rdata = nullptr) {
     AddQueryAndResponse(id, dotted_name, qtype, data, data_length, SYNCHRONOUS,
-                        false, opt_rdata);
+                        Transport::UDP, opt_rdata);
   }
 
   // Add expected query of |dotted_name| and |qtype| and no response.
   void AddQueryAndTimeout(const char* dotted_name, uint16_t qtype) {
     uint16_t id = base::RandInt(0, std::numeric_limits<uint16_t>::max());
     std::unique_ptr<DnsSocketData> data(
-        new DnsSocketData(id, dotted_name, qtype, ASYNC, false));
+        new DnsSocketData(id, dotted_name, qtype, ASYNC, Transport::UDP));
     AddSocketData(std::move(data));
   }
 
@@ -414,11 +739,11 @@
                         uint16_t qtype,
                         int rcode,
                         IoMode mode,
-                        bool use_tcp) {
+                        Transport trans) {
     CHECK_NE(dns_protocol::kRcodeNOERROR, rcode);
     uint16_t id = base::RandInt(0, std::numeric_limits<uint16_t>::max());
     std::unique_ptr<DnsSocketData> data(
-        new DnsSocketData(id, dotted_name, qtype, mode, use_tcp));
+        new DnsSocketData(id, dotted_name, qtype, mode, trans));
     data->AddRcode(rcode, mode);
     AddSocketData(std::move(data));
   }
@@ -426,13 +751,13 @@
   void AddAsyncQueryAndRcode(const char* dotted_name,
                              uint16_t qtype,
                              int rcode) {
-    AddQueryAndRcode(dotted_name, qtype, rcode, ASYNC, false);
+    AddQueryAndRcode(dotted_name, qtype, rcode, ASYNC, Transport::UDP);
   }
 
   void AddSyncQueryAndRcode(const char* dotted_name,
                             uint16_t qtype,
                             int rcode) {
-    AddQueryAndRcode(dotted_name, qtype, rcode, SYNCHRONOUS, false);
+    AddQueryAndRcode(dotted_name, qtype, rcode, SYNCHRONOUS, Transport::UDP);
   }
 
   // Checks if the sockets were connected in the order matching the indices in
@@ -448,6 +773,7 @@
   void SetUp() override {
     NetTestSuite::SetScopedTaskEnvironment(
         base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME);
+
     // By default set one server,
     ConfigureNumServers(1);
     // and no retransmissions,
@@ -459,12 +785,25 @@
 
   void TearDown() override {
     // Check that all socket data was at least written to.
+    if (base::MessageLoop::current() &&
+        base::MessageLoop::current()->IsType(base::MessageLoop::TYPE_IO)) {
+      URLRequestFilter* filter = URLRequestFilter::GetInstance();
+      filter->ClearHandlers();
+    }
     for (size_t i = 0; i < socket_data_.size(); ++i) {
       EXPECT_TRUE(socket_data_[i]->GetProvider()->AllWriteDataConsumed()) << i;
     }
     NetTestSuite::ResetScopedTaskEnvironment();
   }
 
+  void SetResponseModifierCallback(ResponseModifierCallback response_modifier) {
+    response_modifier_ = response_modifier;
+  }
+
+  void SetDohJobMakerCallback(DohJobMakerCallback doh_job_maker) {
+    doh_job_maker_ = doh_job_maker;
+  }
+
  protected:
   int GetNextId(int min, int max) {
     EXPECT_FALSE(transaction_ids_.empty());
@@ -483,6 +822,9 @@
   std::unique_ptr<TestSocketFactory> socket_factory_;
   scoped_refptr<DnsSession> session_;
   std::unique_ptr<DnsTransactionFactory> transaction_factory_;
+
+  ResponseModifierCallback response_modifier_;
+  DohJobMakerCallback doh_job_maker_;
 };
 
 TEST_F(DnsTransactionTest, Lookup) {
@@ -597,8 +939,8 @@
   ConfigureFactory();
 
   // Attempt receives mismatched response followed by valid response.
-  std::unique_ptr<DnsSocketData> data(
-      new DnsSocketData(0 /* id */, kT0HostName, kT0Qtype, SYNCHRONOUS, false));
+  std::unique_ptr<DnsSocketData> data(new DnsSocketData(
+      0 /* id */, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::UDP));
   data->AddResponseData(kT1ResponseDatagram,
                         arraysize(kT1ResponseDatagram), SYNCHRONOUS);
   data->AddResponseData(kT0ResponseDatagram,
@@ -615,8 +957,8 @@
 
   // First attempt receives mismatched response followed by valid response.
   // Second attempt times out.
-  std::unique_ptr<DnsSocketData> data(
-      new DnsSocketData(0 /* id */, kT0HostName, kT0Qtype, ASYNC, false));
+  std::unique_ptr<DnsSocketData> data(new DnsSocketData(
+      0 /* id */, kT0HostName, kT0Qtype, ASYNC, Transport::UDP));
   data->AddResponseData(kT1ResponseDatagram,
                         arraysize(kT1ResponseDatagram), ASYNC);
   data->AddResponseData(kT0ResponseDatagram,
@@ -648,8 +990,8 @@
   // First attempt receives mismatched response followed by valid NXDOMAIN
   // response.
   // Second attempt receives valid NXDOMAIN response.
-  std::unique_ptr<DnsSocketData> data(
-      new DnsSocketData(0 /* id */, kT0HostName, kT0Qtype, SYNCHRONOUS, false));
+  std::unique_ptr<DnsSocketData> data(new DnsSocketData(
+      0 /* id */, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::UDP));
   data->AddResponseData(kT1ResponseDatagram, arraysize(kT1ResponseDatagram),
                         SYNCHRONOUS);
   data->AddRcode(dns_protocol::kRcodeNXDOMAIN, ASYNC);
@@ -938,12 +1280,549 @@
   EXPECT_TRUE(helper0.Run(transaction_factory_.get()));
 }
 
+TEST_F(DnsTransactionTest, HttpsGetLookup) {
+  ConfigDohServers(true /* clear_udp */, false /* use_post */);
+  AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
+                      arraysize(kT0ResponseDatagram), SYNCHRONOUS,
+                      Transport::HTTPS);
+  TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount);
+  EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, HttpsGetFailure) {
+  ConfigDohServers(true /* clear_udp */, false /* use_post */);
+  AddQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeSERVFAIL,
+                   SYNCHRONOUS, Transport::HTTPS);
+
+  TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_SERVER_FAILED);
+  EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, HttpsGetMalformed) {
+  ConfigDohServers(true /* clear_udp */, false /* use_post */);
+  std::unique_ptr<DnsSocketData> data(new DnsSocketData(
+      0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS));
+  std::unique_ptr<DnsResponse> response = std::make_unique<DnsResponse>(
+      reinterpret_cast<const char*>(kT0ResponseDatagram),
+      arraysize(kT0ResponseDatagram), 0);
+  // Change the id of the header to make the response malformed.
+  response->io_buffer()->data()[0]++;
+  data->AddResponse(std::move(response), SYNCHRONOUS);
+  AddSocketData(std::move(data));
+
+  TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_MALFORMED_RESPONSE);
+  EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, HttpsPostLookup) {
+  ConfigDohServers(true /* clear_udp */, true /* use_post */);
+  AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
+                      arraysize(kT0ResponseDatagram), SYNCHRONOUS,
+                      Transport::HTTPS);
+  TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount);
+  EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, HttpsPostFailure) {
+  ConfigDohServers(true /* clear_udp */, true /* use_post */);
+  AddQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeSERVFAIL,
+                   SYNCHRONOUS, Transport::HTTPS);
+
+  TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_SERVER_FAILED);
+  EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, HttpsPostMalformed) {
+  ConfigDohServers(true /* clear_udp */, true /* use_post */);
+  std::unique_ptr<DnsSocketData> data(new DnsSocketData(
+      0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS));
+  std::unique_ptr<DnsResponse> response = std::make_unique<DnsResponse>(
+      reinterpret_cast<const char*>(kT0ResponseDatagram),
+      arraysize(kT0ResponseDatagram), 0);
+  // Change the id of the header to make the response malformed.
+  response->io_buffer()->data()[0]++;
+  data->AddResponse(std::move(response), SYNCHRONOUS);
+  AddSocketData(std::move(data));
+
+  TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_MALFORMED_RESPONSE);
+  EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, HttpsPostLookupAsync) {
+  ConfigDohServers(true /* clear_udp */, true /* use_post */);
+  AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
+                      arraysize(kT0ResponseDatagram), ASYNC, Transport::HTTPS);
+  TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount);
+  EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+}
+
+URLRequestJob* DohJobMakerCallbackFailStart(URLRequest* request,
+                                            NetworkDelegate* network_delegate,
+                                            SocketDataProvider* data) {
+  URLRequestMockDohJob::MatchQueryData(request, data);
+  return new URLRequestFailedJob(request, network_delegate,
+                                 URLRequestFailedJob::START, ERR_FAILED);
+}
+
+TEST_F(DnsTransactionTest, HttpsPostLookupFailStart) {
+  ConfigDohServers(true /* clear_udp */, true /* use_post */);
+  AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
+                      arraysize(kT0ResponseDatagram), SYNCHRONOUS,
+                      Transport::HTTPS);
+  TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_FAILED);
+  SetDohJobMakerCallback(base::BindRepeating(DohJobMakerCallbackFailStart));
+  EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+}
+
+URLRequestJob* DohJobMakerCallbackFailSync(URLRequest* request,
+                                           NetworkDelegate* network_delegate,
+                                           SocketDataProvider* data) {
+  URLRequestMockDohJob::MatchQueryData(request, data);
+  return new URLRequestFailedJob(request, network_delegate,
+                                 URLRequestFailedJob::READ_SYNC, ERR_FAILED);
+}
+
+TEST_F(DnsTransactionTest, HttpsPostLookupFailSync) {
+  ConfigDohServers(true /* clear_udp */, true /* use_post */);
+  std::unique_ptr<DnsSocketData> data(new DnsSocketData(
+      0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS));
+  data->AddResponseWithLength(std::make_unique<DnsResponse>(), SYNCHRONOUS, 0);
+  AddSocketData(std::move(data));
+  TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_MALFORMED_RESPONSE);
+  SetDohJobMakerCallback(base::BindRepeating(DohJobMakerCallbackFailSync));
+  EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+}
+
+URLRequestJob* DohJobMakerCallbackFailAsync(URLRequest* request,
+                                            NetworkDelegate* network_delegate,
+                                            SocketDataProvider* data) {
+  URLRequestMockDohJob::MatchQueryData(request, data);
+  return new URLRequestFailedJob(request, network_delegate,
+                                 URLRequestFailedJob::READ_ASYNC, ERR_FAILED);
+}
+
+TEST_F(DnsTransactionTest, HttpsPostLookupFailAsync) {
+  ConfigDohServers(true /* clear_udp */, true /* use_post */);
+  AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
+                      arraysize(kT0ResponseDatagram), SYNCHRONOUS,
+                      Transport::HTTPS);
+  TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_MALFORMED_RESPONSE);
+  SetDohJobMakerCallback(base::BindRepeating(DohJobMakerCallbackFailAsync));
+  EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, HttpsPostLookup2Sync) {
+  ConfigDohServers(true /* clear_udp */, true /* use_post */);
+  std::unique_ptr<DnsSocketData> data(new DnsSocketData(
+      0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS));
+  data->AddResponseData(kT0ResponseDatagram, 20, SYNCHRONOUS);
+  data->AddResponseData(kT0ResponseDatagram + 20,
+                        arraysize(kT0ResponseDatagram) - 20, SYNCHRONOUS);
+  AddSocketData(std::move(data));
+  TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount);
+  EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, HttpsPostLookup2Async) {
+  ConfigDohServers(true /* clear_udp */, true /* use_post */);
+  std::unique_ptr<DnsSocketData> data(new DnsSocketData(
+      0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS));
+  data->AddResponseData(kT0ResponseDatagram, 20, ASYNC);
+  data->AddResponseData(kT0ResponseDatagram + 20,
+                        arraysize(kT0ResponseDatagram) - 20, ASYNC);
+  AddSocketData(std::move(data));
+  TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount);
+  EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, HttpsPostLookupAsyncWithAsyncZeroRead) {
+  ConfigDohServers(true /* clear_udp */, true /* use_post */);
+  std::unique_ptr<DnsSocketData> data(new DnsSocketData(
+      0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS));
+  data->AddResponseData(kT0ResponseDatagram, arraysize(kT0ResponseDatagram),
+                        ASYNC);
+  data->AddResponseData(kT0ResponseDatagram, 0, ASYNC);
+  AddSocketData(std::move(data));
+  TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount);
+  EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, HttpsPostLookupSyncWithAsyncZeroRead) {
+  ConfigDohServers(true /* clear_udp */, true /* use_post */);
+  std::unique_ptr<DnsSocketData> data(new DnsSocketData(
+      0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS));
+  data->AddResponseData(kT0ResponseDatagram, arraysize(kT0ResponseDatagram),
+                        SYNCHRONOUS);
+  data->AddResponseData(kT0ResponseDatagram, 0, ASYNC);
+  AddSocketData(std::move(data));
+  TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount);
+  EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, HttpsPostLookupAsyncThenSync) {
+  ConfigDohServers(true /* clear_udp */, true /* use_post */);
+  std::unique_ptr<DnsSocketData> data(new DnsSocketData(
+      0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS));
+  data->AddResponseData(kT0ResponseDatagram, 20, ASYNC);
+  data->AddResponseData(kT0ResponseDatagram + 20,
+                        arraysize(kT0ResponseDatagram) - 20, SYNCHRONOUS);
+  AddSocketData(std::move(data));
+  TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount);
+  EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, HttpsPostLookupAsyncThenSyncError) {
+  ConfigDohServers(true /* clear_udp */, true /* use_post */);
+  std::unique_ptr<DnsSocketData> data(new DnsSocketData(
+      0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS));
+  data->AddResponseData(kT0ResponseDatagram, 20, ASYNC);
+  data->AddReadError(ERR_FAILED, SYNCHRONOUS);
+  AddSocketData(std::move(data));
+  TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_FAILED);
+  EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, HttpsPostLookupAsyncThenAsyncError) {
+  ConfigDohServers(true /* clear_udp */, true /* use_post */);
+  std::unique_ptr<DnsSocketData> data(new DnsSocketData(
+      0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS));
+  data->AddResponseData(kT0ResponseDatagram, 20, ASYNC);
+  data->AddReadError(ERR_FAILED, ASYNC);
+  AddSocketData(std::move(data));
+  TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_FAILED);
+  EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, HttpsPostLookupSyncThenAsyncError) {
+  ConfigDohServers(true /* clear_udp */, true /* use_post */);
+  std::unique_ptr<DnsSocketData> data(new DnsSocketData(
+      0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS));
+  data->AddResponseData(kT0ResponseDatagram, 20, SYNCHRONOUS);
+  data->AddReadError(ERR_FAILED, ASYNC);
+  AddSocketData(std::move(data));
+  TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_FAILED);
+  EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, HttpsPostLookupSyncThenSyncError) {
+  ConfigDohServers(true /* clear_udp */, true /* use_post */);
+  std::unique_ptr<DnsSocketData> data(new DnsSocketData(
+      0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS));
+  data->AddResponseData(kT0ResponseDatagram, 20, SYNCHRONOUS);
+  data->AddReadError(ERR_FAILED, SYNCHRONOUS);
+  AddSocketData(std::move(data));
+  TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_FAILED);
+  EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, HttpsPostFailThenUDPFallback) {
+  config_.attempts = 2;
+  ConfigDohServers(false /* clear_udp */, true /* use_post */);
+  AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
+                      arraysize(kT0ResponseDatagram), SYNCHRONOUS,
+                      Transport::HTTPS);
+  AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
+                      arraysize(kT0ResponseDatagram), ASYNC, Transport::UDP);
+  TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount);
+  SetDohJobMakerCallback(base::BindRepeating(DohJobMakerCallbackFailStart));
+  EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, HttpsPostFailThenUDPFailThenUDPFallback) {
+  ConfigureNumServers(3);
+  ConfigDohServers(false /* clear_udp */, true /* use_post */);
+  SetDohJobMakerCallback(base::BindRepeating(DohJobMakerCallbackFailStart));
+
+  AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
+                      arraysize(kT0ResponseDatagram), SYNCHRONOUS,
+                      Transport::HTTPS);
+  AddQueryAndTimeout(kT0HostName, kT0Qtype);
+  AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
+                      arraysize(kT0ResponseDatagram), ASYNC, Transport::UDP);
+
+  transaction_ids_.push_back(0);
+  TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount);
+  EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+
+  // Servers 3 (HTTP) and 0 (UDP) should be marked as bad. 1 and 2 should be
+  // good.
+  EXPECT_EQ(session_->NextGoodServerIndex(0), 1u);
+  EXPECT_EQ(session_->NextGoodServerIndex(1), 1u);
+  EXPECT_EQ(session_->NextGoodServerIndex(2), 2u);
+}
+
+TEST_F(DnsTransactionTest, HttpsMarkUdpBad) {
+  config_.attempts = 1;
+  ConfigureNumServers(2);
+  ConfigDohServers(false /* clear_udp */, true /* use_post */);
+  AddQueryAndErrorResponse(0, kT0HostName, kT0Qtype, ERR_CONNECTION_REFUSED,
+                           SYNCHRONOUS, Transport::HTTPS);
+  AddQueryAndErrorResponse(0, kT0HostName, kT0Qtype, ERR_CONNECTION_REFUSED,
+                           SYNCHRONOUS, Transport::UDP);
+  AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
+                      arraysize(kT0ResponseDatagram), ASYNC, Transport::UDP);
+
+  TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount);
+  EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+  // Server 0 (UDP) should be marked bad. Server 1 (UDP) should be good
+  // and since 2 is our only Doh server, it will be good.
+  EXPECT_EQ(session_->NextGoodServerIndex(0), 1u);
+  EXPECT_EQ(session_->NextGoodServerIndex(1), 1u);
+  EXPECT_EQ(session_->NextGoodDnsOverHttpsServerIndex(2), 2u);
+
+  AddQueryAndErrorResponse(1, kT1HostName, kT1Qtype, ERR_CONNECTION_REFUSED,
+                           SYNCHRONOUS, Transport::HTTPS);
+  AddQueryAndErrorResponse(1, kT1HostName, kT1Qtype, ERR_CONNECTION_REFUSED,
+                           SYNCHRONOUS, Transport::UDP);
+
+  AddQueryAndResponse(1, kT1HostName, kT1Qtype, kT1ResponseDatagram,
+                      arraysize(kT1ResponseDatagram), ASYNC, Transport::UDP);
+
+  TransactionHelper helper1(kT1HostName, kT1Qtype, kT1RecordCount);
+  EXPECT_TRUE(helper1.RunUntilDone(transaction_factory_.get()));
+  // Since 0 was bad to start, we started with 1 which will now be the
+  // most recent failure, so Server 1 (UDP) should be marked bad.
+  // Server 0 (UDP) should be good and since 2 is our only Doh server.
+  EXPECT_EQ(session_->NextGoodServerIndex(0), 0u);
+  EXPECT_EQ(session_->NextGoodServerIndex(1), 0u);
+  EXPECT_EQ(session_->NextGoodDnsOverHttpsServerIndex(2), 2u);
+}
+
+TEST_F(DnsTransactionTest, HttpsMarkHttpsBad) {
+  config_.attempts = 1;
+  ConfigDohServers(false /* clear_udp */, true /* use_post */, 3);
+  AddQueryAndErrorResponse(0, kT0HostName, kT0Qtype, ERR_CONNECTION_REFUSED,
+                           SYNCHRONOUS, Transport::HTTPS);
+  AddQueryAndErrorResponse(0, kT0HostName, kT0Qtype, ERR_CONNECTION_REFUSED,
+                           SYNCHRONOUS, Transport::HTTPS);
+  AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
+                      arraysize(kT0ResponseDatagram), ASYNC, Transport::HTTPS);
+  AddQueryAndErrorResponse(1, kT1HostName, kT1Qtype, ERR_CONNECTION_REFUSED,
+                           SYNCHRONOUS, Transport::HTTPS);
+  AddQueryAndErrorResponse(1, kT1HostName, kT1Qtype, ERR_CONNECTION_REFUSED,
+                           SYNCHRONOUS, Transport::HTTPS);
+
+  AddQueryAndResponse(1, kT1HostName, kT1Qtype, kT1ResponseDatagram,
+                      arraysize(kT1ResponseDatagram), ASYNC, Transport::HTTPS);
+  TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount);
+  TransactionHelper helper1(kT1HostName, kT1Qtype, kT1RecordCount);
+  EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+  // Server 0 is our only UDP server, so it will be good. HTTPS
+  // servers 1 and 2 failed and will be marked bad. Server 3 succeeded
+  // so will be good.
+  EXPECT_EQ(session_->NextGoodServerIndex(0), 0u);
+  EXPECT_EQ(session_->NextGoodDnsOverHttpsServerIndex(1), 3u);
+  EXPECT_EQ(session_->NextGoodDnsOverHttpsServerIndex(2), 3u);
+  EXPECT_EQ(session_->NextGoodDnsOverHttpsServerIndex(3), 3u);
+
+  EXPECT_TRUE(helper1.RunUntilDone(transaction_factory_.get()));
+  // Server 0 is still our only UDP server, so will be good by definition.
+  // Server 3 started out as good, so was tried first and failed. Server 1
+  // then had the oldest failure so would be the next good server and
+  // failed so is marked bad. Next attempt was server 2, which succeded so is
+  // good.
+  EXPECT_EQ(session_->NextGoodServerIndex(0), 0u);
+  EXPECT_EQ(session_->NextGoodDnsOverHttpsServerIndex(1), 2u);
+  EXPECT_EQ(session_->NextGoodDnsOverHttpsServerIndex(2), 2u);
+  EXPECT_EQ(session_->NextGoodDnsOverHttpsServerIndex(3), 2u);
+}
+
+TEST_F(DnsTransactionTest, HttpsPostFailThenHTTPFallback) {
+  ConfigDohServers(true /* clear_udp */, true /* use_post */, 2);
+  AddQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeSERVFAIL, ASYNC,
+                   Transport::HTTPS);
+  AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
+                      arraysize(kT0ResponseDatagram), SYNCHRONOUS,
+                      Transport::HTTPS);
+  TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount);
+  EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, HttpsPostFailTwiceThenUDPFallback) {
+  config_.attempts = 3;
+  ConfigDohServers(false /* clear_udp */, true /* use_post */, 2);
+  AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
+                      arraysize(kT0ResponseDatagram), SYNCHRONOUS,
+                      Transport::HTTPS);
+  AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
+                      arraysize(kT0ResponseDatagram), SYNCHRONOUS,
+                      Transport::HTTPS);
+  AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
+                      arraysize(kT0ResponseDatagram), ASYNC, Transport::UDP);
+  TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount);
+  SetDohJobMakerCallback(base::BindRepeating(DohJobMakerCallbackFailStart));
+  EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, HttpsPostFailTwice) {
+  config_.attempts = 2;
+  ConfigDohServers(true /* clear_udp */, true /* use_post */, 2);
+  AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
+                      arraysize(kT0ResponseDatagram), SYNCHRONOUS,
+                      Transport::HTTPS);
+  AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
+                      arraysize(kT0ResponseDatagram), SYNCHRONOUS,
+                      Transport::HTTPS);
+  TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_FAILED);
+  SetDohJobMakerCallback(base::BindRepeating(DohJobMakerCallbackFailStart));
+  EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+}
+
+void MakeResponseWithCookie(URLRequest* request, HttpResponseInfo* info) {
+  info->headers->AddHeader("Set-Cookie: test-cookie=you-fail");
+}
+
+class CookieCallback {
+ public:
+  CookieCallback()
+      : result_(false), loop_to_quit_(std::make_unique<base::RunLoop>()) {}
+
+  void SetCookieCallback(bool result) {
+    result_ = result;
+    loop_to_quit_->Quit();
+  }
+
+  void GetAllCookiesCallback(const net::CookieList& list) {
+    list_ = list;
+    loop_to_quit_->Quit();
+  }
+
+  void Reset() { loop_to_quit_.reset(new base::RunLoop()); }
+
+  void WaitUntilDone() { loop_to_quit_->Run(); }
+
+  size_t cookie_list_size() { return list_.size(); }
+
+ private:
+  net::CookieList list_;
+  bool result_;
+  std::unique_ptr<base::RunLoop> loop_to_quit_;
+  DISALLOW_COPY_AND_ASSIGN(CookieCallback);
+};
+
+TEST_F(DnsTransactionTest, HttpsPostTestNoCookies) {
+  ConfigDohServers(true /* clear_udp */, true /* use_post */);
+  AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
+                      arraysize(kT0ResponseDatagram), SYNCHRONOUS,
+                      Transport::HTTPS);
+  AddQueryAndResponse(1, kT1HostName, kT1Qtype, kT1ResponseDatagram,
+                      arraysize(kT1ResponseDatagram), SYNCHRONOUS,
+                      Transport::HTTPS);
+  TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount);
+  TransactionHelper helper1(kT1HostName, kT1Qtype, kT1RecordCount);
+  SetResponseModifierCallback(base::BindRepeating(MakeResponseWithCookie));
+  EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+
+  CookieCallback callback;
+  helper0.request_context()->cookie_store()->GetAllCookiesForURLAsync(
+      config_.dns_over_https_servers[0].server,
+      base::Bind(&CookieCallback::GetAllCookiesCallback,
+                 base::Unretained(&callback)));
+  callback.WaitUntilDone();
+  EXPECT_EQ(0u, callback.cookie_list_size());
+  callback.Reset();
+  net::CookieOptions options;
+  helper1.request_context()->cookie_store()->SetCookieWithOptionsAsync(
+      config_.dns_over_https_servers[0].server, "test-cookie=you-still-fail",
+      options,
+      base::Bind(&CookieCallback::SetCookieCallback,
+                 base::Unretained(&callback)));
+  EXPECT_TRUE(helper1.RunUntilDone(transaction_factory_.get()));
+}
+
+void MakeResponseWithoutLength(URLRequest* request, HttpResponseInfo* info) {
+  info->headers->RemoveHeader("Content-Length");
+}
+
+TEST_F(DnsTransactionTest, HttpsPostNoContentLength) {
+  ConfigDohServers(true /* clear_udp */, true /* use_post */);
+  AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
+                      arraysize(kT0ResponseDatagram), SYNCHRONOUS,
+                      Transport::HTTPS);
+  TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount);
+  SetResponseModifierCallback(base::BindRepeating(MakeResponseWithoutLength));
+  EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+}
+
+void MakeResponseWithBadRequestResponse(URLRequest* request,
+                                        HttpResponseInfo* info) {
+  info->headers->ReplaceStatusLine("HTTP/1.1 400 Bad Request");
+}
+
+TEST_F(DnsTransactionTest, HttpsPostWithBadRequestResponse) {
+  ConfigDohServers(true /* clear_udp */, true /* use_post */);
+  AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
+                      arraysize(kT0ResponseDatagram), SYNCHRONOUS,
+                      Transport::HTTPS);
+  TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_MALFORMED_RESPONSE);
+  SetResponseModifierCallback(
+      base::BindRepeating(MakeResponseWithBadRequestResponse));
+  EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+}
+
+void MakeResponseWrongType(URLRequest* request, HttpResponseInfo* info) {
+  info->headers->RemoveHeader("Content-Type");
+  info->headers->AddHeader("Content-Type: text/html");
+}
+
+TEST_F(DnsTransactionTest, HttpsPostWithWrongType) {
+  ConfigDohServers(true /* clear_udp */, true /* use_post */);
+  AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
+                      arraysize(kT0ResponseDatagram), SYNCHRONOUS,
+                      Transport::HTTPS);
+  TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_MALFORMED_RESPONSE);
+  SetResponseModifierCallback(base::BindRepeating(MakeResponseWrongType));
+  EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+}
+
+void MakeResponseRedirect(URLRequest* request, HttpResponseInfo* info) {
+  if (request->url_chain().size() < 2) {
+    info->headers->ReplaceStatusLine("HTTP/1.1 302 Found");
+    info->headers->AddHeader("Location: /redirect-destination?" +
+                             request->url().query());
+  }
+}
+
+TEST_F(DnsTransactionTest, HttpsGetRedirect) {
+  ConfigDohServers(true /* clear_udp */, false /* use_post */);
+  AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
+                      arraysize(kT0ResponseDatagram), SYNCHRONOUS,
+                      Transport::HTTPS);
+  AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
+                      arraysize(kT0ResponseDatagram), SYNCHRONOUS,
+                      Transport::HTTPS);
+  TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount);
+  SetResponseModifierCallback(base::BindRepeating(MakeResponseRedirect));
+  EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+}
+
+void MakeResponseNoType(URLRequest* request, HttpResponseInfo* info) {
+  info->headers->RemoveHeader("Content-Type");
+}
+
+TEST_F(DnsTransactionTest, HttpsPostWithNoType) {
+  ConfigDohServers(true /* clear_udp */, true /* use_post */);
+  AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
+                      arraysize(kT0ResponseDatagram), SYNCHRONOUS,
+                      Transport::HTTPS);
+  TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_MALFORMED_RESPONSE);
+  SetResponseModifierCallback(base::BindRepeating(MakeResponseNoType));
+  EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, HttpsCantLookupDohServers) {
+  ConfigDohServers(true /* clear_udp */, true /* use_post */, 2);
+  TransactionHelper helper0(kMockHostname, kT0Qtype, ERR_CONNECTION_REFUSED);
+  transaction_ids_.push_back(0);
+  transaction_ids_.push_back(1);
+  EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+}
+
 TEST_F(DnsTransactionTest, TCPLookup) {
   AddAsyncQueryAndRcode(kT0HostName, kT0Qtype,
                         dns_protocol::kRcodeNOERROR | dns_protocol::kFlagTC);
-  AddQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype,
-                      kT0ResponseDatagram, arraysize(kT0ResponseDatagram),
-                      ASYNC, true /* use_tcp */);
+  AddQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype, kT0ResponseDatagram,
+                      arraysize(kT0ResponseDatagram), ASYNC, Transport::TCP);
 
   TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount);
   EXPECT_TRUE(helper0.Run(transaction_factory_.get()));
@@ -952,8 +1831,8 @@
 TEST_F(DnsTransactionTest, TCPFailure) {
   AddAsyncQueryAndRcode(kT0HostName, kT0Qtype,
                         dns_protocol::kRcodeNOERROR | dns_protocol::kFlagTC);
-  AddQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeSERVFAIL,
-                   ASYNC, true /* use_tcp */);
+  AddQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeSERVFAIL, ASYNC,
+                   Transport::TCP);
 
   TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_SERVER_FAILED);
   EXPECT_TRUE(helper0.Run(transaction_factory_.get()));
@@ -962,8 +1841,8 @@
 TEST_F(DnsTransactionTest, TCPMalformed) {
   AddAsyncQueryAndRcode(kT0HostName, kT0Qtype,
                         dns_protocol::kRcodeNOERROR | dns_protocol::kFlagTC);
-  std::unique_ptr<DnsSocketData> data(
-      new DnsSocketData(0 /* id */, kT0HostName, kT0Qtype, ASYNC, true));
+  std::unique_ptr<DnsSocketData> data(new DnsSocketData(
+      0 /* id */, kT0HostName, kT0Qtype, ASYNC, Transport::TCP));
   // Valid response but length too short.
   // This must be truncated in the question section. The DnsResponse doesn't
   // examine the answer section until asked to parse it, so truncating it in
@@ -983,8 +1862,8 @@
   ConfigureFactory();
   AddAsyncQueryAndRcode(kT0HostName, kT0Qtype,
                         dns_protocol::kRcodeNOERROR | dns_protocol::kFlagTC);
-  AddSocketData(std::make_unique<DnsSocketData>(1 /* id */, kT0HostName,
-                                                kT0Qtype, ASYNC, true));
+  AddSocketData(std::make_unique<DnsSocketData>(
+      1 /* id */, kT0HostName, kT0Qtype, ASYNC, Transport::TCP));
 
   TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_TIMED_OUT);
   EXPECT_FALSE(helper0.Run(transaction_factory_.get()));
@@ -994,8 +1873,8 @@
 TEST_F(DnsTransactionTest, TCPReadReturnsZeroAsync) {
   AddAsyncQueryAndRcode(kT0HostName, kT0Qtype,
                         dns_protocol::kRcodeNOERROR | dns_protocol::kFlagTC);
-  std::unique_ptr<DnsSocketData> data(
-      new DnsSocketData(0 /* id */, kT0HostName, kT0Qtype, ASYNC, true));
+  std::unique_ptr<DnsSocketData> data(new DnsSocketData(
+      0 /* id */, kT0HostName, kT0Qtype, ASYNC, Transport::TCP));
   // Return all but the last byte of the response.
   data->AddResponseWithLength(
       std::make_unique<DnsResponse>(
@@ -1013,8 +1892,8 @@
 TEST_F(DnsTransactionTest, TCPReadReturnsZeroSynchronous) {
   AddAsyncQueryAndRcode(kT0HostName, kT0Qtype,
                         dns_protocol::kRcodeNOERROR | dns_protocol::kFlagTC);
-  std::unique_ptr<DnsSocketData> data(
-      new DnsSocketData(0 /* id */, kT0HostName, kT0Qtype, ASYNC, true));
+  std::unique_ptr<DnsSocketData> data(new DnsSocketData(
+      0 /* id */, kT0HostName, kT0Qtype, ASYNC, Transport::TCP));
   // Return all but the last byte of the response.
   data->AddResponseWithLength(
       std::make_unique<DnsResponse>(
@@ -1032,8 +1911,8 @@
 TEST_F(DnsTransactionTest, TCPConnectionClosedAsync) {
   AddAsyncQueryAndRcode(kT0HostName, kT0Qtype,
                         dns_protocol::kRcodeNOERROR | dns_protocol::kFlagTC);
-  std::unique_ptr<DnsSocketData> data(
-      new DnsSocketData(0 /* id */, kT0HostName, kT0Qtype, ASYNC, true));
+  std::unique_ptr<DnsSocketData> data(new DnsSocketData(
+      0 /* id */, kT0HostName, kT0Qtype, ASYNC, Transport::TCP));
   data->AddReadError(ERR_CONNECTION_CLOSED, ASYNC);
   AddSocketData(std::move(data));
 
@@ -1044,8 +1923,8 @@
 TEST_F(DnsTransactionTest, TCPConnectionClosedSynchronous) {
   AddAsyncQueryAndRcode(kT0HostName, kT0Qtype,
                         dns_protocol::kRcodeNOERROR | dns_protocol::kFlagTC);
-  std::unique_ptr<DnsSocketData> data(
-      new DnsSocketData(0 /* id */, kT0HostName, kT0Qtype, ASYNC, true));
+  std::unique_ptr<DnsSocketData> data(new DnsSocketData(
+      0 /* id */, kT0HostName, kT0Qtype, ASYNC, Transport::TCP));
   data->AddReadError(ERR_CONNECTION_CLOSED, SYNCHRONOUS);
   AddSocketData(std::move(data));
 
@@ -1056,8 +1935,8 @@
 TEST_F(DnsTransactionTest, MismatchedThenNxdomainThenTCP) {
   config_.attempts = 2;
   ConfigureFactory();
-  std::unique_ptr<DnsSocketData> data(
-      new DnsSocketData(0 /* id */, kT0HostName, kT0Qtype, SYNCHRONOUS, false));
+  std::unique_ptr<DnsSocketData> data(new DnsSocketData(
+      0 /* id */, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::UDP));
   // First attempt gets a mismatched response.
   data->AddResponseData(kT1ResponseDatagram, arraysize(kT1ResponseDatagram),
                         SYNCHRONOUS);
@@ -1066,8 +1945,8 @@
   AddSocketData(std::move(data));
   // Second attempt gets NXDOMAIN, which happens before the TCP required.
   AddSyncQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeNXDOMAIN);
-  std::unique_ptr<DnsSocketData> tcp_data(
-      new DnsSocketData(0 /* id */, kT0HostName, kT0Qtype, ASYNC, true));
+  std::unique_ptr<DnsSocketData> tcp_data(new DnsSocketData(
+      0 /* id */, kT0HostName, kT0Qtype, ASYNC, Transport::TCP));
   tcp_data->AddReadError(ERR_CONNECTION_CLOSED, SYNCHRONOUS);
   AddSocketData(std::move(tcp_data));
 
@@ -1078,8 +1957,8 @@
 TEST_F(DnsTransactionTest, MismatchedThenOkThenTCP) {
   config_.attempts = 2;
   ConfigureFactory();
-  std::unique_ptr<DnsSocketData> data(
-      new DnsSocketData(0 /* id */, kT0HostName, kT0Qtype, SYNCHRONOUS, false));
+  std::unique_ptr<DnsSocketData> data(new DnsSocketData(
+      0 /* id */, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::UDP));
   // First attempt gets a mismatched response.
   data->AddResponseData(kT1ResponseDatagram, arraysize(kT1ResponseDatagram),
                         SYNCHRONOUS);
@@ -1090,8 +1969,8 @@
   // required.
   AddSyncQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype,
                           kT0ResponseDatagram, arraysize(kT0ResponseDatagram));
-  std::unique_ptr<DnsSocketData> tcp_data(
-      new DnsSocketData(0 /* id */, kT0HostName, kT0Qtype, ASYNC, true));
+  std::unique_ptr<DnsSocketData> tcp_data(new DnsSocketData(
+      0 /* id */, kT0HostName, kT0Qtype, ASYNC, Transport::TCP));
   tcp_data->AddReadError(ERR_CONNECTION_CLOSED, SYNCHRONOUS);
   AddSocketData(std::move(tcp_data));
 
diff --git a/net/dns/host_resolver.h b/net/dns/host_resolver.h
index 87cf01d..7876d87 100644
--- a/net/dns/host_resolver.h
+++ b/net/dns/host_resolver.h
@@ -11,11 +11,9 @@
 #include <memory>
 #include <string>
 
-#include "base/macros.h"
 #include "net/base/address_family.h"
 #include "net/base/completion_callback.h"
 #include "net/base/host_port_pair.h"
-#include "net/base/net_export.h"
 #include "net/base/prioritized_dispatcher.h"
 #include "net/base/request_priority.h"
 #include "net/dns/host_cache.h"
@@ -28,9 +26,9 @@
 
 class AddressList;
 class HostResolverImpl;
-class HostResolverProc;
 class NetLog;
 class NetLogWithSource;
+class URLRequestContext;
 
 // This class represents the task of resolving hostnames (or IP address
 // literal) to an AddressList object.
@@ -225,6 +223,10 @@
   virtual void SetNoIPv6OnWifi(bool no_ipv6_on_wifi);
   virtual bool GetNoIPv6OnWifi();
 
+  virtual void SetRequestContext(URLRequestContext* request_context) {}
+  virtual void AddDnsOverHttpsServer(std::string spec, bool use_post) {}
+  virtual void ClearDnsOverHttpsServers() {}
+
   // Creates a HostResolver implementation that queries the underlying system.
   // (Except if a unit-test has changed the global HostResolverProc using
   // ScopedHostResolverProc to intercept requests to the system).
diff --git a/net/dns/host_resolver_impl.cc b/net/dns/host_resolver_impl.cc
index 907a21f..e5a3bfa 100644
--- a/net/dns/host_resolver_impl.cc
+++ b/net/dns/host_resolver_impl.cc
@@ -972,6 +972,9 @@
     // only needs to run one transaction.
     virtual void OnFirstDnsTransactionComplete() = 0;
 
+    virtual URLRequestContext* url_request_context() = 0;
+    virtual RequestPriority priority() const = 0;
+
    protected:
     Delegate() = default;
     virtual ~Delegate() = default;
@@ -1031,13 +1034,17 @@
 
   std::unique_ptr<DnsTransaction> CreateTransaction(AddressFamily family) {
     DCHECK_NE(ADDRESS_FAMILY_UNSPECIFIED, family);
-    return client_->GetTransactionFactory()->CreateTransaction(
-        key_.hostname,
-        family == ADDRESS_FAMILY_IPV6 ? dns_protocol::kTypeAAAA :
-                                        dns_protocol::kTypeA,
-        base::Bind(&DnsTask::OnTransactionComplete, base::Unretained(this),
-                   base::TimeTicks::Now()),
-        net_log_);
+    std::unique_ptr<DnsTransaction> trans =
+        client_->GetTransactionFactory()->CreateTransaction(
+            key_.hostname,
+            family == ADDRESS_FAMILY_IPV6 ? dns_protocol::kTypeAAAA
+                                          : dns_protocol::kTypeA,
+            base::Bind(&DnsTask::OnTransactionComplete, base::Unretained(this),
+                       base::TimeTicks::Now()),
+            net_log_);
+    trans->SetRequestContext(delegate_->url_request_context());
+    trans->SetRequestPriority(delegate_->priority());
+    return trans;
   }
 
   void OnTransactionComplete(const base::TimeTicks& start_time,
@@ -1636,6 +1643,10 @@
       dns_task_->StartSecondTransaction();
   }
 
+  URLRequestContext* url_request_context() override {
+    return resolver_->url_request_context_;
+  }
+
   void RecordJobHistograms(int error) {
     // Used in UMA_HISTOGRAM_ENUMERATION. Do not renumber entries or reuse
     // deprecated values.
@@ -1795,7 +1806,7 @@
                      base::TimeDelta());
   }
 
-  RequestPriority priority() const {
+  RequestPriority priority() const override {
     return priority_tracker_.highest_priority();
   }
 
@@ -2167,6 +2178,33 @@
   return assume_ipv6_failure_on_wifi_;
 }
 
+void HostResolverImpl::SetRequestContext(URLRequestContext* context) {
+  if (context != url_request_context_) {
+    url_request_context_ = context;
+  }
+}
+
+void HostResolverImpl::ClearDnsOverHttpsServers() {
+  if (dns_over_https_servers_.size() == 0)
+    return;
+
+  dns_over_https_servers_.clear();
+
+  if (dns_client_.get() && dns_client_->GetConfig())
+    UpdateDNSConfig(true);
+}
+
+void HostResolverImpl::AddDnsOverHttpsServer(std::string spec, bool use_post) {
+  GURL url(spec);
+  if (!url.SchemeIs("https"))
+    return;
+
+  dns_over_https_servers_.emplace_back(url, use_post);
+
+  if (dns_client_.get() && dns_client_->GetConfig())
+    UpdateDNSConfig(true);
+}
+
 bool HostResolverImpl::ResolveAsIP(const Key& key,
                                    const RequestInfo& info,
                                    const IPAddress* ip_address,
@@ -2521,6 +2559,7 @@
     // wasn't already a DnsConfig or it's the same one.
     DCHECK(config_changed || !dns_client_->GetConfig() ||
            dns_client_->GetConfig()->Equals(dns_config));
+    dns_config.dns_over_https_servers = dns_over_https_servers_;
     dns_client_->SetConfig(dns_config);
     if (dns_client_->GetConfig())
       UMA_HISTOGRAM_BOOLEAN("AsyncDNS.DnsClientEnabled", true);
@@ -2586,6 +2625,7 @@
       num_dns_failures_ < kMaximumDnsFailures) {
     DnsConfig dns_config;
     NetworkChangeNotifier::GetDnsConfig(&dns_config);
+    dns_config.dns_over_https_servers = dns_over_https_servers_;
     dns_client_->SetConfig(dns_config);
     num_dns_failures_ = 0;
     if (dns_client_->GetConfig())
diff --git a/net/dns/host_resolver_impl.h b/net/dns/host_resolver_impl.h
index 58300e5..0eada32 100644
--- a/net/dns/host_resolver_impl.h
+++ b/net/dns/host_resolver_impl.h
@@ -11,17 +11,16 @@
 #include <map>
 #include <memory>
 
-#include "base/macros.h"
 #include "base/memory/weak_ptr.h"
-#include "base/strings/string_piece.h"
-#include "base/threading/thread_checker.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
-#include "net/base/net_export.h"
 #include "net/base/network_change_notifier.h"
+#include "net/dns/dns_config_service.h"
 #include "net/dns/host_cache.h"
 #include "net/dns/host_resolver.h"
 #include "net/dns/host_resolver_proc.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "url/gurl.h"
 
 namespace net {
 
@@ -167,6 +166,10 @@
   void SetNoIPv6OnWifi(bool no_ipv6_on_wifi) override;
   bool GetNoIPv6OnWifi() override;
 
+  void SetRequestContext(URLRequestContext* request_context) override;
+  void AddDnsOverHttpsServer(std::string server, bool use_post) override;
+  void ClearDnsOverHttpsServers() override;
+
   void set_proc_params_for_test(const ProcTaskParams& proc_params) {
     proc_params_ = proc_params;
   }
@@ -378,6 +381,9 @@
   PersistCallback persist_callback_;
   base::OneShotTimer persist_timer_;
 
+  URLRequestContext* url_request_context_;
+  std::vector<DnsConfig::DnsOverHttpsServerConfig> dns_over_https_servers_;
+
   THREAD_CHECKER(thread_checker_);
 
   base::WeakPtrFactory<HostResolverImpl> weak_ptr_factory_;
diff --git a/net/dns/host_resolver_impl_unittest.cc b/net/dns/host_resolver_impl_unittest.cc
index 7f557e8..cbf1dec 100644
--- a/net/dns/host_resolver_impl_unittest.cc
+++ b/net/dns/host_resolver_impl_unittest.cc
@@ -2746,4 +2746,154 @@
       ResolveLocalHostname("foo.localhoste", kLocalhostLookupPort, &addresses));
 }
 
+TEST_F(HostResolverImplDnsTest, AddDnsOverHttpsServerAfterConfig) {
+  resolver_ = nullptr;
+  test::ScopedMockNetworkChangeNotifier notifier;
+  CreateSerialResolver();  // To guarantee order of resolutions.
+  notifier.mock_network_change_notifier()->SetConnectionType(
+      NetworkChangeNotifier::CONNECTION_WIFI);
+  ChangeDnsConfig(CreateValidDnsConfig());
+
+  resolver_->SetDnsClientEnabled(true);
+  std::string spec("https://dns.example.com/");
+  resolver_->AddDnsOverHttpsServer(spec, true);
+  base::DictionaryValue* config;
+
+  auto value = resolver_->GetDnsConfigAsValue();
+  EXPECT_TRUE(value);
+  if (!value)
+    return;
+  value->GetAsDictionary(&config);
+  base::ListValue* doh_servers;
+  config->GetListWithoutPathExpansion("doh_servers", &doh_servers);
+  EXPECT_TRUE(doh_servers);
+  if (!doh_servers)
+    return;
+  EXPECT_EQ(doh_servers->GetSize(), 1u);
+  base::DictionaryValue* server_method;
+  EXPECT_TRUE(doh_servers->GetDictionary(0, &server_method));
+  bool use_post;
+  EXPECT_TRUE(server_method->GetBoolean("use_post", &use_post));
+  EXPECT_TRUE(use_post);
+  std::string server_spec;
+  EXPECT_TRUE(server_method->GetString("server", &server_spec));
+  EXPECT_EQ(server_spec, spec);
+}
+
+TEST_F(HostResolverImplDnsTest, AddDnsOverHttpsServerBeforeConfig) {
+  resolver_ = nullptr;
+  test::ScopedMockNetworkChangeNotifier notifier;
+  CreateSerialResolver();  // To guarantee order of resolutions.
+  resolver_->SetDnsClientEnabled(true);
+  std::string spec("https://dns.example.com/");
+  resolver_->AddDnsOverHttpsServer(spec, true);
+
+  notifier.mock_network_change_notifier()->SetConnectionType(
+      NetworkChangeNotifier::CONNECTION_WIFI);
+  ChangeDnsConfig(CreateValidDnsConfig());
+
+  base::DictionaryValue* config;
+  auto value = resolver_->GetDnsConfigAsValue();
+  EXPECT_TRUE(value);
+  if (!value)
+    return;
+  value->GetAsDictionary(&config);
+  base::ListValue* doh_servers;
+  config->GetListWithoutPathExpansion("doh_servers", &doh_servers);
+  EXPECT_TRUE(doh_servers);
+  if (!doh_servers)
+    return;
+  EXPECT_EQ(doh_servers->GetSize(), 1u);
+  base::DictionaryValue* server_method;
+  EXPECT_TRUE(doh_servers->GetDictionary(0, &server_method));
+  bool use_post;
+  EXPECT_TRUE(server_method->GetBoolean("use_post", &use_post));
+  EXPECT_TRUE(use_post);
+  std::string server_spec;
+  EXPECT_TRUE(server_method->GetString("server", &server_spec));
+  EXPECT_EQ(server_spec, spec);
+}
+
+TEST_F(HostResolverImplDnsTest, AddDnsOverHttpsServerBeforeClient) {
+  resolver_ = nullptr;
+  test::ScopedMockNetworkChangeNotifier notifier;
+  CreateSerialResolver();  // To guarantee order of resolutions.
+  std::string spec("https://dns.example.com/");
+  resolver_->AddDnsOverHttpsServer(spec, true);
+
+  notifier.mock_network_change_notifier()->SetConnectionType(
+      NetworkChangeNotifier::CONNECTION_WIFI);
+  ChangeDnsConfig(CreateValidDnsConfig());
+
+  resolver_->SetDnsClientEnabled(true);
+
+  base::DictionaryValue* config;
+  auto value = resolver_->GetDnsConfigAsValue();
+  EXPECT_TRUE(value);
+  if (!value)
+    return;
+  value->GetAsDictionary(&config);
+  base::ListValue* doh_servers;
+  config->GetListWithoutPathExpansion("doh_servers", &doh_servers);
+  EXPECT_TRUE(doh_servers);
+  if (!doh_servers)
+    return;
+  EXPECT_EQ(doh_servers->GetSize(), 1u);
+  base::DictionaryValue* server_method;
+  EXPECT_TRUE(doh_servers->GetDictionary(0, &server_method));
+  bool use_post;
+  EXPECT_TRUE(server_method->GetBoolean("use_post", &use_post));
+  EXPECT_TRUE(use_post);
+  std::string server_spec;
+  EXPECT_TRUE(server_method->GetString("server", &server_spec));
+  EXPECT_EQ(server_spec, spec);
+}
+
+TEST_F(HostResolverImplDnsTest, AddDnsOverHttpsServerAndThenRemove) {
+  resolver_ = nullptr;
+  test::ScopedMockNetworkChangeNotifier notifier;
+  CreateSerialResolver();  // To guarantee order of resolutions.
+  std::string spec("https://dns.example.com/");
+  resolver_->AddDnsOverHttpsServer(spec, true);
+
+  notifier.mock_network_change_notifier()->SetConnectionType(
+      NetworkChangeNotifier::CONNECTION_WIFI);
+  ChangeDnsConfig(CreateValidDnsConfig());
+
+  resolver_->SetDnsClientEnabled(true);
+
+  base::DictionaryValue* config;
+  auto value = resolver_->GetDnsConfigAsValue();
+  EXPECT_TRUE(value);
+  if (!value)
+    return;
+  value->GetAsDictionary(&config);
+  base::ListValue* doh_servers;
+  config->GetListWithoutPathExpansion("doh_servers", &doh_servers);
+  EXPECT_TRUE(doh_servers);
+  if (!doh_servers)
+    return;
+  EXPECT_EQ(doh_servers->GetSize(), 1u);
+  base::DictionaryValue* server_method;
+  EXPECT_TRUE(doh_servers->GetDictionary(0, &server_method));
+  bool use_post;
+  EXPECT_TRUE(server_method->GetBoolean("use_post", &use_post));
+  EXPECT_TRUE(use_post);
+  std::string server_spec;
+  EXPECT_TRUE(server_method->GetString("server", &server_spec));
+  EXPECT_EQ(server_spec, spec);
+
+  resolver_->ClearDnsOverHttpsServers();
+  value = resolver_->GetDnsConfigAsValue();
+  EXPECT_TRUE(value);
+  if (!value)
+    return;
+  value->GetAsDictionary(&config);
+  config->GetListWithoutPathExpansion("doh_servers", &doh_servers);
+  EXPECT_TRUE(doh_servers);
+  if (!doh_servers)
+    return;
+  EXPECT_EQ(doh_servers->GetSize(), 0u);
+}
+
 }  // namespace net
diff --git a/net/dns/mdns_client_impl.cc b/net/dns/mdns_client_impl.cc
index 04e2bb5..62028b0 100644
--- a/net/dns/mdns_client_impl.cc
+++ b/net/dns/mdns_client_impl.cc
@@ -81,7 +81,7 @@
       connection_->OnDatagramReceived(&response_, recv_addr_, rv);
 
     rv = socket_->RecvFrom(
-        response_.io_buffer(), response_.io_buffer()->size(), &recv_addr_,
+        response_.io_buffer(), response_.io_buffer_size(), &recv_addr_,
         base::Bind(&MDnsConnection::SocketHandler::OnDatagramReceived,
                    base::Unretained(this)));
   } while (rv > 0);
@@ -227,7 +227,7 @@
   // erroneous behavior in case a packet contains multiple exclusive
   // records with the same type and name.
   std::map<MDnsCache::Key, MDnsCache::UpdateType> update_keys;
-
+  DCHECK_GT(bytes_read, 0);
   if (!response->InitParseWithoutQuery(bytes_read)) {
     DVLOG(1) << "Could not understand an mDNS packet.";
     return;  // Message is unreadable.
diff --git a/net/test/url_request/url_request_mock_data_job.h b/net/test/url_request/url_request_mock_data_job.h
index 9799389..00646b5 100644
--- a/net/test/url_request/url_request_mock_data_job.h
+++ b/net/test/url_request/url_request_mock_data_job.h
@@ -57,9 +57,11 @@
                                          const std::string& data,
                                          int repeat_count);
 
+ protected:
+  ~URLRequestMockDataJob() override;
+
  private:
   void GetResponseInfoConst(HttpResponseInfo* info) const;
-  ~URLRequestMockDataJob() override;
 
   void StartAsync();
 
diff --git a/tools/traffic_annotation/summary/annotations.xml b/tools/traffic_annotation/summary/annotations.xml
index a8b6663..f8584b4 100644
--- a/tools/traffic_annotation/summary/annotations.xml
+++ b/tools/traffic_annotation/summary/annotations.xml
@@ -75,6 +75,7 @@
  <item id="devtools_network_resource" hash_code="129652775" type="0" content_hash_code="24059212" os_list="linux,windows" file_path="chrome/browser/devtools/devtools_ui_bindings.cc"/>
  <item id="dial_get_app_info" hash_code="15952025" type="0" content_hash_code="90542080" os_list="linux,windows" file_path="chrome/browser/media/router/discovery/dial/dial_app_info_fetcher.cc"/>
  <item id="dial_get_device_description" hash_code="50422598" type="0" content_hash_code="129827780" os_list="linux,windows" file_path="chrome/browser/media/router/discovery/dial/device_description_fetcher.cc"/>
+ <item id="dns_over_https" hash_code="79895226" type="0" content_hash_code="45123510" os_list="linux,windows" file_path="net/dns/dns_transaction.cc"/>
  <item id="dns_transaction" hash_code="79227717" type="0" content_hash_code="132206495" os_list="linux,windows" file_path="net/dns/dns_transaction.cc"/>
  <item id="dom_distiller" hash_code="3989826" type="0" content_hash_code="106153970" os_list="linux,windows" file_path="components/dom_distiller/core/distiller_url_fetcher.cc"/>
  <item id="domain_reliability_report_upload" hash_code="108804096" type="0" content_hash_code="35902036" os_list="linux,windows" file_path="components/domain_reliability/uploader.cc"/>