123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- // Copyright 2021 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "net/test/test_doh_server.h"
- #include <string.h>
- #include <memory>
- #include "base/base64url.h"
- #include "base/bind.h"
- #include "base/check.h"
- #include "base/logging.h"
- #include "base/memory/scoped_refptr.h"
- #include "base/strings/string_number_conversions.h"
- #include "base/strings/string_piece.h"
- #include "base/synchronization/lock.h"
- #include "net/base/io_buffer.h"
- #include "net/base/url_util.h"
- #include "net/dns/dns_query.h"
- #include "net/dns/dns_response.h"
- #include "net/dns/dns_test_util.h"
- #include "net/dns/dns_util.h"
- #include "net/dns/public/dns_protocol.h"
- #include "net/http/http_status_code.h"
- #include "net/test/embedded_test_server/embedded_test_server.h"
- #include "net/test/embedded_test_server/http_request.h"
- #include "net/test/embedded_test_server/http_response.h"
- #include "url/gurl.h"
- namespace net {
- namespace {
- const char kPath[] = "/dns-query";
- std::unique_ptr<test_server::HttpResponse> MakeHttpErrorResponse(
- HttpStatusCode status,
- base::StringPiece error) {
- auto response = std::make_unique<test_server::BasicHttpResponse>();
- response->set_code(status);
- response->set_content(std::string(error));
- response->set_content_type("text/plain;charset=utf-8");
- return response;
- }
- std::unique_ptr<test_server::HttpResponse> MakeHttpResponseFromDns(
- const DnsResponse& dns_response) {
- if (!dns_response.IsValid()) {
- return MakeHttpErrorResponse(HTTP_INTERNAL_SERVER_ERROR,
- "error making DNS response");
- }
- auto response = std::make_unique<test_server::BasicHttpResponse>();
- response->set_code(HTTP_OK);
- response->set_content(std::string(dns_response.io_buffer()->data(),
- dns_response.io_buffer_size()));
- response->set_content_type("application/dns-message");
- return response;
- }
- } // namespace
- TestDohServer::TestDohServer() {
- server_.RegisterRequestHandler(base::BindRepeating(
- &TestDohServer::HandleRequest, base::Unretained(this)));
- }
- TestDohServer::~TestDohServer() = default;
- void TestDohServer::SetHostname(base::StringPiece name) {
- DCHECK(!server_.Started());
- hostname_ = std::string(name);
- }
- void TestDohServer::SetFailRequests(bool fail_requests) {
- base::AutoLock lock(lock_);
- fail_requests_ = fail_requests;
- }
- void TestDohServer::AddAddressRecord(base::StringPiece name,
- const IPAddress& address,
- base::TimeDelta ttl) {
- AddRecord(BuildTestAddressRecord(std::string(name), address, ttl));
- }
- void TestDohServer::AddRecord(const DnsResourceRecord& record) {
- base::AutoLock lock(lock_);
- records_.insert(
- std::make_pair(std::make_pair(record.name, record.type), record));
- }
- bool TestDohServer::Start() {
- if (!InitializeAndListen()) {
- return false;
- }
- StartAcceptingConnections();
- return true;
- }
- bool TestDohServer::InitializeAndListen() {
- if (hostname_) {
- EmbeddedTestServer::ServerCertificateConfig cert_config;
- cert_config.dns_names = {*hostname_};
- server_.SetSSLConfig(cert_config);
- } else {
- // `CERT_OK` is valid for 127.0.0.1.
- server_.SetSSLConfig(EmbeddedTestServer::CERT_OK);
- }
- return server_.InitializeAndListen();
- }
- void TestDohServer::StartAcceptingConnections() {
- server_.StartAcceptingConnections();
- }
- bool TestDohServer::ShutdownAndWaitUntilComplete() {
- return server_.ShutdownAndWaitUntilComplete();
- }
- std::string TestDohServer::GetTemplate() {
- GURL url =
- hostname_ ? server_.GetURL(*hostname_, kPath) : server_.GetURL(kPath);
- return url.spec() + "{?dns}";
- }
- std::string TestDohServer::GetPostOnlyTemplate() {
- GURL url =
- hostname_ ? server_.GetURL(*hostname_, kPath) : server_.GetURL(kPath);
- return url.spec();
- }
- int TestDohServer::QueriesServed() {
- base::AutoLock lock(lock_);
- return queries_served_;
- }
- std::unique_ptr<test_server::HttpResponse> TestDohServer::HandleRequest(
- const test_server::HttpRequest& request) {
- GURL request_url = request.GetURL();
- if (request_url.path_piece() != kPath) {
- return nullptr;
- }
- base::AutoLock lock(lock_);
- queries_served_++;
- if (fail_requests_) {
- return MakeHttpErrorResponse(HTTP_NOT_FOUND, "failed request");
- }
- // See RFC 8484, Section 4.1.
- std::string query;
- if (request.method == test_server::METHOD_GET) {
- std::string query_b64;
- if (!GetValueForKeyInQuery(request_url, "dns", &query_b64) ||
- !base::Base64UrlDecode(
- query_b64, base::Base64UrlDecodePolicy::IGNORE_PADDING, &query)) {
- return MakeHttpErrorResponse(HTTP_BAD_REQUEST,
- "could not decode query string");
- }
- } else if (request.method == test_server::METHOD_POST) {
- auto content_type = request.headers.find("content-type");
- if (content_type == request.headers.end() ||
- content_type->second != "application/dns-message") {
- return MakeHttpErrorResponse(HTTP_BAD_REQUEST,
- "unsupported content type");
- }
- query = request.content;
- } else {
- return MakeHttpErrorResponse(HTTP_BAD_REQUEST, "invalid method");
- }
- // Parse the DNS query.
- auto query_buf = base::MakeRefCounted<IOBufferWithSize>(query.size());
- memcpy(query_buf->data(), query.data(), query.size());
- DnsQuery dns_query(std::move(query_buf));
- if (!dns_query.Parse(query.size())) {
- return MakeHttpErrorResponse(HTTP_BAD_REQUEST, "invalid DNS query");
- }
- absl::optional<std::string> name =
- DnsDomainToString(dns_query.qname(), /*require_complete=*/true);
- if (!name) {
- DnsResponse response(dns_query.id(), /*is_authoritative=*/false,
- /*answers=*/{}, /*authority_records=*/{},
- /*additional_records=*/{}, dns_query,
- dns_protocol::kRcodeFORMERR);
- return MakeHttpResponseFromDns(response);
- }
- auto range = records_.equal_range(std::make_pair(*name, dns_query.qtype()));
- std::vector<DnsResourceRecord> answers;
- for (auto i = range.first; i != range.second; ++i) {
- answers.push_back(i->second);
- }
- VLOG(1) << "Serving " << answers.size() << " records for " << *name
- << ", qtype " << dns_query.qtype();
- // Note `answers` may be empty. NOERROR with no answers is how to express
- // NODATA, so there is no need handle it specially.
- //
- // For now, this server does not support configuring additional records. When
- // testing more complex HTTPS record cases, this will need to be extended.
- //
- // TODO(crbug.com/1251204): Add SOA records to test the default TTL.
- DnsResponse response(dns_query.id(), /*is_authoritative=*/true,
- /*answers=*/answers, /*authority_records=*/{},
- /*additional_records=*/{}, dns_query);
- return MakeHttpResponseFromDns(response);
- }
- } // namespace net
|