• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2019 The Chromium Authors. All rights reserved.
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 "discovery/mdns/mdns_probe_manager.h"
6 
7 #include <string>
8 #include <utility>
9 
10 #include "discovery/mdns/mdns_sender.h"
11 #include "platform/api/task_runner.h"
12 
13 namespace openscreen {
14 namespace discovery {
15 namespace {
16 
17 // The timespan by which to delay subsequent mDNS Probe queries for the same
18 // domain name when a simultaneous query from another host is detected, as
19 // described in RFC 6762 section 8.2
20 constexpr std::chrono::seconds kSimultaneousProbeDelay =
21     std::chrono::seconds(1);
22 
CreateRetryDomainName(const DomainName & name,int attempt)23 DomainName CreateRetryDomainName(const DomainName& name, int attempt) {
24   OSP_DCHECK(name.labels().size());
25   std::vector<std::string> labels = name.labels();
26   std::string& label = labels[0];
27   std::string attempts_str = std::to_string(attempt);
28   if (label.size() + attempts_str.size() >= kMaxLabelLength) {
29     label = label.substr(0, kMaxLabelLength - attempts_str.size());
30   }
31   label.append(attempts_str);
32 
33   return DomainName(std::move(labels));
34 }
35 
36 }  // namespace
37 
38 MdnsProbeManager::~MdnsProbeManager() = default;
39 
MdnsProbeManagerImpl(MdnsSender * sender,MdnsReceiver * receiver,MdnsRandom * random_delay,TaskRunner * task_runner,ClockNowFunctionPtr now_function)40 MdnsProbeManagerImpl::MdnsProbeManagerImpl(MdnsSender* sender,
41                                            MdnsReceiver* receiver,
42                                            MdnsRandom* random_delay,
43                                            TaskRunner* task_runner,
44                                            ClockNowFunctionPtr now_function)
45     : sender_(sender),
46       receiver_(receiver),
47       random_delay_(random_delay),
48       task_runner_(task_runner),
49       now_function_(now_function) {
50   OSP_DCHECK(sender_);
51   OSP_DCHECK(receiver_);
52   OSP_DCHECK(task_runner_);
53   OSP_DCHECK(random_delay_);
54 }
55 
56 MdnsProbeManagerImpl::~MdnsProbeManagerImpl() = default;
57 
StartProbe(MdnsDomainConfirmedProvider * callback,DomainName requested_name,IPAddress address)58 Error MdnsProbeManagerImpl::StartProbe(MdnsDomainConfirmedProvider* callback,
59                                        DomainName requested_name,
60                                        IPAddress address) {
61   // Check if |requested_name| is already being queried for.
62   if (FindOngoingProbe(requested_name) != ongoing_probes_.end()) {
63     return Error::Code::kOperationInProgress;
64   }
65 
66   // Check if |requested_name| is already claimed.
67   if (IsDomainClaimed(requested_name)) {
68     return Error::Code::kItemAlreadyExists;
69   }
70 
71   OSP_DVLOG << "Starting new mDNS Probe for domain '"
72             << requested_name.ToString() << "'";
73 
74   // Begin a new probe.
75   auto probe = CreateProbe(requested_name, std::move(address));
76   ongoing_probes_.emplace_back(std::move(probe), std::move(requested_name),
77                                callback);
78   return Error::None();
79 }
80 
StopProbe(const DomainName & requested_name)81 Error MdnsProbeManagerImpl::StopProbe(const DomainName& requested_name) {
82   auto it = FindOngoingProbe(requested_name);
83   if (it == ongoing_probes_.end()) {
84     return Error::Code::kItemNotFound;
85   }
86 
87   ongoing_probes_.erase(it);
88   return Error::None();
89 }
90 
IsDomainClaimed(const DomainName & domain) const91 bool MdnsProbeManagerImpl::IsDomainClaimed(const DomainName& domain) const {
92   return FindCompletedProbe(domain) != completed_probes_.end();
93 }
94 
RespondToProbeQuery(const MdnsMessage & message,const IPEndpoint & src)95 void MdnsProbeManagerImpl::RespondToProbeQuery(const MdnsMessage& message,
96                                                const IPEndpoint& src) {
97   OSP_DCHECK(!message.questions().empty());
98 
99   const std::vector<MdnsQuestion>& questions = message.questions();
100   MdnsMessage send_message(CreateMessageId(), MessageType::Response);
101 
102   // Iterate across all questions asked and all completed probes and add A or
103   // AAAA records associated with the endpoints for which the names match.
104   // |questions| is expected to be of size 1 and |completed_probes| should be
105   // small (generally size 1), so this should be fast.
106   for (const auto& question : questions) {
107     for (auto it = completed_probes_.begin(); it != completed_probes_.end();
108          it++) {
109       if (question.name() == (*it)->target_name()) {
110         send_message.AddAnswer((*it)->address_record());
111         break;
112       }
113     }
114   }
115 
116   if (!send_message.answers().empty()) {
117     sender_->SendMessage(send_message, src);
118   } else {
119     // If the name isn't already claimed, check to see if a probe is ongoing. If
120     // so, compare the address record for that probe with the one in the
121     // received message and resolve as specified in RFC 6762 section 8.2.
122     TiebreakSimultaneousProbes(message);
123   }
124 }
125 
TiebreakSimultaneousProbes(const MdnsMessage & message)126 void MdnsProbeManagerImpl::TiebreakSimultaneousProbes(
127     const MdnsMessage& message) {
128   OSP_DCHECK(!message.questions().empty());
129   OSP_DCHECK(!message.authority_records().empty());
130 
131   for (const auto& question : message.questions()) {
132     for (auto it = ongoing_probes_.begin(); it != ongoing_probes_.end(); it++) {
133       if (it->probe->target_name() == question.name()) {
134         // When a host is probing for a set of records with the same name, or a
135         // message is received containing multiple tiebreaker records answering
136         // a given probe question in the Question Section, the host's records
137         // and the tiebreaker records from the message are each sorted into
138         // order, and then compared pairwise, using the same comparison
139         // technique described above, until a difference is found. Because the
140         // probe object is guaranteed to only have the address record, only the
141         // lowest authority record is needed.
142         auto lowest_record_it =
143             std::min_element(message.authority_records().begin(),
144                              message.authority_records().end());
145 
146         // If this host finds that its own data is lexicographically later, it
147         // simply ignores the other host's probe. The other host will have
148         // receive this host's probe simultaneously, and will reject its own
149         // probe through this same calculation.
150         const MdnsRecord& probe_record = it->probe->address_record();
151         if (probe_record > *lowest_record_it) {
152           break;
153         }
154 
155         // If the probe query is only of size one and the record received is
156         // equal to this record, then the received query is the same as what
157         // this probe is sending out. In this case, nothing needs to be done.
158         if (message.authority_records().size() == 1 &&
159             !(probe_record < *lowest_record_it)) {
160           break;
161         }
162 
163         // At this point, one of the following must be true:
164         // - The query's lowest record is greater than this probe's record
165         // - The query's lowest record equals this probe's record but it also
166         //   has additional records.
167         // In either case, the query must take priority over this probe. This
168         // host defers to the winning host by waiting one second, and then
169         // begins probing for this record again. See RFC 6762 section 8.2 for
170         // the logic behind waiting one second.
171         it->probe->Postpone(kSimultaneousProbeDelay);
172         break;
173       }
174     }
175   }
176 }
177 
OnProbeSuccess(MdnsProbe * probe)178 void MdnsProbeManagerImpl::OnProbeSuccess(MdnsProbe* probe) {
179   auto it = FindOngoingProbe(probe);
180   if (it != ongoing_probes_.end()) {
181     DomainName target_name = it->probe->target_name();
182     completed_probes_.push_back(std::move(it->probe));
183     DomainName requested = std::move(it->requested_name);
184     MdnsDomainConfirmedProvider* callback = it->callback;
185     ongoing_probes_.erase(it);
186     callback->OnDomainFound(std::move(requested), std::move(target_name));
187   }
188 }
189 
OnProbeFailure(MdnsProbe * probe)190 void MdnsProbeManagerImpl::OnProbeFailure(MdnsProbe* probe) {
191   auto ongoing_it = FindOngoingProbe(probe);
192   if (ongoing_it == ongoing_probes_.end()) {
193     // This means that the probe was canceled.
194     return;
195   }
196 
197   OSP_DVLOG << "Probe for domain '"
198             << CreateRetryDomainName(ongoing_it->requested_name,
199                                      ongoing_it->num_probes_failed)
200                    .ToString()
201             << "' failed. Trying new domain...";
202 
203   // Create a new probe with a modified domain name.
204   DomainName new_name = CreateRetryDomainName(ongoing_it->requested_name,
205                                               ++ongoing_it->num_probes_failed);
206 
207   // If this domain has already been claimed, skip ahead to knowing it's
208   // claimed.
209   auto completed_it = FindCompletedProbe(new_name);
210   if (completed_it != completed_probes_.end()) {
211     DomainName requested_name = std::move(ongoing_it->requested_name);
212     MdnsDomainConfirmedProvider* callback = ongoing_it->callback;
213     ongoing_probes_.erase(ongoing_it);
214     callback->OnDomainFound(requested_name, (*completed_it)->target_name());
215   } else {
216     std::unique_ptr<MdnsProbe> new_probe =
217         CreateProbe(std::move(new_name), ongoing_it->probe->address());
218     ongoing_it->probe = std::move(new_probe);
219   }
220 }
221 
222 std::vector<std::unique_ptr<MdnsProbe>>::const_iterator
FindCompletedProbe(const DomainName & name) const223 MdnsProbeManagerImpl::FindCompletedProbe(const DomainName& name) const {
224   return std::find_if(completed_probes_.begin(), completed_probes_.end(),
225                       [&name](const std::unique_ptr<MdnsProbe>& completed) {
226                         return completed->target_name() == name;
227                       });
228 }
229 
230 std::vector<MdnsProbeManagerImpl::OngoingProbe>::iterator
FindOngoingProbe(const DomainName & name)231 MdnsProbeManagerImpl::FindOngoingProbe(const DomainName& name) {
232   return std::find_if(ongoing_probes_.begin(), ongoing_probes_.end(),
233                       [&name](const OngoingProbe& ongoing) {
234                         return ongoing.requested_name == name;
235                       });
236 }
237 
238 std::vector<MdnsProbeManagerImpl::OngoingProbe>::iterator
FindOngoingProbe(MdnsProbe * probe)239 MdnsProbeManagerImpl::FindOngoingProbe(MdnsProbe* probe) {
240   return std::find_if(ongoing_probes_.begin(), ongoing_probes_.end(),
241                       [&probe](const OngoingProbe& ongoing) {
242                         return ongoing.probe.get() == probe;
243                       });
244 }
245 
OngoingProbe(std::unique_ptr<MdnsProbe> probe,DomainName name,MdnsDomainConfirmedProvider * callback)246 MdnsProbeManagerImpl::OngoingProbe::OngoingProbe(
247     std::unique_ptr<MdnsProbe> probe,
248     DomainName name,
249     MdnsDomainConfirmedProvider* callback)
250     : probe(std::move(probe)),
251       requested_name(std::move(name)),
252       callback(callback) {}
253 
254 }  // namespace discovery
255 }  // namespace openscreen
256