• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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