blob: 96a953f3f90c55625ec2a2e89ec021158882759c [file] [log] [blame]
// Copyright 2017 The Fuchsia 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 "src/connectivity/network/mdns/service/agents/instance_requestor.h"
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/time.h>
#include <iterator>
#include "src/connectivity/network/mdns/service/common/mdns_names.h"
#include "src/connectivity/network/mdns/service/common/types.h"
namespace mdns {
namespace {
constexpr zx::duration kMaxQueryInterval = zx::hour(1);
constexpr zx::duration kAdditionalInterval = zx::sec(1);
constexpr uint32_t kAdditionalIntervalMultiplier = 2;
constexpr uint32_t kAdditionalMaxQueries = 3;
} // namespace
InstanceRequestor::InstanceRequestor(MdnsAgent::Owner* owner, const std::string& service_name,
Media media, IpVersions ip_versions, bool include_local,
bool include_local_proxies)
: MdnsAgent(owner),
service_name_(service_name),
service_full_name_(MdnsNames::ServiceFullName(service_name)),
media_(media),
ip_versions_(ip_versions),
include_local_(include_local),
include_local_proxies_(include_local_proxies),
question_(std::make_shared<DnsQuestion>(service_full_name_, DnsType::kPtr)) {}
InstanceRequestor::InstanceRequestor(MdnsAgent::Owner* owner, Media media, IpVersions ip_versions,
bool include_local, bool include_local_proxies)
: MdnsAgent(owner),
service_name_(""),
service_full_name_(MdnsNames::kAnyServiceFullName),
media_(media),
ip_versions_(ip_versions),
include_local_(include_local),
include_local_proxies_(include_local_proxies),
question_(std::make_shared<DnsQuestion>(service_full_name_, DnsType::kPtr)) {}
void InstanceRequestor::AddSubscriber(Mdns::Subscriber* subscriber) {
subscribers_.insert(subscriber);
if (started()) {
ReportAllDiscoveries(subscriber);
}
}
void InstanceRequestor::RemoveSubscriber(Mdns::Subscriber* subscriber) {
subscribers_.erase(subscriber);
if (subscribers_.empty()) {
PostTaskForTime([this, own_this = shared_from_this()]() { Quit(); }, now());
}
}
void InstanceRequestor::Start(const std::string& local_host_full_name) {
MdnsAgent::Start(local_host_full_name);
SendQuery();
}
void InstanceRequestor::ReceiveResource(const DnsResource& resource, MdnsResourceSection section,
ReplyAddress sender_address) {
if (!sender_address.Matches(media_) || !sender_address.Matches(ip_versions_)) {
return;
}
switch (resource.type_) {
case DnsType::kPtr:
if (resource.name_.dotted_string_ == service_full_name_ ||
service_full_name_ == MdnsNames::kAnyServiceFullName) {
ReceivePtrResource(resource, section);
}
break;
case DnsType::kSrv: {
auto iter = instance_infos_by_full_name_.find(resource.name_.dotted_string_);
if (iter != instance_infos_by_full_name_.end()) {
ReceiveSrvResource(resource, section, &iter->second);
}
} break;
case DnsType::kTxt: {
auto iter = instance_infos_by_full_name_.find(resource.name_.dotted_string_);
if (iter != instance_infos_by_full_name_.end()) {
ReceiveTxtResource(resource, section, &iter->second);
}
} break;
case DnsType::kA: {
auto iter = target_infos_by_full_name_.find(resource.name_.dotted_string_);
if (iter != target_infos_by_full_name_.end()) {
ReceiveAResource(resource, section, &iter->second, sender_address.interface_id());
}
} break;
case DnsType::kAaaa: {
auto iter = target_infos_by_full_name_.find(resource.name_.dotted_string_);
if (iter != target_infos_by_full_name_.end()) {
ReceiveAaaaResource(resource, section, &iter->second, sender_address.interface_id());
}
} break;
default:
break;
}
}
void InstanceRequestor::EndOfMessage() {
// Report updates.
for (auto& [instance_full_name, instance_info] : instance_infos_by_full_name_) {
if (instance_info.target_.empty()) {
// We haven't yet seen an SRV record for this instance.
if (!instance_info.srv_queried_) {
// We have not received an SRV resource, and we haven't asked for one explicitly. Ask for
// one now.
Query(DnsType::kSrv, instance_full_name, media_, ip_versions_, now(), kAdditionalInterval,
kAdditionalIntervalMultiplier, kAdditionalMaxQueries);
instance_info.srv_queried_ = true;
}
continue;
}
auto iter = target_infos_by_full_name_.find(instance_info.target_full_name_);
if (iter == target_infos_by_full_name_.end()) {
FX_DCHECK(false) << "Failed to find target for SRV target name "
<< instance_info.target_full_name_;
FX_LOGS(ERROR) << "Failed to find target for SRV target name "
<< instance_info.target_full_name_;
continue;
}
const std::string& target_full_name = iter->first;
TargetInfo& target_info = iter->second;
// Keep this target info around.
target_info.keep_ = true;
if (!instance_info.dirty_ && !target_info.dirty_) {
// Both the instance info and target info are clean.
continue;
}
if (!instance_info.txt_received_ && !instance_info.txt_queried_) {
// We have not received a TXT resource, and we haven't asked for one explicitly. Ask for one
// now. We proceed anyway, which means the client may first get the instance without text
// and later get an |InstanceChanged| with text.
Query(DnsType::kTxt, instance_full_name, media_, ip_versions_, now(), kAdditionalInterval,
kAdditionalIntervalMultiplier, kAdditionalMaxQueries);
instance_info.txt_queried_ = true;
}
if (target_info.addresses_.empty()) {
// No addresses. This happens when an instance doesn't include A/AAAA records along with the
// PTR and SRV.
if (!target_info.addresses_queried_) {
// If we haven't explicitly queried for addresses yet, do so. When they arrive, we'll end
// up back in this method with a non-empty |target_info.addresses_|. If they never arrive,
// the instance won't be reported to the client.
//
// Note that these queries will be coalesced into a single message due to the identical
// schedule.
auto when = now();
Query(DnsType::kA, target_full_name, media_, ip_versions_, when, kAdditionalInterval,
kAdditionalIntervalMultiplier, kAdditionalMaxQueries);
Query(DnsType::kAaaa, target_full_name, media_, ip_versions_, when, kAdditionalInterval,
kAdditionalIntervalMultiplier, kAdditionalMaxQueries);
target_info.addresses_queried_ = true;
}
continue;
}
// Something has changed.
std::vector<Mdns::Subscriber*> subscribers(subscribers_.begin(), subscribers_.end());
if (instance_info.new_) {
instance_info.new_ = false;
for (auto subscriber : subscribers) {
subscriber->InstanceDiscovered(instance_info.service_name_, instance_info.instance_name_,
Addresses(target_info, instance_info.port_),
instance_info.text_, instance_info.srv_priority_,
instance_info.srv_weight_, instance_info.target_);
}
} else {
for (auto subscriber : subscribers) {
subscriber->InstanceChanged(instance_info.service_name_, instance_info.instance_name_,
Addresses(target_info, instance_info.port_),
instance_info.text_, instance_info.srv_priority_,
instance_info.srv_weight_, instance_info.target_);
}
}
instance_info.dirty_ = false;
}
// Clean up |target_infos_by_full_name_|.
for (auto iter = target_infos_by_full_name_.begin(); iter != target_infos_by_full_name_.end();) {
if (iter->second.keep_) {
iter->second.dirty_ = false;
iter->second.keep_ = false;
++iter;
} else {
// No instances reference this target. Get rid of it.
iter = target_infos_by_full_name_.erase(iter);
}
}
}
void InstanceRequestor::ReportAllDiscoveries(Mdns::Subscriber* subscriber) {
for (const auto& [_, instance_info] : instance_infos_by_full_name_) {
if (instance_info.target_.empty()) {
// We haven't yet seen an SRV record for this instance.
continue;
}
auto iter = target_infos_by_full_name_.find(instance_info.target_full_name_);
FX_DCHECK(iter != target_infos_by_full_name_.end());
TargetInfo& target_info = iter->second;
if (target_info.addresses_.empty()) {
// No addresses yet.
continue;
}
subscriber->InstanceDiscovered(instance_info.service_name_, instance_info.instance_name_,
Addresses(target_info, instance_info.port_), instance_info.text_,
instance_info.srv_priority_, instance_info.srv_weight_,
instance_info.target_);
}
}
void InstanceRequestor::SendQuery() {
SendQuestion(question_, ReplyAddress::Multicast(media_, ip_versions_));
for (const auto& subscriber : subscribers_) {
subscriber->Query(question_->type_);
}
if (query_delay_ == zx::sec(0)) {
query_delay_ = zx::sec(1);
} else {
query_delay_ = query_delay_ * 2;
if (query_delay_ > kMaxQueryInterval) {
query_delay_ = kMaxQueryInterval;
}
}
PostTaskForTime([this]() { SendQuery(); }, now() + query_delay_);
}
void InstanceRequestor::ReceivePtrResource(const DnsResource& resource,
MdnsResourceSection section) {
const std::string& instance_full_name = resource.ptr_.pointer_domain_name_.dotted_string_;
std::string instance_name;
std::string service_name;
if (!MdnsNames::SplitInstanceFullName(instance_full_name, &instance_name, &service_name)) {
return;
}
if (resource.time_to_live_ == 0) {
RemoveInstance(instance_full_name);
return;
}
if (instance_infos_by_full_name_.find(instance_full_name) == instance_infos_by_full_name_.end()) {
auto [iter, inserted] =
instance_infos_by_full_name_.emplace(instance_full_name, InstanceInfo{});
FX_DCHECK(inserted);
iter->second.service_name_ = service_name;
iter->second.instance_name_ = instance_name;
}
Renew(resource, media_, ip_versions_);
}
void InstanceRequestor::ReceiveSrvResource(const DnsResource& resource, MdnsResourceSection section,
InstanceInfo* instance_info) {
if (resource.time_to_live_ == 0) {
RemoveInstance(resource.name_.dotted_string_);
return;
}
if (instance_info->target_.empty() ||
MdnsNames::HostFullName(instance_info->target_) != resource.srv_.target_.dotted_string_) {
instance_info->target_ = MdnsNames::HostNameFromFullName(resource.srv_.target_.dotted_string_);
instance_info->target_full_name_ = resource.srv_.target_.dotted_string_;
instance_info->dirty_ = true;
if (target_infos_by_full_name_.find(instance_info->target_full_name_) ==
target_infos_by_full_name_.end()) {
target_infos_by_full_name_.emplace(instance_info->target_full_name_, TargetInfo{});
}
}
if (instance_info->srv_priority_ != resource.srv_.priority_) {
instance_info->srv_priority_ = resource.srv_.priority_;
instance_info->dirty_ = true;
}
if (instance_info->srv_weight_ != resource.srv_.weight_) {
instance_info->srv_weight_ = resource.srv_.weight_;
instance_info->dirty_ = true;
}
if (instance_info->port_ != resource.srv_.port_) {
instance_info->port_ = resource.srv_.port_;
instance_info->dirty_ = true;
}
Renew(resource, media_, ip_versions_);
}
void InstanceRequestor::ReceiveTxtResource(const DnsResource& resource, MdnsResourceSection section,
InstanceInfo* instance_info) {
if (resource.time_to_live_ == 0) {
if (!instance_info->text_.empty()) {
instance_info->text_.clear();
instance_info->dirty_ = true;
}
return;
}
instance_info->txt_received_ = true;
if (instance_info->text_.size() != resource.txt_.strings_.size()) {
instance_info->text_.resize(resource.txt_.strings_.size());
instance_info->dirty_ = true;
}
for (size_t i = 0; i < instance_info->text_.size(); ++i) {
if (instance_info->text_[i] != resource.txt_.strings_[i]) {
instance_info->text_[i] = resource.txt_.strings_[i];
instance_info->dirty_ = true;
}
}
Renew(resource, media_, ip_versions_);
}
void InstanceRequestor::ReceiveAResource(const DnsResource& resource, MdnsResourceSection section,
TargetInfo* target_info, uint32_t scope_id) {
inet::SocketAddress address(resource.a_.address_.address_, inet::IpPort(), scope_id);
if (resource.time_to_live_ == 0) {
if (target_info->addresses_.erase(address)) {
target_info->dirty_ = true;
}
return;
}
if (target_info->addresses_.insert(address).second) {
target_info->dirty_ = true;
}
Renew(resource, media_, ip_versions_);
}
void InstanceRequestor::ReceiveAaaaResource(const DnsResource& resource,
MdnsResourceSection section, TargetInfo* target_info,
uint32_t scope_id) {
inet::SocketAddress address(resource.aaaa_.address_.address_, inet::IpPort(), scope_id);
if (resource.time_to_live_ == 0) {
if (target_info->addresses_.erase(address)) {
target_info->dirty_ = true;
}
return;
}
if (target_info->addresses_.insert(address).second) {
target_info->dirty_ = true;
}
Renew(resource, media_, ip_versions_);
}
void InstanceRequestor::RemoveInstance(const std::string& instance_full_name) {
auto iter = instance_infos_by_full_name_.find(instance_full_name);
if (iter != instance_infos_by_full_name_.end()) {
for (auto subscriber : subscribers_) {
subscriber->InstanceLost(iter->second.service_name_, iter->second.instance_name_);
}
instance_infos_by_full_name_.erase(iter);
}
}
std::vector<inet::SocketAddress> InstanceRequestor::Addresses(const TargetInfo& target_info,
inet::IpPort port) {
std::vector<inet::SocketAddress> result;
std::transform(target_info.addresses_.begin(), target_info.addresses_.end(),
std::back_inserter(result), [port](const inet::SocketAddress& address) {
return inet::SocketAddress(address.address(), port, address.scope_id());
});
return result;
}
void InstanceRequestor::OnAddLocalServiceInstance(const Mdns::ServiceInstance& instance,
bool from_proxy) {
if (from_proxy ? !include_local_proxies_ : !include_local_) {
return;
}
if (instance.service_name_ != service_name_ &&
service_full_name_ != MdnsNames::kAnyServiceFullName) {
return;
}
if (instance.addresses_.empty()) {
FX_LOGS(ERROR) << "OnAddLocalServiceInstance called with empty address list.";
return;
}
auto instance_full_name =
MdnsNames::InstanceFullName(instance.instance_name_, instance.service_name_);
auto [instance_iter, inserted] =
instance_infos_by_full_name_.insert(std::make_pair(instance_full_name, InstanceInfo()));
auto& instance_info = instance_iter->second;
instance_info.service_name_ = instance.service_name_;
instance_info.instance_name_ = instance.instance_name_;
instance_info.target_ = instance.target_name_;
instance_info.target_full_name_ = MdnsNames::HostFullName(instance.target_name_);
instance_info.port_ = instance.addresses_[0].port();
instance_info.text_ = instance.text_;
instance_info.srv_priority_ = instance.srv_priority_;
instance_info.srv_weight_ = instance.srv_weight_;
instance_info.new_ = inserted;
auto [target_iter, _] = target_infos_by_full_name_.insert(
std::make_pair(instance_info.target_full_name_, TargetInfo()));
auto& target_info = target_iter->second;
std::copy(instance.addresses_.begin(), instance.addresses_.end(),
std::inserter(target_info.addresses_, target_info.addresses_.end()));
target_info.dirty_ = true;
EndOfMessage();
}
void InstanceRequestor::OnChangeLocalServiceInstance(const Mdns::ServiceInstance& instance,
bool from_proxy) {
if (from_proxy ? !include_local_proxies_ : !include_local_) {
return;
}
if (instance.service_name_ != service_name_ &&
service_full_name_ != MdnsNames::kAnyServiceFullName) {
return;
}
FX_DCHECK(!instance.addresses_.empty());
auto instance_full_name =
MdnsNames::InstanceFullName(instance.instance_name_, instance.service_name_);
auto iter = instance_infos_by_full_name_.find(instance_full_name);
if (iter == instance_infos_by_full_name_.end()) {
return;
}
auto& instance_info = iter->second;
FX_DCHECK(instance_info.instance_name_ == instance.instance_name_);
if (instance_info.target_ != instance.target_name_) {
instance_info.target_ = instance.target_name_;
instance_info.target_full_name_ = MdnsNames::HostFullName(instance.target_name_);
instance_info.dirty_ = true;
}
if (instance_info.port_ != instance.addresses_[0].port()) {
instance_info.port_ = instance.addresses_[0].port();
instance_info.dirty_ = true;
}
if (instance_info.text_ != instance.text_) {
instance_info.text_ = instance.text_;
instance_info.dirty_ = true;
}
if (instance_info.srv_priority_ != instance.srv_priority_) {
instance_info.srv_priority_ = instance.srv_priority_;
instance_info.dirty_ = true;
}
if (instance_info.srv_weight_ != instance.srv_weight_) {
instance_info.srv_weight_ = instance.srv_weight_;
instance_info.dirty_ = true;
}
auto target_full_name = MdnsNames::HostFullName(instance.target_name_);
auto [target_iter, _] =
target_infos_by_full_name_.insert(std::make_pair(target_full_name, TargetInfo()));
auto& target_info = target_iter->second;
std::unordered_set<inet::SocketAddress> addresses;
std::copy(instance.addresses_.begin(), instance.addresses_.end(),
std::inserter(addresses, addresses.end()));
if (target_info.addresses_ != addresses) {
target_info.addresses_ = addresses;
target_info.dirty_ = true;
}
if (instance_info.dirty_ || target_info.dirty_) {
EndOfMessage();
}
}
void InstanceRequestor::OnRemoveLocalServiceInstance(const std::string& service_name,
const std::string& instance_name,
bool from_proxy) {
if (from_proxy ? !include_local_proxies_ : !include_local_) {
return;
}
if (service_name != service_name_ && service_full_name_ != MdnsNames::kAnyServiceFullName) {
return;
}
RemoveInstance(MdnsNames::InstanceFullName(instance_name, service_name));
EndOfMessage();
}
} // namespace mdns