blob: f015a374381f7733f6d08c2a028e2d1ad209dcc5 [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 "garnet/bin/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 "garnet/bin/mdns/service/mdns_fidl_util.h"
#include "garnet/bin/mdns/service/mdns_names.h"
#include "lib/fidl/cpp/type_converter.h"
#include "lib/fsl/types/type_converters.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) {
component_context_->outgoing()->AddPublicService<fuchsia::mdns::Controller>(
fit::bind_member(this, &MdnsServiceImpl::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_.mdns_port(), config_.perform_host_name_probe(),
fit::bind_member(this, &MdnsServiceImpl::OnReady));
}
void MdnsServiceImpl::OnBindRequest(
fidl::InterfaceRequest<fuchsia::mdns::Controller> request) {
if (ready_) {
bindings_.AddBinding(this, std::move(request));
} else {
pending_binding_requests_.push_back(std::move(request));
}
}
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::mdns::Result result) {
if (result != fuchsia::mdns::Result::OK) {
FXL_LOG(ERROR) << "Failed to publish as " << service << ", result "
<< static_cast<uint32_t>(result);
}
});
}
for (auto& request : pending_binding_requests_) {
bindings_.AddBinding(this, std::move(request));
}
pending_binding_requests_.clear();
}
void MdnsServiceImpl::ResolveHostName(std::string host_name, int64_t timeout_ns,
ResolveHostNameCallback callback) {
if (!MdnsNames::IsValidHostName(host_name)) {
callback(nullptr, nullptr);
return;
}
mdns_.ResolveHostName(
host_name,
fxl::TimePoint::Now() + fxl::TimeDelta::FromNanoseconds(timeout_ns),
[this, callback = std::move(callback)](
const std::string& host_name, const inet::IpAddress& v4_address,
const inet::IpAddress& v6_address) {
callback(MdnsFidlUtil::CreateSocketAddressIPv4(v4_address),
MdnsFidlUtil::CreateSocketAddressIPv6(v6_address));
});
}
void MdnsServiceImpl::SubscribeToService(
std::string service_name,
fidl::InterfaceHandle<fuchsia::mdns::ServiceSubscriber> subscriber_handle) {
if (!MdnsNames::IsValidServiceName(service_name)) {
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_name, subscriber.get());
subscribers_by_id_.emplace(id, std::move(subscriber));
}
void MdnsServiceImpl::DEPRECATEDPublishServiceInstance(
std::string service_name, std::string instance_name, uint16_t port,
std::vector<std::string> text, bool perform_probe,
PublishServiceInstanceCallback callback) {
if (!MdnsNames::IsValidServiceName(service_name)) {
callback(fuchsia::mdns::Result::INVALID_SERVICE_NAME);
return;
}
if (!MdnsNames::IsValidInstanceName(instance_name)) {
callback(fuchsia::mdns::Result::INVALID_INSTANCE_NAME);
return;
}
if (!PublishServiceInstance(
service_name, instance_name,
Mdns::Publication::Create(inet::IpPort::From_uint16_t(port),
std::move(text)),
perform_probe, callback.share())) {
callback(fuchsia::mdns::Result::ALREADY_PUBLISHED_LOCALLY);
}
}
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::DEPRECATEDUnpublishServiceInstance(
std::string service_name, std::string instance_name) {
if (!MdnsNames::IsValidServiceName(service_name) ||
!MdnsNames::IsValidInstanceName(instance_name)) {
return;
}
std::string instance_full_name =
MdnsNames::LocalInstanceFullName(instance_name, service_name);
// This will delete the publisher, unpublishing the service instance.
publishers_by_instance_full_name_.erase(instance_full_name);
}
void MdnsServiceImpl::PublishServiceInstance(
std::string service_name, std::string instance_name, bool perform_probe,
fidl::InterfaceHandle<fuchsia::mdns::Responder> responder_handle,
PublishServiceInstanceCallback callback) {
FXL_DCHECK(responder_handle);
auto responder_ptr = responder_handle.Bind();
FXL_DCHECK(responder_ptr);
if (!MdnsNames::IsValidServiceName(service_name)) {
callback(fuchsia::mdns::Result::INVALID_SERVICE_NAME);
return;
}
if (!MdnsNames::IsValidInstanceName(instance_name)) {
callback(fuchsia::mdns::Result::INVALID_INSTANCE_NAME);
return;
}
std::string instance_full_name =
MdnsNames::LocalInstanceFullName(instance_name, service_name);
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_name, instance_name, perform_probe,
publisher.get())) {
callback(fuchsia::mdns::Result::ALREADY_PUBLISHED_LOCALLY);
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));
}
void MdnsServiceImpl::DEPRECATEDSetVerbose(bool value) {
mdns_.SetVerbose(value);
}
////////////////////////////////////////////////////////////////////////////////
// MdnsServiceImpl::Subscriber implementation
MdnsServiceImpl::Subscriber::Subscriber(
fidl::InterfaceHandle<fuchsia::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) {
entries_.push(
{.type = EntryType::kInstanceDiscovered,
.service_instance = fuchsia::mdns::ServiceInstance{
.service_name = service,
.instance_name = instance,
.v4_address = MdnsFidlUtil::CreateSocketAddressIPv4(v4_address),
.v6_address = MdnsFidlUtil::CreateSocketAddressIPv6(v6_address),
.text = fidl::VectorPtr<std::string>(text)}});
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) {
entries_.push(
{.type = EntryType::kInstanceChanged,
.service_instance = fuchsia::mdns::ServiceInstance{
.service_name = service,
.instance_name = instance,
.v4_address = MdnsFidlUtil::CreateSocketAddressIPv4(v4_address),
.v6_address = MdnsFidlUtil::CreateSocketAddressIPv6(v6_address),
.text = fidl::VectorPtr<std::string>(text)}});
MaybeSendNextEntry();
}
void MdnsServiceImpl::Subscriber::InstanceLost(const std::string& service,
const std::string& instance) {
entries_.push({.type = EntryType::kInstanceLost,
.service_instance = fuchsia::mdns::ServiceInstance{
.service_name = service, .instance_name = 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_->InstanceDiscovered(std::move(entry.service_instance),
std::move(on_reply));
break;
case EntryType::kInstanceChanged:
client_->InstanceChanged(std::move(entry.service_instance),
std::move(on_reply));
break;
case EntryType::kInstanceLost:
client_->InstanceLost(entry.service_instance.service_name,
entry.service_instance.instance_name,
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) {
callback_(success ? fuchsia::mdns::Result::OK
: fuchsia::mdns::Result::ALREADY_PUBLISHED_ON_SUBNET);
}
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::mdns::ResponderPtr 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().OnSubtypesChanged =
[this](std::vector<std::string> subtypes) {
for (auto& subtype : subtypes) {
if (!MdnsNames::IsValidSubtypeName(subtype)) {
FXL_LOG(ERROR)
<< "Invalid subtype " << subtype
<< " passed in OnSubtypesChanged event, closing connection.";
responder_ = nullptr;
Unpublish();
return;
}
}
SetSubtypes(std::move(subtypes));
};
responder_.events().OnPublicationChanged = [this]() { Reannounce(); };
}
void MdnsServiceImpl::ResponderPublisher::ReportSuccess(bool success) {
FXL_DCHECK(responder_);
callback_(success ? fuchsia::mdns::Result::OK
: fuchsia::mdns::Result::ALREADY_PUBLISHED_ON_SUBNET);
callback_ = nullptr;
}
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_->GetPublication(
query, subtype,
[this, callback = std::move(callback)](
fuchsia::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