blob: 13b30d06242c15da97ffb73163990d603dcebc2e [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 "garnet/bin/mdns/service/host_name.h"
#include "garnet/bin/mdns/service/mdns_fidl_util.h"
#include "garnet/bin/mdns/service/mdns_names.h"
#include "lib/component/cpp/startup_context.h"
#include "lib/fsl/types/type_converters.h"
#include "lib/fxl/functional/make_copyable.h"
#include "lib/fxl/logging.h"
#include "lib/fxl/type_converter.h"
namespace mdns {
namespace {
static const std::string kPublishAs = "_fuchsia._udp.";
static constexpr uint64_t kPublishPort = 5353;
} // namespace
MdnsServiceImpl::MdnsServiceImpl(component::StartupContext* startup_context)
: startup_context_(startup_context) {
startup_context_->outgoing().AddPublicService(bindings_.GetHandler(this));
Start();
}
MdnsServiceImpl::~MdnsServiceImpl() {}
void MdnsServiceImpl::Start() {
// TODO(NET-79): Remove this check when NET-79 is fixed.
if (!NetworkIsReady()) {
async::PostDelayedTask(async_get_default_dispatcher(),
[this]() { Start(); }, zx::sec(5));
return;
}
mdns_.Start(startup_context_
->ConnectToEnvironmentService<fuchsia::netstack::Netstack>(),
GetHostName());
// Publish this device as "_fuchsia._udp.".
// TODO(dalesat): Make this a config item or delegate to another party.
PublishServiceInstance(kPublishAs, GetHostName(), kPublishPort,
fidl::VectorPtr<fidl::StringPtr>(),
[this](fuchsia::mdns::MdnsResult result) {
if (result != fuchsia::mdns::MdnsResult::OK) {
FXL_LOG(ERROR) << "Failed to publish as "
<< kPublishAs << ", result "
<< static_cast<uint32_t>(result);
}
});
}
void MdnsServiceImpl::ResolveHostName(fidl::StringPtr host_name,
uint32_t timeout_ms,
ResolveHostNameCallback callback) {
if (!MdnsNames::IsValidHostName(host_name)) {
callback(nullptr, nullptr);
return;
}
mdns_.ResolveHostName(
host_name,
fxl::TimePoint::Now() + fxl::TimeDelta::FromMilliseconds(timeout_ms),
[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(
fidl::StringPtr service_name,
fidl::InterfaceRequest<fuchsia::mdns::MdnsServiceSubscription>
subscription_request) {
if (!MdnsNames::IsValidServiceName(service_name)) {
return;
}
size_t id = next_subscriber_id_++;
auto subscriber = std::make_unique<Subscriber>(
std::move(subscription_request),
[this, id]() { subscribers_by_id_.erase(id); });
mdns_.SubscribeToService(service_name, subscriber.get());
subscribers_by_id_.emplace(id, std::move(subscriber));
}
void MdnsServiceImpl::PublishServiceInstance(
fidl::StringPtr service_name, fidl::StringPtr instance_name, uint16_t port,
fidl::VectorPtr<fidl::StringPtr> text,
PublishServiceInstanceCallback callback) {
if (!MdnsNames::IsValidServiceName(service_name)) {
callback(fuchsia::mdns::MdnsResult::INVALID_SERVICE_NAME);
return;
}
if (!MdnsNames::IsValidInstanceName(instance_name)) {
callback(fuchsia::mdns::MdnsResult::INVALID_INSTANCE_NAME);
return;
}
auto publisher = std::make_unique<SimplePublisher>(
inet::IpPort::From_uint16_t(port), std::move(text), callback.share());
if (!mdns_.PublishServiceInstance(service_name, instance_name,
publisher.get())) {
callback(fuchsia::mdns::MdnsResult::ALREADY_PUBLISHED_LOCALLY);
return;
}
MdnsNames::LocalInstanceFullName(instance_name, service_name);
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));
}
void MdnsServiceImpl::UnpublishServiceInstance(fidl::StringPtr service_name,
fidl::StringPtr 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::AddResponder(
fidl::StringPtr service_name, fidl::StringPtr instance_name,
fidl::InterfaceHandle<fuchsia::mdns::MdnsResponder> responder_handle) {
FXL_DCHECK(responder_handle);
auto responder_ptr = responder_handle.Bind();
FXL_DCHECK(responder_ptr);
if (!MdnsNames::IsValidServiceName(service_name)) {
responder_ptr->UpdateStatus(
fuchsia::mdns::MdnsResult::INVALID_SERVICE_NAME);
return;
}
if (!MdnsNames::IsValidInstanceName(instance_name)) {
responder_ptr->UpdateStatus(
fuchsia::mdns::MdnsResult::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), [this, instance_full_name]() {
publishers_by_instance_full_name_.erase(instance_full_name);
});
if (!mdns_.PublishServiceInstance(service_name, instance_name,
publisher.get())) {
publisher->responder_->UpdateStatus(
fuchsia::mdns::MdnsResult::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::SetSubtypes(fidl::StringPtr service_name,
fidl::StringPtr instance_name,
fidl::VectorPtr<fidl::StringPtr> subtypes) {
if (!MdnsNames::IsValidServiceName(service_name) ||
!MdnsNames::IsValidInstanceName(instance_name)) {
return;
}
std::string instance_full_name =
MdnsNames::LocalInstanceFullName(instance_name, service_name);
auto iter = publishers_by_instance_full_name_.find(instance_full_name);
if (iter == publishers_by_instance_full_name_.end()) {
return;
}
iter->second->SetSubtypes(fxl::To<std::vector<std::string>>(subtypes));
}
void MdnsServiceImpl::ReannounceInstance(fidl::StringPtr service_name,
fidl::StringPtr instance_name) {
if (!MdnsNames::IsValidServiceName(service_name) ||
!MdnsNames::IsValidInstanceName(instance_name)) {
return;
}
std::string instance_full_name =
MdnsNames::LocalInstanceFullName(instance_name, service_name);
auto iter = publishers_by_instance_full_name_.find(instance_full_name);
if (iter == publishers_by_instance_full_name_.end()) {
return;
}
iter->second->Reannounce();
}
void MdnsServiceImpl::SetVerbose(bool value) { mdns_.SetVerbose(value); }
MdnsServiceImpl::Subscriber::Subscriber(
fidl::InterfaceRequest<fuchsia::mdns::MdnsServiceSubscription> request,
fit::closure deleter)
: binding_(this, std::move(request)) {
binding_.set_error_handler(
[this, deleter = std::move(deleter)](zx_status_t status) {
binding_.set_error_handler(nullptr);
deleter();
});
instances_publisher_.SetCallbackRunner(
[this](GetInstancesCallback callback, uint64_t version) {
fidl::VectorPtr<fuchsia::mdns::MdnsServiceInstance> instances(
instances_by_name_.size());
size_t i = 0;
for (auto& pair : instances_by_name_) {
pair.second->Clone(&instances->at(i++));
}
callback(version, std::move(instances));
});
}
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) {
instances_by_name_.emplace(
instance, MdnsFidlUtil::CreateServiceInstance(
service, instance, v4_address, v6_address, text));
}
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) {
auto iter = instances_by_name_.find(instance);
if (iter != instances_by_name_.end()) {
MdnsFidlUtil::UpdateServiceInstance(iter->second, v4_address, v6_address,
text);
}
}
void MdnsServiceImpl::Subscriber::InstanceLost(const std::string& service,
const std::string& instance) {
instances_by_name_.erase(instance);
}
void MdnsServiceImpl::Subscriber::UpdatesComplete() {
instances_publisher_.SendUpdates();
}
void MdnsServiceImpl::Subscriber::GetInstances(uint64_t version_last_seen,
GetInstancesCallback callback) {
instances_publisher_.Get(version_last_seen, std::move(callback));
}
MdnsServiceImpl::SimplePublisher::SimplePublisher(
inet::IpPort port, fidl::VectorPtr<fidl::StringPtr> text,
PublishServiceInstanceCallback callback)
: port_(port),
text_(fxl::To<std::vector<std::string>>(text)),
callback_(std::move(callback)) {}
void MdnsServiceImpl::SimplePublisher::ReportSuccess(bool success) {
callback_(success ? fuchsia::mdns::MdnsResult::OK
: fuchsia::mdns::MdnsResult::ALREADY_PUBLISHED_ON_SUBNET);
}
void MdnsServiceImpl::SimplePublisher::GetPublication(
bool query, const std::string& subtype,
fit::function<void(std::unique_ptr<Mdns::Publication>)> callback) {
callback(Mdns::Publication::Create(port_, text_));
}
MdnsServiceImpl::ResponderPublisher::ResponderPublisher(
fuchsia::mdns::MdnsResponderPtr responder, fit::closure deleter)
: responder_(std::move(responder)) {
FXL_DCHECK(responder_);
responder_.set_error_handler(
[this, deleter = std::move(deleter)](zx_status_t status) {
responder_.set_error_handler(nullptr);
deleter();
});
}
void MdnsServiceImpl::ResponderPublisher::ReportSuccess(bool success) {
FXL_DCHECK(responder_);
responder_->UpdateStatus(
success ? fuchsia::mdns::MdnsResult::OK
: fuchsia::mdns::MdnsResult::ALREADY_PUBLISHED_ON_SUBNET);
}
void MdnsServiceImpl::ResponderPublisher::GetPublication(
bool query, const std::string& subtype,
fit::function<void(std::unique_ptr<Mdns::Publication>)> callback) {
FXL_DCHECK(responder_);
responder_->GetPublication(
query, subtype,
fxl::MakeCopyable([callback = std::move(callback)](
fuchsia::mdns::MdnsPublicationPtr publication_ptr) {
callback(MdnsFidlUtil::Convert(publication_ptr));
}));
}
} // namespace mdns