blob: 7cfcc657d58cd86615b9ceb5f75741986713ba85 [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/mdns_service_impl.h"
#include <fuchsia/device/cpp/fidl.h>
#include <fuchsia/netstack/cpp/fidl.h>
#include <lib/async/cpp/task.h>
#include <lib/async/default.h>
#include <lib/sys/cpp/component_context.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/mdns_fidl_util.h"
#include "src/connectivity/network/mdns/service/mdns_names.h"
#include "src/lib/fsl/types/type_converters.h"
#include "src/lib/syslog/cpp/logger.h"
namespace mdns {
namespace {
static constexpr zx::duration kReadyPollingInterval = zx::sec(1);
std::string GetHostName() {
char host_name_buffer[HOST_NAME_MAX + 1];
int result = gethostname(host_name_buffer, sizeof(host_name_buffer));
std::string host_name;
if (result < 0) {
FX_LOGS(ERROR) << "gethostname failed, " << strerror(errno);
host_name = fuchsia::device::DEFAULT_DEVICE_NAME;
} else {
host_name = host_name_buffer;
}
return host_name;
}
fuchsia::net::mdns::ServiceInstance MakeOldServiceInstance(
fuchsia::net::mdns::ServiceInstance2 instance2) {
fuchsia::net::mdns::ServiceInstance old;
old.service = std::move(instance2.service());
old.instance = std::move(instance2.instance());
old.text = std::move(instance2.text());
old.srv_priority = instance2.srv_priority();
old.srv_weight = instance2.srv_weight();
if (instance2.has_ipv4_endpoint()) {
auto& endpoint = old.endpoints.emplace_back();
endpoint.port = instance2.ipv4_endpoint().port;
endpoint.addr.set_ipv4(instance2.ipv4_endpoint().address);
}
if (instance2.has_ipv6_endpoint()) {
auto& endpoint = old.endpoints.emplace_back();
endpoint.port = instance2.ipv6_endpoint().port;
endpoint.addr.set_ipv6(instance2.ipv6_endpoint().address);
}
return old;
}
} // namespace
MdnsServiceImpl::MdnsServiceImpl(sys::ComponentContext* component_context)
: component_context_(component_context),
resolver_bindings_(this, "Resolver"),
subscriber_bindings_(this, "Subscriber"),
publisher_bindings_(this, "Publisher") {
component_context_->outgoing()->AddPublicService<fuchsia::net::mdns::Resolver>(fit::bind_member(
&resolver_bindings_, &BindingSet<fuchsia::net::mdns::Resolver>::OnBindRequest));
component_context_->outgoing()->AddPublicService<fuchsia::net::mdns::Subscriber>(fit::bind_member(
&subscriber_bindings_, &BindingSet<fuchsia::net::mdns::Subscriber>::OnBindRequest));
component_context_->outgoing()->AddPublicService<fuchsia::net::mdns::Publisher>(fit::bind_member(
&publisher_bindings_, &BindingSet<fuchsia::net::mdns::Publisher>::OnBindRequest));
Start();
}
MdnsServiceImpl::~MdnsServiceImpl() {}
void MdnsServiceImpl::Start() {
std::string host_name = GetHostName();
if (host_name == fuchsia::device::DEFAULT_DEVICE_NAME) {
// Host name not set. Try again soon.
async::PostDelayedTask(
async_get_default_dispatcher(), [this]() { Start(); }, kReadyPollingInterval);
return;
}
config_.ReadConfigFiles(host_name);
if (!config_.valid()) {
FX_LOGS(FATAL) << "Invalid config file(s), terminating: " << config_.error();
return;
}
mdns_.Start(component_context_->svc()->Connect<fuchsia::netstack::Netstack>(), host_name,
config_.addresses(), config_.perform_host_name_probe(),
fit::bind_member(this, &MdnsServiceImpl::OnReady));
}
void MdnsServiceImpl::OnReady() {
ready_ = true;
// Publish as indicated in config files.
for (auto& publication : config_.publications()) {
PublishServiceInstance(publication.service_, publication.instance_,
publication.publication_->Clone(), true,
[service = publication.service_](
fuchsia::net::mdns::Publisher_PublishServiceInstance_Result result) {
if (result.is_err()) {
FX_LOGS(ERROR) << "Failed to publish as " << service << ", result "
<< static_cast<uint32_t>(result.err());
}
});
}
resolver_bindings_.OnReady();
subscriber_bindings_.OnReady();
publisher_bindings_.OnReady();
}
bool MdnsServiceImpl::PublishServiceInstance(std::string service_name, std::string instance_name,
std::unique_ptr<Mdns::Publication> publication,
bool perform_probe,
PublishServiceInstanceCallback callback) {
auto publisher = std::make_unique<SimplePublisher>(std::move(publication), callback.share());
if (!mdns_.PublishServiceInstance(service_name, instance_name, perform_probe, publisher.get())) {
return false;
}
std::string instance_full_name = MdnsNames::LocalInstanceFullName(instance_name, service_name);
// |Mdns| told us our instance is unique locally, so the full name should
// not appear in our collection.
FX_DCHECK(publishers_by_instance_full_name_.find(instance_full_name) ==
publishers_by_instance_full_name_.end());
publishers_by_instance_full_name_.emplace(instance_full_name, std::move(publisher));
return true;
}
void MdnsServiceImpl::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::clock::get_monotonic() + zx::nsec(timeout_ns),
[callback = std::move(callback)](const std::string& host, const inet::IpAddress& v4_address,
const inet::IpAddress& v6_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 MdnsServiceImpl::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, subscriber.get());
subscribers_by_id_.emplace(id, std::move(subscriber));
}
void MdnsServiceImpl::SubscribeToService2(
std::string service,
fidl::InterfaceHandle<fuchsia::net::mdns::ServiceSubscriber2> 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, subscriber.get());
subscribers_by_id_.emplace(id, std::move(subscriber));
}
void MdnsServiceImpl::PublishServiceInstance(
std::string service, std::string instance, 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;
}
auto responder_ptr = responder_handle.Bind();
FX_DCHECK(responder_ptr);
std::string instance_full_name = MdnsNames::LocalInstanceFullName(instance, service);
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);
});
if (!mdns_.PublishServiceInstance(service, instance, perform_probe, publisher.get())) {
if (publisher->callback_) {
fuchsia::net::mdns::Publisher_PublishServiceInstance_Result result;
result.set_err(fuchsia::net::mdns::Error::ALREADY_PUBLISHED_LOCALLY);
publisher->callback_(std::move(result));
} else {
fuchsia::net::mdns::Publisher_PublishServiceInstance2_Result result;
result.set_err(fuchsia::net::mdns::Error::ALREADY_PUBLISHED_LOCALLY);
publisher->callback2_(std::move(result));
}
return;
}
// |Mdns| told us our instance is unique locally, so the full name should
// not appear in our collection.
FX_DCHECK(publishers_by_instance_full_name_.find(instance_full_name) ==
publishers_by_instance_full_name_.end());
publishers_by_instance_full_name_.emplace(instance_full_name, std::move(publisher));
}
void MdnsServiceImpl::PublishServiceInstance2(
std::string service, std::string instance, bool perform_probe,
fidl::InterfaceHandle<fuchsia::net::mdns::PublicationResponder2> responder_handle,
PublishServiceInstance2Callback callback) {
FX_DCHECK(responder_handle);
if (!MdnsNames::IsValidServiceName(service)) {
FX_LOGS(ERROR) << "PublishServiceInstance2 called with invalid service name " << service;
fuchsia::net::mdns::Publisher_PublishServiceInstance2_Result result;
result.set_err(fuchsia::net::mdns::Error::INVALID_SERVICE_NAME);
callback(std::move(result));
return;
}
if (!MdnsNames::IsValidInstanceName(instance)) {
FX_LOGS(ERROR) << "PublishServiceInstance2 called with invalid instance name " << instance;
fuchsia::net::mdns::Publisher_PublishServiceInstance2_Result result;
result.set_err(fuchsia::net::mdns::Error::INVALID_INSTANCE_NAME);
callback(std::move(result));
return;
}
auto responder_ptr = responder_handle.Bind();
FX_DCHECK(responder_ptr);
std::string instance_full_name = MdnsNames::LocalInstanceFullName(instance, service);
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);
});
if (!mdns_.PublishServiceInstance(service, instance, perform_probe, publisher.get())) {
fuchsia::net::mdns::Publisher_PublishServiceInstance2_Result result;
result.set_err(fuchsia::net::mdns::Error::ALREADY_PUBLISHED_LOCALLY);
publisher->callback2_(std::move(result));
return;
}
// |Mdns| told us our instance is unique locally, so the full name should
// not appear in our collection.
FX_DCHECK(publishers_by_instance_full_name_.find(instance_full_name) ==
publishers_by_instance_full_name_.end());
publishers_by_instance_full_name_.emplace(instance_full_name, std::move(publisher));
}
////////////////////////////////////////////////////////////////////////////////
// MdnsServiceImpl::Subscriber implementation
MdnsServiceImpl::Subscriber::Subscriber(
fidl::InterfaceHandle<fuchsia::net::mdns::ServiceSubscriber> handle, fit::closure deleter) {
client_.Bind(std::move(handle));
client_.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);
client_.set_error_handler(nullptr);
client_.Unbind();
save_deleter();
});
}
MdnsServiceImpl::Subscriber::Subscriber(
fidl::InterfaceHandle<fuchsia::net::mdns::ServiceSubscriber2> handle, fit::closure deleter) {
client2_.Bind(std::move(handle));
client2_.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);
client2_.set_error_handler(nullptr);
client2_.Unbind();
save_deleter();
});
}
MdnsServiceImpl::Subscriber::~Subscriber() {
client_.set_error_handler(nullptr);
if (client_.is_bound()) {
client_.Unbind();
}
client2_.set_error_handler(nullptr);
if (client2_.is_bound()) {
client2_.Unbind();
}
}
void MdnsServiceImpl::Subscriber::InstanceDiscovered(const std::string& service,
const std::string& instance,
const inet::SocketAddress& v4_address,
const inet::SocketAddress& v6_address,
const std::vector<std::string>& text,
uint16_t srv_priority, uint16_t srv_weight) {
Entry entry{.type = EntryType::kInstanceDiscovered};
MdnsFidlUtil::FillServiceInstance2(&entry.service_instance, service, instance, v4_address,
v6_address, text, srv_priority, srv_weight);
entries_.push(std::move(entry));
MaybeSendNextEntry();
}
void MdnsServiceImpl::Subscriber::InstanceChanged(const std::string& service,
const std::string& instance,
const inet::SocketAddress& v4_address,
const inet::SocketAddress& v6_address,
const std::vector<std::string>& text,
uint16_t srv_priority, uint16_t srv_weight) {
Entry entry{.type = EntryType::kInstanceChanged};
MdnsFidlUtil::FillServiceInstance2(&entry.service_instance, service, instance, v4_address,
v6_address, text, srv_priority, srv_weight);
entries_.push(std::move(entry));
MaybeSendNextEntry();
}
void MdnsServiceImpl::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 MdnsServiceImpl::Subscriber::Query(DnsType type_queried) {
entries_.push({.type = EntryType::kQuery, .type_queried = type_queried});
MaybeSendNextEntry();
}
void MdnsServiceImpl::Subscriber::MaybeSendNextEntry() {
FX_DCHECK(pipeline_depth_ <= kMaxPipelineDepth);
if (pipeline_depth_ == kMaxPipelineDepth || entries_.empty()) {
return;
}
Entry& entry = entries_.front();
auto on_reply = fit::bind_member(this, &MdnsServiceImpl::Subscriber::ReplyReceived);
if (client_) {
switch (entry.type) {
case EntryType::kInstanceDiscovered:
client_->OnInstanceDiscovered(MakeOldServiceInstance(std::move(entry.service_instance)),
std::move(on_reply));
break;
case EntryType::kInstanceChanged:
client_->OnInstanceChanged(MakeOldServiceInstance(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:
break;
}
} else {
FX_DCHECK(client2_);
switch (entry.type) {
case EntryType::kInstanceDiscovered:
client2_->OnInstanceDiscovered(std::move(entry.service_instance), std::move(on_reply));
break;
case EntryType::kInstanceChanged:
client2_->OnInstanceChanged(std::move(entry.service_instance), std::move(on_reply));
break;
case EntryType::kInstanceLost:
client2_->OnInstanceLost(entry.service_instance.service(),
entry.service_instance.instance(), std::move(on_reply));
break;
case EntryType::kQuery:
client2_->OnQuery(MdnsFidlUtil::Convert(entry.type_queried), std::move(on_reply));
break;
}
}
++pipeline_depth_;
entries_.pop();
}
void MdnsServiceImpl::Subscriber::ReplyReceived() {
FX_DCHECK(pipeline_depth_ != 0);
--pipeline_depth_;
MaybeSendNextEntry();
}
////////////////////////////////////////////////////////////////////////////////
// MdnsServiceImpl::SimplePublisher implementation
MdnsServiceImpl::SimplePublisher::SimplePublisher(std::unique_ptr<Mdns::Publication> publication,
PublishServiceInstanceCallback callback)
: publication_(std::move(publication)), callback_(std::move(callback)) {}
void MdnsServiceImpl::SimplePublisher::ReportSuccess(bool success) {
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 MdnsServiceImpl::SimplePublisher::GetPublication(
bool query, const std::string& subtype,
const std::vector<inet::SocketAddress>& source_addresses,
fit::function<void(std::unique_ptr<Mdns::Publication>)> callback) {
FX_DCHECK(subtype.empty() || MdnsNames::IsValidSubtypeName(subtype));
callback(publication_->Clone());
}
////////////////////////////////////////////////////////////////////////////////
// MdnsServiceImpl::ResponderPublisher implementation
MdnsServiceImpl::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 (auto& subtype : subtypes) {
if (!MdnsNames::IsValidSubtypeName(subtype)) {
FX_LOGS(ERROR) << "Invalid subtype " << subtype
<< " passed in SetSubtypes event, closing connection.";
responder_ = nullptr;
Unpublish();
return;
}
}
SetSubtypes(std::move(subtypes));
};
responder_.events().Reannounce = [this]() { Reannounce(); };
}
MdnsServiceImpl::ResponderPublisher::ResponderPublisher(
fuchsia::net::mdns::PublicationResponder2Ptr responder,
PublishServiceInstance2Callback callback, fit::closure deleter)
: responder2_(std::move(responder)), callback2_(std::move(callback)) {
FX_DCHECK(responder2_);
responder2_.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);
responder2_.set_error_handler(nullptr);
save_deleter();
});
responder2_.events().SetSubtypes = [this](std::vector<std::string> subtypes) {
for (auto& subtype : subtypes) {
if (!MdnsNames::IsValidSubtypeName(subtype)) {
FX_LOGS(ERROR) << "Invalid subtype " << subtype
<< " passed in SetSubtypes event, closing connection.";
responder2_ = nullptr;
Unpublish();
return;
}
}
SetSubtypes(std::move(subtypes));
};
responder2_.events().Reannounce = [this]() { Reannounce(); };
}
MdnsServiceImpl::ResponderPublisher::~ResponderPublisher() {
responder_.set_error_handler(nullptr);
if (responder_.is_bound()) {
responder_.Unbind();
}
responder2_.set_error_handler(nullptr);
if (responder2_.is_bound()) {
responder2_.Unbind();
}
}
void MdnsServiceImpl::ResponderPublisher::ReportSuccess(bool success) {
if (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));
} else {
fuchsia::net::mdns::Publisher_PublishServiceInstance2_Result result;
if (success) {
result.set_response(fuchsia::net::mdns::Publisher_PublishServiceInstance2_Response());
} else {
result.set_err(fuchsia::net::mdns::Error::ALREADY_PUBLISHED_ON_SUBNET);
}
callback2_(std::move(result));
}
}
void MdnsServiceImpl::ResponderPublisher::GetPublication(
bool query, const std::string& subtype,
const std::vector<inet::SocketAddress>& source_addresses,
fit::function<void(std::unique_ptr<Mdns::Publication>)> callback) {
FX_DCHECK(subtype.empty() || MdnsNames::IsValidSubtypeName(subtype));
if (responder_) {
responder_->OnPublication(
query, subtype,
[this, callback = std::move(callback)](fuchsia::net::mdns::PublicationPtr publication_ptr) {
if (publication_ptr) {
for (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(MdnsFidlUtil::Convert(publication_ptr));
});
} else {
FX_DCHECK(responder2_);
responder2_->OnPublication(
query, subtype, MdnsFidlUtil::Convert(source_addresses),
[this, callback = std::move(callback)](fuchsia::net::mdns::PublicationPtr publication_ptr) {
if (publication_ptr) {
for (auto& text : publication_ptr->text) {
if (!MdnsNames::IsValidTextString(text)) {
FX_LOGS(ERROR) << "Invalid text string returned by "
"Responder.GetPublication, closing connection.";
responder2_ = 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.";
responder2_ = nullptr;
Unpublish();
return;
}
}
callback(MdnsFidlUtil::Convert(publication_ptr));
});
}
}
} // namespace mdns