• 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_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