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