blob: 585ed771e3b48dc46437e24d65216446365b0cdc [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/netstack/cpp/fidl.h>
#include <lib/async/cpp/task.h>
#include <lib/async/default.h>
#include <lib/sys/cpp/component_context.h>
#include <unistd.h>
#include "lib/fidl/cpp/type_converter.h"
#include "lib/fsl/types/type_converters.h"
#include "src/connectivity/network/mdns/service/mdns_fidl_util.h"
#include "src/connectivity/network/mdns/service/mdns_names.h"
#include "src/lib/fxl/logging.h"
namespace mdns {
namespace {
static const std::string kUnsetHostName = "fuchsia-unset-device-name";
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) {
FXL_LOG(ERROR) << "gethostname failed, " << strerror(errno);
host_name = kUnsetHostName;
} else {
host_name = host_name_buffer;
}
return host_name;
}
} // 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 == kUnsetHostName) {
// Host name not set. Try again soon.
async::PostDelayedTask(
async_get_default_dispatcher(), [this]() { Start(); },
kReadyPollingInterval);
return;
}
config_.ReadConfigFiles(host_name);
if (!config_.valid()) {
FXL_LOG(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,
[this, service = publication.service_](
fuchsia::net::mdns::Publisher_PublishServiceInstance_Result
result) {
if (result.is_err()) {
FXL_LOG(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.
FXL_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)) {
FXL_LOG(ERROR) << "ResolveHostName called with invalid host name " << host;
callback(nullptr, nullptr);
return;
}
mdns_.ResolveHostName(
host, fxl::TimePoint::Now() + fxl::TimeDelta::FromNanoseconds(timeout_ns),
[this, 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)) {
FXL_LOG(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) {
FXL_DCHECK(responder_handle);
if (!MdnsNames::IsValidServiceName(service)) {
FXL_LOG(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)) {
FXL_LOG(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();
FXL_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_PublishServiceInstance_Result result;
result.set_err(fuchsia::net::mdns::Error::ALREADY_PUBLISHED_LOCALLY);
callback(std::move(result));
return;
}
// |Mdns| told us our instance is unique locally, so the full name should
// not appear in our collection.
FXL_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) {
client_.set_error_handler(nullptr);
client_.Unbind();
deleter();
});
}
MdnsServiceImpl::Subscriber::~Subscriber() {}
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,
.service_instance = fuchsia::net::mdns::ServiceInstance{
.service = service,
.instance = instance,
.text = fidl::VectorPtr<std::string>(text),
.srv_priority = srv_priority,
.srv_weight = srv_weight}};
if (v4_address) {
entry.service_instance.endpoints.push_back(
MdnsFidlUtil::CreateEndpointV4(v4_address));
}
if (v6_address) {
entry.service_instance.endpoints.push_back(
MdnsFidlUtil::CreateEndpointV6(v6_address));
}
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,
.service_instance = fuchsia::net::mdns::ServiceInstance{
.service = service,
.instance = instance,
.text = fidl::VectorPtr<std::string>(text),
.srv_priority = srv_priority,
.srv_weight = srv_weight}};
if (v4_address) {
entry.service_instance.endpoints.push_back(
MdnsFidlUtil::CreateEndpointV4(v4_address));
}
if (v6_address) {
entry.service_instance.endpoints.push_back(
MdnsFidlUtil::CreateEndpointV6(v6_address));
}
entries_.push(std::move(entry));
MaybeSendNextEntry();
}
void MdnsServiceImpl::Subscriber::InstanceLost(const std::string& service,
const std::string& instance) {
entries_.push({.type = EntryType::kInstanceLost,
.service_instance = fuchsia::net::mdns::ServiceInstance{
.service = service, .instance = instance}});
MaybeSendNextEntry();
}
void MdnsServiceImpl::Subscriber::MaybeSendNextEntry() {
FXL_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);
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;
}
++pipeline_depth_;
entries_.pop();
}
void MdnsServiceImpl::Subscriber::ReplyReceived() {
FXL_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,
fit::function<void(std::unique_ptr<Mdns::Publication>)> callback) {
FXL_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)) {
FXL_DCHECK(responder_);
responder_.set_error_handler(
[this, deleter = std::move(deleter)](zx_status_t status) {
responder_.set_error_handler(nullptr);
deleter();
});
responder_.events().SetSubtypes = [this](std::vector<std::string> subtypes) {
for (auto& subtype : subtypes) {
if (!MdnsNames::IsValidSubtypeName(subtype)) {
FXL_LOG(ERROR) << "Invalid subtype " << subtype
<< " passed in SetSubtypes event, closing connection.";
responder_ = nullptr;
Unpublish();
return;
}
}
SetSubtypes(std::move(subtypes));
};
responder_.events().Reannounce = [this]() { Reannounce(); };
}
void MdnsServiceImpl::ResponderPublisher::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::ResponderPublisher::GetPublication(
bool query, const std::string& subtype,
fit::function<void(std::unique_ptr<Mdns::Publication>)> callback) {
FXL_DCHECK(subtype.empty() || MdnsNames::IsValidSubtypeName(subtype));
FXL_DCHECK(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)) {
FXL_LOG(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)) {
FXL_LOG(ERROR) << "TTL less than one second returned by "
"Responder.GetPublication, closing connection.";
responder_ = nullptr;
Unpublish();
return;
}
}
callback(MdnsFidlUtil::Convert(publication_ptr));
});
}
} // namespace mdns