/* * 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 mDNS publisher. */ #define OTBR_LOG_TAG "MDNS" #include "mdns/mdns.hpp" #if OTBR_ENABLE_MDNS #include #include #include #include "common/code_utils.hpp" #include "utils/dns_utils.hpp" namespace otbr { namespace Mdns { void Publisher::PublishService(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; mServiceRegistrationBeginTime[std::make_pair(aName, aType)] = Clock::now(); error = PublishServiceImpl(aHostName, aName, aType, aSubTypeList, aPort, aTxtData, std::move(aCallback)); if (error != OTBR_ERROR_NONE) { UpdateMdnsResponseCounters(mTelemetryInfo.mServiceRegistrations, error); } } void Publisher::PublishHost(const std::string &aName, const AddressList &aAddresses, ResultCallback &&aCallback) { otbrError error; mHostRegistrationBeginTime[aName] = Clock::now(); error = PublishHostImpl(aName, aAddresses, std::move(aCallback)); if (error != OTBR_ERROR_NONE) { UpdateMdnsResponseCounters(mTelemetryInfo.mHostRegistrations, error); } } void Publisher::PublishKey(const std::string &aName, const KeyData &aKeyData, ResultCallback &&aCallback) { otbrError error; mKeyRegistrationBeginTime[aName] = Clock::now(); error = PublishKeyImpl(aName, aKeyData, std::move(aCallback)); if (error != OTBR_ERROR_NONE) { UpdateMdnsResponseCounters(mTelemetryInfo.mKeyRegistrations, error); } } void Publisher::OnServiceResolveFailed(std::string aType, std::string aInstanceName, int32_t aErrorCode) { UpdateMdnsResponseCounters(mTelemetryInfo.mServiceResolutions, DnsErrorToOtbrError(aErrorCode)); UpdateServiceInstanceResolutionEmaLatency(aInstanceName, aType, DnsErrorToOtbrError(aErrorCode)); OnServiceResolveFailedImpl(aType, aInstanceName, aErrorCode); } void Publisher::OnHostResolveFailed(std::string aHostName, int32_t aErrorCode) { UpdateMdnsResponseCounters(mTelemetryInfo.mHostResolutions, DnsErrorToOtbrError(aErrorCode)); UpdateHostResolutionEmaLatency(aHostName, DnsErrorToOtbrError(aErrorCode)); OnHostResolveFailedImpl(aHostName, aErrorCode); } otbrError Publisher::EncodeTxtData(const TxtList &aTxtList, std::vector &aTxtData) { otbrError error = OTBR_ERROR_NONE; aTxtData.clear(); for (const TxtEntry &txtEntry : aTxtList) { size_t entryLength = txtEntry.mKey.length(); if (!txtEntry.mIsBooleanAttribute) { entryLength += txtEntry.mValue.size() + sizeof(uint8_t); // for `=` char. } VerifyOrExit(entryLength <= kMaxTextEntrySize, error = OTBR_ERROR_INVALID_ARGS); aTxtData.push_back(static_cast(entryLength)); aTxtData.insert(aTxtData.end(), txtEntry.mKey.begin(), txtEntry.mKey.end()); if (!txtEntry.mIsBooleanAttribute) { aTxtData.push_back('='); aTxtData.insert(aTxtData.end(), txtEntry.mValue.begin(), txtEntry.mValue.end()); } } if (aTxtData.empty()) { aTxtData.push_back(0); } exit: return error; } otbrError Publisher::DecodeTxtData(Publisher::TxtList &aTxtList, const uint8_t *aTxtData, uint16_t aTxtLength) { otbrError error = OTBR_ERROR_NONE; aTxtList.clear(); for (uint16_t r = 0; r < aTxtLength;) { uint16_t entrySize = aTxtData[r]; uint16_t keyStart = r + 1; uint16_t entryEnd = keyStart + entrySize; uint16_t keyEnd = keyStart; VerifyOrExit(entryEnd <= aTxtLength, error = OTBR_ERROR_PARSE); while (keyEnd < entryEnd && aTxtData[keyEnd] != '=') { keyEnd++; } if (keyEnd == entryEnd) { if (keyEnd > keyStart) { // No `=`, treat as a boolean attribute. aTxtList.emplace_back(reinterpret_cast(&aTxtData[keyStart]), keyEnd - keyStart); } } else { uint16_t valStart = keyEnd + 1; // To skip over `=` aTxtList.emplace_back(reinterpret_cast(&aTxtData[keyStart]), keyEnd - keyStart, &aTxtData[valStart], entryEnd - valStart); } r += entrySize + 1; } exit: return error; } void Publisher::RemoveSubscriptionCallbacks(uint64_t aSubscriberId) { mDiscoverCallbacks.remove_if( [aSubscriberId](const DiscoverCallback &aCallback) { return (aCallback.mId == aSubscriberId); }); } uint64_t Publisher::AddSubscriptionCallbacks(Publisher::DiscoveredServiceInstanceCallback aInstanceCallback, Publisher::DiscoveredHostCallback aHostCallback) { uint64_t id = mNextSubscriberId++; assert(id > 0); mDiscoverCallbacks.emplace_back(id, aInstanceCallback, aHostCallback); return id; } void Publisher::OnServiceResolved(std::string aType, DiscoveredInstanceInfo aInstanceInfo) { bool checkToInvoke = false; otbrLogInfo("Service %s is resolved successfully: %s %s host %s addresses %zu", aType.c_str(), aInstanceInfo.mRemoved ? "remove" : "add", aInstanceInfo.mName.c_str(), aInstanceInfo.mHostName.c_str(), aInstanceInfo.mAddresses.size()); if (!aInstanceInfo.mRemoved) { std::string addressesString; for (const auto &address : aInstanceInfo.mAddresses) { addressesString += address.ToString() + ","; } if (addressesString.size()) { addressesString.pop_back(); } otbrLogInfo("addresses: [ %s ]", addressesString.c_str()); } DnsUtils::CheckServiceNameSanity(aType); assert(aInstanceInfo.mNetifIndex > 0); if (!aInstanceInfo.mRemoved) { DnsUtils::CheckHostnameSanity(aInstanceInfo.mHostName); } UpdateMdnsResponseCounters(mTelemetryInfo.mServiceResolutions, OTBR_ERROR_NONE); UpdateServiceInstanceResolutionEmaLatency(aInstanceInfo.mName, aType, OTBR_ERROR_NONE); // The `mDiscoverCallbacks` list can get updated as the callbacks // are invoked. We first mark `mShouldInvoke` on all non-null // service callbacks. We clear it before invoking the callback // and restart the iteration over the `mDiscoverCallbacks` list // to find the next one to signal, since the list may have changed. for (DiscoverCallback &callback : mDiscoverCallbacks) { if (callback.mServiceCallback != nullptr) { callback.mShouldInvoke = true; checkToInvoke = true; } } while (checkToInvoke) { checkToInvoke = false; for (DiscoverCallback &callback : mDiscoverCallbacks) { if (callback.mShouldInvoke) { callback.mShouldInvoke = false; checkToInvoke = true; callback.mServiceCallback(aType, aInstanceInfo); break; } } } } void Publisher::OnServiceRemoved(uint32_t aNetifIndex, std::string aType, std::string aInstanceName) { DiscoveredInstanceInfo instanceInfo; otbrLogInfo("Service %s.%s is removed from netif %u.", aInstanceName.c_str(), aType.c_str(), aNetifIndex); instanceInfo.mRemoved = true; instanceInfo.mNetifIndex = aNetifIndex; instanceInfo.mName = aInstanceName; OnServiceResolved(aType, instanceInfo); } void Publisher::OnHostResolved(std::string aHostName, Publisher::DiscoveredHostInfo aHostInfo) { bool checkToInvoke = false; otbrLogInfo("Host %s is resolved successfully: host %s addresses %zu ttl %u", aHostName.c_str(), aHostInfo.mHostName.c_str(), aHostInfo.mAddresses.size(), aHostInfo.mTtl); if (!aHostInfo.mHostName.empty()) { DnsUtils::CheckHostnameSanity(aHostInfo.mHostName); } UpdateMdnsResponseCounters(mTelemetryInfo.mHostResolutions, OTBR_ERROR_NONE); UpdateHostResolutionEmaLatency(aHostName, OTBR_ERROR_NONE); // The `mDiscoverCallbacks` list can get updated as the callbacks // are invoked. We first mark `mShouldInvoke` on all non-null // host callbacks. We clear it before invoking the callback // and restart the iteration over the `mDiscoverCallbacks` list // to find the next one to signal, since the list may have changed. for (DiscoverCallback &callback : mDiscoverCallbacks) { if (callback.mHostCallback != nullptr) { callback.mShouldInvoke = true; checkToInvoke = true; } } while (checkToInvoke) { checkToInvoke = false; for (DiscoverCallback &callback : mDiscoverCallbacks) { if (callback.mShouldInvoke) { callback.mShouldInvoke = false; checkToInvoke = true; callback.mHostCallback(aHostName, aHostInfo); break; } } } } Publisher::SubTypeList Publisher::SortSubTypeList(SubTypeList aSubTypeList) { std::sort(aSubTypeList.begin(), aSubTypeList.end()); return aSubTypeList; } Publisher::AddressList Publisher::SortAddressList(AddressList aAddressList) { std::sort(aAddressList.begin(), aAddressList.end()); return aAddressList; } std::string Publisher::MakeFullServiceName(const std::string &aName, const std::string &aType) { return aName + "." + aType + ".local"; } std::string Publisher::MakeFullName(const std::string &aName) { return aName + ".local"; } void Publisher::AddServiceRegistration(ServiceRegistrationPtr &&aServiceReg) { mServiceRegistrations.emplace(MakeFullServiceName(aServiceReg->mName, aServiceReg->mType), std::move(aServiceReg)); } void Publisher::RemoveServiceRegistration(const std::string &aName, const std::string &aType, otbrError aError) { auto it = mServiceRegistrations.find(MakeFullServiceName(aName, aType)); ServiceRegistrationPtr serviceReg; otbrLogInfo("Removing service %s.%s", aName.c_str(), aType.c_str()); VerifyOrExit(it != mServiceRegistrations.end()); // Keep the ServiceRegistration around before calling `Complete` // to invoke the callback. This is for avoiding invalid access // to the ServiceRegistration when it's freed from the callback. serviceReg = std::move(it->second); mServiceRegistrations.erase(it); serviceReg->Complete(aError); exit: return; } Publisher::ServiceRegistration *Publisher::FindServiceRegistration(const std::string &aName, const std::string &aType) { auto it = mServiceRegistrations.find(MakeFullServiceName(aName, aType)); return it != mServiceRegistrations.end() ? it->second.get() : nullptr; } Publisher::ServiceRegistration *Publisher::FindServiceRegistration(const std::string &aNameAndType) { auto it = mServiceRegistrations.find(MakeFullName(aNameAndType)); return it != mServiceRegistrations.end() ? it->second.get() : nullptr; } Publisher::ResultCallback Publisher::HandleDuplicateServiceRegistration(const std::string &aHostName, const std::string &aName, const std::string &aType, const SubTypeList &aSubTypeList, uint16_t aPort, const TxtData &aTxtData, ResultCallback &&aCallback) { ServiceRegistration *serviceReg = FindServiceRegistration(aName, aType); VerifyOrExit(serviceReg != nullptr); if (serviceReg->IsOutdated(aHostName, aName, aType, aSubTypeList, aPort, aTxtData)) { otbrLogInfo("Removing existing service %s.%s: outdated", aName.c_str(), aType.c_str()); RemoveServiceRegistration(aName, aType, OTBR_ERROR_ABORTED); } else if (serviceReg->IsCompleted()) { // Returns success if the same service has already been // registered with exactly the same parameters. std::move(aCallback)(OTBR_ERROR_NONE); } else { // If the same service is being registered with the same parameters, // let's join the waiting queue for the result. serviceReg->mCallback = std::bind( [](std::shared_ptr aExistingCallback, std::shared_ptr aNewCallback, otbrError aError) { std::move (*aExistingCallback)(aError); std::move (*aNewCallback)(aError); }, std::make_shared(std::move(serviceReg->mCallback)), std::make_shared(std::move(aCallback)), std::placeholders::_1); } exit: return std::move(aCallback); } Publisher::ResultCallback Publisher::HandleDuplicateHostRegistration(const std::string &aName, const AddressList &aAddresses, ResultCallback &&aCallback) { HostRegistration *hostReg = FindHostRegistration(aName); VerifyOrExit(hostReg != nullptr); if (hostReg->IsOutdated(aName, aAddresses)) { otbrLogInfo("Removing existing host %s: outdated", aName.c_str()); RemoveHostRegistration(hostReg->mName, OTBR_ERROR_ABORTED); } else if (hostReg->IsCompleted()) { // Returns success if the same service has already been // registered with exactly the same parameters. std::move(aCallback)(OTBR_ERROR_NONE); } else { // If the same service is being registered with the same parameters, // let's join the waiting queue for the result. hostReg->mCallback = std::bind( [](std::shared_ptr aExistingCallback, std::shared_ptr aNewCallback, otbrError aError) { std::move (*aExistingCallback)(aError); std::move (*aNewCallback)(aError); }, std::make_shared(std::move(hostReg->mCallback)), std::make_shared(std::move(aCallback)), std::placeholders::_1); } exit: return std::move(aCallback); } void Publisher::AddHostRegistration(HostRegistrationPtr &&aHostReg) { mHostRegistrations.emplace(MakeFullHostName(aHostReg->mName), std::move(aHostReg)); } void Publisher::RemoveHostRegistration(const std::string &aName, otbrError aError) { auto it = mHostRegistrations.find(MakeFullHostName(aName)); HostRegistrationPtr hostReg; otbrLogInfo("Removing host %s", aName.c_str()); VerifyOrExit(it != mHostRegistrations.end()); // Keep the HostRegistration around before calling `Complete` // to invoke the callback. This is for avoiding invalid access // to the HostRegistration when it's freed from the callback. hostReg = std::move(it->second); mHostRegistrations.erase(it); hostReg->Complete(aError); otbrLogInfo("Removed host %s", aName.c_str()); exit: return; } Publisher::HostRegistration *Publisher::FindHostRegistration(const std::string &aName) { auto it = mHostRegistrations.find(MakeFullHostName(aName)); return it != mHostRegistrations.end() ? it->second.get() : nullptr; } Publisher::ResultCallback Publisher::HandleDuplicateKeyRegistration(const std::string &aName, const KeyData &aKeyData, ResultCallback &&aCallback) { KeyRegistration *keyReg = FindKeyRegistration(aName); VerifyOrExit(keyReg != nullptr); if (keyReg->IsOutdated(aName, aKeyData)) { otbrLogInfo("Removing existing key %s: outdated", aName.c_str()); RemoveKeyRegistration(keyReg->mName, OTBR_ERROR_ABORTED); } else if (keyReg->IsCompleted()) { // Returns success if the same key has already been // registered with exactly the same parameters. std::move(aCallback)(OTBR_ERROR_NONE); } else { // If the same key is being registered with the same parameters, // let's join the waiting queue for the result. keyReg->mCallback = std::bind( [](std::shared_ptr aExistingCallback, std::shared_ptr aNewCallback, otbrError aError) { std::move (*aExistingCallback)(aError); std::move (*aNewCallback)(aError); }, std::make_shared(std::move(keyReg->mCallback)), std::make_shared(std::move(aCallback)), std::placeholders::_1); } exit: return std::move(aCallback); } void Publisher::AddKeyRegistration(KeyRegistrationPtr &&aKeyReg) { mKeyRegistrations.emplace(MakeFullKeyName(aKeyReg->mName), std::move(aKeyReg)); } void Publisher::RemoveKeyRegistration(const std::string &aName, otbrError aError) { auto it = mKeyRegistrations.find(MakeFullKeyName(aName)); KeyRegistrationPtr keyReg; otbrLogInfo("Removing key %s", aName.c_str()); VerifyOrExit(it != mKeyRegistrations.end()); // Keep the KeyRegistration around before calling `Complete` // to invoke the callback. This is for avoiding invalid access // to the KeyRegistration when it's freed from the callback. keyReg = std::move(it->second); mKeyRegistrations.erase(it); keyReg->Complete(aError); otbrLogInfo("Removed key %s", aName.c_str()); exit: return; } Publisher::KeyRegistration *Publisher::FindKeyRegistration(const std::string &aName) { auto it = mKeyRegistrations.find(MakeFullKeyName(aName)); return it != mKeyRegistrations.end() ? it->second.get() : nullptr; } Publisher::KeyRegistration *Publisher::FindKeyRegistration(const std::string &aName, const std::string &aType) { auto it = mKeyRegistrations.find(MakeFullServiceName(aName, aType)); return it != mKeyRegistrations.end() ? it->second.get() : nullptr; } Publisher::Registration::~Registration(void) { TriggerCompleteCallback(OTBR_ERROR_ABORTED); } bool Publisher::ServiceRegistration::IsOutdated(const std::string &aHostName, const std::string &aName, const std::string &aType, const SubTypeList &aSubTypeList, uint16_t aPort, const TxtData &aTxtData) const { return !(mHostName == aHostName && mName == aName && mType == aType && mSubTypeList == aSubTypeList && mPort == aPort && mTxtData == aTxtData); } void Publisher::ServiceRegistration::Complete(otbrError aError) { OnComplete(aError); Registration::TriggerCompleteCallback(aError); } void Publisher::ServiceRegistration::OnComplete(otbrError aError) { if (!IsCompleted()) { mPublisher->UpdateMdnsResponseCounters(mPublisher->mTelemetryInfo.mServiceRegistrations, aError); mPublisher->UpdateServiceRegistrationEmaLatency(mName, mType, aError); } } bool Publisher::HostRegistration::IsOutdated(const std::string &aName, const AddressList &aAddresses) const { return !(mName == aName && mAddresses == aAddresses); } void Publisher::HostRegistration::Complete(otbrError aError) { OnComplete(aError); Registration::TriggerCompleteCallback(aError); } void Publisher::HostRegistration::OnComplete(otbrError aError) { if (!IsCompleted()) { mPublisher->UpdateMdnsResponseCounters(mPublisher->mTelemetryInfo.mHostRegistrations, aError); mPublisher->UpdateHostRegistrationEmaLatency(mName, aError); } } bool Publisher::KeyRegistration::IsOutdated(const std::string &aName, const KeyData &aKeyData) const { return !(mName == aName && mKeyData == aKeyData); } void Publisher::KeyRegistration::Complete(otbrError aError) { OnComplete(aError); Registration::TriggerCompleteCallback(aError); } void Publisher::KeyRegistration::OnComplete(otbrError aError) { if (!IsCompleted()) { mPublisher->UpdateMdnsResponseCounters(mPublisher->mTelemetryInfo.mKeyRegistrations, aError); mPublisher->UpdateKeyRegistrationEmaLatency(mName, aError); } } void Publisher::UpdateMdnsResponseCounters(otbr::MdnsResponseCounters &aCounters, otbrError aError) { switch (aError) { case OTBR_ERROR_NONE: ++aCounters.mSuccess; break; case OTBR_ERROR_NOT_FOUND: ++aCounters.mNotFound; break; case OTBR_ERROR_INVALID_ARGS: ++aCounters.mInvalidArgs; break; case OTBR_ERROR_DUPLICATED: ++aCounters.mDuplicated; break; case OTBR_ERROR_NOT_IMPLEMENTED: ++aCounters.mNotImplemented; break; case OTBR_ERROR_ABORTED: ++aCounters.mAborted; break; case OTBR_ERROR_INVALID_STATE: ++aCounters.mInvalidState; break; case OTBR_ERROR_MDNS: default: ++aCounters.mUnknownError; break; } } void Publisher::UpdateEmaLatency(uint32_t &aEmaLatency, uint32_t aLatency, otbrError aError) { VerifyOrExit(aError != OTBR_ERROR_ABORTED); if (!aEmaLatency) { aEmaLatency = aLatency; } else { aEmaLatency = (aLatency * MdnsTelemetryInfo::kEmaFactorNumerator + aEmaLatency * (MdnsTelemetryInfo::kEmaFactorDenominator - MdnsTelemetryInfo::kEmaFactorNumerator)) / MdnsTelemetryInfo::kEmaFactorDenominator; } exit: return; } void Publisher::UpdateServiceRegistrationEmaLatency(const std::string &aInstanceName, const std::string &aType, otbrError aError) { auto it = mServiceRegistrationBeginTime.find(std::make_pair(aInstanceName, aType)); if (it != mServiceRegistrationBeginTime.end()) { uint32_t latency = std::chrono::duration_cast(Clock::now() - it->second).count(); UpdateEmaLatency(mTelemetryInfo.mServiceRegistrationEmaLatency, latency, aError); mServiceRegistrationBeginTime.erase(it); } } void Publisher::UpdateHostRegistrationEmaLatency(const std::string &aHostName, otbrError aError) { auto it = mHostRegistrationBeginTime.find(aHostName); if (it != mHostRegistrationBeginTime.end()) { uint32_t latency = std::chrono::duration_cast(Clock::now() - it->second).count(); UpdateEmaLatency(mTelemetryInfo.mHostRegistrationEmaLatency, latency, aError); mHostRegistrationBeginTime.erase(it); } } void Publisher::UpdateKeyRegistrationEmaLatency(const std::string &aKeyName, otbrError aError) { auto it = mKeyRegistrationBeginTime.find(aKeyName); if (it != mKeyRegistrationBeginTime.end()) { uint32_t latency = std::chrono::duration_cast(Clock::now() - it->second).count(); UpdateEmaLatency(mTelemetryInfo.mKeyRegistrationEmaLatency, latency, aError); mKeyRegistrationBeginTime.erase(it); } } void Publisher::UpdateServiceInstanceResolutionEmaLatency(const std::string &aInstanceName, const std::string &aType, otbrError aError) { auto it = mServiceInstanceResolutionBeginTime.find(std::make_pair(aInstanceName, aType)); if (it != mServiceInstanceResolutionBeginTime.end()) { uint32_t latency = std::chrono::duration_cast(Clock::now() - it->second).count(); UpdateEmaLatency(mTelemetryInfo.mServiceResolutionEmaLatency, latency, aError); mServiceInstanceResolutionBeginTime.erase(it); } } void Publisher::UpdateHostResolutionEmaLatency(const std::string &aHostName, otbrError aError) { auto it = mHostResolutionBeginTime.find(aHostName); if (it != mHostResolutionBeginTime.end()) { uint32_t latency = std::chrono::duration_cast(Clock::now() - it->second).count(); UpdateEmaLatency(mTelemetryInfo.mHostResolutionEmaLatency, latency, aError); mHostResolutionBeginTime.erase(it); } } void Publisher::AddAddress(AddressList &aAddressList, const Ip6Address &aAddress) { aAddressList.push_back(aAddress); } void Publisher::RemoveAddress(AddressList &aAddressList, const Ip6Address &aAddress) { auto it = std::find(aAddressList.begin(), aAddressList.end(), aAddress); if (it != aAddressList.end()) { aAddressList.erase(it); } } } // namespace Mdns } // namespace otbr #endif // OTBR_ENABLE_MDNS