1 // Copyright 2018 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/dns/host_resolver_mdns_task.h"
6
7 #include <utility>
8
9 #include "base/check_op.h"
10 #include "base/functional/bind.h"
11 #include "base/location.h"
12 #include "base/memory/raw_ptr.h"
13 #include "base/notreached.h"
14 #include "base/ranges/algorithm.h"
15 #include "base/strings/string_util.h"
16 #include "base/task/sequenced_task_runner.h"
17 #include "net/base/ip_endpoint.h"
18 #include "net/base/net_errors.h"
19 #include "net/dns/dns_util.h"
20 #include "net/dns/public/dns_protocol.h"
21 #include "net/dns/public/dns_query_type.h"
22 #include "net/dns/record_parsed.h"
23 #include "net/dns/record_rdata.h"
24
25 namespace net {
26
27 namespace {
ParseHostnameResult(const std::string & host,uint16_t port)28 HostCache::Entry ParseHostnameResult(const std::string& host, uint16_t port) {
29 // Filter out root domain. Depending on the type, it either means no-result
30 // or is simply not a result important to any expected Chrome usecases.
31 if (host.empty()) {
32 return HostCache::Entry(ERR_NAME_NOT_RESOLVED,
33 HostCache::Entry::SOURCE_UNKNOWN);
34 }
35 return HostCache::Entry(OK,
36 std::vector<HostPortPair>({HostPortPair(host, port)}),
37 HostCache::Entry::SOURCE_UNKNOWN);
38 }
39 } // namespace
40
41 class HostResolverMdnsTask::Transaction {
42 public:
Transaction(DnsQueryType query_type,HostResolverMdnsTask * task)43 Transaction(DnsQueryType query_type, HostResolverMdnsTask* task)
44 : query_type_(query_type),
45 results_(ERR_IO_PENDING, HostCache::Entry::SOURCE_UNKNOWN),
46 task_(task) {}
47
Start()48 void Start() {
49 DCHECK_CALLED_ON_VALID_SEQUENCE(task_->sequence_checker_);
50
51 // Should not be completed or running yet.
52 DCHECK_EQ(ERR_IO_PENDING, results_.error());
53 DCHECK(!async_transaction_);
54
55 // TODO(crbug.com/926300): Use |allow_cached_response| to set the
56 // QUERY_CACHE flag or not.
57 int flags = MDnsTransaction::SINGLE_RESULT | MDnsTransaction::QUERY_CACHE |
58 MDnsTransaction::QUERY_NETWORK;
59 // If |this| is destroyed, destruction of |internal_transaction_| should
60 // cancel and prevent invocation of OnComplete.
61 std::unique_ptr<MDnsTransaction> inner_transaction =
62 task_->mdns_client_->CreateTransaction(
63 DnsQueryTypeToQtype(query_type_), task_->hostname_, flags,
64 base::BindRepeating(&HostResolverMdnsTask::Transaction::OnComplete,
65 base::Unretained(this)));
66
67 // Side effect warning: Start() may finish and invoke callbacks inline.
68 bool start_result = inner_transaction->Start();
69
70 if (!start_result)
71 task_->Complete(true /* post_needed */);
72 else if (results_.error() == ERR_IO_PENDING)
73 async_transaction_ = std::move(inner_transaction);
74 }
75
IsDone() const76 bool IsDone() const { return results_.error() != ERR_IO_PENDING; }
IsError() const77 bool IsError() const {
78 return IsDone() && results_.error() != OK &&
79 results_.error() != ERR_NAME_NOT_RESOLVED;
80 }
results() const81 const HostCache::Entry& results() const { return results_; }
82
Cancel()83 void Cancel() {
84 DCHECK_CALLED_ON_VALID_SEQUENCE(task_->sequence_checker_);
85 DCHECK_EQ(ERR_IO_PENDING, results_.error());
86
87 results_ = HostCache::Entry(ERR_FAILED, HostCache::Entry::SOURCE_UNKNOWN);
88 async_transaction_ = nullptr;
89 }
90
91 private:
OnComplete(MDnsTransaction::Result result,const RecordParsed * parsed)92 void OnComplete(MDnsTransaction::Result result, const RecordParsed* parsed) {
93 DCHECK_CALLED_ON_VALID_SEQUENCE(task_->sequence_checker_);
94 DCHECK_EQ(ERR_IO_PENDING, results_.error());
95
96 int error = ERR_UNEXPECTED;
97 switch (result) {
98 case MDnsTransaction::RESULT_RECORD:
99 DCHECK(parsed);
100 error = OK;
101 break;
102 case MDnsTransaction::RESULT_NO_RESULTS:
103 case MDnsTransaction::RESULT_NSEC:
104 error = ERR_NAME_NOT_RESOLVED;
105 break;
106 default:
107 // No other results should be possible with the request flags used.
108 NOTREACHED();
109 }
110
111 results_ = HostResolverMdnsTask::ParseResult(error, query_type_, parsed,
112 task_->hostname_);
113
114 // If we don't have a saved async_transaction, it means OnComplete was
115 // invoked inline in MDnsTransaction::Start. Callbacks will need to be
116 // invoked via post.
117 task_->CheckCompletion(!async_transaction_);
118 }
119
120 const DnsQueryType query_type_;
121
122 // ERR_IO_PENDING until transaction completes (or is cancelled).
123 HostCache::Entry results_;
124
125 // Not saved until MDnsTransaction::Start completes to differentiate inline
126 // completion.
127 std::unique_ptr<MDnsTransaction> async_transaction_;
128
129 // Back pointer. Expected to destroy |this| before destroying itself.
130 const raw_ptr<HostResolverMdnsTask> task_;
131 };
132
HostResolverMdnsTask(MDnsClient * mdns_client,std::string hostname,DnsQueryTypeSet query_types)133 HostResolverMdnsTask::HostResolverMdnsTask(MDnsClient* mdns_client,
134 std::string hostname,
135 DnsQueryTypeSet query_types)
136 : mdns_client_(mdns_client), hostname_(std::move(hostname)) {
137 DCHECK(!query_types.Empty());
138 DCHECK(!query_types.Has(DnsQueryType::UNSPECIFIED));
139
140 static constexpr DnsQueryTypeSet kUnwantedQueries(DnsQueryType::HTTPS);
141
142 for (DnsQueryType query_type : Difference(query_types, kUnwantedQueries))
143 transactions_.emplace_back(query_type, this);
144 }
145
~HostResolverMdnsTask()146 HostResolverMdnsTask::~HostResolverMdnsTask() {
147 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
148 transactions_.clear();
149 }
150
Start(base::OnceClosure completion_closure)151 void HostResolverMdnsTask::Start(base::OnceClosure completion_closure) {
152 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
153 DCHECK(!completion_closure_);
154 DCHECK(mdns_client_);
155
156 completion_closure_ = std::move(completion_closure);
157
158 for (auto& transaction : transactions_) {
159 // Only start transaction if it is not already marked done. A transaction
160 // could be marked done before starting if it is preemptively canceled by
161 // a previously started transaction finishing with an error.
162 if (!transaction.IsDone())
163 transaction.Start();
164 }
165 }
166
GetResults() const167 HostCache::Entry HostResolverMdnsTask::GetResults() const {
168 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
169 DCHECK(!transactions_.empty());
170 DCHECK(!completion_closure_);
171 DCHECK(base::ranges::all_of(transactions_,
172 [](const Transaction& t) { return t.IsDone(); }));
173
174 auto found_error =
175 base::ranges::find_if(transactions_, &Transaction::IsError);
176 if (found_error != transactions_.end()) {
177 return found_error->results();
178 }
179
180 HostCache::Entry combined_results = transactions_.front().results();
181 for (auto it = ++transactions_.begin(); it != transactions_.end(); ++it) {
182 combined_results = HostCache::Entry::MergeEntries(
183 std::move(combined_results), it->results());
184 }
185
186 return combined_results;
187 }
188
189 // static
ParseResult(int error,DnsQueryType query_type,const RecordParsed * parsed,const std::string & expected_hostname)190 HostCache::Entry HostResolverMdnsTask::ParseResult(
191 int error,
192 DnsQueryType query_type,
193 const RecordParsed* parsed,
194 const std::string& expected_hostname) {
195 if (error != OK) {
196 return HostCache::Entry(error, HostCache::Entry::SOURCE_UNKNOWN);
197 }
198 DCHECK(parsed);
199
200 // Expected to be validated by MDnsClient.
201 DCHECK_EQ(DnsQueryTypeToQtype(query_type), parsed->type());
202 DCHECK(base::EqualsCaseInsensitiveASCII(expected_hostname, parsed->name()));
203
204 switch (query_type) {
205 case DnsQueryType::UNSPECIFIED:
206 // Should create two separate transactions with specified type.
207 case DnsQueryType::HTTPS:
208 // Not supported.
209 // TODO(ericorth@chromium.org): Consider support for HTTPS in mDNS if it
210 // is ever decided to support HTTPS via non-DoH.
211 NOTREACHED();
212 return HostCache::Entry(ERR_FAILED, HostCache::Entry::SOURCE_UNKNOWN);
213 case DnsQueryType::A:
214 return HostCache::Entry(
215 OK, {IPEndPoint(parsed->rdata<net::ARecordRdata>()->address(), 0)},
216 /*aliases=*/{}, HostCache::Entry::SOURCE_UNKNOWN);
217 case DnsQueryType::AAAA:
218 return HostCache::Entry(
219 OK, {IPEndPoint(parsed->rdata<net::AAAARecordRdata>()->address(), 0)},
220 /*aliases=*/{}, HostCache::Entry::SOURCE_UNKNOWN);
221 case DnsQueryType::TXT:
222 return HostCache::Entry(OK, parsed->rdata<net::TxtRecordRdata>()->texts(),
223 HostCache::Entry::SOURCE_UNKNOWN);
224 case DnsQueryType::PTR:
225 return ParseHostnameResult(parsed->rdata<PtrRecordRdata>()->ptrdomain(),
226 0 /* port */);
227 case DnsQueryType::SRV:
228 return ParseHostnameResult(parsed->rdata<SrvRecordRdata>()->target(),
229 parsed->rdata<SrvRecordRdata>()->port());
230 }
231 }
232
CheckCompletion(bool post_needed)233 void HostResolverMdnsTask::CheckCompletion(bool post_needed) {
234 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
235
236 // Finish immediately if any transactions completed with an error.
237 if (base::ranges::any_of(transactions_,
238 [](const Transaction& t) { return t.IsError(); })) {
239 Complete(post_needed);
240 return;
241 }
242
243 if (base::ranges::all_of(transactions_,
244 [](const Transaction& t) { return t.IsDone(); })) {
245 Complete(post_needed);
246 return;
247 }
248 }
249
Complete(bool post_needed)250 void HostResolverMdnsTask::Complete(bool post_needed) {
251 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
252
253 // Cancel any incomplete async transactions.
254 for (auto& transaction : transactions_) {
255 if (!transaction.IsDone())
256 transaction.Cancel();
257 }
258
259 if (post_needed) {
260 base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
261 FROM_HERE, base::BindOnce(
262 [](base::WeakPtr<HostResolverMdnsTask> task) {
263 if (task)
264 std::move(task->completion_closure_).Run();
265 },
266 weak_ptr_factory_.GetWeakPtr()));
267 } else {
268 std::move(completion_closure_).Run();
269 }
270 }
271
272 } // namespace net
273