blob: e2f9726553d3d7e5e9e7e877cc505cef108c75af [file] [log] [blame]
/*
* 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
* This file includes implementation of TREL DNS-SD over mDNS.
*/
#if OTBR_ENABLE_TREL
#define OTBR_LOG_TAG "TrelDns"
#include "trel_dnssd/trel_dnssd.hpp"
#include <net/if.h>
#include <openthread/instance.h>
#include <openthread/link.h>
#include <openthread/platform/trel.h>
#include "common/code_utils.hpp"
#include "utils/hex.hpp"
#include "utils/string_utils.hpp"
static const char kTrelServiceName[] = "_trel._udp";
static otbr::TrelDnssd::TrelDnssd *sTrelDnssd = nullptr;
void trelDnssdInitialize(const char *aTrelNetif)
{
sTrelDnssd->Initialize(aTrelNetif);
}
void trelDnssdStartBrowse(void)
{
sTrelDnssd->StartBrowse();
}
void trelDnssdStopBrowse(void)
{
sTrelDnssd->StopBrowse();
}
void trelDnssdRegisterService(uint16_t aPort, const uint8_t *aTxtData, uint8_t aTxtLength)
{
sTrelDnssd->RegisterService(aPort, aTxtData, aTxtLength);
}
void trelDnssdRemoveService(void)
{
sTrelDnssd->UnregisterService();
}
namespace otbr {
namespace TrelDnssd {
TrelDnssd::TrelDnssd(Ncp::ControllerOpenThread &aNcp, Mdns::Publisher &aPublisher)
: mPublisher(aPublisher)
, mNcp(aNcp)
{
sTrelDnssd = this;
}
void TrelDnssd::Initialize(std::string aTrelNetif)
{
mTrelNetif = std::move(aTrelNetif);
if (IsInitialized())
{
otbrLogDebug("Initialized on netif \"%s\"", mTrelNetif.c_str());
CheckTrelNetifReady();
}
else
{
otbrLogDebug("Not initialized");
}
}
void TrelDnssd::StartBrowse(void)
{
VerifyOrExit(IsInitialized());
otbrLogDebug("Start browsing %s services ...", kTrelServiceName);
assert(mSubscriberId == 0);
mSubscriberId = mPublisher.AddSubscriptionCallbacks(
[this](const std::string &aType, const Mdns::Publisher::DiscoveredInstanceInfo &aInstanceInfo) {
OnTrelServiceInstanceResolved(aType, aInstanceInfo);
},
/* aHostCallback */ nullptr);
if (IsReady())
{
mPublisher.SubscribeService(kTrelServiceName, /* aInstanceName */ "");
}
exit:
return;
}
void TrelDnssd::StopBrowse(void)
{
VerifyOrExit(IsInitialized());
otbrLogDebug("Stop browsing %s service.", kTrelServiceName);
assert(mSubscriberId > 0);
mPublisher.RemoveSubscriptionCallbacks(mSubscriberId);
mSubscriberId = 0;
if (IsReady())
{
mPublisher.UnsubscribeService(kTrelServiceName, "");
}
exit:
return;
}
void TrelDnssd::RegisterService(uint16_t aPort, const uint8_t *aTxtData, uint8_t aTxtLength)
{
assert(aPort > 0);
assert(aTxtData != nullptr);
VerifyOrExit(IsInitialized());
otbrLogDebug("Register %s service: port=%u, TXT=%d bytes", kTrelServiceName, aPort, aTxtLength);
otbrDump(OTBR_LOG_DEBUG, OTBR_LOG_TAG, "TXT", aTxtData, aTxtLength);
if (mRegisterInfo.IsValid() && IsReady())
{
UnpublishTrelService();
}
mRegisterInfo.Assign(aPort, aTxtData, aTxtLength);
if (IsReady())
{
PublishTrelService();
}
exit:
return;
}
void TrelDnssd::UnregisterService(void)
{
VerifyOrExit(IsInitialized());
otbrLogDebug("Remove %s service", kTrelServiceName);
assert(mRegisterInfo.IsValid());
if (IsReady())
{
UnpublishTrelService();
}
mRegisterInfo.Clear();
exit:
return;
}
void TrelDnssd::OnMdnsPublisherReady(void)
{
VerifyOrExit(IsInitialized());
otbrLogDebug("mDNS Publisher is Ready");
mMdnsPublisherReady = true;
RemoveAllPeers();
if (mRegisterInfo.IsPublished())
{
mRegisterInfo.mInstanceName = "";
}
OnBecomeReady();
exit:
return;
}
void TrelDnssd::OnTrelServiceInstanceResolved(const std::string & aType,
const Mdns::Publisher::DiscoveredInstanceInfo &aInstanceInfo)
{
VerifyOrExit(StringUtils::EqualCaseInsensitive(aType, kTrelServiceName));
VerifyOrExit(aInstanceInfo.mNetifIndex == mTrelNetifIndex);
if (aInstanceInfo.mRemoved)
{
OnTrelServiceInstanceRemoved(aInstanceInfo.mName);
}
else
{
OnTrelServiceInstanceAdded(aInstanceInfo);
}
exit:
return;
}
std::string TrelDnssd::GetTrelInstanceName(void)
{
const otExtAddress *extaddr = otLinkGetExtendedAddress(mNcp.GetInstance());
std::string name;
char nameBuf[sizeof(extaddr) * 2 + 1];
Utils::Bytes2Hex(extaddr->m8, sizeof(extaddr), nameBuf);
name = StringUtils::ToLowercase(nameBuf);
assert(name.length() == sizeof(extaddr) * 2);
otbrLogDebug("Using instance name %s", name.c_str());
return name;
}
void TrelDnssd::PublishTrelService(void)
{
assert(mRegisterInfo.IsValid());
assert(!mRegisterInfo.IsPublished());
assert(mTrelNetifIndex > 0);
mRegisterInfo.mInstanceName = GetTrelInstanceName();
mPublisher.PublishService(/* aHostName */ "", mRegisterInfo.mPort, mRegisterInfo.mInstanceName, kTrelServiceName,
Mdns::Publisher::SubTypeList{}, mRegisterInfo.mTxtEntries);
}
void TrelDnssd::UnpublishTrelService(void)
{
assert(mRegisterInfo.IsValid());
assert(mRegisterInfo.IsPublished());
mPublisher.UnpublishService(mRegisterInfo.mInstanceName, kTrelServiceName);
mRegisterInfo.mInstanceName = "";
}
void TrelDnssd::OnTrelServiceInstanceAdded(const Mdns::Publisher::DiscoveredInstanceInfo &aInstanceInfo)
{
std::string instanceName = StringUtils::ToLowercase(aInstanceInfo.mName);
otPlatTrelPeerInfo peerInfo;
// Remove any existing TREL service instance before adding
OnTrelServiceInstanceRemoved(instanceName);
otbrLogDebug("Peer discovered: %s hostname %s addresses %zu port %d priority %d "
"weight %d",
aInstanceInfo.mName.c_str(), aInstanceInfo.mHostName.c_str(), aInstanceInfo.mAddresses.size(),
aInstanceInfo.mPort, aInstanceInfo.mPriority, aInstanceInfo.mWeight);
for (const auto &addr : aInstanceInfo.mAddresses)
{
otbrLogDebug("Peer address: %s", addr.ToString().c_str());
}
if (aInstanceInfo.mAddresses.empty())
{
otbrLogWarning("Peer %s does not have any IPv6 address, ignored", aInstanceInfo.mName.c_str());
ExitNow();
}
peerInfo.mRemoved = false;
memcpy(&peerInfo.mSockAddr.mAddress, &aInstanceInfo.mAddresses[0], sizeof(peerInfo.mSockAddr.mAddress));
peerInfo.mSockAddr.mPort = aInstanceInfo.mPort;
peerInfo.mTxtData = aInstanceInfo.mTxtData.data();
peerInfo.mTxtLength = aInstanceInfo.mTxtData.size();
{
Peer peer(aInstanceInfo.mTxtData, peerInfo.mSockAddr);
VerifyOrExit(peer.mValid, otbrLogWarning("Peer %s is invalid", aInstanceInfo.mName.c_str()));
otPlatTrelHandleDiscoveredPeerInfo(mNcp.GetInstance(), &peerInfo);
mPeers.emplace(instanceName, peer);
CheckPeersNumLimit();
}
exit:
return;
}
void TrelDnssd::OnTrelServiceInstanceRemoved(const std::string &aInstanceName)
{
std::string instanceName = StringUtils::ToLowercase(aInstanceName);
auto it = mPeers.find(instanceName);
VerifyOrExit(it != mPeers.end());
otbrLogDebug("Peer removed: %s", instanceName.c_str());
// Remove the peer only when all instances are removed because one peer can have multiple instances if expired
// instances were not properly removed by mDNS.
if (CountDuplicatePeers(it->second) == 0)
{
NotifyRemovePeer(it->second);
}
mPeers.erase(it);
exit:
return;
}
void TrelDnssd::CheckPeersNumLimit(void)
{
const PeerMap::value_type *oldestPeer = nullptr;
VerifyOrExit(mPeers.size() >= kPeerCacheSize);
for (const auto &entry : mPeers)
{
if (oldestPeer == nullptr || entry.second.mDiscoverTime < oldestPeer->second.mDiscoverTime)
{
oldestPeer = &entry;
}
}
OnTrelServiceInstanceRemoved(oldestPeer->first);
exit:
return;
}
void TrelDnssd::NotifyRemovePeer(const Peer &aPeer)
{
otPlatTrelPeerInfo peerInfo;
peerInfo.mRemoved = true;
peerInfo.mTxtData = aPeer.mTxtData.data();
peerInfo.mTxtLength = aPeer.mTxtData.size();
peerInfo.mSockAddr = aPeer.mSockAddr;
otPlatTrelHandleDiscoveredPeerInfo(mNcp.GetInstance(), &peerInfo);
}
void TrelDnssd::RemoveAllPeers(void)
{
for (const auto &entry : mPeers)
{
NotifyRemovePeer(entry.second);
}
mPeers.clear();
}
void TrelDnssd::CheckTrelNetifReady(void)
{
assert(IsInitialized());
if (mTrelNetifIndex == 0)
{
mTrelNetifIndex = if_nametoindex(mTrelNetif.c_str());
if (mTrelNetifIndex != 0)
{
otbrLogDebug("Netif %s is ready: index = %d", mTrelNetif.c_str(), mTrelNetifIndex);
OnBecomeReady();
}
else
{
uint16_t delay = kCheckNetifReadyIntervalMs;
otbrLogWarning("Netif %s is not ready (%s), will retry after %d seconds", mTrelNetif.c_str(),
strerror(errno), delay / 1000);
mTaskRunner.Post(Milliseconds(delay), [this]() { CheckTrelNetifReady(); });
}
}
}
bool TrelDnssd::IsReady(void) const
{
assert(IsInitialized());
return mTrelNetifIndex > 0 && mMdnsPublisherReady;
}
void TrelDnssd::OnBecomeReady(void)
{
if (IsReady())
{
otbrLogInfo("TREL DNS-SD Is Now Ready: Netif=%s(%u), SubscriberId=%u, Register=%s!", mTrelNetif.c_str(),
mTrelNetifIndex, mSubscriberId, mRegisterInfo.mInstanceName.c_str());
if (mSubscriberId > 0)
{
mPublisher.SubscribeService(kTrelServiceName, /* aInstanceName */ "");
}
if (mRegisterInfo.IsValid())
{
PublishTrelService();
}
}
}
uint16_t TrelDnssd::CountDuplicatePeers(const TrelDnssd::Peer &aPeer)
{
uint16_t count = 0;
for (const auto &entry : mPeers)
{
if (&entry.second == &aPeer)
{
continue;
}
if (!memcmp(&entry.second.mSockAddr, &aPeer.mSockAddr, sizeof(otSockAddr)) &&
!memcmp(&entry.second.mExtAddr, &aPeer.mExtAddr, sizeof(otExtAddress)))
{
count++;
}
}
return count;
}
void TrelDnssd::RegisterInfo::Assign(uint16_t aPort, const uint8_t *aTxtData, uint8_t aTxtLength)
{
otbrError error;
OTBR_UNUSED_VARIABLE(error);
assert(!IsPublished());
assert(aPort > 0);
mPort = aPort;
mTxtEntries.clear();
error = Mdns::Publisher::DecodeTxtData(mTxtEntries, aTxtData, aTxtLength);
assert(error == OTBR_ERROR_NONE);
}
void TrelDnssd::RegisterInfo::Clear(void)
{
assert(!IsPublished());
mPort = 0;
mTxtEntries.clear();
}
const char TrelDnssd::Peer::kTxtRecordExtAddressKey[] = "xa";
void TrelDnssd::Peer::ReadExtAddrFromTxtData(void)
{
std::vector<Mdns::Publisher::TxtEntry> txtEntries;
memset(&mExtAddr, 0, sizeof(mExtAddr));
SuccessOrExit(Mdns::Publisher::DecodeTxtData(txtEntries, mTxtData.data(), mTxtData.size()));
for (const auto &txtEntry : txtEntries)
{
if (StringUtils::EqualCaseInsensitive(txtEntry.mName, kTxtRecordExtAddressKey))
{
char extAddrHexBuf[sizeof(mExtAddr) * 2 + 1];
VerifyOrExit(txtEntry.mValue.size() == sizeof(mExtAddr) * 2);
memcpy(extAddrHexBuf, txtEntry.mValue.data(), sizeof(mExtAddr) * 2);
extAddrHexBuf[sizeof(mExtAddr) * 2] = '\0';
VerifyOrExit(Utils::Hex2Bytes(extAddrHexBuf, mExtAddr.m8, sizeof(mExtAddr)) == sizeof(mExtAddr));
mValid = true;
break;
}
}
exit:
return;
}
} // namespace TrelDnssd
} // namespace otbr
#endif // OTBR_ENABLE_TREL