You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
622 lines
24 KiB
622 lines
24 KiB
// Copyright 2019 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "discovery/mdns/mdns_responder.h"
|
|
|
|
#include <array>
|
|
#include <string>
|
|
#include <utility>
|
|
|
|
#include "discovery/common/config.h"
|
|
#include "discovery/mdns/mdns_probe_manager.h"
|
|
#include "discovery/mdns/mdns_publisher.h"
|
|
#include "discovery/mdns/mdns_querier.h"
|
|
#include "discovery/mdns/mdns_random.h"
|
|
#include "discovery/mdns/mdns_receiver.h"
|
|
#include "discovery/mdns/mdns_sender.h"
|
|
#include "platform/api/task_runner.h"
|
|
|
|
namespace openscreen {
|
|
namespace discovery {
|
|
namespace {
|
|
|
|
constexpr std::array<const char*, 3> kServiceEnumerationDomainLabels{
|
|
"_services", "_dns-sd", "_udp"};
|
|
|
|
enum AddResult { kNonePresent = 0, kAdded, kAlreadyKnown };
|
|
|
|
std::chrono::seconds GetTtlForNsecTargetingType(DnsType type) {
|
|
// NOTE: A 'default' switch statement has intentionally been avoided below to
|
|
// enforce that new DnsTypes added must be added below through a compile-time
|
|
// check.
|
|
switch (type) {
|
|
case DnsType::kA:
|
|
return kARecordTtl;
|
|
case DnsType::kAAAA:
|
|
return kAAAARecordTtl;
|
|
case DnsType::kPTR:
|
|
return kPtrRecordTtl;
|
|
case DnsType::kSRV:
|
|
return kSrvRecordTtl;
|
|
case DnsType::kTXT:
|
|
return kTXTRecordTtl;
|
|
case DnsType::kANY:
|
|
// If no records are present, re-querying should happen at the minimum
|
|
// of any record that might be retrieved at that time.
|
|
return kSrvRecordTtl;
|
|
case DnsType::kNSEC:
|
|
case DnsType::kOPT:
|
|
// Neither of these types should ever be hit. We should never be creating
|
|
// an NSEC record for type NSEC, and OPT record querying is not supported,
|
|
// so creating NSEC records for type OPT is not valid.
|
|
break;
|
|
}
|
|
|
|
OSP_NOTREACHED();
|
|
}
|
|
|
|
MdnsRecord CreateNsecRecord(DomainName target_name,
|
|
DnsType target_type,
|
|
DnsClass target_class) {
|
|
auto rdata = NsecRecordRdata(target_name, target_type);
|
|
std::chrono::seconds ttl = GetTtlForNsecTargetingType(target_type);
|
|
return MdnsRecord(std::move(target_name), DnsType::kNSEC, target_class,
|
|
RecordType::kUnique, ttl, std::move(rdata));
|
|
}
|
|
|
|
inline bool IsValidAdditionalRecordType(DnsType type) {
|
|
return type == DnsType::kSRV || type == DnsType::kTXT ||
|
|
type == DnsType::kA || type == DnsType::kAAAA;
|
|
}
|
|
|
|
AddResult 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) {
|
|
auto records = record_handler->GetRecords(domain, type, clazz);
|
|
if (records.empty()) {
|
|
if (add_negative_on_unknown) {
|
|
// TODO(rwkeane): Aggregate all NSEC records together into a single NSEC
|
|
// record to reduce traffic.
|
|
add_func(CreateNsecRecord(domain, type, clazz));
|
|
}
|
|
return AddResult::kNonePresent;
|
|
} else {
|
|
bool added_any_records = false;
|
|
for (auto it = records.begin(); it != records.end(); it++) {
|
|
if (std::find(known_answers.begin(), known_answers.end(), *it) ==
|
|
known_answers.end()) {
|
|
added_any_records = true;
|
|
add_func(std::move(*it));
|
|
}
|
|
}
|
|
return added_any_records ? AddResult::kAdded : AddResult::kAlreadyKnown;
|
|
}
|
|
}
|
|
|
|
inline AddResult 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) {
|
|
OSP_DCHECK(IsValidAdditionalRecordType(type));
|
|
|
|
auto add_func = [message](MdnsRecord record) {
|
|
message->AddAdditionalRecord(std::move(record));
|
|
};
|
|
return AddRecords(std::move(add_func), record_handler, domain, known_answers,
|
|
type, clazz, add_negative_on_unknown);
|
|
}
|
|
|
|
inline AddResult 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) {
|
|
auto add_func = [message](MdnsRecord record) {
|
|
message->AddAnswer(std::move(record));
|
|
};
|
|
return AddRecords(std::move(add_func), record_handler, domain, known_answers,
|
|
type, clazz, add_negative_on_unknown);
|
|
}
|
|
|
|
void ApplyQueryResults(MdnsMessage* message,
|
|
MdnsResponder::RecordHandler* record_handler,
|
|
const DomainName& domain,
|
|
const std::vector<MdnsRecord>& known_answers,
|
|
DnsType type,
|
|
DnsClass clazz,
|
|
bool is_exclusive_owner) {
|
|
OSP_DCHECK(type != DnsType::kNSEC);
|
|
|
|
// All records matching the provided query which have been published by this
|
|
// host should be added to the response message per RFC 6762 section 6. If
|
|
// this host is the exclusive owner of the queried domain name, then a
|
|
// negative response NSEC record should be added in the case where the queried
|
|
// record does not exist, per RFC 6762 section 6.1.
|
|
if (AddResponseRecords(message, record_handler, domain, known_answers, type,
|
|
clazz, is_exclusive_owner) != AddResult::kAdded) {
|
|
return;
|
|
}
|
|
|
|
// Per RFC 6763 section 12.1, when querying for a PTR record, all SRV records
|
|
// and TXT records named in the PTR record's rdata should be added to the
|
|
// messages additional records, as well as the address records of types A and
|
|
// AAAA associated with the added SRV records. Per RFC 6762 section 6.1,
|
|
// records with names matching those of reverse address mappings for PTR
|
|
// records may be added as negative response NSEC records if they do not
|
|
// exist.
|
|
if (type == DnsType::kPTR) {
|
|
// Add all SRV and TXT records to the additional records section.
|
|
for (const MdnsRecord& record : message->answers()) {
|
|
OSP_DCHECK(record.dns_type() == DnsType::kPTR);
|
|
|
|
const DomainName& target =
|
|
absl::get<PtrRecordRdata>(record.rdata()).ptr_domain();
|
|
AddAdditionalRecords(message, record_handler, target, known_answers,
|
|
DnsType::kSRV, clazz, true);
|
|
AddAdditionalRecords(message, record_handler, target, known_answers,
|
|
DnsType::kTXT, clazz, true);
|
|
}
|
|
|
|
// Add A and AAAA records associated with an added SRV record to the
|
|
// additional records section.
|
|
const int max = message->additional_records().size();
|
|
for (int i = 0; i < max; i++) {
|
|
if (message->additional_records()[i].dns_type() != DnsType::kSRV) {
|
|
continue;
|
|
}
|
|
|
|
{
|
|
const MdnsRecord& srv_record = message->additional_records()[i];
|
|
const DomainName& target =
|
|
absl::get<SrvRecordRdata>(srv_record.rdata()).target();
|
|
AddAdditionalRecords(message, record_handler, target, known_answers,
|
|
DnsType::kA, clazz, target == domain);
|
|
}
|
|
|
|
// Must re-calculate the |srv_record|, |target| refs in case a resize of
|
|
// the additional_records() vector has invalidated them.
|
|
{
|
|
const MdnsRecord& srv_record = message->additional_records()[i];
|
|
const DomainName& target =
|
|
absl::get<SrvRecordRdata>(srv_record.rdata()).target();
|
|
AddAdditionalRecords(message, record_handler, target, known_answers,
|
|
DnsType::kAAAA, clazz, target == domain);
|
|
}
|
|
}
|
|
} else if (type == DnsType::kSRV) {
|
|
// Per RFC 6763 section 12.2, when querying for an SRV record, all address
|
|
// records of type A and AAAA should be added to the additional records
|
|
// section. Per RFC 6762 section 6.1, if these records are not present and
|
|
// their name and class match that which is being queried for, a negative
|
|
// response NSEC record may be added to show their non-existence.
|
|
for (const auto& srv_record : message->answers()) {
|
|
OSP_DCHECK(srv_record.dns_type() == DnsType::kSRV);
|
|
|
|
const DomainName& target =
|
|
absl::get<SrvRecordRdata>(srv_record.rdata()).target();
|
|
AddAdditionalRecords(message, record_handler, target, known_answers,
|
|
DnsType::kA, clazz, target == domain);
|
|
AddAdditionalRecords(message, record_handler, target, known_answers,
|
|
DnsType::kAAAA, clazz, target == domain);
|
|
}
|
|
} else if (type == DnsType::kA) {
|
|
// Per RFC 6762 section 6.2, when querying for an address record of type A
|
|
// or AAAA, the record of the opposite type should be added to the
|
|
// additional records section if present. Else, a negative response NSEC
|
|
// record should be added to show its non-existence.
|
|
AddAdditionalRecords(message, record_handler, domain, known_answers,
|
|
DnsType::kAAAA, clazz, true);
|
|
} else if (type == DnsType::kAAAA) {
|
|
AddAdditionalRecords(message, record_handler, domain, known_answers,
|
|
DnsType::kA, clazz, true);
|
|
}
|
|
|
|
// The remaining supported records types are TXT, NSEC, and ANY. RFCs 6762 and
|
|
// 6763 do not recommend sending any records in the additional records section
|
|
// for queries of types TXT or ANY, and NSEC records are not supported for
|
|
// queries.
|
|
}
|
|
|
|
// Determines if the provided query is a type enumeration query as described in
|
|
// RFC 6763 section 9.
|
|
bool IsServiceTypeEnumerationQuery(const MdnsQuestion& question) {
|
|
if (question.dns_type() != DnsType::kPTR) {
|
|
return false;
|
|
}
|
|
|
|
if (question.name().labels().size() <
|
|
kServiceEnumerationDomainLabels.size()) {
|
|
return false;
|
|
}
|
|
|
|
const auto question_it = question.name().labels().begin();
|
|
return std::equal(question_it,
|
|
question_it + kServiceEnumerationDomainLabels.size(),
|
|
kServiceEnumerationDomainLabels.begin(),
|
|
kServiceEnumerationDomainLabels.end());
|
|
}
|
|
|
|
// Creates the expected response to a type enumeration query as described in RFC
|
|
// 6763 section 9.
|
|
void ApplyServiceTypeEnumerationResults(
|
|
MdnsMessage* message,
|
|
MdnsResponder::RecordHandler* record_handler,
|
|
const DomainName& name,
|
|
DnsClass clazz) {
|
|
if (name.labels().size() < kServiceEnumerationDomainLabels.size()) {
|
|
return;
|
|
}
|
|
|
|
std::vector<MdnsRecord::ConstRef> records =
|
|
record_handler->GetPtrRecords(clazz);
|
|
|
|
// skip "_services._dns-sd._udp." which was already checked for in above
|
|
// method and just use the domain.
|
|
const auto domain_it =
|
|
name.labels().begin() + kServiceEnumerationDomainLabels.size();
|
|
for (const MdnsRecord& record : records) {
|
|
// Skip the 2 label service name in the PTR record's name.
|
|
const auto record_it = record.name().labels().begin() + 2;
|
|
if (std::equal(domain_it, name.labels().end(), record_it,
|
|
record.name().labels().end())) {
|
|
message->AddAnswer(MdnsRecord(name, DnsType::kPTR, record.dns_class(),
|
|
RecordType::kShared, record.ttl(),
|
|
PtrRecordRdata(record.name())));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool IsMultiPacketTruncatedQueryMessage(const MdnsMessage& message) {
|
|
return message.is_truncated() || message.questions().empty();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
MdnsResponder::RecordHandler::~RecordHandler() = default;
|
|
|
|
MdnsResponder::TruncatedQuery::TruncatedQuery(MdnsResponder* responder,
|
|
TaskRunner* task_runner,
|
|
ClockNowFunctionPtr now_function,
|
|
IPEndpoint src,
|
|
const MdnsMessage& message,
|
|
const Config& config)
|
|
: max_allowed_messages_(config.maximum_truncated_messages_per_query),
|
|
max_allowed_records_(config.maximum_known_answer_records_per_query),
|
|
src_(std::move(src)),
|
|
responder_(responder),
|
|
questions_(message.questions()),
|
|
known_answers_(message.answers()),
|
|
alarm_(now_function, task_runner) {
|
|
OSP_DCHECK(responder_);
|
|
OSP_DCHECK_GT(max_allowed_messages_, 0);
|
|
OSP_DCHECK_GT(max_allowed_records_, 0);
|
|
|
|
RescheduleSend();
|
|
}
|
|
|
|
void MdnsResponder::TruncatedQuery::SetQuery(const MdnsMessage& message) {
|
|
OSP_DCHECK(questions_.empty());
|
|
questions_.insert(questions_.end(), message.questions().begin(),
|
|
message.questions().end());
|
|
|
|
// |messages_received_so_far| does not need to be validated here because it is
|
|
// checked as part of RescheduleSend().
|
|
known_answers_.insert(known_answers_.end(), message.answers().begin(),
|
|
message.answers().end());
|
|
messages_received_so_far++;
|
|
|
|
RescheduleSend();
|
|
}
|
|
|
|
void MdnsResponder::TruncatedQuery::AddKnownAnswers(
|
|
const std::vector<MdnsRecord>& records) {
|
|
// |messages_received_so_far| does not need to be validated here because it is
|
|
// checked as part of RescheduleSend().
|
|
known_answers_.insert(known_answers_.end(), records.begin(), records.end());
|
|
messages_received_so_far++;
|
|
|
|
RescheduleSend();
|
|
}
|
|
|
|
void MdnsResponder::TruncatedQuery::RescheduleSend() {
|
|
alarm_.Cancel();
|
|
|
|
Clock::duration send_delay;
|
|
if (messages_received_so_far >= max_allowed_messages_) {
|
|
// Maximum number of truncated messages have already been received for this
|
|
// query.
|
|
send_delay = Clock::duration(0);
|
|
} else if (known_answers_.size() >=
|
|
static_cast<size_t>(max_allowed_records_)) {
|
|
// Maximum number of known answer records have already been received for
|
|
// this query.
|
|
send_delay = Clock::duration(0);
|
|
} else {
|
|
// Reschedule to send after a random delay, per RFC 6762.
|
|
send_delay = responder_->random_delay_->GetTruncatedQueryResponseDelay();
|
|
}
|
|
|
|
alarm_.ScheduleFromNow([this]() { SendResponse(); }, send_delay);
|
|
}
|
|
|
|
void MdnsResponder::TruncatedQuery::SendResponse() {
|
|
alarm_.Cancel();
|
|
|
|
if (questions_.empty()) {
|
|
OSP_DVLOG << "Known answers received for unknown query, and non received "
|
|
"after delay. Dropping them...";
|
|
return;
|
|
}
|
|
|
|
responder_->RespondToTruncatedQuery(this);
|
|
}
|
|
|
|
MdnsResponder::MdnsResponder(RecordHandler* record_handler,
|
|
MdnsProbeManager* ownership_handler,
|
|
MdnsSender* sender,
|
|
MdnsReceiver* receiver,
|
|
TaskRunner* task_runner,
|
|
ClockNowFunctionPtr now_function,
|
|
MdnsRandom* random_delay,
|
|
const Config& config)
|
|
: record_handler_(record_handler),
|
|
ownership_handler_(ownership_handler),
|
|
sender_(sender),
|
|
receiver_(receiver),
|
|
task_runner_(task_runner),
|
|
now_function_(now_function),
|
|
random_delay_(random_delay),
|
|
config_(config) {
|
|
OSP_DCHECK(record_handler_);
|
|
OSP_DCHECK(ownership_handler_);
|
|
OSP_DCHECK(sender_);
|
|
OSP_DCHECK(receiver_);
|
|
OSP_DCHECK(task_runner_);
|
|
OSP_DCHECK(random_delay_);
|
|
OSP_DCHECK_GT(config_.maximum_truncated_messages_per_query, 0);
|
|
OSP_DCHECK_GT(config_.maximum_concurrent_truncated_queries_per_interface, 0);
|
|
|
|
auto func = [this](const MdnsMessage& message, const IPEndpoint& src) {
|
|
OnMessageReceived(message, src);
|
|
};
|
|
receiver_->SetQueryCallback(std::move(func));
|
|
}
|
|
|
|
MdnsResponder::~MdnsResponder() {
|
|
receiver_->SetQueryCallback(nullptr);
|
|
}
|
|
|
|
void MdnsResponder::OnMessageReceived(const MdnsMessage& message,
|
|
const IPEndpoint& src) {
|
|
OSP_DCHECK(task_runner_->IsRunningOnTaskRunner());
|
|
OSP_DCHECK(message.type() == MessageType::Query);
|
|
|
|
// Handle multi-packet known answer suppression.
|
|
if (IsMultiPacketTruncatedQueryMessage(message)) {
|
|
// If there have been an excessive number of known answers received already,
|
|
// then skip them. This would most likely mean that:
|
|
// - A host on the network is misbehaving.
|
|
// - There is a malicious actor on the network.
|
|
// In either of these cases, optimize for this host's resource usage.
|
|
if (truncated_queries_.size() >
|
|
static_cast<size_t>(
|
|
config_.maximum_concurrent_truncated_queries_per_interface)) {
|
|
OSP_DVLOG << "Too many truncated queries have been received. Treating "
|
|
"new multi-packet known answer message as normal query";
|
|
} else {
|
|
ProcessMultiPacketTruncatedMessage(message, src);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// If the query is a probe query, it will be handled separately by the
|
|
// MdnsProbeManager. Ignore it here.
|
|
if (message.IsProbeQuery()) {
|
|
ownership_handler_->RespondToProbeQuery(message, src);
|
|
return;
|
|
}
|
|
|
|
// Else, this is a normal query. Process it as such.
|
|
// This is the case that should be hit 95+% of the time.
|
|
OSP_DVLOG << "Received mDNS Query with " << message.questions().size()
|
|
<< " questions. Processing...";
|
|
const std::vector<MdnsRecord>& known_answers = message.answers();
|
|
const std::vector<MdnsQuestion>& questions = message.questions();
|
|
ProcessQueries(src, questions, known_answers);
|
|
}
|
|
|
|
void MdnsResponder::ProcessMultiPacketTruncatedMessage(
|
|
const MdnsMessage& message,
|
|
const IPEndpoint& src) {
|
|
OSP_DVLOG << "Multi-packet truncated message received. Processing...";
|
|
|
|
const bool message_has_question = !message.questions().empty();
|
|
const bool message_is_truncated = message.is_truncated();
|
|
OSP_DCHECK(!message_has_question || message_is_truncated);
|
|
|
|
auto pair =
|
|
truncated_queries_.emplace(src, std::unique_ptr<TruncatedQuery>());
|
|
std::unique_ptr<TruncatedQuery>& stored_query = pair.first->second;
|
|
|
|
// First, handle the case where this host doesn't have a known answer query
|
|
// tracked yet. In this case, start tracking the new query.
|
|
if (pair.second) {
|
|
// Create a new query and swap it with the old one to save an extra lookup.
|
|
auto new_query = std::make_unique<TruncatedQuery>(
|
|
this, task_runner_, now_function_, src, message, config_);
|
|
stored_query.swap(new_query);
|
|
return;
|
|
}
|
|
|
|
// Else, there was already a message received from this host.
|
|
const bool are_questions_already_stored = !stored_query->questions().empty();
|
|
|
|
// If the new message doesn't have a question, then it must be additional
|
|
// known answers. Add them to the set of known answers for this truncated
|
|
// query.
|
|
if (!message_has_question) {
|
|
stored_query->AddKnownAnswers(message.answers());
|
|
return;
|
|
}
|
|
|
|
// Alternatively, if a record for this host existed, it might be because the
|
|
// messages were received out-of-order and known answers have already been
|
|
// received. In this case, associate the new message's query with the known
|
|
// answers already received.
|
|
if (!are_questions_already_stored) {
|
|
stored_query->SetQuery(message);
|
|
return;
|
|
}
|
|
|
|
// Else, an ongoing truncated query is already associated with this host and a
|
|
// new one has also been received. This implies one of the following occurred:
|
|
// - The sender must have finished sending packets.
|
|
// - The known answers completing this query somehow got lost on the network.
|
|
// - A second truncated query was started by the same host, and this host
|
|
// won't be able to differentiate which query future known answers are
|
|
// associated with.
|
|
// In any of these cases, there's no reason to continue tracking the old
|
|
// query. So process it.
|
|
//
|
|
// Create a new query and swap it with the old one to save an extra lookup.
|
|
auto new_query = std::make_unique<TruncatedQuery>(
|
|
this, task_runner_, now_function_, src, message, config_);
|
|
stored_query.swap(new_query);
|
|
|
|
// Now that the pointers have been swapped, process the previously stored
|
|
// query.
|
|
new_query->SendResponse();
|
|
}
|
|
|
|
void MdnsResponder::RespondToTruncatedQuery(TruncatedQuery* query) {
|
|
ProcessQueries(query->src(), query->questions(), query->known_answers());
|
|
auto it = truncated_queries_.find(query->src());
|
|
|
|
if (it == truncated_queries_.end()) {
|
|
return;
|
|
}
|
|
|
|
// If a second query for this same host arrives, then the question found may
|
|
// not match what is being sent due to the swap done in OnMessageReceived().
|
|
if (it->second.get() == query) {
|
|
truncated_queries_.erase(it);
|
|
}
|
|
}
|
|
|
|
void MdnsResponder::ProcessQueries(
|
|
const IPEndpoint& src,
|
|
const std::vector<MdnsQuestion>& questions,
|
|
const std::vector<MdnsRecord>& known_answers) {
|
|
for (const auto& question : questions) {
|
|
OSP_DVLOG << "\tProcessing mDNS Query for domain: '"
|
|
<< question.name().ToString() << "', type: '"
|
|
<< question.dns_type() << "' from '" << src << "'";
|
|
|
|
// NSEC records should not be queried for.
|
|
if (question.dns_type() == DnsType::kNSEC) {
|
|
continue;
|
|
}
|
|
|
|
// Only respond to queries for which one of the following is true:
|
|
// - This host is the sole owner of that domain.
|
|
// - A record corresponding to this question has been published.
|
|
// - The query is a service enumeration query.
|
|
const bool is_service_enumeration = IsServiceTypeEnumerationQuery(question);
|
|
const bool is_exclusive_owner =
|
|
ownership_handler_->IsDomainClaimed(question.name());
|
|
if (!is_service_enumeration && !is_exclusive_owner &&
|
|
!record_handler_->HasRecords(question.name(), question.dns_type(),
|
|
question.dns_class())) {
|
|
OSP_DVLOG << "\tmDNS Query processed and no relevant records found!";
|
|
continue;
|
|
} else if (is_service_enumeration) {
|
|
OSP_DVLOG << "\tmDNS Query is for service type enumeration!";
|
|
}
|
|
|
|
// Relevant records are published, so send them out using the response type
|
|
// dictated in the question.
|
|
std::function<void(const MdnsMessage&)> send_response;
|
|
if (question.response_type() == ResponseType::kMulticast) {
|
|
send_response = [this](const MdnsMessage& message) {
|
|
sender_->SendMulticast(message);
|
|
};
|
|
} else {
|
|
OSP_DCHECK(question.response_type() == ResponseType::kUnicast);
|
|
send_response = [this, src](const MdnsMessage& message) {
|
|
sender_->SendMessage(message, src);
|
|
};
|
|
}
|
|
|
|
// If this host is the exclusive owner, respond immediately. Else, there may
|
|
// be network contention if all hosts respond simultaneously, so delay the
|
|
// response as dictated by RFC 6762.
|
|
if (is_exclusive_owner) {
|
|
SendResponse(question, known_answers, send_response, is_exclusive_owner);
|
|
} else {
|
|
const auto delay = random_delay_->GetSharedRecordResponseDelay();
|
|
std::function<void()> response = [this, question, known_answers,
|
|
send_response, is_exclusive_owner]() {
|
|
SendResponse(question, known_answers, send_response,
|
|
is_exclusive_owner);
|
|
};
|
|
task_runner_->PostTaskWithDelay(response, delay);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MdnsResponder::SendResponse(
|
|
const MdnsQuestion& question,
|
|
const std::vector<MdnsRecord>& known_answers,
|
|
std::function<void(const MdnsMessage&)> send_response,
|
|
bool is_exclusive_owner) {
|
|
OSP_DCHECK(task_runner_->IsRunningOnTaskRunner());
|
|
|
|
MdnsMessage message(CreateMessageId(), MessageType::Response);
|
|
|
|
if (IsServiceTypeEnumerationQuery(question)) {
|
|
// This is a special case defined in RFC 6763 section 9, so handle it
|
|
// separately.
|
|
ApplyServiceTypeEnumerationResults(&message, record_handler_,
|
|
question.name(), question.dns_class());
|
|
} else {
|
|
// NOTE: The exclusive ownership of this record cannot change before this
|
|
// method is called. Exclusive ownership cannot be gained for a record which
|
|
// has previously been published, and if this host is the exclusive owner
|
|
// then this method will have been called without any delay on the task
|
|
// runner.
|
|
ApplyQueryResults(&message, record_handler_, question.name(), known_answers,
|
|
question.dns_type(), question.dns_class(),
|
|
is_exclusive_owner);
|
|
}
|
|
|
|
// Send the response only if it contains answers to the query.
|
|
OSP_DVLOG << "\tCompleted Processing mDNS Query for domain: '"
|
|
<< question.name().ToString() << "', type: '" << question.dns_type()
|
|
<< "', with " << message.answers().size() << " results:";
|
|
for (const auto& record : message.answers()) {
|
|
OSP_DVLOG << "\t\tanswer (" << record.ToString() << ")";
|
|
}
|
|
for (const auto& record : message.additional_records()) {
|
|
OSP_DVLOG << "\t\tadditional record ('" << record.ToString() << ")";
|
|
}
|
|
|
|
if (!message.answers().empty()) {
|
|
send_response(message);
|
|
}
|
|
}
|
|
|
|
} // namespace discovery
|
|
} // namespace openscreen
|