blob: 0cf5733347d042784ae1a5998c642b0c91c24608 [file] [edit]
/*
* Copyright (c) 2021, The OpenThread Authors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @file
* The file implements the DNS-SD Discovery Proxy.
*/
#if OTBR_ENABLE_DNSSD_DISCOVERY_PROXY
#define OTBR_LOG_TAG "DPROXY"
#include "sdp_proxy/discovery_proxy.hpp"
#include <algorithm>
#include <string>
#include <assert.h>
#include <openthread/dnssd_server.h>
#include "common/code_utils.hpp"
#include "common/dns_utils.hpp"
#include "common/logging.hpp"
#include "utils/dns_utils.hpp"
#include "utils/string_utils.hpp"
namespace otbr {
namespace Dnssd {
static inline bool DnsLabelsEqual(const std::string &aLabel1, const std::string &aLabel2)
{
return StringUtils::EqualCaseInsensitive(aLabel1, aLabel2);
}
DiscoveryProxy::DiscoveryProxy(Ncp::ControllerOpenThread &aNcp, Mdns::Publisher &aPublisher)
: mNcp(aNcp)
, mMdnsPublisher(aPublisher)
, mIsEnabled(false)
{
mNcp.RegisterResetHandler([this]() {
otDnssdQuerySetCallbacks(mNcp.GetInstance(), &DiscoveryProxy::OnDiscoveryProxySubscribe,
&DiscoveryProxy::OnDiscoveryProxyUnsubscribe, this);
});
}
void DiscoveryProxy::SetEnabled(bool aIsEnabled)
{
VerifyOrExit(IsEnabled() != aIsEnabled);
mIsEnabled = aIsEnabled;
if (mIsEnabled)
{
Start();
}
else
{
Stop();
}
exit:
return;
}
void DiscoveryProxy::Start(void)
{
assert(mSubscriberId == 0);
otDnssdQuerySetCallbacks(mNcp.GetInstance(), &DiscoveryProxy::OnDiscoveryProxySubscribe,
&DiscoveryProxy::OnDiscoveryProxyUnsubscribe, this);
mSubscriberId = mMdnsPublisher.AddSubscriptionCallbacks(
[this](const std::string &aType, const Mdns::Publisher::DiscoveredInstanceInfo &aInstanceInfo) {
if (!aInstanceInfo.mRemoved)
{
OnServiceDiscovered(aType, aInstanceInfo);
}
},
[this](const std::string &aHostName, const Mdns::Publisher::DiscoveredHostInfo &aHostInfo) {
OnHostDiscovered(aHostName, aHostInfo);
});
otbrLogInfo("Started");
}
void DiscoveryProxy::Stop(void)
{
otDnssdQuerySetCallbacks(mNcp.GetInstance(), nullptr, nullptr, nullptr);
if (mSubscriberId > 0)
{
mMdnsPublisher.RemoveSubscriptionCallbacks(mSubscriberId);
mSubscriberId = 0;
}
otbrLogInfo("Stopped");
}
void DiscoveryProxy::OnDiscoveryProxySubscribe(void *aContext, const char *aFullName)
{
reinterpret_cast<DiscoveryProxy *>(aContext)->OnDiscoveryProxySubscribe(aFullName);
}
void DiscoveryProxy::OnDiscoveryProxySubscribe(const char *aFullName)
{
std::string fullName(aFullName);
DnsNameInfo nameInfo = SplitFullDnsName(fullName);
otbrLogInfo("Subscribe: %s", fullName.c_str());
if (GetServiceSubscriptionCount(nameInfo) == 1)
{
if (nameInfo.mHostName.empty())
{
mMdnsPublisher.SubscribeService(nameInfo.mServiceName, nameInfo.mInstanceName);
}
else
{
mMdnsPublisher.SubscribeHost(nameInfo.mHostName);
}
}
}
void DiscoveryProxy::OnDiscoveryProxyUnsubscribe(void *aContext, const char *aFullName)
{
reinterpret_cast<DiscoveryProxy *>(aContext)->OnDiscoveryProxyUnsubscribe(aFullName);
}
void DiscoveryProxy::OnDiscoveryProxyUnsubscribe(const char *aFullName)
{
std::string fullName(aFullName);
DnsNameInfo nameInfo = SplitFullDnsName(fullName);
otbrLogInfo("Unsubscribe: %s", fullName.c_str());
if (GetServiceSubscriptionCount(nameInfo) == 1)
{
if (nameInfo.mHostName.empty())
{
mMdnsPublisher.UnsubscribeService(nameInfo.mServiceName, nameInfo.mInstanceName);
}
else
{
mMdnsPublisher.UnsubscribeHost(nameInfo.mHostName);
}
}
}
void DiscoveryProxy::OnServiceDiscovered(const std::string &aType,
const Mdns::Publisher::DiscoveredInstanceInfo &aInstanceInfo)
{
otDnssdServiceInstanceInfo instanceInfo;
const otDnssdQuery *query = nullptr;
std::string unescapedInstanceName = DnsUtils::UnescapeInstanceName(aInstanceInfo.mName);
otbrLogInfo("Service discovered: %s, instance %s hostname %s addresses %zu port %d priority %d "
"weight %d",
aType.c_str(), aInstanceInfo.mName.c_str(), aInstanceInfo.mHostName.c_str(),
aInstanceInfo.mAddresses.size(), aInstanceInfo.mPort, aInstanceInfo.mPriority, aInstanceInfo.mWeight);
instanceInfo.mAddressNum = aInstanceInfo.mAddresses.size();
if (!aInstanceInfo.mAddresses.empty())
{
instanceInfo.mAddresses = reinterpret_cast<const otIp6Address *>(&aInstanceInfo.mAddresses[0]);
}
else
{
instanceInfo.mAddresses = nullptr;
}
instanceInfo.mPort = aInstanceInfo.mPort;
instanceInfo.mPriority = aInstanceInfo.mPriority;
instanceInfo.mWeight = aInstanceInfo.mWeight;
instanceInfo.mTxtLength = static_cast<uint16_t>(aInstanceInfo.mTxtData.size());
instanceInfo.mTxtData = aInstanceInfo.mTxtData.data();
instanceInfo.mTtl = CapTtl(aInstanceInfo.mTtl);
while ((query = otDnssdGetNextQuery(mNcp.GetInstance(), query)) != nullptr)
{
std::string instanceName;
std::string serviceName;
std::string hostName;
std::string domain;
char queryName[OT_DNS_MAX_NAME_SIZE];
otDnssdQueryType type = otDnssdGetQueryTypeAndName(query, &queryName);
otbrError splitError;
switch (type)
{
case OT_DNSSD_QUERY_TYPE_BROWSE:
splitError = SplitFullServiceName(queryName, serviceName, domain);
assert(splitError == OTBR_ERROR_NONE);
break;
case OT_DNSSD_QUERY_TYPE_RESOLVE:
splitError = SplitFullServiceInstanceName(queryName, instanceName, serviceName, domain);
assert(splitError == OTBR_ERROR_NONE);
break;
default:
splitError = OTBR_ERROR_NOT_FOUND;
break;
}
if (splitError != OTBR_ERROR_NONE)
{
continue;
}
if (DnsLabelsEqual(serviceName, aType) &&
(instanceName.empty() || DnsLabelsEqual(instanceName, unescapedInstanceName)))
{
std::string serviceFullName = aType + "." + domain;
std::string translatedHostName = TranslateDomain(aInstanceInfo.mHostName, domain);
std::string instanceFullName = unescapedInstanceName + "." + serviceFullName;
instanceInfo.mFullName = instanceFullName.c_str();
instanceInfo.mHostName = translatedHostName.c_str();
otDnssdQueryHandleDiscoveredServiceInstance(mNcp.GetInstance(), serviceFullName.c_str(), &instanceInfo);
}
}
}
void DiscoveryProxy::OnHostDiscovered(const std::string &aHostName,
const Mdns::Publisher::DiscoveredHostInfo &aHostInfo)
{
otDnssdHostInfo hostInfo;
const otDnssdQuery *query = nullptr;
std::string resolvedHostName = aHostInfo.mHostName;
otbrLogInfo("Host discovered: %s hostname %s addresses %zu", aHostName.c_str(), aHostInfo.mHostName.c_str(),
aHostInfo.mAddresses.size());
if (resolvedHostName.empty())
{
resolvedHostName = aHostName + ".local.";
}
hostInfo.mAddressNum = aHostInfo.mAddresses.size();
if (!aHostInfo.mAddresses.empty())
{
hostInfo.mAddresses = reinterpret_cast<const otIp6Address *>(&aHostInfo.mAddresses[0]);
}
else
{
hostInfo.mAddresses = nullptr;
}
hostInfo.mTtl = CapTtl(aHostInfo.mTtl);
while ((query = otDnssdGetNextQuery(mNcp.GetInstance(), query)) != nullptr)
{
std::string hostName, domain;
char queryName[OT_DNS_MAX_NAME_SIZE];
otDnssdQueryType type = otDnssdGetQueryTypeAndName(query, &queryName);
otbrError splitError;
OTBR_UNUSED_VARIABLE(splitError);
if (type != OT_DNSSD_QUERY_TYPE_RESOLVE_HOST)
{
continue;
}
splitError = SplitFullHostName(queryName, hostName, domain);
assert(splitError == OTBR_ERROR_NONE);
if (DnsLabelsEqual(hostName, aHostName))
{
std::string hostFullName = TranslateDomain(resolvedHostName, domain);
otDnssdQueryHandleDiscoveredHost(mNcp.GetInstance(), hostFullName.c_str(), &hostInfo);
}
}
}
std::string DiscoveryProxy::TranslateDomain(const std::string &aName, const std::string &aTargetDomain)
{
std::string targetName;
std::string hostName;
std::string domain;
VerifyOrExit(OTBR_ERROR_NONE == SplitFullHostName(aName, hostName, domain), targetName = aName);
VerifyOrExit(DnsLabelsEqual(domain, "local."), targetName = aName);
targetName = hostName + "." + aTargetDomain;
exit:
otbrLogDebug("Translate domain: %s => %s", aName.c_str(), targetName.c_str());
return targetName;
}
int DiscoveryProxy::GetServiceSubscriptionCount(const DnsNameInfo &aNameInfo) const
{
const otDnssdQuery *query = nullptr;
int count = 0;
while ((query = otDnssdGetNextQuery(mNcp.GetInstance(), query)) != nullptr)
{
char queryName[OT_DNS_MAX_NAME_SIZE];
DnsNameInfo queryInfo;
otDnssdGetQueryTypeAndName(query, &queryName);
queryInfo = SplitFullDnsName(queryName);
count += (DnsLabelsEqual(aNameInfo.mInstanceName, queryInfo.mInstanceName) &&
DnsLabelsEqual(aNameInfo.mServiceName, queryInfo.mServiceName) &&
DnsLabelsEqual(aNameInfo.mHostName, queryInfo.mHostName));
}
return count;
}
uint32_t DiscoveryProxy::CapTtl(uint32_t aTtl)
{
return std::min(aTtl, static_cast<uint32_t>(kServiceTtlCapLimit));
}
} // namespace Dnssd
} // namespace otbr
#endif // OTBR_ENABLE_DNSSD_DISCOVERY_PROXY