1*6777b538SAndroid Build Coastguard Worker // Copyright 2021 The Chromium Authors
2*6777b538SAndroid Build Coastguard Worker // Use of this source code is governed by a BSD-style license that can be
3*6777b538SAndroid Build Coastguard Worker // found in the LICENSE file.
4*6777b538SAndroid Build Coastguard Worker
5*6777b538SAndroid Build Coastguard Worker #include "net/test/test_doh_server.h"
6*6777b538SAndroid Build Coastguard Worker
7*6777b538SAndroid Build Coastguard Worker #include <string.h>
8*6777b538SAndroid Build Coastguard Worker
9*6777b538SAndroid Build Coastguard Worker #include <memory>
10*6777b538SAndroid Build Coastguard Worker #include <string_view>
11*6777b538SAndroid Build Coastguard Worker
12*6777b538SAndroid Build Coastguard Worker #include "base/base64url.h"
13*6777b538SAndroid Build Coastguard Worker #include "base/check.h"
14*6777b538SAndroid Build Coastguard Worker #include "base/functional/bind.h"
15*6777b538SAndroid Build Coastguard Worker #include "base/logging.h"
16*6777b538SAndroid Build Coastguard Worker #include "base/memory/scoped_refptr.h"
17*6777b538SAndroid Build Coastguard Worker #include "base/ranges/algorithm.h"
18*6777b538SAndroid Build Coastguard Worker #include "base/strings/string_number_conversions.h"
19*6777b538SAndroid Build Coastguard Worker #include "base/synchronization/lock.h"
20*6777b538SAndroid Build Coastguard Worker #include "base/time/time.h"
21*6777b538SAndroid Build Coastguard Worker #include "net/base/io_buffer.h"
22*6777b538SAndroid Build Coastguard Worker #include "net/base/url_util.h"
23*6777b538SAndroid Build Coastguard Worker #include "net/dns/dns_names_util.h"
24*6777b538SAndroid Build Coastguard Worker #include "net/dns/dns_query.h"
25*6777b538SAndroid Build Coastguard Worker #include "net/dns/dns_response.h"
26*6777b538SAndroid Build Coastguard Worker #include "net/dns/dns_test_util.h"
27*6777b538SAndroid Build Coastguard Worker #include "net/dns/public/dns_protocol.h"
28*6777b538SAndroid Build Coastguard Worker #include "net/http/http_status_code.h"
29*6777b538SAndroid Build Coastguard Worker #include "net/test/embedded_test_server/embedded_test_server.h"
30*6777b538SAndroid Build Coastguard Worker #include "net/test/embedded_test_server/http_request.h"
31*6777b538SAndroid Build Coastguard Worker #include "net/test/embedded_test_server/http_response.h"
32*6777b538SAndroid Build Coastguard Worker #include "url/gurl.h"
33*6777b538SAndroid Build Coastguard Worker
34*6777b538SAndroid Build Coastguard Worker namespace net {
35*6777b538SAndroid Build Coastguard Worker
36*6777b538SAndroid Build Coastguard Worker namespace {
37*6777b538SAndroid Build Coastguard Worker
38*6777b538SAndroid Build Coastguard Worker const char kPath[] = "/dns-query";
39*6777b538SAndroid Build Coastguard Worker
MakeHttpErrorResponse(HttpStatusCode status,std::string_view error)40*6777b538SAndroid Build Coastguard Worker std::unique_ptr<test_server::HttpResponse> MakeHttpErrorResponse(
41*6777b538SAndroid Build Coastguard Worker HttpStatusCode status,
42*6777b538SAndroid Build Coastguard Worker std::string_view error) {
43*6777b538SAndroid Build Coastguard Worker auto response = std::make_unique<test_server::BasicHttpResponse>();
44*6777b538SAndroid Build Coastguard Worker response->set_code(status);
45*6777b538SAndroid Build Coastguard Worker response->set_content(std::string(error));
46*6777b538SAndroid Build Coastguard Worker response->set_content_type("text/plain;charset=utf-8");
47*6777b538SAndroid Build Coastguard Worker return response;
48*6777b538SAndroid Build Coastguard Worker }
49*6777b538SAndroid Build Coastguard Worker
MakeHttpResponseFromDns(const DnsResponse & dns_response)50*6777b538SAndroid Build Coastguard Worker std::unique_ptr<test_server::HttpResponse> MakeHttpResponseFromDns(
51*6777b538SAndroid Build Coastguard Worker const DnsResponse& dns_response) {
52*6777b538SAndroid Build Coastguard Worker if (!dns_response.IsValid()) {
53*6777b538SAndroid Build Coastguard Worker return MakeHttpErrorResponse(HTTP_INTERNAL_SERVER_ERROR,
54*6777b538SAndroid Build Coastguard Worker "error making DNS response");
55*6777b538SAndroid Build Coastguard Worker }
56*6777b538SAndroid Build Coastguard Worker
57*6777b538SAndroid Build Coastguard Worker auto response = std::make_unique<test_server::BasicHttpResponse>();
58*6777b538SAndroid Build Coastguard Worker response->set_code(HTTP_OK);
59*6777b538SAndroid Build Coastguard Worker response->set_content(std::string(dns_response.io_buffer()->data(),
60*6777b538SAndroid Build Coastguard Worker dns_response.io_buffer_size()));
61*6777b538SAndroid Build Coastguard Worker response->set_content_type("application/dns-message");
62*6777b538SAndroid Build Coastguard Worker return response;
63*6777b538SAndroid Build Coastguard Worker }
64*6777b538SAndroid Build Coastguard Worker
65*6777b538SAndroid Build Coastguard Worker } // namespace
66*6777b538SAndroid Build Coastguard Worker
TestDohServer()67*6777b538SAndroid Build Coastguard Worker TestDohServer::TestDohServer() {
68*6777b538SAndroid Build Coastguard Worker server_.RegisterRequestHandler(base::BindRepeating(
69*6777b538SAndroid Build Coastguard Worker &TestDohServer::HandleRequest, base::Unretained(this)));
70*6777b538SAndroid Build Coastguard Worker }
71*6777b538SAndroid Build Coastguard Worker
72*6777b538SAndroid Build Coastguard Worker TestDohServer::~TestDohServer() = default;
73*6777b538SAndroid Build Coastguard Worker
SetHostname(std::string_view name)74*6777b538SAndroid Build Coastguard Worker void TestDohServer::SetHostname(std::string_view name) {
75*6777b538SAndroid Build Coastguard Worker DCHECK(!server_.Started());
76*6777b538SAndroid Build Coastguard Worker hostname_ = std::string(name);
77*6777b538SAndroid Build Coastguard Worker }
78*6777b538SAndroid Build Coastguard Worker
SetFailRequests(bool fail_requests)79*6777b538SAndroid Build Coastguard Worker void TestDohServer::SetFailRequests(bool fail_requests) {
80*6777b538SAndroid Build Coastguard Worker base::AutoLock lock(lock_);
81*6777b538SAndroid Build Coastguard Worker fail_requests_ = fail_requests;
82*6777b538SAndroid Build Coastguard Worker }
83*6777b538SAndroid Build Coastguard Worker
AddAddressRecord(std::string_view name,const IPAddress & address,base::TimeDelta ttl)84*6777b538SAndroid Build Coastguard Worker void TestDohServer::AddAddressRecord(std::string_view name,
85*6777b538SAndroid Build Coastguard Worker const IPAddress& address,
86*6777b538SAndroid Build Coastguard Worker base::TimeDelta ttl) {
87*6777b538SAndroid Build Coastguard Worker AddRecord(BuildTestAddressRecord(std::string(name), address, ttl));
88*6777b538SAndroid Build Coastguard Worker }
89*6777b538SAndroid Build Coastguard Worker
AddRecord(const DnsResourceRecord & record)90*6777b538SAndroid Build Coastguard Worker void TestDohServer::AddRecord(const DnsResourceRecord& record) {
91*6777b538SAndroid Build Coastguard Worker base::AutoLock lock(lock_);
92*6777b538SAndroid Build Coastguard Worker records_.emplace(std::pair(record.name, record.type), record);
93*6777b538SAndroid Build Coastguard Worker }
94*6777b538SAndroid Build Coastguard Worker
Start()95*6777b538SAndroid Build Coastguard Worker bool TestDohServer::Start() {
96*6777b538SAndroid Build Coastguard Worker if (!InitializeAndListen()) {
97*6777b538SAndroid Build Coastguard Worker return false;
98*6777b538SAndroid Build Coastguard Worker }
99*6777b538SAndroid Build Coastguard Worker StartAcceptingConnections();
100*6777b538SAndroid Build Coastguard Worker return true;
101*6777b538SAndroid Build Coastguard Worker }
102*6777b538SAndroid Build Coastguard Worker
InitializeAndListen()103*6777b538SAndroid Build Coastguard Worker bool TestDohServer::InitializeAndListen() {
104*6777b538SAndroid Build Coastguard Worker if (hostname_) {
105*6777b538SAndroid Build Coastguard Worker EmbeddedTestServer::ServerCertificateConfig cert_config;
106*6777b538SAndroid Build Coastguard Worker cert_config.dns_names = {*hostname_};
107*6777b538SAndroid Build Coastguard Worker server_.SetSSLConfig(cert_config);
108*6777b538SAndroid Build Coastguard Worker } else {
109*6777b538SAndroid Build Coastguard Worker // `CERT_OK` is valid for 127.0.0.1.
110*6777b538SAndroid Build Coastguard Worker server_.SetSSLConfig(EmbeddedTestServer::CERT_OK);
111*6777b538SAndroid Build Coastguard Worker }
112*6777b538SAndroid Build Coastguard Worker return server_.InitializeAndListen();
113*6777b538SAndroid Build Coastguard Worker }
114*6777b538SAndroid Build Coastguard Worker
StartAcceptingConnections()115*6777b538SAndroid Build Coastguard Worker void TestDohServer::StartAcceptingConnections() {
116*6777b538SAndroid Build Coastguard Worker server_.StartAcceptingConnections();
117*6777b538SAndroid Build Coastguard Worker }
118*6777b538SAndroid Build Coastguard Worker
ShutdownAndWaitUntilComplete()119*6777b538SAndroid Build Coastguard Worker bool TestDohServer::ShutdownAndWaitUntilComplete() {
120*6777b538SAndroid Build Coastguard Worker return server_.ShutdownAndWaitUntilComplete();
121*6777b538SAndroid Build Coastguard Worker }
122*6777b538SAndroid Build Coastguard Worker
GetTemplate()123*6777b538SAndroid Build Coastguard Worker std::string TestDohServer::GetTemplate() {
124*6777b538SAndroid Build Coastguard Worker GURL url =
125*6777b538SAndroid Build Coastguard Worker hostname_ ? server_.GetURL(*hostname_, kPath) : server_.GetURL(kPath);
126*6777b538SAndroid Build Coastguard Worker return url.spec() + "{?dns}";
127*6777b538SAndroid Build Coastguard Worker }
128*6777b538SAndroid Build Coastguard Worker
GetPostOnlyTemplate()129*6777b538SAndroid Build Coastguard Worker std::string TestDohServer::GetPostOnlyTemplate() {
130*6777b538SAndroid Build Coastguard Worker GURL url =
131*6777b538SAndroid Build Coastguard Worker hostname_ ? server_.GetURL(*hostname_, kPath) : server_.GetURL(kPath);
132*6777b538SAndroid Build Coastguard Worker return url.spec();
133*6777b538SAndroid Build Coastguard Worker }
134*6777b538SAndroid Build Coastguard Worker
QueriesServed()135*6777b538SAndroid Build Coastguard Worker int TestDohServer::QueriesServed() {
136*6777b538SAndroid Build Coastguard Worker base::AutoLock lock(lock_);
137*6777b538SAndroid Build Coastguard Worker return queries_served_;
138*6777b538SAndroid Build Coastguard Worker }
139*6777b538SAndroid Build Coastguard Worker
QueriesServedForSubdomains(std::string_view domain)140*6777b538SAndroid Build Coastguard Worker int TestDohServer::QueriesServedForSubdomains(std::string_view domain) {
141*6777b538SAndroid Build Coastguard Worker CHECK(net::dns_names_util::IsValidDnsName(domain));
142*6777b538SAndroid Build Coastguard Worker auto is_subdomain = [&domain](std::string_view candidate) {
143*6777b538SAndroid Build Coastguard Worker return net::IsSubdomainOf(candidate, domain);
144*6777b538SAndroid Build Coastguard Worker };
145*6777b538SAndroid Build Coastguard Worker base::AutoLock lock(lock_);
146*6777b538SAndroid Build Coastguard Worker return base::ranges::count_if(query_qnames_, is_subdomain);
147*6777b538SAndroid Build Coastguard Worker }
148*6777b538SAndroid Build Coastguard Worker
HandleRequest(const test_server::HttpRequest & request)149*6777b538SAndroid Build Coastguard Worker std::unique_ptr<test_server::HttpResponse> TestDohServer::HandleRequest(
150*6777b538SAndroid Build Coastguard Worker const test_server::HttpRequest& request) {
151*6777b538SAndroid Build Coastguard Worker GURL request_url = request.GetURL();
152*6777b538SAndroid Build Coastguard Worker if (request_url.path_piece() != kPath) {
153*6777b538SAndroid Build Coastguard Worker return nullptr;
154*6777b538SAndroid Build Coastguard Worker }
155*6777b538SAndroid Build Coastguard Worker
156*6777b538SAndroid Build Coastguard Worker base::AutoLock lock(lock_);
157*6777b538SAndroid Build Coastguard Worker queries_served_++;
158*6777b538SAndroid Build Coastguard Worker
159*6777b538SAndroid Build Coastguard Worker if (fail_requests_) {
160*6777b538SAndroid Build Coastguard Worker return MakeHttpErrorResponse(HTTP_NOT_FOUND, "failed request");
161*6777b538SAndroid Build Coastguard Worker }
162*6777b538SAndroid Build Coastguard Worker
163*6777b538SAndroid Build Coastguard Worker // See RFC 8484, Section 4.1.
164*6777b538SAndroid Build Coastguard Worker std::string query;
165*6777b538SAndroid Build Coastguard Worker if (request.method == test_server::METHOD_GET) {
166*6777b538SAndroid Build Coastguard Worker std::string query_b64;
167*6777b538SAndroid Build Coastguard Worker if (!GetValueForKeyInQuery(request_url, "dns", &query_b64) ||
168*6777b538SAndroid Build Coastguard Worker !base::Base64UrlDecode(
169*6777b538SAndroid Build Coastguard Worker query_b64, base::Base64UrlDecodePolicy::IGNORE_PADDING, &query)) {
170*6777b538SAndroid Build Coastguard Worker return MakeHttpErrorResponse(HTTP_BAD_REQUEST,
171*6777b538SAndroid Build Coastguard Worker "could not decode query string");
172*6777b538SAndroid Build Coastguard Worker }
173*6777b538SAndroid Build Coastguard Worker } else if (request.method == test_server::METHOD_POST) {
174*6777b538SAndroid Build Coastguard Worker auto content_type = request.headers.find("content-type");
175*6777b538SAndroid Build Coastguard Worker if (content_type == request.headers.end() ||
176*6777b538SAndroid Build Coastguard Worker content_type->second != "application/dns-message") {
177*6777b538SAndroid Build Coastguard Worker return MakeHttpErrorResponse(HTTP_BAD_REQUEST,
178*6777b538SAndroid Build Coastguard Worker "unsupported content type");
179*6777b538SAndroid Build Coastguard Worker }
180*6777b538SAndroid Build Coastguard Worker query = request.content;
181*6777b538SAndroid Build Coastguard Worker } else {
182*6777b538SAndroid Build Coastguard Worker return MakeHttpErrorResponse(HTTP_BAD_REQUEST, "invalid method");
183*6777b538SAndroid Build Coastguard Worker }
184*6777b538SAndroid Build Coastguard Worker
185*6777b538SAndroid Build Coastguard Worker // Parse the DNS query.
186*6777b538SAndroid Build Coastguard Worker auto query_buf = base::MakeRefCounted<IOBufferWithSize>(query.size());
187*6777b538SAndroid Build Coastguard Worker memcpy(query_buf->data(), query.data(), query.size());
188*6777b538SAndroid Build Coastguard Worker DnsQuery dns_query(std::move(query_buf));
189*6777b538SAndroid Build Coastguard Worker if (!dns_query.Parse(query.size())) {
190*6777b538SAndroid Build Coastguard Worker return MakeHttpErrorResponse(HTTP_BAD_REQUEST, "invalid DNS query");
191*6777b538SAndroid Build Coastguard Worker }
192*6777b538SAndroid Build Coastguard Worker
193*6777b538SAndroid Build Coastguard Worker std::optional<std::string> name = dns_names_util::NetworkToDottedName(
194*6777b538SAndroid Build Coastguard Worker dns_query.qname(), /*require_complete=*/true);
195*6777b538SAndroid Build Coastguard Worker if (!name) {
196*6777b538SAndroid Build Coastguard Worker DnsResponse response(dns_query.id(), /*is_authoritative=*/false,
197*6777b538SAndroid Build Coastguard Worker /*answers=*/{}, /*authority_records=*/{},
198*6777b538SAndroid Build Coastguard Worker /*additional_records=*/{}, dns_query,
199*6777b538SAndroid Build Coastguard Worker dns_protocol::kRcodeFORMERR);
200*6777b538SAndroid Build Coastguard Worker return MakeHttpResponseFromDns(response);
201*6777b538SAndroid Build Coastguard Worker }
202*6777b538SAndroid Build Coastguard Worker query_qnames_.push_back(*name);
203*6777b538SAndroid Build Coastguard Worker
204*6777b538SAndroid Build Coastguard Worker auto range = records_.equal_range(std::pair(*name, dns_query.qtype()));
205*6777b538SAndroid Build Coastguard Worker std::vector<DnsResourceRecord> answers;
206*6777b538SAndroid Build Coastguard Worker for (auto i = range.first; i != range.second; ++i) {
207*6777b538SAndroid Build Coastguard Worker answers.push_back(i->second);
208*6777b538SAndroid Build Coastguard Worker }
209*6777b538SAndroid Build Coastguard Worker
210*6777b538SAndroid Build Coastguard Worker LOG(INFO) << "Serving " << answers.size() << " records for " << *name
211*6777b538SAndroid Build Coastguard Worker << ", qtype " << dns_query.qtype();
212*6777b538SAndroid Build Coastguard Worker
213*6777b538SAndroid Build Coastguard Worker // Note `answers` may be empty. NOERROR with no answers is how to express
214*6777b538SAndroid Build Coastguard Worker // NODATA, so there is no need handle it specially.
215*6777b538SAndroid Build Coastguard Worker //
216*6777b538SAndroid Build Coastguard Worker // For now, this server does not support configuring additional records. When
217*6777b538SAndroid Build Coastguard Worker // testing more complex HTTPS record cases, this will need to be extended.
218*6777b538SAndroid Build Coastguard Worker //
219*6777b538SAndroid Build Coastguard Worker // TODO(crbug.com/1251204): Add SOA records to test the default TTL.
220*6777b538SAndroid Build Coastguard Worker DnsResponse response(dns_query.id(), /*is_authoritative=*/true,
221*6777b538SAndroid Build Coastguard Worker /*answers=*/answers, /*authority_records=*/{},
222*6777b538SAndroid Build Coastguard Worker /*additional_records=*/{}, dns_query);
223*6777b538SAndroid Build Coastguard Worker return MakeHttpResponseFromDns(response);
224*6777b538SAndroid Build Coastguard Worker }
225*6777b538SAndroid Build Coastguard Worker
226*6777b538SAndroid Build Coastguard Worker } // namespace net
227