blob: 84dd5356db708405a4bee8d25164be5c5faa494c [file] [log] [blame]
// Copyright 2022 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/services/mdns_deprecated_service_impl.h"
#include <fuchsia/device/cpp/fidl.h>
#include <fuchsia/net/interfaces/cpp/fidl.h>
#include <lib/async/cpp/task.h>
#include <lib/async/default.h>
#include <lib/sys/cpp/component_context.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/clock.h>
#include <lib/zx/time.h>
#include <unistd.h>
#include "lib/fidl/cpp/type_converter.h"
#include "src/connectivity/network/mdns/service/common/mdns_fidl_util.h"
#include "src/connectivity/network/mdns/service/common/mdns_names.h"
#include "src/connectivity/network/mdns/service/common/type_converters.h"
namespace mdns {
MdnsDeprecatedServiceImpl::MdnsDeprecatedServiceImpl(Mdns& mdns,
sys::ComponentContext* component_context)
: resolver_bindings_(this, "Resolver"),
subscriber_bindings_(this, "Subscriber"),
publisher_bindings_(this, "Publisher"),
mdns_(mdns) {
component_context->outgoing()->AddPublicService<fuchsia::net::mdns::Resolver>(
fit::bind_member<&BindingSet<fuchsia::net::mdns::Resolver>::OnBindRequest>(
&resolver_bindings_));
component_context->outgoing()->AddPublicService<fuchsia::net::mdns::Subscriber>(
fit::bind_member<&BindingSet<fuchsia::net::mdns::Subscriber>::OnBindRequest>(
&subscriber_bindings_));
component_context->outgoing()->AddPublicService<fuchsia::net::mdns::Publisher>(
fit::bind_member<&BindingSet<fuchsia::net::mdns::Publisher>::OnBindRequest>(
&publisher_bindings_));
}
MdnsDeprecatedServiceImpl::~MdnsDeprecatedServiceImpl() {}
void MdnsDeprecatedServiceImpl::OnReady() {
resolver_bindings_.OnReady();
subscriber_bindings_.OnReady();
publisher_bindings_.OnReady();
}
void MdnsDeprecatedServiceImpl::ResolveHostName(std::string host, int64_t timeout_ns,
ResolveHostNameCallback callback) {
if (!MdnsNames::IsValidHostName(host)) {
FX_LOGS(ERROR) << "ResolveHostName called with invalid host name " << host;
callback(nullptr, nullptr);
return;
}
mdns_.ResolveHostName(host, zx::nsec(timeout_ns), Media::kBoth, IpVersions::kBoth,
false, // include_local
true, // include_local_proxies
[callback = std::move(callback)](const std::string& host,
std::vector<HostAddress> addresses) {
inet::IpAddress v4_address;
inet::IpAddress v6_address;
for (const auto& address : addresses) {
if (address.address().is_v4() && !v4_address) {
v4_address = address.address();
} else if (address.address().is_v6() && !v6_address) {
v6_address = address.address();
}
}
callback(v4_address ? std::make_unique<fuchsia::net::Ipv4Address>(
MdnsFidlUtil::CreateIpv4Address(v4_address))
: nullptr,
v6_address ? std::make_unique<fuchsia::net::Ipv6Address>(
MdnsFidlUtil::CreateIpv6Address(v6_address))
: nullptr);
});
}
void MdnsDeprecatedServiceImpl::SubscribeToService(
std::string service,
fidl::InterfaceHandle<fuchsia::net::mdns::ServiceSubscriber> subscriber_handle) {
if (!MdnsNames::IsValidServiceName(service)) {
FX_LOGS(ERROR) << "ResolveHostName called with invalid service name " << service;
return;
}
size_t id = next_subscriber_id_++;
auto subscriber = std::make_unique<Subscriber>(std::move(subscriber_handle),
[this, id]() { subscribers_by_id_.erase(id); });
mdns_.SubscribeToService(service, Media::kBoth, IpVersions::kBoth,
false, // include_local
true, // include_local_proxies
subscriber.get());
subscribers_by_id_.emplace(id, std::move(subscriber));
}
void MdnsDeprecatedServiceImpl::PublishServiceInstance(
std::string service, std::string instance, fuchsia::net::mdns::Media media, bool perform_probe,
fidl::InterfaceHandle<fuchsia::net::mdns::PublicationResponder> responder_handle,
PublishServiceInstanceCallback callback) {
FX_DCHECK(responder_handle);
if (!MdnsNames::IsValidServiceName(service)) {
FX_LOGS(ERROR) << "PublishServiceInstance called with invalid service name " << service;
fuchsia::net::mdns::Publisher_PublishServiceInstance_Result result;
result.set_err(fuchsia::net::mdns::Error::INVALID_SERVICE_NAME);
callback(std::move(result));
return;
}
if (!MdnsNames::IsValidInstanceName(instance)) {
FX_LOGS(ERROR) << "PublishServiceInstance called with invalid instance name " << instance;
fuchsia::net::mdns::Publisher_PublishServiceInstance_Result result;
result.set_err(fuchsia::net::mdns::Error::INVALID_INSTANCE_NAME);
callback(std::move(result));
return;
}
if (media != fuchsia::net::mdns::Media::WIRED && media != fuchsia::net::mdns::Media::WIRELESS &&
media != (fuchsia::net::mdns::Media::WIRED | fuchsia::net::mdns::Media::WIRELESS)) {
FX_LOGS(ERROR) << "PublishServiceInstance called with invalid media "
<< static_cast<uint32_t>(media);
fuchsia::net::mdns::Publisher_PublishServiceInstance_Result result;
result.set_err(fuchsia::net::mdns::Error::INVALID_MEDIA);
callback(std::move(result));
return;
}
// TODO(https://fxbug.dev/42134330): Review this approach to conflicts.
std::string instance_full_name = MdnsNames::InstanceFullName(instance, service);
// If there's an existing publisher for this full name, destroy it so the new publication
// supercedes the old one.
publishers_by_instance_full_name_.erase(instance_full_name);
auto responder_ptr = responder_handle.Bind();
FX_DCHECK(responder_ptr);
auto publisher = std::make_unique<ResponderPublisher>(
std::move(responder_ptr), std::move(callback), [this, instance_full_name]() {
publishers_by_instance_full_name_.erase(instance_full_name);
});
bool result = mdns_.PublishServiceInstance(service, instance, fidl::To<Media>(media),
IpVersions::kBoth, perform_probe, publisher.get());
// Because of the erase call above, |PublishServiceInstance| should always succeed.
FX_DCHECK(result);
publishers_by_instance_full_name_.emplace(instance_full_name, std::move(publisher));
}
////////////////////////////////////////////////////////////////////////////////
// MdnsDeprecatedServiceImpl::Subscriber implementation
MdnsDeprecatedServiceImpl::Subscriber::Subscriber(
fidl::InterfaceHandle<fuchsia::net::mdns::ServiceSubscriber> handle, fit::closure deleter)
: deleter_(std::move(deleter)) {
client_.Bind(std::move(handle));
client_.set_error_handler([this](zx_status_t status) mutable { MaybeDelete(); });
}
MdnsDeprecatedServiceImpl::Subscriber::~Subscriber() {
client_.set_error_handler(nullptr);
if (client_.is_bound()) {
client_.Unbind();
}
}
void MdnsDeprecatedServiceImpl::Subscriber::InstanceDiscovered(
const std::string& service, const std::string& instance,
const std::vector<inet::SocketAddress>& addresses,
const std::vector<std::vector<uint8_t>>& text, uint16_t srv_priority, uint16_t srv_weight,
const std::string& target) {
Entry entry{.type = EntryType::kInstanceDiscovered};
MdnsFidlUtil::FillServiceInstance(&entry.service_instance, service, instance, addresses, text,
srv_priority, srv_weight, target);
entries_.push(std::move(entry));
MaybeSendNextEntry();
}
void MdnsDeprecatedServiceImpl::Subscriber::InstanceChanged(
const std::string& service, const std::string& instance,
const std::vector<inet::SocketAddress>& addresses,
const std::vector<std::vector<uint8_t>>& text, uint16_t srv_priority, uint16_t srv_weight,
const std::string& target) {
Entry entry{.type = EntryType::kInstanceChanged};
MdnsFidlUtil::FillServiceInstance(&entry.service_instance, service, instance, addresses, text,
srv_priority, srv_weight, target);
entries_.push(std::move(entry));
MaybeSendNextEntry();
}
void MdnsDeprecatedServiceImpl::Subscriber::InstanceLost(const std::string& service,
const std::string& instance) {
Entry entry{.type = EntryType::kInstanceLost};
entry.service_instance.set_service(service);
entry.service_instance.set_instance(instance);
entries_.push(std::move(entry));
MaybeSendNextEntry();
}
void MdnsDeprecatedServiceImpl::Subscriber::Query(DnsType type_queried) {
entries_.push({.type = EntryType::kQuery, .type_queried = type_queried});
MaybeSendNextEntry();
}
void MdnsDeprecatedServiceImpl::Subscriber::MaybeSendNextEntry() {
FX_DCHECK(pipeline_depth_ <= kMaxPipelineDepth);
if (pipeline_depth_ == kMaxPipelineDepth || entries_.empty()) {
return;
}
Entry& entry = entries_.front();
auto on_reply = fit::bind_member<&MdnsDeprecatedServiceImpl::Subscriber::ReplyReceived>(this);
// The error handler for |client_| may be called synchronously in any of the proxy calls below.
// To ensure |this| doesn't get deleted while this method is running, we defer deletion until
// the method terminates.
DeferDeletion();
FX_DCHECK(client_);
switch (entry.type) {
case EntryType::kInstanceDiscovered:
client_->OnInstanceDiscovered(std::move(entry.service_instance), std::move(on_reply));
break;
case EntryType::kInstanceChanged:
client_->OnInstanceChanged(std::move(entry.service_instance), std::move(on_reply));
break;
case EntryType::kInstanceLost:
client_->OnInstanceLost(entry.service_instance.service(), entry.service_instance.instance(),
std::move(on_reply));
break;
case EntryType::kQuery:
client_->OnQuery(fidl::To<fuchsia::net::mdns::ResourceType>(entry.type_queried),
std::move(on_reply));
break;
}
++pipeline_depth_;
entries_.pop();
MaybeDelete();
}
void MdnsDeprecatedServiceImpl::Subscriber::ReplyReceived() {
FX_DCHECK(pipeline_depth_ != 0);
--pipeline_depth_;
MaybeSendNextEntry();
}
void MdnsDeprecatedServiceImpl::Subscriber::DeferDeletion() { ++one_based_delete_counter_; }
void MdnsDeprecatedServiceImpl::Subscriber::MaybeDelete() {
if (--one_based_delete_counter_ != 0) {
return;
}
client_.set_error_handler(nullptr);
client_.Unbind();
deleter_();
}
////////////////////////////////////////////////////////////////////////////////
// MdnsDeprecatedServiceImpl::ResponderPublisher implementation
MdnsDeprecatedServiceImpl::ResponderPublisher::ResponderPublisher(
fuchsia::net::mdns::PublicationResponderPtr responder, PublishServiceInstanceCallback callback,
fit::closure deleter)
: responder_(std::move(responder)), callback_(std::move(callback)) {
FX_DCHECK(responder_);
responder_.set_error_handler([this, deleter = std::move(deleter)](zx_status_t status) mutable {
// Clearing the error handler frees the capture list, so we need to save |deleter|.
auto save_deleter = std::move(deleter);
responder_.set_error_handler(nullptr);
save_deleter();
});
responder_.events().SetSubtypes = [this](std::vector<std::string> subtypes) {
for (const auto& subtype : subtypes) {
if (!MdnsNames::IsValidSubtypeName(subtype)) {
FX_LOGS(ERROR) << "Invalid subtype " << subtype
<< " passed in SetSubtypes event, closing connection.";
// TODO(https://fxbug.dev/42150906): Should also call deleter here and at other Unpublish call sites.
responder_ = nullptr;
Unpublish();
return;
}
}
SetSubtypes(std::move(subtypes));
};
responder_.events().Reannounce = [this]() { Reannounce(); };
}
MdnsDeprecatedServiceImpl::ResponderPublisher::~ResponderPublisher() {
responder_.set_error_handler(nullptr);
if (responder_.is_bound()) {
responder_.Unbind();
}
}
void MdnsDeprecatedServiceImpl::ResponderPublisher::ReportSuccess(bool success) {
FX_DCHECK(callback_);
fuchsia::net::mdns::Publisher_PublishServiceInstance_Result result;
if (success) {
result.set_response(fuchsia::net::mdns::Publisher_PublishServiceInstance_Response());
} else {
result.set_err(fuchsia::net::mdns::Error::ALREADY_PUBLISHED_ON_SUBNET);
}
callback_(std::move(result));
}
void MdnsDeprecatedServiceImpl::ResponderPublisher::GetPublication(
PublicationCause publication_cause, const std::string& subtype,
const std::vector<inet::SocketAddress>& source_addresses, GetPublicationCallback callback) {
if (on_publication_calls_in_progress_ < kMaxOnPublicationCallsInProgress) {
++on_publication_calls_in_progress_;
GetPublicationNow(publication_cause, subtype, source_addresses, std::move(callback));
} else {
pending_publications_.emplace(publication_cause, subtype, source_addresses,
std::move(callback));
}
}
void MdnsDeprecatedServiceImpl::ResponderPublisher::OnGetPublicationComplete() {
--on_publication_calls_in_progress_;
if (!pending_publications_.empty() &&
on_publication_calls_in_progress_ < kMaxOnPublicationCallsInProgress) {
++on_publication_calls_in_progress_;
auto& entry = pending_publications_.front();
GetPublicationNow(entry.publication_cause_, entry.subtype_, entry.source_addresses_,
std::move(entry.callback_));
// Note that if |GetPublicationNow| calls this method back synchronously, the pop call below
// would happen too late. However, the calls happen asynchronously, so we're ok.
pending_publications_.pop();
}
}
void MdnsDeprecatedServiceImpl::ResponderPublisher::GetPublicationNow(
PublicationCause publication_cause, const std::string& subtype,
const std::vector<inet::SocketAddress>& source_addresses, GetPublicationCallback callback) {
FX_DCHECK(subtype.empty() || MdnsNames::IsValidSubtypeName(subtype));
FX_DCHECK(responder_);
responder_->OnPublication(
fidl::To<fuchsia::net::mdns::PublicationCause>(publication_cause), subtype,
fidl::To<std::vector<fuchsia::net::IpAddress>>(source_addresses),
[this, callback = std::move(callback)](fuchsia::net::mdns::PublicationPtr publication_ptr) {
if (publication_ptr) {
for (const auto& text : publication_ptr->text) {
if (!MdnsNames::IsValidTextString(text)) {
FX_LOGS(ERROR) << "Invalid text string returned by "
"Responder.GetPublication, closing connection.";
responder_ = nullptr;
Unpublish();
return;
}
}
if (publication_ptr->ptr_ttl < ZX_SEC(1) || publication_ptr->srv_ttl < ZX_SEC(1) ||
publication_ptr->txt_ttl < ZX_SEC(1)) {
FX_LOGS(ERROR) << "TTL less than one second returned by "
"Responder.GetPublication, closing connection.";
responder_ = nullptr;
Unpublish();
return;
}
}
callback(fidl::To<std::unique_ptr<Mdns::Publication>>(publication_ptr));
OnGetPublicationComplete();
});
}
} // namespace mdns