blob: 548bad5f7f4ce388e8cf8806400e26f638e691d7 [file]
/*
* Copyright (c) 2017, 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 implements mDNS publisher based on avahi.
*/
#define OTBR_LOG_TAG "MDNS"
#include "mdns/mdns_avahi.hpp"
#include <algorithm>
#include <avahi-client/client.h>
#include <avahi-common/alternative.h>
#include <avahi-common/error.h>
#include <avahi-common/malloc.h>
#include <avahi-common/timeval.h>
#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include "common/code_utils.hpp"
#include "common/logging.hpp"
#include "common/time.hpp"
namespace otbr {
namespace Mdns {
class AvahiPoller;
} // namespace Mdns
} // namespace otbr
struct AvahiWatch
{
typedef otbr::Mdns::AvahiPoller AvahiPoller;
int mFd; ///< The file descriptor to watch.
AvahiWatchEvent mEvents; ///< The interested events.
int mHappened; ///< The events happened.
AvahiWatchCallback mCallback; ///< The function to be called to report events happened on `mFd`.
void *mContext; ///< A pointer to application-specific context to use with `mCallback`.
bool mShouldReport; ///< Whether or not we need to report events (invoking callback).
AvahiPoller &mPoller; ///< The poller owning this watch.
/**
* The constructor to initialize an Avahi watch.
*
* @param[in] aFd The file descriptor to watch.
* @param[in] aEvents The events to watch.
* @param[in] aCallback The function to be called when events happened on this file descriptor.
* @param[in] aContext A pointer to application-specific context.
* @param[in] aPoller The AvahiPoller this watcher belongs to.
*/
AvahiWatch(int aFd, AvahiWatchEvent aEvents, AvahiWatchCallback aCallback, void *aContext, AvahiPoller &aPoller)
: mFd(aFd)
, mEvents(aEvents)
, mCallback(aCallback)
, mContext(aContext)
, mShouldReport(false)
, mPoller(aPoller)
{
}
};
/**
* This structure implements the AvahiTimeout.
*/
struct AvahiTimeout
{
typedef otbr::Mdns::AvahiPoller AvahiPoller;
otbr::Timepoint mTimeout; ///< Absolute time when this timer timeout.
AvahiTimeoutCallback mCallback; ///< The function to be called when timeout.
void *mContext; ///< The pointer to application-specific context.
bool mShouldReport; ///< Whether or not timeout occurred and need to reported (invoking callback).
AvahiPoller &mPoller; ///< The poller created this timer.
/**
* The constructor to initialize an AvahiTimeout.
*
* @param[in] aTimeout A pointer to the time after which the callback should be called.
* @param[in] aCallback The function to be called after timeout.
* @param[in] aContext A pointer to application-specific context.
* @param[in] aPoller The AvahiPoller this timeout belongs to.
*/
AvahiTimeout(const struct timeval *aTimeout, AvahiTimeoutCallback aCallback, void *aContext, AvahiPoller &aPoller)
: mCallback(aCallback)
, mContext(aContext)
, mShouldReport(false)
, mPoller(aPoller)
{
if (aTimeout)
{
mTimeout = otbr::Clock::now() + otbr::FromTimeval<otbr::Microseconds>(*aTimeout);
}
else
{
mTimeout = otbr::Timepoint::min();
}
}
};
namespace otbr {
namespace Mdns {
static otbrError DnsErrorToOtbrError(int aAvahiError)
{
otbrError error;
switch (aAvahiError)
{
case AVAHI_OK:
case AVAHI_ERR_INVALID_ADDRESS:
error = OTBR_ERROR_NONE;
break;
case AVAHI_ERR_NOT_FOUND:
error = OTBR_ERROR_NOT_FOUND;
break;
case AVAHI_ERR_INVALID_ARGUMENT:
error = OTBR_ERROR_INVALID_ARGS;
break;
case AVAHI_ERR_COLLISION:
error = OTBR_ERROR_DUPLICATED;
break;
case AVAHI_ERR_DNS_NOTIMP:
case AVAHI_ERR_NOT_SUPPORTED:
error = OTBR_ERROR_NOT_IMPLEMENTED;
break;
default:
error = OTBR_ERROR_MDNS;
break;
}
return error;
}
class AvahiPoller : public MainloopProcessor
{
public:
AvahiPoller(void);
// Implementation of MainloopProcessor.
void Update(MainloopContext &aMainloop) override;
void Process(const MainloopContext &aMainloop) override;
const AvahiPoll *GetAvahiPoll(void) const { return &mAvahiPoll; }
private:
typedef std::vector<AvahiWatch *> Watches;
typedef std::vector<AvahiTimeout *> Timers;
static AvahiWatch *WatchNew(const struct AvahiPoll *aPoll,
int aFd,
AvahiWatchEvent aEvent,
AvahiWatchCallback aCallback,
void *aContext);
AvahiWatch *WatchNew(int aFd, AvahiWatchEvent aEvent, AvahiWatchCallback aCallback, void *aContext);
static void WatchUpdate(AvahiWatch *aWatch, AvahiWatchEvent aEvent);
static AvahiWatchEvent WatchGetEvents(AvahiWatch *aWatch);
static void WatchFree(AvahiWatch *aWatch);
void WatchFree(AvahiWatch &aWatch);
static AvahiTimeout *TimeoutNew(const AvahiPoll *aPoll,
const struct timeval *aTimeout,
AvahiTimeoutCallback aCallback,
void *aContext);
AvahiTimeout *TimeoutNew(const struct timeval *aTimeout, AvahiTimeoutCallback aCallback, void *aContext);
static void TimeoutUpdate(AvahiTimeout *aTimer, const struct timeval *aTimeout);
static void TimeoutFree(AvahiTimeout *aTimer);
void TimeoutFree(AvahiTimeout &aTimer);
Watches mWatches;
Timers mTimers;
AvahiPoll mAvahiPoll;
};
AvahiPoller::AvahiPoller(void)
{
mAvahiPoll.userdata = this;
mAvahiPoll.watch_new = WatchNew;
mAvahiPoll.watch_update = WatchUpdate;
mAvahiPoll.watch_get_events = WatchGetEvents;
mAvahiPoll.watch_free = WatchFree;
mAvahiPoll.timeout_new = TimeoutNew;
mAvahiPoll.timeout_update = TimeoutUpdate;
mAvahiPoll.timeout_free = TimeoutFree;
}
AvahiWatch *AvahiPoller::WatchNew(const struct AvahiPoll *aPoll,
int aFd,
AvahiWatchEvent aEvent,
AvahiWatchCallback aCallback,
void *aContext)
{
return reinterpret_cast<AvahiPoller *>(aPoll->userdata)->WatchNew(aFd, aEvent, aCallback, aContext);
}
AvahiWatch *AvahiPoller::WatchNew(int aFd, AvahiWatchEvent aEvent, AvahiWatchCallback aCallback, void *aContext)
{
assert(aEvent && aCallback && aFd >= 0);
mWatches.push_back(new AvahiWatch(aFd, aEvent, aCallback, aContext, *this));
return mWatches.back();
}
void AvahiPoller::WatchUpdate(AvahiWatch *aWatch, AvahiWatchEvent aEvent)
{
aWatch->mEvents = aEvent;
}
AvahiWatchEvent AvahiPoller::WatchGetEvents(AvahiWatch *aWatch)
{
return static_cast<AvahiWatchEvent>(aWatch->mHappened);
}
void AvahiPoller::WatchFree(AvahiWatch *aWatch)
{
aWatch->mPoller.WatchFree(*aWatch);
}
void AvahiPoller::WatchFree(AvahiWatch &aWatch)
{
for (Watches::iterator it = mWatches.begin(); it != mWatches.end(); ++it)
{
if (*it == &aWatch)
{
mWatches.erase(it);
delete &aWatch;
break;
}
}
}
AvahiTimeout *AvahiPoller::TimeoutNew(const AvahiPoll *aPoll,
const struct timeval *aTimeout,
AvahiTimeoutCallback aCallback,
void *aContext)
{
assert(aPoll && aCallback);
return static_cast<AvahiPoller *>(aPoll->userdata)->TimeoutNew(aTimeout, aCallback, aContext);
}
AvahiTimeout *AvahiPoller::TimeoutNew(const struct timeval *aTimeout, AvahiTimeoutCallback aCallback, void *aContext)
{
mTimers.push_back(new AvahiTimeout(aTimeout, aCallback, aContext, *this));
return mTimers.back();
}
void AvahiPoller::TimeoutUpdate(AvahiTimeout *aTimer, const struct timeval *aTimeout)
{
if (aTimeout == nullptr)
{
aTimer->mTimeout = Timepoint::min();
}
else
{
aTimer->mTimeout = Clock::now() + FromTimeval<Microseconds>(*aTimeout);
}
}
void AvahiPoller::TimeoutFree(AvahiTimeout *aTimer)
{
aTimer->mPoller.TimeoutFree(*aTimer);
}
void AvahiPoller::TimeoutFree(AvahiTimeout &aTimer)
{
for (Timers::iterator it = mTimers.begin(); it != mTimers.end(); ++it)
{
if (*it == &aTimer)
{
mTimers.erase(it);
delete &aTimer;
break;
}
}
}
void AvahiPoller::Update(MainloopContext &aMainloop)
{
Timepoint now = Clock::now();
for (AvahiWatch *watch : mWatches)
{
int fd = watch->mFd;
AvahiWatchEvent events = watch->mEvents;
if (AVAHI_WATCH_IN & events)
{
FD_SET(fd, &aMainloop.mReadFdSet);
}
if (AVAHI_WATCH_OUT & events)
{
FD_SET(fd, &aMainloop.mWriteFdSet);
}
if (AVAHI_WATCH_ERR & events)
{
FD_SET(fd, &aMainloop.mErrorFdSet);
}
if (AVAHI_WATCH_HUP & events)
{
// TODO what do with this event type?
}
aMainloop.mMaxFd = std::max(aMainloop.mMaxFd, fd);
watch->mHappened = 0;
}
for (AvahiTimeout *timer : mTimers)
{
Timepoint timeout = timer->mTimeout;
if (timeout == Timepoint::min())
{
continue;
}
if (timeout <= now)
{
aMainloop.mTimeout = ToTimeval(Microseconds::zero());
break;
}
else
{
auto delay = std::chrono::duration_cast<Microseconds>(timeout - now);
if (delay < FromTimeval<Microseconds>(aMainloop.mTimeout))
{
aMainloop.mTimeout = ToTimeval(delay);
}
}
}
}
void AvahiPoller::Process(const MainloopContext &aMainloop)
{
Timepoint now = Clock::now();
bool shouldReport = false;
for (AvahiWatch *watch : mWatches)
{
int fd = watch->mFd;
AvahiWatchEvent events = watch->mEvents;
watch->mHappened = 0;
if ((AVAHI_WATCH_IN & events) && FD_ISSET(fd, &aMainloop.mReadFdSet))
{
watch->mHappened |= AVAHI_WATCH_IN;
}
if ((AVAHI_WATCH_OUT & events) && FD_ISSET(fd, &aMainloop.mWriteFdSet))
{
watch->mHappened |= AVAHI_WATCH_OUT;
}
if ((AVAHI_WATCH_ERR & events) && FD_ISSET(fd, &aMainloop.mErrorFdSet))
{
watch->mHappened |= AVAHI_WATCH_ERR;
}
if (watch->mHappened != 0)
{
watch->mShouldReport = true;
shouldReport = true;
}
}
// When we invoke the callback for an `AvahiWatch` or `AvahiTimeout`,
// the Avahi module can call any of `mAvahiPoll` APIs we provided to
// it. For example, it can update or free any of `AvahiWatch/Timeout`
// entries, which in turn, modifies our `mWatches` or `mTimers` list.
// So, before invoking the callback, we update the entry's state and
// then restart the iteration over the `mWacthes` list to find the
// next entry to report, as the list may have changed.
while (shouldReport)
{
shouldReport = false;
for (AvahiWatch *watch : mWatches)
{
if (watch->mShouldReport)
{
shouldReport = true;
watch->mShouldReport = false;
watch->mCallback(watch, watch->mFd, WatchGetEvents(watch), watch->mContext);
break;
}
}
}
for (AvahiTimeout *timer : mTimers)
{
if (timer->mTimeout == Timepoint::min())
{
continue;
}
if (timer->mTimeout <= now)
{
timer->mShouldReport = true;
shouldReport = true;
}
}
while (shouldReport)
{
shouldReport = false;
for (AvahiTimeout *timer : mTimers)
{
if (timer->mShouldReport)
{
shouldReport = true;
timer->mShouldReport = false;
timer->mCallback(timer, timer->mContext);
break;
}
}
}
}
PublisherAvahi::PublisherAvahi(StateCallback aStateCallback)
: mClient(nullptr)
, mPoller(MakeUnique<AvahiPoller>())
, mState(State::kIdle)
, mStateCallback(std::move(aStateCallback))
{
}
PublisherAvahi::~PublisherAvahi(void)
{
Stop();
}
PublisherAvahi::AvahiServiceRegistration::~AvahiServiceRegistration(void)
{
ReleaseGroup(mEntryGroup);
}
PublisherAvahi::AvahiHostRegistration::~AvahiHostRegistration(void)
{
ReleaseGroup(mEntryGroup);
}
PublisherAvahi::AvahiKeyRegistration::~AvahiKeyRegistration(void)
{
ReleaseGroup(mEntryGroup);
}
otbrError PublisherAvahi::Start(void)
{
otbrError error = OTBR_ERROR_NONE;
int avahiError = AVAHI_OK;
assert(mClient == nullptr);
mClient = avahi_client_new(mPoller->GetAvahiPoll(), AVAHI_CLIENT_NO_FAIL, HandleClientState, this, &avahiError);
if (avahiError != AVAHI_OK)
{
otbrLogErr("Failed to create avahi client: %s!", avahi_strerror(avahiError));
error = OTBR_ERROR_MDNS;
}
return error;
}
bool PublisherAvahi::IsStarted(void) const
{
return mClient != nullptr;
}
void PublisherAvahi::Stop(void)
{
mServiceRegistrations.clear();
mHostRegistrations.clear();
mSubscribedServices.clear();
mSubscribedHosts.clear();
if (mClient)
{
avahi_client_free(mClient);
mClient = nullptr;
}
mState = Mdns::Publisher::State::kIdle;
}
void PublisherAvahi::HandleClientState(AvahiClient *aClient, AvahiClientState aState, void *aContext)
{
static_cast<PublisherAvahi *>(aContext)->HandleClientState(aClient, aState);
}
void PublisherAvahi::HandleGroupState(AvahiEntryGroup *aGroup, AvahiEntryGroupState aState, void *aContext)
{
static_cast<PublisherAvahi *>(aContext)->HandleGroupState(aGroup, aState);
}
void PublisherAvahi::HandleGroupState(AvahiEntryGroup *aGroup, AvahiEntryGroupState aState)
{
switch (aState)
{
case AVAHI_ENTRY_GROUP_ESTABLISHED:
otbrLogInfo("Avahi group (@%p) is established", aGroup);
CallHostOrServiceCallback(aGroup, OTBR_ERROR_NONE);
break;
case AVAHI_ENTRY_GROUP_COLLISION:
otbrLogInfo("Avahi group (@%p) name conflicted", aGroup);
CallHostOrServiceCallback(aGroup, OTBR_ERROR_DUPLICATED);
break;
case AVAHI_ENTRY_GROUP_FAILURE:
otbrLogErr("Avahi group (@%p) failed: %s!", aGroup,
avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(aGroup))));
CallHostOrServiceCallback(aGroup, OTBR_ERROR_MDNS);
break;
case AVAHI_ENTRY_GROUP_UNCOMMITED:
case AVAHI_ENTRY_GROUP_REGISTERING:
break;
}
}
void PublisherAvahi::CallHostOrServiceCallback(AvahiEntryGroup *aGroup, otbrError aError)
{
ServiceRegistration *serviceReg;
HostRegistration *hostReg;
KeyRegistration *keyReg;
if ((serviceReg = FindServiceRegistration(aGroup)) != nullptr)
{
if (aError == OTBR_ERROR_NONE)
{
serviceReg->Complete(aError);
}
else
{
RemoveServiceRegistration(serviceReg->mName, serviceReg->mType, aError);
}
}
else if ((hostReg = FindHostRegistration(aGroup)) != nullptr)
{
if (aError == OTBR_ERROR_NONE)
{
hostReg->Complete(aError);
}
else
{
RemoveHostRegistration(hostReg->mName, aError);
}
}
else if ((keyReg = FindKeyRegistration(aGroup)) != nullptr)
{
if (aError == OTBR_ERROR_NONE)
{
keyReg->Complete(aError);
}
else
{
RemoveKeyRegistration(keyReg->mName, aError);
}
}
else
{
otbrLogWarning("No registered service or host matches avahi group @%p", aGroup);
}
}
AvahiEntryGroup *PublisherAvahi::CreateGroup(AvahiClient *aClient)
{
AvahiEntryGroup *group = avahi_entry_group_new(aClient, HandleGroupState, this);
if (group == nullptr)
{
otbrLogErr("Failed to create entry avahi group: %s", avahi_strerror(avahi_client_errno(aClient)));
}
return group;
}
void PublisherAvahi::ReleaseGroup(AvahiEntryGroup *aGroup)
{
int error;
otbrLogInfo("Releasing avahi entry group @%p", aGroup);
error = avahi_entry_group_reset(aGroup);
if (error != 0)
{
otbrLogErr("Failed to reset entry group for avahi error: %s", avahi_strerror(error));
}
error = avahi_entry_group_free(aGroup);
if (error != 0)
{
otbrLogErr("Failed to free entry group for avahi error: %s", avahi_strerror(error));
}
}
void PublisherAvahi::HandleClientState(AvahiClient *aClient, AvahiClientState aState)
{
otbrLogInfo("Avahi client state changed to %d", aState);
switch (aState)
{
case AVAHI_CLIENT_S_RUNNING:
// The server has startup successfully and registered its host
// name on the network, so it's time to create our services.
otbrLogInfo("Avahi client is ready");
mClient = aClient;
mState = State::kReady;
mStateCallback(mState);
break;
case AVAHI_CLIENT_FAILURE:
otbrLogErr("Avahi client failed to start: %s", avahi_strerror(avahi_client_errno(aClient)));
mState = State::kIdle;
mStateCallback(mState);
Stop();
Start();
break;
case AVAHI_CLIENT_S_COLLISION:
// Let's drop our registered services. When the server is back
// in AVAHI_SERVER_RUNNING state we will register them again
// with the new host name.
otbrLogErr("Avahi client collision detected: %s", avahi_strerror(avahi_client_errno(aClient)));
// fall through
case AVAHI_CLIENT_S_REGISTERING:
// The server records are now being established. This might be
// caused by a host name change. We need to wait for our own
// records to register until the host name is properly established.
mServiceRegistrations.clear();
mHostRegistrations.clear();
break;
case AVAHI_CLIENT_CONNECTING:
otbrLogInfo("Avahi client is connecting to the server");
break;
}
}
otbrError PublisherAvahi::PublishServiceImpl(const std::string &aHostName,
const std::string &aName,
const std::string &aType,
const SubTypeList &aSubTypeList,
uint16_t aPort,
const TxtData &aTxtData,
ResultCallback &&aCallback)
{
otbrError error = OTBR_ERROR_NONE;
int avahiError = AVAHI_OK;
SubTypeList sortedSubTypeList = SortSubTypeList(aSubTypeList);
const std::string logHostName = !aHostName.empty() ? aHostName : "localhost";
std::string fullHostName;
std::string serviceName = aName;
AvahiEntryGroup *group = nullptr;
// Aligned with AvahiStringList
AvahiStringList txtBuffer[(kMaxSizeOfTxtRecord - 1) / sizeof(AvahiStringList) + 1];
AvahiStringList *txtHead = nullptr;
VerifyOrExit(mState == State::kReady, error = OTBR_ERROR_INVALID_STATE);
VerifyOrExit(mClient != nullptr, error = OTBR_ERROR_INVALID_STATE);
if (!aHostName.empty())
{
fullHostName = MakeFullHostName(aHostName);
}
if (serviceName.empty())
{
serviceName = avahi_client_get_host_name(mClient);
}
aCallback = HandleDuplicateServiceRegistration(aHostName, serviceName, aType, sortedSubTypeList, aPort, aTxtData,
std::move(aCallback));
VerifyOrExit(!aCallback.IsNull());
SuccessOrExit(error = TxtDataToAvahiStringList(aTxtData, txtBuffer, sizeof(txtBuffer), txtHead));
VerifyOrExit((group = CreateGroup(mClient)) != nullptr, error = OTBR_ERROR_MDNS);
avahiError = avahi_entry_group_add_service_strlst(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, AvahiPublishFlags{},
serviceName.c_str(), aType.c_str(),
/* domain */ nullptr, fullHostName.c_str(), aPort, txtHead);
VerifyOrExit(avahiError == AVAHI_OK);
for (const std::string &subType : aSubTypeList)
{
otbrLogInfo("Add subtype %s for service %s.%s", subType.c_str(), serviceName.c_str(), aType.c_str());
std::string fullSubType = subType + "._sub." + aType;
avahiError = avahi_entry_group_add_service_subtype(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
AvahiPublishFlags{}, serviceName.c_str(), aType.c_str(),
/* domain */ nullptr, fullSubType.c_str());
VerifyOrExit(avahiError == AVAHI_OK);
}
otbrLogInfo("Commit avahi service %s.%s", serviceName.c_str(), aType.c_str());
avahiError = avahi_entry_group_commit(group);
VerifyOrExit(avahiError == AVAHI_OK);
AddServiceRegistration(std::unique_ptr<AvahiServiceRegistration>(new AvahiServiceRegistration(
aHostName, serviceName, aType, sortedSubTypeList, aPort, aTxtData, std::move(aCallback), group, this)));
exit:
if (avahiError != AVAHI_OK || error != OTBR_ERROR_NONE)
{
if (avahiError != AVAHI_OK)
{
error = OTBR_ERROR_MDNS;
otbrLogErr("Failed to publish service for avahi error: %s!", avahi_strerror(avahiError));
}
if (group != nullptr)
{
ReleaseGroup(group);
}
std::move(aCallback)(error);
}
return error;
}
void PublisherAvahi::UnpublishService(const std::string &aName, const std::string &aType, ResultCallback &&aCallback)
{
otbrError error = OTBR_ERROR_NONE;
VerifyOrExit(mState == Publisher::State::kReady, error = OTBR_ERROR_INVALID_STATE);
RemoveServiceRegistration(aName, aType, OTBR_ERROR_ABORTED);
exit:
std::move(aCallback)(error);
}
otbrError PublisherAvahi::PublishHostImpl(const std::string &aName,
const AddressList &aAddresses,
ResultCallback &&aCallback)
{
otbrError error = OTBR_ERROR_NONE;
int avahiError = AVAHI_OK;
std::string fullHostName;
AvahiEntryGroup *group = nullptr;
VerifyOrExit(mState == State::kReady, error = OTBR_ERROR_INVALID_STATE);
VerifyOrExit(mClient != nullptr, error = OTBR_ERROR_INVALID_STATE);
aCallback = HandleDuplicateHostRegistration(aName, aAddresses, std::move(aCallback));
VerifyOrExit(!aCallback.IsNull());
VerifyOrExit(!aAddresses.empty(), std::move(aCallback)(OTBR_ERROR_NONE));
VerifyOrExit((group = CreateGroup(mClient)) != nullptr, error = OTBR_ERROR_MDNS);
fullHostName = MakeFullHostName(aName);
for (const auto &address : aAddresses)
{
AvahiAddress avahiAddress;
avahiAddress.proto = AVAHI_PROTO_INET6;
memcpy(avahiAddress.data.ipv6.address, address.m8, sizeof(address.m8));
avahiError = avahi_entry_group_add_address(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, AVAHI_PUBLISH_NO_REVERSE,
fullHostName.c_str(), &avahiAddress);
VerifyOrExit(avahiError == AVAHI_OK);
}
otbrLogInfo("Commit avahi host %s", aName.c_str());
avahiError = avahi_entry_group_commit(group);
VerifyOrExit(avahiError == AVAHI_OK);
AddHostRegistration(std::unique_ptr<AvahiHostRegistration>(
new AvahiHostRegistration(aName, aAddresses, std::move(aCallback), group, this)));
exit:
if (avahiError != AVAHI_OK || error != OTBR_ERROR_NONE)
{
if (avahiError != AVAHI_OK)
{
error = OTBR_ERROR_MDNS;
otbrLogErr("Failed to publish host for avahi error: %s!", avahi_strerror(avahiError));
}
if (group != nullptr)
{
ReleaseGroup(group);
}
std::move(aCallback)(error);
}
return error;
}
void PublisherAvahi::UnpublishHost(const std::string &aName, ResultCallback &&aCallback)
{
otbrError error = OTBR_ERROR_NONE;
VerifyOrExit(mState == Publisher::State::kReady, error = OTBR_ERROR_INVALID_STATE);
RemoveHostRegistration(aName, OTBR_ERROR_ABORTED);
exit:
std::move(aCallback)(error);
}
otbrError PublisherAvahi::PublishKeyImpl(const std::string &aName, const KeyData &aKeyData, ResultCallback &&aCallback)
{
otbrError error = OTBR_ERROR_NONE;
int avahiError = AVAHI_OK;
std::string fullKeyName;
AvahiEntryGroup *group = nullptr;
VerifyOrExit(mState == State::kReady, error = OTBR_ERROR_INVALID_STATE);
VerifyOrExit(mClient != nullptr, error = OTBR_ERROR_INVALID_STATE);
aCallback = HandleDuplicateKeyRegistration(aName, aKeyData, std::move(aCallback));
VerifyOrExit(!aCallback.IsNull());
VerifyOrExit((group = CreateGroup(mClient)) != nullptr, error = OTBR_ERROR_MDNS);
fullKeyName = MakeFullKeyName(aName);
avahiError = avahi_entry_group_add_record(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, AVAHI_PUBLISH_UNIQUE,
fullKeyName.c_str(), AVAHI_DNS_CLASS_IN, kDnsKeyRecordType, kDefaultTtl,
aKeyData.data(), aKeyData.size());
VerifyOrExit(avahiError == AVAHI_OK);
otbrLogInfo("Commit avahi key record for %s", aName.c_str());
avahiError = avahi_entry_group_commit(group);
VerifyOrExit(avahiError == AVAHI_OK);
AddKeyRegistration(std::unique_ptr<AvahiKeyRegistration>(
new AvahiKeyRegistration(aName, aKeyData, std::move(aCallback), group, this)));
exit:
if (avahiError != AVAHI_OK || error != OTBR_ERROR_NONE)
{
if (avahiError != AVAHI_OK)
{
error = OTBR_ERROR_MDNS;
otbrLogErr("Failed to publish key record - avahi error: %s!", avahi_strerror(avahiError));
}
if (group != nullptr)
{
ReleaseGroup(group);
}
std::move(aCallback)(error);
}
return error;
}
void PublisherAvahi::UnpublishKey(const std::string &aName, ResultCallback &&aCallback)
{
otbrError error = OTBR_ERROR_NONE;
VerifyOrExit(mState == Publisher::State::kReady, error = OTBR_ERROR_INVALID_STATE);
RemoveKeyRegistration(aName, OTBR_ERROR_ABORTED);
exit:
std::move(aCallback)(error);
}
otbrError PublisherAvahi::TxtDataToAvahiStringList(const TxtData &aTxtData,
AvahiStringList *aBuffer,
size_t aBufferSize,
AvahiStringList *&aHead)
{
otbrError error = OTBR_ERROR_NONE;
size_t used = 0;
AvahiStringList *last = nullptr;
AvahiStringList *curr = aBuffer;
const uint8_t *next;
const uint8_t *data = aTxtData.data();
const uint8_t *dataEnd = aTxtData.data() + aTxtData.size();
aHead = nullptr;
while (data < dataEnd)
{
uint8_t entryLength = *data++;
size_t needed = sizeof(AvahiStringList) - sizeof(AvahiStringList::text) + entryLength;
if (entryLength == 0)
{
continue;
}
VerifyOrExit(data + entryLength <= dataEnd, error = OTBR_ERROR_PARSE);
VerifyOrExit(used + needed <= aBufferSize, error = OTBR_ERROR_INVALID_ARGS);
curr->next = last;
last = curr;
memcpy(curr->text, data, entryLength);
curr->size = entryLength;
data += entryLength;
next = curr->text + curr->size;
curr = OTBR_ALIGNED(next, AvahiStringList *);
used = static_cast<size_t>(reinterpret_cast<uint8_t *>(curr) - reinterpret_cast<uint8_t *>(aBuffer));
}
aHead = last;
exit:
return error;
}
Publisher::ServiceRegistration *PublisherAvahi::FindServiceRegistration(const AvahiEntryGroup *aEntryGroup)
{
ServiceRegistration *result = nullptr;
for (const auto &kv : mServiceRegistrations)
{
const auto &serviceReg = static_cast<const AvahiServiceRegistration &>(*kv.second);
if (serviceReg.GetEntryGroup() == aEntryGroup)
{
result = kv.second.get();
break;
}
}
return result;
}
Publisher::HostRegistration *PublisherAvahi::FindHostRegistration(const AvahiEntryGroup *aEntryGroup)
{
HostRegistration *result = nullptr;
for (const auto &kv : mHostRegistrations)
{
const auto &hostReg = static_cast<const AvahiHostRegistration &>(*kv.second);
if (hostReg.GetEntryGroup() == aEntryGroup)
{
result = kv.second.get();
break;
}
}
return result;
}
Publisher::KeyRegistration *PublisherAvahi::FindKeyRegistration(const AvahiEntryGroup *aEntryGroup)
{
KeyRegistration *result = nullptr;
for (const auto &entry : mKeyRegistrations)
{
const auto &keyReg = static_cast<const AvahiKeyRegistration &>(*entry.second);
if (keyReg.GetEntryGroup() == aEntryGroup)
{
result = entry.second.get();
break;
}
}
return result;
}
void PublisherAvahi::SubscribeService(const std::string &aType, const std::string &aInstanceName)
{
auto service = MakeUnique<ServiceSubscription>(*this, aType, aInstanceName);
VerifyOrExit(mState == Publisher::State::kReady);
mSubscribedServices.push_back(std::move(service));
otbrLogInfo("Subscribe service %s.%s (total %zu)", aInstanceName.c_str(), aType.c_str(),
mSubscribedServices.size());
if (aInstanceName.empty())
{
mSubscribedServices.back()->Browse();
}
else
{
mSubscribedServices.back()->Resolve(AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, aInstanceName, aType);
}
exit:
return;
}
void PublisherAvahi::UnsubscribeService(const std::string &aType, const std::string &aInstanceName)
{
ServiceSubscriptionList::iterator it;
VerifyOrExit(mState == Publisher::State::kReady);
it = std::find_if(mSubscribedServices.begin(), mSubscribedServices.end(),
[&aType, &aInstanceName](const std::unique_ptr<ServiceSubscription> &aService) {
return aService->mType == aType && aService->mInstanceName == aInstanceName;
});
VerifyOrExit(it != mSubscribedServices.end());
{
std::unique_ptr<ServiceSubscription> service = std::move(*it);
mSubscribedServices.erase(it);
service->Release();
}
otbrLogInfo("Unsubscribe service %s.%s (left %zu)", aInstanceName.c_str(), aType.c_str(),
mSubscribedServices.size());
exit:
return;
}
void PublisherAvahi::OnServiceResolveFailedImpl(const std::string &aType,
const std::string &aInstanceName,
int32_t aErrorCode)
{
otbrLogWarning("Resolve service %s.%s failed: %s", aInstanceName.c_str(), aType.c_str(),
avahi_strerror(aErrorCode));
}
void PublisherAvahi::OnHostResolveFailedImpl(const std::string &aHostName, int32_t aErrorCode)
{
otbrLogWarning("Resolve host %s failed: %s", aHostName.c_str(), avahi_strerror(aErrorCode));
}
otbrError PublisherAvahi::DnsErrorToOtbrError(int32_t aErrorCode)
{
return otbr::Mdns::DnsErrorToOtbrError(aErrorCode);
}
void PublisherAvahi::SubscribeHost(const std::string &aHostName)
{
auto host = MakeUnique<HostSubscription>(*this, aHostName);
VerifyOrExit(mState == Publisher::State::kReady);
mSubscribedHosts.push_back(std::move(host));
otbrLogInfo("Subscribe host %s (total %zu)", aHostName.c_str(), mSubscribedHosts.size());
mSubscribedHosts.back()->Resolve();
exit:
return;
}
void PublisherAvahi::UnsubscribeHost(const std::string &aHostName)
{
HostSubscriptionList::iterator it;
VerifyOrExit(mState == Publisher::State::kReady);
it = std::find_if(
mSubscribedHosts.begin(), mSubscribedHosts.end(),
[&aHostName](const std::unique_ptr<HostSubscription> &aHost) { return aHost->mHostName == aHostName; });
VerifyOrExit(it != mSubscribedHosts.end());
{
std::unique_ptr<HostSubscription> host = std::move(*it);
mSubscribedHosts.erase(it);
host->Release();
}
otbrLogInfo("Unsubscribe host %s (remaining %zu)", aHostName.c_str(), mSubscribedHosts.size());
exit:
return;
}
Publisher *Publisher::Create(StateCallback aStateCallback)
{
return new PublisherAvahi(std::move(aStateCallback));
}
void Publisher::Destroy(Publisher *aPublisher)
{
delete static_cast<PublisherAvahi *>(aPublisher);
}
void PublisherAvahi::ServiceSubscription::Browse(void)
{
assert(mPublisherAvahi->mClient != nullptr);
otbrLogInfo("Browse service %s", mType.c_str());
mServiceBrowser =
avahi_service_browser_new(mPublisherAvahi->mClient, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, mType.c_str(),
/* domain */ nullptr, static_cast<AvahiLookupFlags>(0), HandleBrowseResult, this);
if (!mServiceBrowser)
{
otbrLogWarning("Failed to browse service %s: %s", mType.c_str(),
avahi_strerror(avahi_client_errno(mPublisherAvahi->mClient)));
}
}
void PublisherAvahi::ServiceSubscription::Release(void)
{
std::vector<std::string> instanceNames;
for (const auto &resolvers : mServiceResolvers)
{
instanceNames.push_back(resolvers.first);
}
for (const auto &name : instanceNames)
{
RemoveServiceResolver(name);
}
if (mServiceBrowser != nullptr)
{
avahi_service_browser_free(mServiceBrowser);
mServiceBrowser = nullptr;
}
}
void PublisherAvahi::ServiceSubscription::HandleBrowseResult(AvahiServiceBrowser *aServiceBrowser,
AvahiIfIndex aInterfaceIndex,
AvahiProtocol aProtocol,
AvahiBrowserEvent aEvent,
const char *aName,
const char *aType,
const char *aDomain,
AvahiLookupResultFlags aFlags,
void *aContext)
{
static_cast<PublisherAvahi::ServiceSubscription *>(aContext)->HandleBrowseResult(
aServiceBrowser, aInterfaceIndex, aProtocol, aEvent, aName, aType, aDomain, aFlags);
}
void PublisherAvahi::ServiceSubscription::HandleBrowseResult(AvahiServiceBrowser *aServiceBrowser,
AvahiIfIndex aInterfaceIndex,
AvahiProtocol aProtocol,
AvahiBrowserEvent aEvent,
const char *aName,
const char *aType,
const char *aDomain,
AvahiLookupResultFlags aFlags)
{
OTBR_UNUSED_VARIABLE(aServiceBrowser);
OTBR_UNUSED_VARIABLE(aProtocol);
OTBR_UNUSED_VARIABLE(aDomain);
assert(mServiceBrowser == aServiceBrowser);
otbrLogInfo("Browse service reply: %s.%s proto %d inf %u event %d flags %d", aName, aType, aProtocol,
aInterfaceIndex, static_cast<int>(aEvent), static_cast<int>(aFlags));
switch (aEvent)
{
case AVAHI_BROWSER_NEW:
Resolve(aInterfaceIndex, aProtocol, aName, aType);
break;
case AVAHI_BROWSER_REMOVE:
mPublisherAvahi->OnServiceRemoved(static_cast<uint32_t>(aInterfaceIndex), aType, aName);
RemoveServiceResolver(aName);
break;
case AVAHI_BROWSER_CACHE_EXHAUSTED:
case AVAHI_BROWSER_ALL_FOR_NOW:
// do nothing
break;
case AVAHI_BROWSER_FAILURE:
mPublisherAvahi->OnServiceResolveFailed(aType, aName, avahi_client_errno(mPublisherAvahi->mClient));
break;
}
}
void PublisherAvahi::ServiceSubscription::Resolve(uint32_t aInterfaceIndex,
AvahiProtocol aProtocol,
const std::string &aInstanceName,
const std::string &aType)
{
auto serviceResolver = MakeUnique<ServiceResolver>();
mPublisherAvahi->mServiceInstanceResolutionBeginTime[std::make_pair(aInstanceName, aType)] = Clock::now();
otbrLogInfo("Resolve service %s.%s inf %" PRIu32, aInstanceName.c_str(), aType.c_str(), aInterfaceIndex);
serviceResolver->mType = aType;
serviceResolver->mPublisherAvahi = this->mPublisherAvahi;
serviceResolver->mServiceResolver = avahi_service_resolver_new(
mPublisherAvahi->mClient, aInterfaceIndex, aProtocol, aInstanceName.c_str(), aType.c_str(),
/* domain */ nullptr, AVAHI_PROTO_UNSPEC, static_cast<AvahiLookupFlags>(AVAHI_LOOKUP_NO_ADDRESS),
&ServiceResolver::HandleResolveServiceResult, serviceResolver.get());
if (serviceResolver->mServiceResolver != nullptr)
{
AddServiceResolver(aInstanceName, serviceResolver.release());
}
else
{
otbrLogErr("Failed to resolve serivce %s: %s", mType.c_str(),
avahi_strerror(avahi_client_errno(mPublisherAvahi->mClient)));
}
}
void PublisherAvahi::ServiceResolver::HandleResolveServiceResult(AvahiServiceResolver *aServiceResolver,
AvahiIfIndex aInterfaceIndex,
AvahiProtocol aProtocol,
AvahiResolverEvent aEvent,
const char *aName,
const char *aType,
const char *aDomain,
const char *aHostName,
const AvahiAddress *aAddress,
uint16_t aPort,
AvahiStringList *aTxt,
AvahiLookupResultFlags aFlags,
void *aContext)
{
static_cast<PublisherAvahi::ServiceResolver *>(aContext)->HandleResolveServiceResult(
aServiceResolver, aInterfaceIndex, aProtocol, aEvent, aName, aType, aDomain, aHostName, aAddress, aPort, aTxt,
aFlags);
}
void PublisherAvahi::ServiceResolver::HandleResolveServiceResult(AvahiServiceResolver *aServiceResolver,
AvahiIfIndex aInterfaceIndex,
AvahiProtocol aProtocol,
AvahiResolverEvent aEvent,
const char *aName,
const char *aType,
const char *aDomain,
const char *aHostName,
const AvahiAddress *aAddress,
uint16_t aPort,
AvahiStringList *aTxt,
AvahiLookupResultFlags aFlags)
{
OT_UNUSED_VARIABLE(aServiceResolver);
OT_UNUSED_VARIABLE(aInterfaceIndex);
OT_UNUSED_VARIABLE(aProtocol);
OT_UNUSED_VARIABLE(aType);
OT_UNUSED_VARIABLE(aDomain);
OT_UNUSED_VARIABLE(aAddress);
size_t totalTxtSize = 0;
bool resolved = false;
int avahiError = AVAHI_OK;
otbrLog(aEvent == AVAHI_RESOLVER_FOUND ? OTBR_LOG_INFO : OTBR_LOG_WARNING, OTBR_LOG_TAG,
"Resolve service reply: protocol %d %s.%s.%s = host %s port %" PRIu16 " flags %d event %d", aProtocol,
aName, aType, aDomain, aHostName, aPort, static_cast<int>(aFlags), static_cast<int>(aEvent));
VerifyOrExit(aEvent == AVAHI_RESOLVER_FOUND, avahiError = avahi_client_errno(mPublisherAvahi->mClient));
VerifyOrExit(aHostName != nullptr, avahiError = AVAHI_ERR_INVALID_HOST_NAME);
mInstanceInfo.mNetifIndex = static_cast<uint32_t>(aInterfaceIndex);
mInstanceInfo.mName = aName;
mInstanceInfo.mHostName = std::string(aHostName) + ".";
mInstanceInfo.mPort = aPort;
otbrLogInfo("Resolve service reply: flags=%u, host=%s", aFlags, aHostName);
// TODO priority
// TODO weight
// TODO use a more proper TTL
mInstanceInfo.mTtl = kDefaultTtl;
for (auto p = aTxt; p; p = avahi_string_list_get_next(p))
{
totalTxtSize += avahi_string_list_get_size(p) + 1;
}
mInstanceInfo.mTxtData.resize(totalTxtSize);
avahi_string_list_serialize(aTxt, mInstanceInfo.mTxtData.data(), totalTxtSize);
// NOTE: Avahi only returns one of the host's addresses in the service resolution callback. However, the address may
// be link-local so it may not be preferred from Thread's perspective. We want to go through the complete list of
// addresses associated with the host and choose a routable address. Therefore, as below we will resolve the host
// and go through all its addresses.
resolved = true;
exit:
if (resolved)
{
// In case the callback is triggered when a service instance is updated, there may already be a record browser.
// We should free it before switching to the new record browser.
if (mRecordBrowser)
{
avahi_record_browser_free(mRecordBrowser);
mRecordBrowser = nullptr;
mInstanceInfo.mAddresses.clear();
}
// NOTE: This `ServiceResolver` object may be freed in `OnServiceResolved`.
mRecordBrowser = avahi_record_browser_new(mPublisherAvahi->mClient, aInterfaceIndex, AVAHI_PROTO_UNSPEC,
aHostName, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_AAAA,
static_cast<AvahiLookupFlags>(0), HandleResolveHostResult, this);
if (!mRecordBrowser)
{
resolved = false;
avahiError = avahi_client_errno(mPublisherAvahi->mClient);
}
}
if (!resolved && avahiError != AVAHI_OK)
{
mPublisherAvahi->OnServiceResolveFailed(aType, aName, avahiError);
}
}
void PublisherAvahi::ServiceResolver::HandleResolveHostResult(AvahiRecordBrowser *aRecordBrowser,
AvahiIfIndex aInterfaceIndex,
AvahiProtocol aProtocol,
AvahiBrowserEvent aEvent,
const char *aName,
uint16_t aClazz,
uint16_t aType,
const void *aRdata,
size_t aSize,
AvahiLookupResultFlags aFlags,
void *aContext)
{
static_cast<PublisherAvahi::ServiceResolver *>(aContext)->HandleResolveHostResult(
aRecordBrowser, aInterfaceIndex, aProtocol, aEvent, aName, aClazz, aType, aRdata, aSize, aFlags);
}
void PublisherAvahi::ServiceResolver::HandleResolveHostResult(AvahiRecordBrowser *aRecordBrowser,
AvahiIfIndex aInterfaceIndex,
AvahiProtocol aProtocol,
AvahiBrowserEvent aEvent,
const char *aName,
uint16_t aClazz,
uint16_t aType,
const void *aRdata,
size_t aSize,
AvahiLookupResultFlags aFlags)
{
OTBR_UNUSED_VARIABLE(aRecordBrowser);
OTBR_UNUSED_VARIABLE(aInterfaceIndex);
OTBR_UNUSED_VARIABLE(aProtocol);
OTBR_UNUSED_VARIABLE(aEvent);
OTBR_UNUSED_VARIABLE(aClazz);
OTBR_UNUSED_VARIABLE(aType);
OTBR_UNUSED_VARIABLE(aFlags);
Ip6Address address;
bool resolved = false;
int avahiError = AVAHI_OK;
otbrLog(aEvent != AVAHI_BROWSER_FAILURE ? OTBR_LOG_INFO : OTBR_LOG_WARNING, OTBR_LOG_TAG,
"Resolve host reply: %s inf %d protocol %d class %" PRIu16 " type %" PRIu16 " size %zu flags %d event %d",
aName, aInterfaceIndex, aProtocol, aClazz, aType, aSize, static_cast<int>(aFlags),
static_cast<int>(aEvent));
VerifyOrExit(aEvent == AVAHI_BROWSER_NEW || aEvent == AVAHI_BROWSER_REMOVE);
VerifyOrExit(aSize == OTBR_IP6_ADDRESS_SIZE || aSize == OTBR_IP4_ADDRESS_SIZE,
otbrLogErr("Unexpected address data length: %zu", aSize), avahiError = AVAHI_ERR_INVALID_ADDRESS);
VerifyOrExit(aSize == OTBR_IP6_ADDRESS_SIZE, otbrLogInfo("IPv4 address ignored"),
avahiError = AVAHI_ERR_INVALID_ADDRESS);
address = Ip6Address(*static_cast<const uint8_t(*)[OTBR_IP6_ADDRESS_SIZE]>(aRdata));
VerifyOrExit(!address.IsMulticast() && !address.IsLoopback() && !address.IsUnspecified(),
avahiError = AVAHI_ERR_INVALID_ADDRESS);
otbrLogInfo("Resolved host address: %s %s", aEvent == AVAHI_BROWSER_NEW ? "add" : "remove",
address.ToString().c_str());
if (aEvent == AVAHI_BROWSER_NEW)
{
mInstanceInfo.AddAddress(address);
}
else
{
mInstanceInfo.RemoveAddress(address);
}
resolved = true;
exit:
if (resolved)
{
// NOTE: This `HostSubscrption` object may be freed in `OnHostResolved`.
mPublisherAvahi->OnServiceResolved(mType, mInstanceInfo);
}
else if (avahiError != AVAHI_OK)
{
mPublisherAvahi->OnServiceResolveFailed(mType, mInstanceInfo.mName, avahiError);
}
}
void PublisherAvahi::ServiceSubscription::AddServiceResolver(const std::string &aInstanceName,
ServiceResolver *aServiceResolver)
{
assert(aServiceResolver != nullptr);
mServiceResolvers[aInstanceName].insert(aServiceResolver);
otbrLogDebug("Added service resolver for instance %s", aInstanceName.c_str());
}
void PublisherAvahi::ServiceSubscription::RemoveServiceResolver(const std::string &aInstanceName)
{
int numResolvers = 0;
VerifyOrExit(mServiceResolvers.find(aInstanceName) != mServiceResolvers.end());
numResolvers = mServiceResolvers[aInstanceName].size();
for (auto resolver : mServiceResolvers[aInstanceName])
{
delete resolver;
}
mServiceResolvers.erase(aInstanceName);
exit:
otbrLogDebug("Removed %d service resolver for instance %s", numResolvers, aInstanceName.c_str());
return;
}
void PublisherAvahi::HostSubscription::Release(void)
{
if (mRecordBrowser != nullptr)
{
avahi_record_browser_free(mRecordBrowser);
mRecordBrowser = nullptr;
}
}
void PublisherAvahi::HostSubscription::Resolve(void)
{
std::string fullHostName = MakeFullHostName(mHostName);
mPublisherAvahi->mHostResolutionBeginTime[mHostName] = Clock::now();
otbrLogInfo("Resolve host %s inf %d", fullHostName.c_str(), static_cast<int>(AVAHI_IF_UNSPEC));
mRecordBrowser = avahi_record_browser_new(mPublisherAvahi->mClient, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
fullHostName.c_str(), AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_AAAA,
static_cast<AvahiLookupFlags>(0), HandleResolveResult, this);
if (!mRecordBrowser)
{
otbrLogErr("Failed to resolve host %s: %s", fullHostName.c_str(),
avahi_strerror(avahi_client_errno(mPublisherAvahi->mClient)));
}
}
void PublisherAvahi::HostSubscription::HandleResolveResult(AvahiRecordBrowser *aRecordBrowser,
AvahiIfIndex aInterfaceIndex,
AvahiProtocol aProtocol,
AvahiBrowserEvent aEvent,
const char *aName,
uint16_t aClazz,
uint16_t aType,
const void *aRdata,
size_t aSize,
AvahiLookupResultFlags aFlags,
void *aContext)
{
static_cast<PublisherAvahi::HostSubscription *>(aContext)->HandleResolveResult(
aRecordBrowser, aInterfaceIndex, aProtocol, aEvent, aName, aClazz, aType, aRdata, aSize, aFlags);
}
void PublisherAvahi::HostSubscription::HandleResolveResult(AvahiRecordBrowser *aRecordBrowser,
AvahiIfIndex aInterfaceIndex,
AvahiProtocol aProtocol,
AvahiBrowserEvent aEvent,
const char *aName,
uint16_t aClazz,
uint16_t aType,
const void *aRdata,
size_t aSize,
AvahiLookupResultFlags aFlags)
{
OTBR_UNUSED_VARIABLE(aRecordBrowser);
OTBR_UNUSED_VARIABLE(aProtocol);
OTBR_UNUSED_VARIABLE(aEvent);
OTBR_UNUSED_VARIABLE(aClazz);
OTBR_UNUSED_VARIABLE(aType);
OTBR_UNUSED_VARIABLE(aFlags);
Ip6Address address;
bool resolved = false;
int avahiError = AVAHI_OK;
otbrLog(aEvent != AVAHI_BROWSER_FAILURE ? OTBR_LOG_INFO : OTBR_LOG_WARNING, OTBR_LOG_TAG,
"Resolve host reply: %s inf %d protocol %d class %" PRIu16 " type %" PRIu16 " size %zu flags %d event %d",
aName, aInterfaceIndex, aProtocol, aClazz, aType, aSize, static_cast<int>(aFlags),
static_cast<int>(aEvent));
VerifyOrExit(aEvent == AVAHI_BROWSER_NEW || aEvent == AVAHI_BROWSER_REMOVE);
VerifyOrExit(aSize == OTBR_IP6_ADDRESS_SIZE || aSize == OTBR_IP4_ADDRESS_SIZE,
otbrLogErr("Unexpected address data length: %zu", aSize), avahiError = AVAHI_ERR_INVALID_ADDRESS);
VerifyOrExit(aSize == OTBR_IP6_ADDRESS_SIZE, otbrLogInfo("IPv4 address ignored"),
avahiError = AVAHI_ERR_INVALID_ADDRESS);
address = Ip6Address(*static_cast<const uint8_t(*)[OTBR_IP6_ADDRESS_SIZE]>(aRdata));
VerifyOrExit(!address.IsMulticast() && !address.IsLoopback() && !address.IsUnspecified(),
avahiError = AVAHI_ERR_INVALID_ADDRESS);
otbrLogInfo("Resolved host address: %s %s", aEvent == AVAHI_BROWSER_NEW ? "add" : "remove",
address.ToString().c_str());
mHostInfo.mHostName = std::string(aName) + ".";
if (aEvent == AVAHI_BROWSER_NEW)
{
mHostInfo.AddAddress(address);
}
else
{
mHostInfo.RemoveAddress(address);
}
mHostInfo.mNetifIndex = static_cast<uint32_t>(aInterfaceIndex);
// TODO: Use a more proper TTL
mHostInfo.mTtl = kDefaultTtl;
resolved = true;
exit:
if (resolved)
{
// NOTE: This `HostSubscrption` object may be freed in `OnHostResolved`.
mPublisherAvahi->OnHostResolved(mHostName, mHostInfo);
}
else if (avahiError != AVAHI_OK)
{
mPublisherAvahi->OnHostResolveFailed(mHostName, avahiError);
}
}
} // namespace Mdns
} // namespace otbr