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_publisher.h"
6
7 #include <chrono>
8 #include <cmath>
9
10 #include "discovery/common/config.h"
11 #include "discovery/mdns/mdns_probe_manager.h"
12 #include "discovery/mdns/mdns_records.h"
13 #include "discovery/mdns/mdns_sender.h"
14 #include "platform/api/task_runner.h"
15 #include "platform/base/trivial_clock_traits.h"
16
17 namespace openscreen {
18 namespace discovery {
19 namespace {
20
21 // Minimum delay between announcements of a given record in seconds.
22 constexpr std::chrono::seconds kMinAnnounceDelay{1};
23
24 // Intervals between successive announcements must increase by at least a
25 // factor of 2.
26 constexpr int kIntervalIncreaseFactor = 2;
27
28 // TTL for a goodbye record in seconds. This constant is called out in RFC 6762
29 // section 10.1.
30 constexpr std::chrono::seconds kGoodbyeTtl{0};
31
32 // Timespan between sending batches of announcement and goodbye records, in
33 // microseconds.
34 constexpr Clock::duration kDelayBetweenBatchedRecords =
35 std::chrono::milliseconds(20);
36
CreateGoodbyeRecord(const MdnsRecord & record)37 inline MdnsRecord CreateGoodbyeRecord(const MdnsRecord& record) {
38 if (record.ttl() == kGoodbyeTtl) {
39 return record;
40 }
41 return MdnsRecord(record.name(), record.dns_type(), record.dns_class(),
42 record.record_type(), kGoodbyeTtl, record.rdata());
43 }
44
45 } // namespace
46
MdnsPublisher(MdnsSender * sender,MdnsProbeManager * ownership_manager,TaskRunner * task_runner,ClockNowFunctionPtr now_function,const Config & config)47 MdnsPublisher::MdnsPublisher(MdnsSender* sender,
48 MdnsProbeManager* ownership_manager,
49 TaskRunner* task_runner,
50 ClockNowFunctionPtr now_function,
51 const Config& config)
52 : sender_(sender),
53 ownership_manager_(ownership_manager),
54 task_runner_(task_runner),
55 now_function_(now_function),
56 max_announcement_attempts_(config.new_record_announcement_count) {
57 OSP_DCHECK(ownership_manager_);
58 OSP_DCHECK(sender_);
59 OSP_DCHECK(task_runner_);
60 OSP_DCHECK_GE(max_announcement_attempts_, 0);
61 }
62
~MdnsPublisher()63 MdnsPublisher::~MdnsPublisher() {
64 if (batch_records_alarm_.has_value()) {
65 batch_records_alarm_.value().Cancel();
66 ProcessRecordQueue();
67 }
68 }
69
RegisterRecord(const MdnsRecord & record)70 Error MdnsPublisher::RegisterRecord(const MdnsRecord& record) {
71 OSP_DCHECK(task_runner_->IsRunningOnTaskRunner());
72 OSP_DCHECK(record.dns_class() != DnsClass::kANY);
73
74 if (!CanBePublished(record.dns_type())) {
75 return Error::Code::kParameterInvalid;
76 }
77
78 if (!IsRecordNameClaimed(record)) {
79 return Error::Code::kParameterInvalid;
80 }
81
82 const DomainName& name = record.name();
83 auto it = records_.emplace(name, std::vector<RecordAnnouncerPtr>{}).first;
84 for (const RecordAnnouncerPtr& publisher : it->second) {
85 if (publisher->record() == record) {
86 return Error::Code::kItemAlreadyExists;
87 }
88 }
89
90 OSP_DVLOG << "Registering record of type '" << record.dns_type() << "'";
91
92 it->second.push_back(CreateAnnouncer(record));
93
94 return Error::None();
95 }
96
UnregisterRecord(const MdnsRecord & record)97 Error MdnsPublisher::UnregisterRecord(const MdnsRecord& record) {
98 OSP_DCHECK(task_runner_->IsRunningOnTaskRunner());
99 OSP_DCHECK(record.dns_class() != DnsClass::kANY);
100
101 if (!CanBePublished(record.dns_type())) {
102 return Error::Code::kParameterInvalid;
103 }
104
105 OSP_DVLOG << "Unregistering record of type '" << record.dns_type() << "'";
106
107 return RemoveRecord(record, true);
108 }
109
UpdateRegisteredRecord(const MdnsRecord & old_record,const MdnsRecord & new_record)110 Error MdnsPublisher::UpdateRegisteredRecord(const MdnsRecord& old_record,
111 const MdnsRecord& new_record) {
112 OSP_DCHECK(task_runner_->IsRunningOnTaskRunner());
113
114 if (!CanBePublished(new_record.dns_type())) {
115 return Error::Code::kParameterInvalid;
116 }
117
118 if (old_record.dns_type() == DnsType::kPTR) {
119 return Error::Code::kParameterInvalid;
120 }
121
122 // Check that the old record and new record are compatible.
123 if (old_record.name() != new_record.name() ||
124 old_record.dns_type() != new_record.dns_type() ||
125 old_record.dns_class() != new_record.dns_class() ||
126 old_record.record_type() != new_record.record_type()) {
127 return Error::Code::kParameterInvalid;
128 }
129
130 OSP_DVLOG << "Updating record of type '" << new_record.dns_type() << "'";
131
132 // Remove the old record. Per RFC 6762 section 8.4, a goodbye message will not
133 // be sent, as all records which can be removed here are unique records, which
134 // will be overwritten during the announcement phase when the updated record
135 // is re-registered due to the cache-flush-bit's presence.
136 const Error remove_result = RemoveRecord(old_record, false);
137 if (!remove_result.ok()) {
138 return remove_result;
139 }
140
141 // Register the new record.
142 return RegisterRecord(new_record);
143 }
144
GetRecordCount() const145 size_t MdnsPublisher::GetRecordCount() const {
146 OSP_DCHECK(task_runner_->IsRunningOnTaskRunner());
147
148 size_t count = 0;
149 for (const auto& pair : records_) {
150 count += pair.second.size();
151 }
152
153 return count;
154 }
155
HasRecords(const DomainName & name,DnsType type,DnsClass clazz)156 bool MdnsPublisher::HasRecords(const DomainName& name,
157 DnsType type,
158 DnsClass clazz) {
159 return !GetRecords(name, type, clazz).empty();
160 }
161
GetRecords(const DomainName & name,DnsType type,DnsClass clazz)162 std::vector<MdnsRecord::ConstRef> MdnsPublisher::GetRecords(
163 const DomainName& name,
164 DnsType type,
165 DnsClass clazz) {
166 OSP_DCHECK(task_runner_->IsRunningOnTaskRunner());
167
168 std::vector<MdnsRecord::ConstRef> records;
169 auto it = records_.find(name);
170 if (it != records_.end()) {
171 for (const RecordAnnouncerPtr& announcer : it->second) {
172 OSP_DCHECK(announcer.get());
173 const DnsType record_dns_type = announcer->record().dns_type();
174 const DnsClass record_dns_class = announcer->record().dns_class();
175 if ((type == DnsType::kANY || type == record_dns_type) &&
176 (clazz == DnsClass::kANY || clazz == record_dns_class)) {
177 records.push_back(announcer->record());
178 }
179 }
180 }
181
182 return records;
183 }
184
GetPtrRecords(DnsClass clazz)185 std::vector<MdnsRecord::ConstRef> MdnsPublisher::GetPtrRecords(DnsClass clazz) {
186 std::vector<MdnsRecord::ConstRef> records;
187
188 // There should be few records associated with any given domain name, so it is
189 // simpler and less error prone to iterate across all records than to check
190 // the domain name against format '[^.]+\.(_tcp)|(_udp)\..*''
191 for (auto it = records_.begin(); it != records_.end(); it++) {
192 for (const RecordAnnouncerPtr& announcer : it->second) {
193 OSP_DCHECK(announcer.get());
194 const DnsType record_dns_type = announcer->record().dns_type();
195 if (record_dns_type != DnsType::kPTR) {
196 continue;
197 }
198
199 const DnsClass record_dns_class = announcer->record().dns_class();
200 if ((clazz == DnsClass::kANY || clazz == record_dns_class)) {
201 records.push_back(announcer->record());
202 }
203 }
204 }
205
206 return records;
207 }
208
RemoveRecord(const MdnsRecord & record,bool should_announce_deletion)209 Error MdnsPublisher::RemoveRecord(const MdnsRecord& record,
210 bool should_announce_deletion) {
211 const DomainName& name = record.name();
212
213 // Check for the domain and fail if it's not found.
214 const auto it = records_.find(name);
215 if (it == records_.end()) {
216 return Error::Code::kItemNotFound;
217 }
218
219 // Check for the record to be removed.
220 const auto records_it =
221 std::find_if(it->second.begin(), it->second.end(),
222 [&record](const RecordAnnouncerPtr& publisher) {
223 return publisher->record() == record;
224 });
225 if (records_it == it->second.end()) {
226 return Error::Code::kItemNotFound;
227 }
228 if (!should_announce_deletion) {
229 (*records_it)->DisableGoodbyeMessageTransmission();
230 }
231
232 it->second.erase(records_it);
233 if (it->second.empty()) {
234 records_.erase(it);
235 }
236
237 return Error::None();
238 }
239
IsRecordNameClaimed(const MdnsRecord & record) const240 bool MdnsPublisher::IsRecordNameClaimed(const MdnsRecord& record) const {
241 const DomainName& name =
242 record.dns_type() == DnsType::kPTR
243 ? absl::get<PtrRecordRdata>(record.rdata()).ptr_domain()
244 : record.name();
245 return ownership_manager_->IsDomainClaimed(name);
246 }
247
RecordAnnouncer(MdnsRecord record,MdnsPublisher * publisher,TaskRunner * task_runner,ClockNowFunctionPtr now_function,int target_announcement_attempts)248 MdnsPublisher::RecordAnnouncer::RecordAnnouncer(
249 MdnsRecord record,
250 MdnsPublisher* publisher,
251 TaskRunner* task_runner,
252 ClockNowFunctionPtr now_function,
253 int target_announcement_attempts)
254 : publisher_(publisher),
255 task_runner_(task_runner),
256 now_function_(now_function),
257 record_(std::move(record)),
258 alarm_(now_function_, task_runner_),
259 target_announcement_attempts_(target_announcement_attempts) {
260 OSP_DCHECK(publisher_);
261 OSP_DCHECK(task_runner_);
262 OSP_DCHECK(record_.ttl() != Clock::duration::zero());
263
264 QueueAnnouncement();
265 }
266
~RecordAnnouncer()267 MdnsPublisher::RecordAnnouncer::~RecordAnnouncer() {
268 alarm_.Cancel();
269 if (should_send_goodbye_message_) {
270 QueueGoodbye();
271 }
272 }
273
QueueGoodbye()274 void MdnsPublisher::RecordAnnouncer::QueueGoodbye() {
275 OSP_DCHECK(task_runner_->IsRunningOnTaskRunner());
276
277 publisher_->QueueRecord(CreateGoodbyeRecord(record_));
278 }
279
QueueAnnouncement()280 void MdnsPublisher::RecordAnnouncer::QueueAnnouncement() {
281 OSP_DCHECK(task_runner_->IsRunningOnTaskRunner());
282
283 if (attempts_ >= target_announcement_attempts_) {
284 return;
285 }
286
287 publisher_->QueueRecord(record_);
288
289 const Clock::duration new_delay = GetNextAnnounceDelay();
290 attempts_++;
291 alarm_.ScheduleFromNow([this]() { QueueAnnouncement(); }, new_delay);
292 }
293
QueueRecord(MdnsRecord record)294 void MdnsPublisher::QueueRecord(MdnsRecord record) {
295 if (!batch_records_alarm_.has_value()) {
296 OSP_DCHECK(records_to_send_.empty());
297 batch_records_alarm_.emplace(now_function_, task_runner_);
298 batch_records_alarm_.value().ScheduleFromNow(
299 [this]() { ProcessRecordQueue(); }, kDelayBetweenBatchedRecords);
300 }
301
302 // Check that we aren't announcing and goodbye'ing a record in the same batch.
303 // We expect to be sending no more than 5 records at a time, so don't worry
304 // about iterating across this vector for each insert.
305 auto goodbye = CreateGoodbyeRecord(record);
306 auto existing_record_it =
307 std::find_if(records_to_send_.begin(), records_to_send_.end(),
308 [&goodbye](const MdnsRecord& record) {
309 return goodbye == CreateGoodbyeRecord(record);
310 });
311
312 // If we didn't find it, simply add it to the queue. Else, only send the
313 // goodbye record.
314 if (existing_record_it == records_to_send_.end()) {
315 records_to_send_.push_back(std::move(record));
316 } else if (*existing_record_it == goodbye) {
317 // This means that the goodbye record is already queued to be sent. This
318 // means that there is no reason to also announce it, so exit early.
319 return;
320 } else if (record == goodbye) {
321 // This means that we are sending a goodbye record right as it would also
322 // be announced. Skip the announcement since the record is being
323 // unregistered.
324 *existing_record_it = std::move(record);
325 } else if (record == *existing_record_it) {
326 // This case shouldn't happen, but there is no work to do if it does. Log
327 // to surface that something weird is going on.
328 OSP_LOG_INFO << "Same record being announced multiple times.";
329 } else {
330 // This case should never occur. Support it just in case, but log to
331 // surface that something weird is happening.
332 OSP_LOG_INFO << "Updating the same record multiple times with multiple "
333 "TTL values.";
334 }
335 }
336
ProcessRecordQueue()337 void MdnsPublisher::ProcessRecordQueue() {
338 OSP_DCHECK(task_runner_->IsRunningOnTaskRunner());
339
340 if (records_to_send_.empty()) {
341 return;
342 }
343
344 MdnsMessage message(CreateMessageId(), MessageType::Response);
345 for (auto it = records_to_send_.begin(); it != records_to_send_.end();) {
346 if (message.CanAddRecord(*it)) {
347 message.AddAnswer(std::move(*it++));
348 } else if (message.answers().empty()) {
349 // This case should never happen, because it means a record is too large
350 // to fit into its own message.
351 OSP_LOG_INFO
352 << "Encountered unreasonably large message in cache. Skipping "
353 << "known answer in suppressions...";
354 it++;
355 } else {
356 sender_->SendMulticast(message);
357 message = MdnsMessage(CreateMessageId(), MessageType::Response);
358 }
359 }
360
361 if (!message.answers().empty()) {
362 sender_->SendMulticast(message);
363 }
364
365 batch_records_alarm_ = absl::nullopt;
366 records_to_send_.clear();
367 }
368
GetNextAnnounceDelay()369 Clock::duration MdnsPublisher::RecordAnnouncer::GetNextAnnounceDelay() {
370 return Clock::to_duration(kMinAnnounceDelay *
371 pow(kIntervalIncreaseFactor, attempts_));
372 }
373
374 } // namespace discovery
375 } // namespace openscreen
376