1*3f982cf4SFabien Sanglard // Copyright 2019 The Chromium Authors. All rights reserved.
2*3f982cf4SFabien Sanglard // Use of this source code is governed by a BSD-style license that can be
3*3f982cf4SFabien Sanglard // found in the LICENSE file.
4*3f982cf4SFabien Sanglard
5*3f982cf4SFabien Sanglard #include "discovery/mdns/mdns_responder.h"
6*3f982cf4SFabien Sanglard
7*3f982cf4SFabien Sanglard #include <array>
8*3f982cf4SFabien Sanglard #include <string>
9*3f982cf4SFabien Sanglard #include <utility>
10*3f982cf4SFabien Sanglard
11*3f982cf4SFabien Sanglard #include "discovery/common/config.h"
12*3f982cf4SFabien Sanglard #include "discovery/mdns/mdns_probe_manager.h"
13*3f982cf4SFabien Sanglard #include "discovery/mdns/mdns_publisher.h"
14*3f982cf4SFabien Sanglard #include "discovery/mdns/mdns_querier.h"
15*3f982cf4SFabien Sanglard #include "discovery/mdns/mdns_random.h"
16*3f982cf4SFabien Sanglard #include "discovery/mdns/mdns_receiver.h"
17*3f982cf4SFabien Sanglard #include "discovery/mdns/mdns_sender.h"
18*3f982cf4SFabien Sanglard #include "platform/api/task_runner.h"
19*3f982cf4SFabien Sanglard
20*3f982cf4SFabien Sanglard namespace openscreen {
21*3f982cf4SFabien Sanglard namespace discovery {
22*3f982cf4SFabien Sanglard namespace {
23*3f982cf4SFabien Sanglard
24*3f982cf4SFabien Sanglard constexpr std::array<const char*, 3> kServiceEnumerationDomainLabels{
25*3f982cf4SFabien Sanglard "_services", "_dns-sd", "_udp"};
26*3f982cf4SFabien Sanglard
27*3f982cf4SFabien Sanglard enum AddResult { kNonePresent = 0, kAdded, kAlreadyKnown };
28*3f982cf4SFabien Sanglard
GetTtlForNsecTargetingType(DnsType type)29*3f982cf4SFabien Sanglard std::chrono::seconds GetTtlForNsecTargetingType(DnsType type) {
30*3f982cf4SFabien Sanglard // NOTE: A 'default' switch statement has intentionally been avoided below to
31*3f982cf4SFabien Sanglard // enforce that new DnsTypes added must be added below through a compile-time
32*3f982cf4SFabien Sanglard // check.
33*3f982cf4SFabien Sanglard switch (type) {
34*3f982cf4SFabien Sanglard case DnsType::kA:
35*3f982cf4SFabien Sanglard return kARecordTtl;
36*3f982cf4SFabien Sanglard case DnsType::kAAAA:
37*3f982cf4SFabien Sanglard return kAAAARecordTtl;
38*3f982cf4SFabien Sanglard case DnsType::kPTR:
39*3f982cf4SFabien Sanglard return kPtrRecordTtl;
40*3f982cf4SFabien Sanglard case DnsType::kSRV:
41*3f982cf4SFabien Sanglard return kSrvRecordTtl;
42*3f982cf4SFabien Sanglard case DnsType::kTXT:
43*3f982cf4SFabien Sanglard return kTXTRecordTtl;
44*3f982cf4SFabien Sanglard case DnsType::kANY:
45*3f982cf4SFabien Sanglard // If no records are present, re-querying should happen at the minimum
46*3f982cf4SFabien Sanglard // of any record that might be retrieved at that time.
47*3f982cf4SFabien Sanglard return kSrvRecordTtl;
48*3f982cf4SFabien Sanglard case DnsType::kNSEC:
49*3f982cf4SFabien Sanglard case DnsType::kOPT:
50*3f982cf4SFabien Sanglard // Neither of these types should ever be hit. We should never be creating
51*3f982cf4SFabien Sanglard // an NSEC record for type NSEC, and OPT record querying is not supported,
52*3f982cf4SFabien Sanglard // so creating NSEC records for type OPT is not valid.
53*3f982cf4SFabien Sanglard break;
54*3f982cf4SFabien Sanglard }
55*3f982cf4SFabien Sanglard
56*3f982cf4SFabien Sanglard OSP_NOTREACHED();
57*3f982cf4SFabien Sanglard }
58*3f982cf4SFabien Sanglard
CreateNsecRecord(DomainName target_name,DnsType target_type,DnsClass target_class)59*3f982cf4SFabien Sanglard MdnsRecord CreateNsecRecord(DomainName target_name,
60*3f982cf4SFabien Sanglard DnsType target_type,
61*3f982cf4SFabien Sanglard DnsClass target_class) {
62*3f982cf4SFabien Sanglard auto rdata = NsecRecordRdata(target_name, target_type);
63*3f982cf4SFabien Sanglard std::chrono::seconds ttl = GetTtlForNsecTargetingType(target_type);
64*3f982cf4SFabien Sanglard return MdnsRecord(std::move(target_name), DnsType::kNSEC, target_class,
65*3f982cf4SFabien Sanglard RecordType::kUnique, ttl, std::move(rdata));
66*3f982cf4SFabien Sanglard }
67*3f982cf4SFabien Sanglard
IsValidAdditionalRecordType(DnsType type)68*3f982cf4SFabien Sanglard inline bool IsValidAdditionalRecordType(DnsType type) {
69*3f982cf4SFabien Sanglard return type == DnsType::kSRV || type == DnsType::kTXT ||
70*3f982cf4SFabien Sanglard type == DnsType::kA || type == DnsType::kAAAA;
71*3f982cf4SFabien Sanglard }
72*3f982cf4SFabien Sanglard
AddRecords(std::function<void (MdnsRecord record)> add_func,MdnsResponder::RecordHandler * record_handler,const DomainName & domain,const std::vector<MdnsRecord> & known_answers,DnsType type,DnsClass clazz,bool add_negative_on_unknown)73*3f982cf4SFabien Sanglard AddResult AddRecords(std::function<void(MdnsRecord record)> add_func,
74*3f982cf4SFabien Sanglard MdnsResponder::RecordHandler* record_handler,
75*3f982cf4SFabien Sanglard const DomainName& domain,
76*3f982cf4SFabien Sanglard const std::vector<MdnsRecord>& known_answers,
77*3f982cf4SFabien Sanglard DnsType type,
78*3f982cf4SFabien Sanglard DnsClass clazz,
79*3f982cf4SFabien Sanglard bool add_negative_on_unknown) {
80*3f982cf4SFabien Sanglard auto records = record_handler->GetRecords(domain, type, clazz);
81*3f982cf4SFabien Sanglard if (records.empty()) {
82*3f982cf4SFabien Sanglard if (add_negative_on_unknown) {
83*3f982cf4SFabien Sanglard // TODO(rwkeane): Aggregate all NSEC records together into a single NSEC
84*3f982cf4SFabien Sanglard // record to reduce traffic.
85*3f982cf4SFabien Sanglard add_func(CreateNsecRecord(domain, type, clazz));
86*3f982cf4SFabien Sanglard }
87*3f982cf4SFabien Sanglard return AddResult::kNonePresent;
88*3f982cf4SFabien Sanglard } else {
89*3f982cf4SFabien Sanglard bool added_any_records = false;
90*3f982cf4SFabien Sanglard for (auto it = records.begin(); it != records.end(); it++) {
91*3f982cf4SFabien Sanglard if (std::find(known_answers.begin(), known_answers.end(), *it) ==
92*3f982cf4SFabien Sanglard known_answers.end()) {
93*3f982cf4SFabien Sanglard added_any_records = true;
94*3f982cf4SFabien Sanglard add_func(std::move(*it));
95*3f982cf4SFabien Sanglard }
96*3f982cf4SFabien Sanglard }
97*3f982cf4SFabien Sanglard return added_any_records ? AddResult::kAdded : AddResult::kAlreadyKnown;
98*3f982cf4SFabien Sanglard }
99*3f982cf4SFabien Sanglard }
100*3f982cf4SFabien Sanglard
AddAdditionalRecords(MdnsMessage * message,MdnsResponder::RecordHandler * record_handler,const DomainName & domain,const std::vector<MdnsRecord> & known_answers,DnsType type,DnsClass clazz,bool add_negative_on_unknown)101*3f982cf4SFabien Sanglard inline AddResult AddAdditionalRecords(
102*3f982cf4SFabien Sanglard MdnsMessage* message,
103*3f982cf4SFabien Sanglard MdnsResponder::RecordHandler* record_handler,
104*3f982cf4SFabien Sanglard const DomainName& domain,
105*3f982cf4SFabien Sanglard const std::vector<MdnsRecord>& known_answers,
106*3f982cf4SFabien Sanglard DnsType type,
107*3f982cf4SFabien Sanglard DnsClass clazz,
108*3f982cf4SFabien Sanglard bool add_negative_on_unknown) {
109*3f982cf4SFabien Sanglard OSP_DCHECK(IsValidAdditionalRecordType(type));
110*3f982cf4SFabien Sanglard
111*3f982cf4SFabien Sanglard auto add_func = [message](MdnsRecord record) {
112*3f982cf4SFabien Sanglard message->AddAdditionalRecord(std::move(record));
113*3f982cf4SFabien Sanglard };
114*3f982cf4SFabien Sanglard return AddRecords(std::move(add_func), record_handler, domain, known_answers,
115*3f982cf4SFabien Sanglard type, clazz, add_negative_on_unknown);
116*3f982cf4SFabien Sanglard }
117*3f982cf4SFabien Sanglard
AddResponseRecords(MdnsMessage * message,MdnsResponder::RecordHandler * record_handler,const DomainName & domain,const std::vector<MdnsRecord> & known_answers,DnsType type,DnsClass clazz,bool add_negative_on_unknown)118*3f982cf4SFabien Sanglard inline AddResult AddResponseRecords(
119*3f982cf4SFabien Sanglard MdnsMessage* message,
120*3f982cf4SFabien Sanglard MdnsResponder::RecordHandler* record_handler,
121*3f982cf4SFabien Sanglard const DomainName& domain,
122*3f982cf4SFabien Sanglard const std::vector<MdnsRecord>& known_answers,
123*3f982cf4SFabien Sanglard DnsType type,
124*3f982cf4SFabien Sanglard DnsClass clazz,
125*3f982cf4SFabien Sanglard bool add_negative_on_unknown) {
126*3f982cf4SFabien Sanglard auto add_func = [message](MdnsRecord record) {
127*3f982cf4SFabien Sanglard message->AddAnswer(std::move(record));
128*3f982cf4SFabien Sanglard };
129*3f982cf4SFabien Sanglard return AddRecords(std::move(add_func), record_handler, domain, known_answers,
130*3f982cf4SFabien Sanglard type, clazz, add_negative_on_unknown);
131*3f982cf4SFabien Sanglard }
132*3f982cf4SFabien Sanglard
ApplyQueryResults(MdnsMessage * message,MdnsResponder::RecordHandler * record_handler,const DomainName & domain,const std::vector<MdnsRecord> & known_answers,DnsType type,DnsClass clazz,bool is_exclusive_owner)133*3f982cf4SFabien Sanglard void ApplyQueryResults(MdnsMessage* message,
134*3f982cf4SFabien Sanglard MdnsResponder::RecordHandler* record_handler,
135*3f982cf4SFabien Sanglard const DomainName& domain,
136*3f982cf4SFabien Sanglard const std::vector<MdnsRecord>& known_answers,
137*3f982cf4SFabien Sanglard DnsType type,
138*3f982cf4SFabien Sanglard DnsClass clazz,
139*3f982cf4SFabien Sanglard bool is_exclusive_owner) {
140*3f982cf4SFabien Sanglard OSP_DCHECK(type != DnsType::kNSEC);
141*3f982cf4SFabien Sanglard
142*3f982cf4SFabien Sanglard // All records matching the provided query which have been published by this
143*3f982cf4SFabien Sanglard // host should be added to the response message per RFC 6762 section 6. If
144*3f982cf4SFabien Sanglard // this host is the exclusive owner of the queried domain name, then a
145*3f982cf4SFabien Sanglard // negative response NSEC record should be added in the case where the queried
146*3f982cf4SFabien Sanglard // record does not exist, per RFC 6762 section 6.1.
147*3f982cf4SFabien Sanglard if (AddResponseRecords(message, record_handler, domain, known_answers, type,
148*3f982cf4SFabien Sanglard clazz, is_exclusive_owner) != AddResult::kAdded) {
149*3f982cf4SFabien Sanglard return;
150*3f982cf4SFabien Sanglard }
151*3f982cf4SFabien Sanglard
152*3f982cf4SFabien Sanglard // Per RFC 6763 section 12.1, when querying for a PTR record, all SRV records
153*3f982cf4SFabien Sanglard // and TXT records named in the PTR record's rdata should be added to the
154*3f982cf4SFabien Sanglard // messages additional records, as well as the address records of types A and
155*3f982cf4SFabien Sanglard // AAAA associated with the added SRV records. Per RFC 6762 section 6.1,
156*3f982cf4SFabien Sanglard // records with names matching those of reverse address mappings for PTR
157*3f982cf4SFabien Sanglard // records may be added as negative response NSEC records if they do not
158*3f982cf4SFabien Sanglard // exist.
159*3f982cf4SFabien Sanglard if (type == DnsType::kPTR) {
160*3f982cf4SFabien Sanglard // Add all SRV and TXT records to the additional records section.
161*3f982cf4SFabien Sanglard for (const MdnsRecord& record : message->answers()) {
162*3f982cf4SFabien Sanglard OSP_DCHECK(record.dns_type() == DnsType::kPTR);
163*3f982cf4SFabien Sanglard
164*3f982cf4SFabien Sanglard const DomainName& target =
165*3f982cf4SFabien Sanglard absl::get<PtrRecordRdata>(record.rdata()).ptr_domain();
166*3f982cf4SFabien Sanglard AddAdditionalRecords(message, record_handler, target, known_answers,
167*3f982cf4SFabien Sanglard DnsType::kSRV, clazz, true);
168*3f982cf4SFabien Sanglard AddAdditionalRecords(message, record_handler, target, known_answers,
169*3f982cf4SFabien Sanglard DnsType::kTXT, clazz, true);
170*3f982cf4SFabien Sanglard }
171*3f982cf4SFabien Sanglard
172*3f982cf4SFabien Sanglard // Add A and AAAA records associated with an added SRV record to the
173*3f982cf4SFabien Sanglard // additional records section.
174*3f982cf4SFabien Sanglard const int max = message->additional_records().size();
175*3f982cf4SFabien Sanglard for (int i = 0; i < max; i++) {
176*3f982cf4SFabien Sanglard if (message->additional_records()[i].dns_type() != DnsType::kSRV) {
177*3f982cf4SFabien Sanglard continue;
178*3f982cf4SFabien Sanglard }
179*3f982cf4SFabien Sanglard
180*3f982cf4SFabien Sanglard {
181*3f982cf4SFabien Sanglard const MdnsRecord& srv_record = message->additional_records()[i];
182*3f982cf4SFabien Sanglard const DomainName& target =
183*3f982cf4SFabien Sanglard absl::get<SrvRecordRdata>(srv_record.rdata()).target();
184*3f982cf4SFabien Sanglard AddAdditionalRecords(message, record_handler, target, known_answers,
185*3f982cf4SFabien Sanglard DnsType::kA, clazz, target == domain);
186*3f982cf4SFabien Sanglard }
187*3f982cf4SFabien Sanglard
188*3f982cf4SFabien Sanglard // Must re-calculate the |srv_record|, |target| refs in case a resize of
189*3f982cf4SFabien Sanglard // the additional_records() vector has invalidated them.
190*3f982cf4SFabien Sanglard {
191*3f982cf4SFabien Sanglard const MdnsRecord& srv_record = message->additional_records()[i];
192*3f982cf4SFabien Sanglard const DomainName& target =
193*3f982cf4SFabien Sanglard absl::get<SrvRecordRdata>(srv_record.rdata()).target();
194*3f982cf4SFabien Sanglard AddAdditionalRecords(message, record_handler, target, known_answers,
195*3f982cf4SFabien Sanglard DnsType::kAAAA, clazz, target == domain);
196*3f982cf4SFabien Sanglard }
197*3f982cf4SFabien Sanglard }
198*3f982cf4SFabien Sanglard } else if (type == DnsType::kSRV) {
199*3f982cf4SFabien Sanglard // Per RFC 6763 section 12.2, when querying for an SRV record, all address
200*3f982cf4SFabien Sanglard // records of type A and AAAA should be added to the additional records
201*3f982cf4SFabien Sanglard // section. Per RFC 6762 section 6.1, if these records are not present and
202*3f982cf4SFabien Sanglard // their name and class match that which is being queried for, a negative
203*3f982cf4SFabien Sanglard // response NSEC record may be added to show their non-existence.
204*3f982cf4SFabien Sanglard for (const auto& srv_record : message->answers()) {
205*3f982cf4SFabien Sanglard OSP_DCHECK(srv_record.dns_type() == DnsType::kSRV);
206*3f982cf4SFabien Sanglard
207*3f982cf4SFabien Sanglard const DomainName& target =
208*3f982cf4SFabien Sanglard absl::get<SrvRecordRdata>(srv_record.rdata()).target();
209*3f982cf4SFabien Sanglard AddAdditionalRecords(message, record_handler, target, known_answers,
210*3f982cf4SFabien Sanglard DnsType::kA, clazz, target == domain);
211*3f982cf4SFabien Sanglard AddAdditionalRecords(message, record_handler, target, known_answers,
212*3f982cf4SFabien Sanglard DnsType::kAAAA, clazz, target == domain);
213*3f982cf4SFabien Sanglard }
214*3f982cf4SFabien Sanglard } else if (type == DnsType::kA) {
215*3f982cf4SFabien Sanglard // Per RFC 6762 section 6.2, when querying for an address record of type A
216*3f982cf4SFabien Sanglard // or AAAA, the record of the opposite type should be added to the
217*3f982cf4SFabien Sanglard // additional records section if present. Else, a negative response NSEC
218*3f982cf4SFabien Sanglard // record should be added to show its non-existence.
219*3f982cf4SFabien Sanglard AddAdditionalRecords(message, record_handler, domain, known_answers,
220*3f982cf4SFabien Sanglard DnsType::kAAAA, clazz, true);
221*3f982cf4SFabien Sanglard } else if (type == DnsType::kAAAA) {
222*3f982cf4SFabien Sanglard AddAdditionalRecords(message, record_handler, domain, known_answers,
223*3f982cf4SFabien Sanglard DnsType::kA, clazz, true);
224*3f982cf4SFabien Sanglard }
225*3f982cf4SFabien Sanglard
226*3f982cf4SFabien Sanglard // The remaining supported records types are TXT, NSEC, and ANY. RFCs 6762 and
227*3f982cf4SFabien Sanglard // 6763 do not recommend sending any records in the additional records section
228*3f982cf4SFabien Sanglard // for queries of types TXT or ANY, and NSEC records are not supported for
229*3f982cf4SFabien Sanglard // queries.
230*3f982cf4SFabien Sanglard }
231*3f982cf4SFabien Sanglard
232*3f982cf4SFabien Sanglard // Determines if the provided query is a type enumeration query as described in
233*3f982cf4SFabien Sanglard // RFC 6763 section 9.
IsServiceTypeEnumerationQuery(const MdnsQuestion & question)234*3f982cf4SFabien Sanglard bool IsServiceTypeEnumerationQuery(const MdnsQuestion& question) {
235*3f982cf4SFabien Sanglard if (question.dns_type() != DnsType::kPTR) {
236*3f982cf4SFabien Sanglard return false;
237*3f982cf4SFabien Sanglard }
238*3f982cf4SFabien Sanglard
239*3f982cf4SFabien Sanglard if (question.name().labels().size() <
240*3f982cf4SFabien Sanglard kServiceEnumerationDomainLabels.size()) {
241*3f982cf4SFabien Sanglard return false;
242*3f982cf4SFabien Sanglard }
243*3f982cf4SFabien Sanglard
244*3f982cf4SFabien Sanglard const auto question_it = question.name().labels().begin();
245*3f982cf4SFabien Sanglard return std::equal(question_it,
246*3f982cf4SFabien Sanglard question_it + kServiceEnumerationDomainLabels.size(),
247*3f982cf4SFabien Sanglard kServiceEnumerationDomainLabels.begin(),
248*3f982cf4SFabien Sanglard kServiceEnumerationDomainLabels.end());
249*3f982cf4SFabien Sanglard }
250*3f982cf4SFabien Sanglard
251*3f982cf4SFabien Sanglard // Creates the expected response to a type enumeration query as described in RFC
252*3f982cf4SFabien Sanglard // 6763 section 9.
ApplyServiceTypeEnumerationResults(MdnsMessage * message,MdnsResponder::RecordHandler * record_handler,const DomainName & name,DnsClass clazz)253*3f982cf4SFabien Sanglard void ApplyServiceTypeEnumerationResults(
254*3f982cf4SFabien Sanglard MdnsMessage* message,
255*3f982cf4SFabien Sanglard MdnsResponder::RecordHandler* record_handler,
256*3f982cf4SFabien Sanglard const DomainName& name,
257*3f982cf4SFabien Sanglard DnsClass clazz) {
258*3f982cf4SFabien Sanglard if (name.labels().size() < kServiceEnumerationDomainLabels.size()) {
259*3f982cf4SFabien Sanglard return;
260*3f982cf4SFabien Sanglard }
261*3f982cf4SFabien Sanglard
262*3f982cf4SFabien Sanglard std::vector<MdnsRecord::ConstRef> records =
263*3f982cf4SFabien Sanglard record_handler->GetPtrRecords(clazz);
264*3f982cf4SFabien Sanglard
265*3f982cf4SFabien Sanglard // skip "_services._dns-sd._udp." which was already checked for in above
266*3f982cf4SFabien Sanglard // method and just use the domain.
267*3f982cf4SFabien Sanglard const auto domain_it =
268*3f982cf4SFabien Sanglard name.labels().begin() + kServiceEnumerationDomainLabels.size();
269*3f982cf4SFabien Sanglard for (const MdnsRecord& record : records) {
270*3f982cf4SFabien Sanglard // Skip the 2 label service name in the PTR record's name.
271*3f982cf4SFabien Sanglard const auto record_it = record.name().labels().begin() + 2;
272*3f982cf4SFabien Sanglard if (std::equal(domain_it, name.labels().end(), record_it,
273*3f982cf4SFabien Sanglard record.name().labels().end())) {
274*3f982cf4SFabien Sanglard message->AddAnswer(MdnsRecord(name, DnsType::kPTR, record.dns_class(),
275*3f982cf4SFabien Sanglard RecordType::kShared, record.ttl(),
276*3f982cf4SFabien Sanglard PtrRecordRdata(record.name())));
277*3f982cf4SFabien Sanglard }
278*3f982cf4SFabien Sanglard }
279*3f982cf4SFabien Sanglard }
280*3f982cf4SFabien Sanglard
IsMultiPacketTruncatedQueryMessage(const MdnsMessage & message)281*3f982cf4SFabien Sanglard bool IsMultiPacketTruncatedQueryMessage(const MdnsMessage& message) {
282*3f982cf4SFabien Sanglard return message.is_truncated() || message.questions().empty();
283*3f982cf4SFabien Sanglard }
284*3f982cf4SFabien Sanglard
285*3f982cf4SFabien Sanglard } // namespace
286*3f982cf4SFabien Sanglard
287*3f982cf4SFabien Sanglard MdnsResponder::RecordHandler::~RecordHandler() = default;
288*3f982cf4SFabien Sanglard
TruncatedQuery(MdnsResponder * responder,TaskRunner * task_runner,ClockNowFunctionPtr now_function,IPEndpoint src,const MdnsMessage & message,const Config & config)289*3f982cf4SFabien Sanglard MdnsResponder::TruncatedQuery::TruncatedQuery(MdnsResponder* responder,
290*3f982cf4SFabien Sanglard TaskRunner* task_runner,
291*3f982cf4SFabien Sanglard ClockNowFunctionPtr now_function,
292*3f982cf4SFabien Sanglard IPEndpoint src,
293*3f982cf4SFabien Sanglard const MdnsMessage& message,
294*3f982cf4SFabien Sanglard const Config& config)
295*3f982cf4SFabien Sanglard : max_allowed_messages_(config.maximum_truncated_messages_per_query),
296*3f982cf4SFabien Sanglard max_allowed_records_(config.maximum_known_answer_records_per_query),
297*3f982cf4SFabien Sanglard src_(std::move(src)),
298*3f982cf4SFabien Sanglard responder_(responder),
299*3f982cf4SFabien Sanglard questions_(message.questions()),
300*3f982cf4SFabien Sanglard known_answers_(message.answers()),
301*3f982cf4SFabien Sanglard alarm_(now_function, task_runner) {
302*3f982cf4SFabien Sanglard OSP_DCHECK(responder_);
303*3f982cf4SFabien Sanglard OSP_DCHECK_GT(max_allowed_messages_, 0);
304*3f982cf4SFabien Sanglard OSP_DCHECK_GT(max_allowed_records_, 0);
305*3f982cf4SFabien Sanglard
306*3f982cf4SFabien Sanglard RescheduleSend();
307*3f982cf4SFabien Sanglard }
308*3f982cf4SFabien Sanglard
SetQuery(const MdnsMessage & message)309*3f982cf4SFabien Sanglard void MdnsResponder::TruncatedQuery::SetQuery(const MdnsMessage& message) {
310*3f982cf4SFabien Sanglard OSP_DCHECK(questions_.empty());
311*3f982cf4SFabien Sanglard questions_.insert(questions_.end(), message.questions().begin(),
312*3f982cf4SFabien Sanglard message.questions().end());
313*3f982cf4SFabien Sanglard
314*3f982cf4SFabien Sanglard // |messages_received_so_far| does not need to be validated here because it is
315*3f982cf4SFabien Sanglard // checked as part of RescheduleSend().
316*3f982cf4SFabien Sanglard known_answers_.insert(known_answers_.end(), message.answers().begin(),
317*3f982cf4SFabien Sanglard message.answers().end());
318*3f982cf4SFabien Sanglard messages_received_so_far++;
319*3f982cf4SFabien Sanglard
320*3f982cf4SFabien Sanglard RescheduleSend();
321*3f982cf4SFabien Sanglard }
322*3f982cf4SFabien Sanglard
AddKnownAnswers(const std::vector<MdnsRecord> & records)323*3f982cf4SFabien Sanglard void MdnsResponder::TruncatedQuery::AddKnownAnswers(
324*3f982cf4SFabien Sanglard const std::vector<MdnsRecord>& records) {
325*3f982cf4SFabien Sanglard // |messages_received_so_far| does not need to be validated here because it is
326*3f982cf4SFabien Sanglard // checked as part of RescheduleSend().
327*3f982cf4SFabien Sanglard known_answers_.insert(known_answers_.end(), records.begin(), records.end());
328*3f982cf4SFabien Sanglard messages_received_so_far++;
329*3f982cf4SFabien Sanglard
330*3f982cf4SFabien Sanglard RescheduleSend();
331*3f982cf4SFabien Sanglard }
332*3f982cf4SFabien Sanglard
RescheduleSend()333*3f982cf4SFabien Sanglard void MdnsResponder::TruncatedQuery::RescheduleSend() {
334*3f982cf4SFabien Sanglard alarm_.Cancel();
335*3f982cf4SFabien Sanglard
336*3f982cf4SFabien Sanglard Clock::duration send_delay;
337*3f982cf4SFabien Sanglard if (messages_received_so_far >= max_allowed_messages_) {
338*3f982cf4SFabien Sanglard // Maximum number of truncated messages have already been received for this
339*3f982cf4SFabien Sanglard // query.
340*3f982cf4SFabien Sanglard send_delay = Clock::duration(0);
341*3f982cf4SFabien Sanglard } else if (known_answers_.size() >=
342*3f982cf4SFabien Sanglard static_cast<size_t>(max_allowed_records_)) {
343*3f982cf4SFabien Sanglard // Maximum number of known answer records have already been received for
344*3f982cf4SFabien Sanglard // this query.
345*3f982cf4SFabien Sanglard send_delay = Clock::duration(0);
346*3f982cf4SFabien Sanglard } else {
347*3f982cf4SFabien Sanglard // Reschedule to send after a random delay, per RFC 6762.
348*3f982cf4SFabien Sanglard send_delay = responder_->random_delay_->GetTruncatedQueryResponseDelay();
349*3f982cf4SFabien Sanglard }
350*3f982cf4SFabien Sanglard
351*3f982cf4SFabien Sanglard alarm_.ScheduleFromNow([this]() { SendResponse(); }, send_delay);
352*3f982cf4SFabien Sanglard }
353*3f982cf4SFabien Sanglard
SendResponse()354*3f982cf4SFabien Sanglard void MdnsResponder::TruncatedQuery::SendResponse() {
355*3f982cf4SFabien Sanglard alarm_.Cancel();
356*3f982cf4SFabien Sanglard
357*3f982cf4SFabien Sanglard if (questions_.empty()) {
358*3f982cf4SFabien Sanglard OSP_DVLOG << "Known answers received for unknown query, and non received "
359*3f982cf4SFabien Sanglard "after delay. Dropping them...";
360*3f982cf4SFabien Sanglard return;
361*3f982cf4SFabien Sanglard }
362*3f982cf4SFabien Sanglard
363*3f982cf4SFabien Sanglard responder_->RespondToTruncatedQuery(this);
364*3f982cf4SFabien Sanglard }
365*3f982cf4SFabien Sanglard
MdnsResponder(RecordHandler * record_handler,MdnsProbeManager * ownership_handler,MdnsSender * sender,MdnsReceiver * receiver,TaskRunner * task_runner,ClockNowFunctionPtr now_function,MdnsRandom * random_delay,const Config & config)366*3f982cf4SFabien Sanglard MdnsResponder::MdnsResponder(RecordHandler* record_handler,
367*3f982cf4SFabien Sanglard MdnsProbeManager* ownership_handler,
368*3f982cf4SFabien Sanglard MdnsSender* sender,
369*3f982cf4SFabien Sanglard MdnsReceiver* receiver,
370*3f982cf4SFabien Sanglard TaskRunner* task_runner,
371*3f982cf4SFabien Sanglard ClockNowFunctionPtr now_function,
372*3f982cf4SFabien Sanglard MdnsRandom* random_delay,
373*3f982cf4SFabien Sanglard const Config& config)
374*3f982cf4SFabien Sanglard : record_handler_(record_handler),
375*3f982cf4SFabien Sanglard ownership_handler_(ownership_handler),
376*3f982cf4SFabien Sanglard sender_(sender),
377*3f982cf4SFabien Sanglard receiver_(receiver),
378*3f982cf4SFabien Sanglard task_runner_(task_runner),
379*3f982cf4SFabien Sanglard now_function_(now_function),
380*3f982cf4SFabien Sanglard random_delay_(random_delay),
381*3f982cf4SFabien Sanglard config_(config) {
382*3f982cf4SFabien Sanglard OSP_DCHECK(record_handler_);
383*3f982cf4SFabien Sanglard OSP_DCHECK(ownership_handler_);
384*3f982cf4SFabien Sanglard OSP_DCHECK(sender_);
385*3f982cf4SFabien Sanglard OSP_DCHECK(receiver_);
386*3f982cf4SFabien Sanglard OSP_DCHECK(task_runner_);
387*3f982cf4SFabien Sanglard OSP_DCHECK(random_delay_);
388*3f982cf4SFabien Sanglard OSP_DCHECK_GT(config_.maximum_truncated_messages_per_query, 0);
389*3f982cf4SFabien Sanglard OSP_DCHECK_GT(config_.maximum_concurrent_truncated_queries_per_interface, 0);
390*3f982cf4SFabien Sanglard
391*3f982cf4SFabien Sanglard auto func = [this](const MdnsMessage& message, const IPEndpoint& src) {
392*3f982cf4SFabien Sanglard OnMessageReceived(message, src);
393*3f982cf4SFabien Sanglard };
394*3f982cf4SFabien Sanglard receiver_->SetQueryCallback(std::move(func));
395*3f982cf4SFabien Sanglard }
396*3f982cf4SFabien Sanglard
~MdnsResponder()397*3f982cf4SFabien Sanglard MdnsResponder::~MdnsResponder() {
398*3f982cf4SFabien Sanglard receiver_->SetQueryCallback(nullptr);
399*3f982cf4SFabien Sanglard }
400*3f982cf4SFabien Sanglard
OnMessageReceived(const MdnsMessage & message,const IPEndpoint & src)401*3f982cf4SFabien Sanglard void MdnsResponder::OnMessageReceived(const MdnsMessage& message,
402*3f982cf4SFabien Sanglard const IPEndpoint& src) {
403*3f982cf4SFabien Sanglard OSP_DCHECK(task_runner_->IsRunningOnTaskRunner());
404*3f982cf4SFabien Sanglard OSP_DCHECK(message.type() == MessageType::Query);
405*3f982cf4SFabien Sanglard
406*3f982cf4SFabien Sanglard // Handle multi-packet known answer suppression.
407*3f982cf4SFabien Sanglard if (IsMultiPacketTruncatedQueryMessage(message)) {
408*3f982cf4SFabien Sanglard // If there have been an excessive number of known answers received already,
409*3f982cf4SFabien Sanglard // then skip them. This would most likely mean that:
410*3f982cf4SFabien Sanglard // - A host on the network is misbehaving.
411*3f982cf4SFabien Sanglard // - There is a malicious actor on the network.
412*3f982cf4SFabien Sanglard // In either of these cases, optimize for this host's resource usage.
413*3f982cf4SFabien Sanglard if (truncated_queries_.size() >
414*3f982cf4SFabien Sanglard static_cast<size_t>(
415*3f982cf4SFabien Sanglard config_.maximum_concurrent_truncated_queries_per_interface)) {
416*3f982cf4SFabien Sanglard OSP_DVLOG << "Too many truncated queries have been received. Treating "
417*3f982cf4SFabien Sanglard "new multi-packet known answer message as normal query";
418*3f982cf4SFabien Sanglard } else {
419*3f982cf4SFabien Sanglard ProcessMultiPacketTruncatedMessage(message, src);
420*3f982cf4SFabien Sanglard return;
421*3f982cf4SFabien Sanglard }
422*3f982cf4SFabien Sanglard }
423*3f982cf4SFabien Sanglard
424*3f982cf4SFabien Sanglard // If the query is a probe query, it will be handled separately by the
425*3f982cf4SFabien Sanglard // MdnsProbeManager. Ignore it here.
426*3f982cf4SFabien Sanglard if (message.IsProbeQuery()) {
427*3f982cf4SFabien Sanglard ownership_handler_->RespondToProbeQuery(message, src);
428*3f982cf4SFabien Sanglard return;
429*3f982cf4SFabien Sanglard }
430*3f982cf4SFabien Sanglard
431*3f982cf4SFabien Sanglard // Else, this is a normal query. Process it as such.
432*3f982cf4SFabien Sanglard // This is the case that should be hit 95+% of the time.
433*3f982cf4SFabien Sanglard OSP_DVLOG << "Received mDNS Query with " << message.questions().size()
434*3f982cf4SFabien Sanglard << " questions. Processing...";
435*3f982cf4SFabien Sanglard const std::vector<MdnsRecord>& known_answers = message.answers();
436*3f982cf4SFabien Sanglard const std::vector<MdnsQuestion>& questions = message.questions();
437*3f982cf4SFabien Sanglard ProcessQueries(src, questions, known_answers);
438*3f982cf4SFabien Sanglard }
439*3f982cf4SFabien Sanglard
ProcessMultiPacketTruncatedMessage(const MdnsMessage & message,const IPEndpoint & src)440*3f982cf4SFabien Sanglard void MdnsResponder::ProcessMultiPacketTruncatedMessage(
441*3f982cf4SFabien Sanglard const MdnsMessage& message,
442*3f982cf4SFabien Sanglard const IPEndpoint& src) {
443*3f982cf4SFabien Sanglard OSP_DVLOG << "Multi-packet truncated message received. Processing...";
444*3f982cf4SFabien Sanglard
445*3f982cf4SFabien Sanglard const bool message_has_question = !message.questions().empty();
446*3f982cf4SFabien Sanglard const bool message_is_truncated = message.is_truncated();
447*3f982cf4SFabien Sanglard OSP_DCHECK(!message_has_question || message_is_truncated);
448*3f982cf4SFabien Sanglard
449*3f982cf4SFabien Sanglard auto pair =
450*3f982cf4SFabien Sanglard truncated_queries_.emplace(src, std::unique_ptr<TruncatedQuery>());
451*3f982cf4SFabien Sanglard std::unique_ptr<TruncatedQuery>& stored_query = pair.first->second;
452*3f982cf4SFabien Sanglard
453*3f982cf4SFabien Sanglard // First, handle the case where this host doesn't have a known answer query
454*3f982cf4SFabien Sanglard // tracked yet. In this case, start tracking the new query.
455*3f982cf4SFabien Sanglard if (pair.second) {
456*3f982cf4SFabien Sanglard // Create a new query and swap it with the old one to save an extra lookup.
457*3f982cf4SFabien Sanglard auto new_query = std::make_unique<TruncatedQuery>(
458*3f982cf4SFabien Sanglard this, task_runner_, now_function_, src, message, config_);
459*3f982cf4SFabien Sanglard stored_query.swap(new_query);
460*3f982cf4SFabien Sanglard return;
461*3f982cf4SFabien Sanglard }
462*3f982cf4SFabien Sanglard
463*3f982cf4SFabien Sanglard // Else, there was already a message received from this host.
464*3f982cf4SFabien Sanglard const bool are_questions_already_stored = !stored_query->questions().empty();
465*3f982cf4SFabien Sanglard
466*3f982cf4SFabien Sanglard // If the new message doesn't have a question, then it must be additional
467*3f982cf4SFabien Sanglard // known answers. Add them to the set of known answers for this truncated
468*3f982cf4SFabien Sanglard // query.
469*3f982cf4SFabien Sanglard if (!message_has_question) {
470*3f982cf4SFabien Sanglard stored_query->AddKnownAnswers(message.answers());
471*3f982cf4SFabien Sanglard return;
472*3f982cf4SFabien Sanglard }
473*3f982cf4SFabien Sanglard
474*3f982cf4SFabien Sanglard // Alternatively, if a record for this host existed, it might be because the
475*3f982cf4SFabien Sanglard // messages were received out-of-order and known answers have already been
476*3f982cf4SFabien Sanglard // received. In this case, associate the new message's query with the known
477*3f982cf4SFabien Sanglard // answers already received.
478*3f982cf4SFabien Sanglard if (!are_questions_already_stored) {
479*3f982cf4SFabien Sanglard stored_query->SetQuery(message);
480*3f982cf4SFabien Sanglard return;
481*3f982cf4SFabien Sanglard }
482*3f982cf4SFabien Sanglard
483*3f982cf4SFabien Sanglard // Else, an ongoing truncated query is already associated with this host and a
484*3f982cf4SFabien Sanglard // new one has also been received. This implies one of the following occurred:
485*3f982cf4SFabien Sanglard // - The sender must have finished sending packets.
486*3f982cf4SFabien Sanglard // - The known answers completing this query somehow got lost on the network.
487*3f982cf4SFabien Sanglard // - A second truncated query was started by the same host, and this host
488*3f982cf4SFabien Sanglard // won't be able to differentiate which query future known answers are
489*3f982cf4SFabien Sanglard // associated with.
490*3f982cf4SFabien Sanglard // In any of these cases, there's no reason to continue tracking the old
491*3f982cf4SFabien Sanglard // query. So process it.
492*3f982cf4SFabien Sanglard //
493*3f982cf4SFabien Sanglard // Create a new query and swap it with the old one to save an extra lookup.
494*3f982cf4SFabien Sanglard auto new_query = std::make_unique<TruncatedQuery>(
495*3f982cf4SFabien Sanglard this, task_runner_, now_function_, src, message, config_);
496*3f982cf4SFabien Sanglard stored_query.swap(new_query);
497*3f982cf4SFabien Sanglard
498*3f982cf4SFabien Sanglard // Now that the pointers have been swapped, process the previously stored
499*3f982cf4SFabien Sanglard // query.
500*3f982cf4SFabien Sanglard new_query->SendResponse();
501*3f982cf4SFabien Sanglard }
502*3f982cf4SFabien Sanglard
RespondToTruncatedQuery(TruncatedQuery * query)503*3f982cf4SFabien Sanglard void MdnsResponder::RespondToTruncatedQuery(TruncatedQuery* query) {
504*3f982cf4SFabien Sanglard ProcessQueries(query->src(), query->questions(), query->known_answers());
505*3f982cf4SFabien Sanglard auto it = truncated_queries_.find(query->src());
506*3f982cf4SFabien Sanglard
507*3f982cf4SFabien Sanglard if (it == truncated_queries_.end()) {
508*3f982cf4SFabien Sanglard return;
509*3f982cf4SFabien Sanglard }
510*3f982cf4SFabien Sanglard
511*3f982cf4SFabien Sanglard // If a second query for this same host arrives, then the question found may
512*3f982cf4SFabien Sanglard // not match what is being sent due to the swap done in OnMessageReceived().
513*3f982cf4SFabien Sanglard if (it->second.get() == query) {
514*3f982cf4SFabien Sanglard truncated_queries_.erase(it);
515*3f982cf4SFabien Sanglard }
516*3f982cf4SFabien Sanglard }
517*3f982cf4SFabien Sanglard
ProcessQueries(const IPEndpoint & src,const std::vector<MdnsQuestion> & questions,const std::vector<MdnsRecord> & known_answers)518*3f982cf4SFabien Sanglard void MdnsResponder::ProcessQueries(
519*3f982cf4SFabien Sanglard const IPEndpoint& src,
520*3f982cf4SFabien Sanglard const std::vector<MdnsQuestion>& questions,
521*3f982cf4SFabien Sanglard const std::vector<MdnsRecord>& known_answers) {
522*3f982cf4SFabien Sanglard for (const auto& question : questions) {
523*3f982cf4SFabien Sanglard OSP_DVLOG << "\tProcessing mDNS Query for domain: '"
524*3f982cf4SFabien Sanglard << question.name().ToString() << "', type: '"
525*3f982cf4SFabien Sanglard << question.dns_type() << "' from '" << src << "'";
526*3f982cf4SFabien Sanglard
527*3f982cf4SFabien Sanglard // NSEC records should not be queried for.
528*3f982cf4SFabien Sanglard if (question.dns_type() == DnsType::kNSEC) {
529*3f982cf4SFabien Sanglard continue;
530*3f982cf4SFabien Sanglard }
531*3f982cf4SFabien Sanglard
532*3f982cf4SFabien Sanglard // Only respond to queries for which one of the following is true:
533*3f982cf4SFabien Sanglard // - This host is the sole owner of that domain.
534*3f982cf4SFabien Sanglard // - A record corresponding to this question has been published.
535*3f982cf4SFabien Sanglard // - The query is a service enumeration query.
536*3f982cf4SFabien Sanglard const bool is_service_enumeration = IsServiceTypeEnumerationQuery(question);
537*3f982cf4SFabien Sanglard const bool is_exclusive_owner =
538*3f982cf4SFabien Sanglard ownership_handler_->IsDomainClaimed(question.name());
539*3f982cf4SFabien Sanglard if (!is_service_enumeration && !is_exclusive_owner &&
540*3f982cf4SFabien Sanglard !record_handler_->HasRecords(question.name(), question.dns_type(),
541*3f982cf4SFabien Sanglard question.dns_class())) {
542*3f982cf4SFabien Sanglard OSP_DVLOG << "\tmDNS Query processed and no relevant records found!";
543*3f982cf4SFabien Sanglard continue;
544*3f982cf4SFabien Sanglard } else if (is_service_enumeration) {
545*3f982cf4SFabien Sanglard OSP_DVLOG << "\tmDNS Query is for service type enumeration!";
546*3f982cf4SFabien Sanglard }
547*3f982cf4SFabien Sanglard
548*3f982cf4SFabien Sanglard // Relevant records are published, so send them out using the response type
549*3f982cf4SFabien Sanglard // dictated in the question.
550*3f982cf4SFabien Sanglard std::function<void(const MdnsMessage&)> send_response;
551*3f982cf4SFabien Sanglard if (question.response_type() == ResponseType::kMulticast) {
552*3f982cf4SFabien Sanglard send_response = [this](const MdnsMessage& message) {
553*3f982cf4SFabien Sanglard sender_->SendMulticast(message);
554*3f982cf4SFabien Sanglard };
555*3f982cf4SFabien Sanglard } else {
556*3f982cf4SFabien Sanglard OSP_DCHECK(question.response_type() == ResponseType::kUnicast);
557*3f982cf4SFabien Sanglard send_response = [this, src](const MdnsMessage& message) {
558*3f982cf4SFabien Sanglard sender_->SendMessage(message, src);
559*3f982cf4SFabien Sanglard };
560*3f982cf4SFabien Sanglard }
561*3f982cf4SFabien Sanglard
562*3f982cf4SFabien Sanglard // If this host is the exclusive owner, respond immediately. Else, there may
563*3f982cf4SFabien Sanglard // be network contention if all hosts respond simultaneously, so delay the
564*3f982cf4SFabien Sanglard // response as dictated by RFC 6762.
565*3f982cf4SFabien Sanglard if (is_exclusive_owner) {
566*3f982cf4SFabien Sanglard SendResponse(question, known_answers, send_response, is_exclusive_owner);
567*3f982cf4SFabien Sanglard } else {
568*3f982cf4SFabien Sanglard const auto delay = random_delay_->GetSharedRecordResponseDelay();
569*3f982cf4SFabien Sanglard std::function<void()> response = [this, question, known_answers,
570*3f982cf4SFabien Sanglard send_response, is_exclusive_owner]() {
571*3f982cf4SFabien Sanglard SendResponse(question, known_answers, send_response,
572*3f982cf4SFabien Sanglard is_exclusive_owner);
573*3f982cf4SFabien Sanglard };
574*3f982cf4SFabien Sanglard task_runner_->PostTaskWithDelay(response, delay);
575*3f982cf4SFabien Sanglard }
576*3f982cf4SFabien Sanglard }
577*3f982cf4SFabien Sanglard }
578*3f982cf4SFabien Sanglard
SendResponse(const MdnsQuestion & question,const std::vector<MdnsRecord> & known_answers,std::function<void (const MdnsMessage &)> send_response,bool is_exclusive_owner)579*3f982cf4SFabien Sanglard void MdnsResponder::SendResponse(
580*3f982cf4SFabien Sanglard const MdnsQuestion& question,
581*3f982cf4SFabien Sanglard const std::vector<MdnsRecord>& known_answers,
582*3f982cf4SFabien Sanglard std::function<void(const MdnsMessage&)> send_response,
583*3f982cf4SFabien Sanglard bool is_exclusive_owner) {
584*3f982cf4SFabien Sanglard OSP_DCHECK(task_runner_->IsRunningOnTaskRunner());
585*3f982cf4SFabien Sanglard
586*3f982cf4SFabien Sanglard MdnsMessage message(CreateMessageId(), MessageType::Response);
587*3f982cf4SFabien Sanglard
588*3f982cf4SFabien Sanglard if (IsServiceTypeEnumerationQuery(question)) {
589*3f982cf4SFabien Sanglard // This is a special case defined in RFC 6763 section 9, so handle it
590*3f982cf4SFabien Sanglard // separately.
591*3f982cf4SFabien Sanglard ApplyServiceTypeEnumerationResults(&message, record_handler_,
592*3f982cf4SFabien Sanglard question.name(), question.dns_class());
593*3f982cf4SFabien Sanglard } else {
594*3f982cf4SFabien Sanglard // NOTE: The exclusive ownership of this record cannot change before this
595*3f982cf4SFabien Sanglard // method is called. Exclusive ownership cannot be gained for a record which
596*3f982cf4SFabien Sanglard // has previously been published, and if this host is the exclusive owner
597*3f982cf4SFabien Sanglard // then this method will have been called without any delay on the task
598*3f982cf4SFabien Sanglard // runner.
599*3f982cf4SFabien Sanglard ApplyQueryResults(&message, record_handler_, question.name(), known_answers,
600*3f982cf4SFabien Sanglard question.dns_type(), question.dns_class(),
601*3f982cf4SFabien Sanglard is_exclusive_owner);
602*3f982cf4SFabien Sanglard }
603*3f982cf4SFabien Sanglard
604*3f982cf4SFabien Sanglard // Send the response only if it contains answers to the query.
605*3f982cf4SFabien Sanglard OSP_DVLOG << "\tCompleted Processing mDNS Query for domain: '"
606*3f982cf4SFabien Sanglard << question.name().ToString() << "', type: '" << question.dns_type()
607*3f982cf4SFabien Sanglard << "', with " << message.answers().size() << " results:";
608*3f982cf4SFabien Sanglard for (const auto& record : message.answers()) {
609*3f982cf4SFabien Sanglard OSP_DVLOG << "\t\tanswer (" << record.ToString() << ")";
610*3f982cf4SFabien Sanglard }
611*3f982cf4SFabien Sanglard for (const auto& record : message.additional_records()) {
612*3f982cf4SFabien Sanglard OSP_DVLOG << "\t\tadditional record ('" << record.ToString() << ")";
613*3f982cf4SFabien Sanglard }
614*3f982cf4SFabien Sanglard
615*3f982cf4SFabien Sanglard if (!message.answers().empty()) {
616*3f982cf4SFabien Sanglard send_response(message);
617*3f982cf4SFabien Sanglard }
618*3f982cf4SFabien Sanglard }
619*3f982cf4SFabien Sanglard
620*3f982cf4SFabien Sanglard } // namespace discovery
621*3f982cf4SFabien Sanglard } // namespace openscreen
622