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.
256 lines
9.8 KiB
256 lines
9.8 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_probe_manager.h"
|
|
|
|
#include <string>
|
|
#include <utility>
|
|
|
|
#include "discovery/mdns/mdns_sender.h"
|
|
#include "platform/api/task_runner.h"
|
|
|
|
namespace openscreen {
|
|
namespace discovery {
|
|
namespace {
|
|
|
|
// The timespan by which to delay subsequent mDNS Probe queries for the same
|
|
// domain name when a simultaneous query from another host is detected, as
|
|
// described in RFC 6762 section 8.2
|
|
constexpr std::chrono::seconds kSimultaneousProbeDelay =
|
|
std::chrono::seconds(1);
|
|
|
|
DomainName CreateRetryDomainName(const DomainName& name, int attempt) {
|
|
OSP_DCHECK(name.labels().size());
|
|
std::vector<std::string> labels = name.labels();
|
|
std::string& label = labels[0];
|
|
std::string attempts_str = std::to_string(attempt);
|
|
if (label.size() + attempts_str.size() >= kMaxLabelLength) {
|
|
label = label.substr(0, kMaxLabelLength - attempts_str.size());
|
|
}
|
|
label.append(attempts_str);
|
|
|
|
return DomainName(std::move(labels));
|
|
}
|
|
|
|
} // namespace
|
|
|
|
MdnsProbeManager::~MdnsProbeManager() = default;
|
|
|
|
MdnsProbeManagerImpl::MdnsProbeManagerImpl(MdnsSender* sender,
|
|
MdnsReceiver* receiver,
|
|
MdnsRandom* random_delay,
|
|
TaskRunner* task_runner,
|
|
ClockNowFunctionPtr now_function)
|
|
: sender_(sender),
|
|
receiver_(receiver),
|
|
random_delay_(random_delay),
|
|
task_runner_(task_runner),
|
|
now_function_(now_function) {
|
|
OSP_DCHECK(sender_);
|
|
OSP_DCHECK(receiver_);
|
|
OSP_DCHECK(task_runner_);
|
|
OSP_DCHECK(random_delay_);
|
|
}
|
|
|
|
MdnsProbeManagerImpl::~MdnsProbeManagerImpl() = default;
|
|
|
|
Error MdnsProbeManagerImpl::StartProbe(MdnsDomainConfirmedProvider* callback,
|
|
DomainName requested_name,
|
|
IPAddress address) {
|
|
// Check if |requested_name| is already being queried for.
|
|
if (FindOngoingProbe(requested_name) != ongoing_probes_.end()) {
|
|
return Error::Code::kOperationInProgress;
|
|
}
|
|
|
|
// Check if |requested_name| is already claimed.
|
|
if (IsDomainClaimed(requested_name)) {
|
|
return Error::Code::kItemAlreadyExists;
|
|
}
|
|
|
|
OSP_DVLOG << "Starting new mDNS Probe for domain '"
|
|
<< requested_name.ToString() << "'";
|
|
|
|
// Begin a new probe.
|
|
auto probe = CreateProbe(requested_name, std::move(address));
|
|
ongoing_probes_.emplace_back(std::move(probe), std::move(requested_name),
|
|
callback);
|
|
return Error::None();
|
|
}
|
|
|
|
Error MdnsProbeManagerImpl::StopProbe(const DomainName& requested_name) {
|
|
auto it = FindOngoingProbe(requested_name);
|
|
if (it == ongoing_probes_.end()) {
|
|
return Error::Code::kItemNotFound;
|
|
}
|
|
|
|
ongoing_probes_.erase(it);
|
|
return Error::None();
|
|
}
|
|
|
|
bool MdnsProbeManagerImpl::IsDomainClaimed(const DomainName& domain) const {
|
|
return FindCompletedProbe(domain) != completed_probes_.end();
|
|
}
|
|
|
|
void MdnsProbeManagerImpl::RespondToProbeQuery(const MdnsMessage& message,
|
|
const IPEndpoint& src) {
|
|
OSP_DCHECK(!message.questions().empty());
|
|
|
|
const std::vector<MdnsQuestion>& questions = message.questions();
|
|
MdnsMessage send_message(CreateMessageId(), MessageType::Response);
|
|
|
|
// Iterate across all questions asked and all completed probes and add A or
|
|
// AAAA records associated with the endpoints for which the names match.
|
|
// |questions| is expected to be of size 1 and |completed_probes| should be
|
|
// small (generally size 1), so this should be fast.
|
|
for (const auto& question : questions) {
|
|
for (auto it = completed_probes_.begin(); it != completed_probes_.end();
|
|
it++) {
|
|
if (question.name() == (*it)->target_name()) {
|
|
send_message.AddAnswer((*it)->address_record());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!send_message.answers().empty()) {
|
|
sender_->SendMessage(send_message, src);
|
|
} else {
|
|
// If the name isn't already claimed, check to see if a probe is ongoing. If
|
|
// so, compare the address record for that probe with the one in the
|
|
// received message and resolve as specified in RFC 6762 section 8.2.
|
|
TiebreakSimultaneousProbes(message);
|
|
}
|
|
}
|
|
|
|
void MdnsProbeManagerImpl::TiebreakSimultaneousProbes(
|
|
const MdnsMessage& message) {
|
|
OSP_DCHECK(!message.questions().empty());
|
|
OSP_DCHECK(!message.authority_records().empty());
|
|
|
|
for (const auto& question : message.questions()) {
|
|
for (auto it = ongoing_probes_.begin(); it != ongoing_probes_.end(); it++) {
|
|
if (it->probe->target_name() == question.name()) {
|
|
// When a host is probing for a set of records with the same name, or a
|
|
// message is received containing multiple tiebreaker records answering
|
|
// a given probe question in the Question Section, the host's records
|
|
// and the tiebreaker records from the message are each sorted into
|
|
// order, and then compared pairwise, using the same comparison
|
|
// technique described above, until a difference is found. Because the
|
|
// probe object is guaranteed to only have the address record, only the
|
|
// lowest authority record is needed.
|
|
auto lowest_record_it =
|
|
std::min_element(message.authority_records().begin(),
|
|
message.authority_records().end());
|
|
|
|
// If this host finds that its own data is lexicographically later, it
|
|
// simply ignores the other host's probe. The other host will have
|
|
// receive this host's probe simultaneously, and will reject its own
|
|
// probe through this same calculation.
|
|
const MdnsRecord& probe_record = it->probe->address_record();
|
|
if (probe_record > *lowest_record_it) {
|
|
break;
|
|
}
|
|
|
|
// If the probe query is only of size one and the record received is
|
|
// equal to this record, then the received query is the same as what
|
|
// this probe is sending out. In this case, nothing needs to be done.
|
|
if (message.authority_records().size() == 1 &&
|
|
!(probe_record < *lowest_record_it)) {
|
|
break;
|
|
}
|
|
|
|
// At this point, one of the following must be true:
|
|
// - The query's lowest record is greater than this probe's record
|
|
// - The query's lowest record equals this probe's record but it also
|
|
// has additional records.
|
|
// In either case, the query must take priority over this probe. This
|
|
// host defers to the winning host by waiting one second, and then
|
|
// begins probing for this record again. See RFC 6762 section 8.2 for
|
|
// the logic behind waiting one second.
|
|
it->probe->Postpone(kSimultaneousProbeDelay);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void MdnsProbeManagerImpl::OnProbeSuccess(MdnsProbe* probe) {
|
|
auto it = FindOngoingProbe(probe);
|
|
if (it != ongoing_probes_.end()) {
|
|
DomainName target_name = it->probe->target_name();
|
|
completed_probes_.push_back(std::move(it->probe));
|
|
DomainName requested = std::move(it->requested_name);
|
|
MdnsDomainConfirmedProvider* callback = it->callback;
|
|
ongoing_probes_.erase(it);
|
|
callback->OnDomainFound(std::move(requested), std::move(target_name));
|
|
}
|
|
}
|
|
|
|
void MdnsProbeManagerImpl::OnProbeFailure(MdnsProbe* probe) {
|
|
auto ongoing_it = FindOngoingProbe(probe);
|
|
if (ongoing_it == ongoing_probes_.end()) {
|
|
// This means that the probe was canceled.
|
|
return;
|
|
}
|
|
|
|
OSP_DVLOG << "Probe for domain '"
|
|
<< CreateRetryDomainName(ongoing_it->requested_name,
|
|
ongoing_it->num_probes_failed)
|
|
.ToString()
|
|
<< "' failed. Trying new domain...";
|
|
|
|
// Create a new probe with a modified domain name.
|
|
DomainName new_name = CreateRetryDomainName(ongoing_it->requested_name,
|
|
++ongoing_it->num_probes_failed);
|
|
|
|
// If this domain has already been claimed, skip ahead to knowing it's
|
|
// claimed.
|
|
auto completed_it = FindCompletedProbe(new_name);
|
|
if (completed_it != completed_probes_.end()) {
|
|
DomainName requested_name = std::move(ongoing_it->requested_name);
|
|
MdnsDomainConfirmedProvider* callback = ongoing_it->callback;
|
|
ongoing_probes_.erase(ongoing_it);
|
|
callback->OnDomainFound(requested_name, (*completed_it)->target_name());
|
|
} else {
|
|
std::unique_ptr<MdnsProbe> new_probe =
|
|
CreateProbe(std::move(new_name), ongoing_it->probe->address());
|
|
ongoing_it->probe = std::move(new_probe);
|
|
}
|
|
}
|
|
|
|
std::vector<std::unique_ptr<MdnsProbe>>::const_iterator
|
|
MdnsProbeManagerImpl::FindCompletedProbe(const DomainName& name) const {
|
|
return std::find_if(completed_probes_.begin(), completed_probes_.end(),
|
|
[&name](const std::unique_ptr<MdnsProbe>& completed) {
|
|
return completed->target_name() == name;
|
|
});
|
|
}
|
|
|
|
std::vector<MdnsProbeManagerImpl::OngoingProbe>::iterator
|
|
MdnsProbeManagerImpl::FindOngoingProbe(const DomainName& name) {
|
|
return std::find_if(ongoing_probes_.begin(), ongoing_probes_.end(),
|
|
[&name](const OngoingProbe& ongoing) {
|
|
return ongoing.requested_name == name;
|
|
});
|
|
}
|
|
|
|
std::vector<MdnsProbeManagerImpl::OngoingProbe>::iterator
|
|
MdnsProbeManagerImpl::FindOngoingProbe(MdnsProbe* probe) {
|
|
return std::find_if(ongoing_probes_.begin(), ongoing_probes_.end(),
|
|
[&probe](const OngoingProbe& ongoing) {
|
|
return ongoing.probe.get() == probe;
|
|
});
|
|
}
|
|
|
|
MdnsProbeManagerImpl::OngoingProbe::OngoingProbe(
|
|
std::unique_ptr<MdnsProbe> probe,
|
|
DomainName name,
|
|
MdnsDomainConfirmedProvider* callback)
|
|
: probe(std::move(probe)),
|
|
requested_name(std::move(name)),
|
|
callback(callback) {}
|
|
|
|
} // namespace discovery
|
|
} // namespace openscreen
|