/* * 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 #include #include #include #include #include #include #include #include #include #include #include #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(*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 Watches; typedef std::vector 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(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(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(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(*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(timeout - now); if (delay < FromTimeval(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()) , 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(aContext)->HandleClientState(aClient, aState); } void PublisherAvahi::HandleGroupState(AvahiEntryGroup *aGroup, AvahiEntryGroupState aState, void *aContext) { static_cast(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(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( 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( 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(reinterpret_cast(curr) - reinterpret_cast(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(*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(*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(*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(*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 &aService) { return aService->mType == aType && aService->mInstanceName == aInstanceName; }); VerifyOrExit(it != mSubscribedServices.end()); { std::unique_ptr 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(*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 &aHost) { return aHost->mHostName == aHostName; }); VerifyOrExit(it != mSubscribedHosts.end()); { std::unique_ptr 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(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(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 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(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(aEvent), static_cast(aFlags)); switch (aEvent) { case AVAHI_BROWSER_NEW: Resolve(aInterfaceIndex, aProtocol, aName, aType); break; case AVAHI_BROWSER_REMOVE: mPublisherAvahi->OnServiceRemoved(static_cast(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(); 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(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(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(aFlags), static_cast(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(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(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(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(aFlags), static_cast(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(aRdata)); VerifyOrExit(!address.IsLinkLocal() && !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(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(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(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(aFlags), static_cast(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(aRdata)); VerifyOrExit(!address.IsLinkLocal() && !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(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