diff --git a/include/openthread/instance.h b/include/openthread/instance.h index c53d6a2b0..4297df615 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -52,7 +52,7 @@ extern "C" { * * @note This number versions both OpenThread platform and user APIs. */ -#define OPENTHREAD_API_VERSION (490) +#define OPENTHREAD_API_VERSION (491) /** * @addtogroup api-instance diff --git a/include/openthread/mdns.h b/include/openthread/mdns.h index 3193aa24c..05f79812c 100644 --- a/include/openthread/mdns.h +++ b/include/openthread/mdns.h @@ -203,6 +203,29 @@ bool otMdnsIsQuestionUnicastAllowed(otInstance *aInstance); */ void otMdnsSetConflictCallback(otInstance *aInstance, otMdnsConflictCallback aCallback); +/** + * Gets the local host name. + * + * @param[in] aInstance The OpenThread instance. + * + * @returns The local host name. + */ +const char *otMdnsGetLocalHostName(otInstance *aInstance); + +/** + * Sets the local host name. + * + * The local host name can be set only when the mDNS module is disabled. If not set the mDNS module itself will + * auto-generate the local host name. + * + * @param[in] aInstance The OpenThread instance. + * @param[in] aName The local host name to use, can be to `NULL` to allow the mDNS module to choose the name. + * + * @retval OT_ERROR_NONE The local host name was successfully set. + * @retval OT_ERROR_INVALID_STATE mDNS module is already enabled. + */ +otError otMdnsSetLocalHostName(otInstance *aInstance, const char *aName); + /** * Registers or updates a host on mDNS. * diff --git a/include/openthread/platform/mdns_socket.h b/include/openthread/platform/mdns_socket.h index e6a205281..e17ef6e6f 100644 --- a/include/openthread/platform/mdns_socket.h +++ b/include/openthread/platform/mdns_socket.h @@ -75,6 +75,10 @@ typedef struct otPlatMdnsAddressInfo * * While enabled, all received messages MUST be reported back using `otPlatMdnsHandleReceive()` callback. * + * When enabled, the platform MUST also monitor and report all IPv4 and IPv6 addresses assigned to the network + * interface using the `otPlatMdnsHandleHostAddressEvent()` callback function. Refer to the documentation of this + * callback for detailed information on the callback's usage and parameters. + * * @param[in] aInstance The OpernThread instance. * @param[in] aEnable Indicate whether to enable or disable. * @param[in] aInfraInfIndex The infrastructure network interface index. @@ -154,6 +158,47 @@ extern void otPlatMdnsHandleReceive(otInstance *aInstance, bool aIsUnicast, const otPlatMdnsAddressInfo *aAddress); +/** + * Callback to notify OpenThread mDNS module of host address changes. + * + * When `otPlatMdnsSetListeningEnabled()` enables mDNS listening on an @p aInfraIfIndex, the platform MUST monitor and + * report ALL IPv4 and IPv6 addresses assigned to this network interface. + * + * When mDNS is enabled: + * - The platform MUST retrieve ALL currently assigned IPv4 and IPv6 addresses on the specified interface. + * - For each retrieved address, the platform MUST call `otPlatMdnsHandleHostAddressEvent()`. + * - The IPv4 addresses are represented using IPv4-mapped IPv6 format. + * + * Ongoing monitoring (while enabled): + * - The platform MUST continuously monitor the specified interface for address changes. + * - If any addresses are added or removed, the platform MUST call this callback for each affected address, indicating + * the change (addition or removal using @p aAdded). + * + * When mDNS is disabled: + * - The platform MUST cease monitoring for address changes on the interface. + * - The platform does NOT need to explicitly signal the removal of addresses upon disable. The OpenThread stack + * automatically clears its internal address list. + * - If address monitoring is re-enabled later, the platform MUST repeat the "enable" steps again, retrieving and + * reporting ALL current addresses. + * + * The OpenThread stack maintains an internal list of host addresses. It updates this list automatically upon receiving + * calls to `otPlatMdnsHandleHostAddressEvent()`. + * - OpenThread's mDNS implementation uses a short guard time (4 msec) before taking action (e.g., announcing new + * addresses). This allows multiple changes to be grouped and announced together. + * - OpenThread's mDNS implementation also handles transient changes, e.g., an address is removed and then quickly + * re-added. It ensures that announcements are only made when there is a change to the list (from what was + * announced before). This simplifies the platform's responsibility as it can simply report all observed changes. + * + * @param[in] aInstance The OpenThread instance. + * @param[in] aAddress IP Address. IPv4-mapped IPv6 format is used to represent an IPv4 address. + * @param[in] aAdded Boolean to indicate whether the address added (`TRUE`) or removed (`FALSE`). + * @param[in] aInfraIfIndex The interface index. + */ +extern void otPlatMdnsHandleHostAddressEvent(otInstance *aInstance, + const otIp6Address *aAddress, + bool aAdded, + uint32_t aInfraIfIndex); + /** * @} */ diff --git a/src/cli/cli_mdns.cpp b/src/cli/cli_mdns.cpp index e04adc4b4..68f0c9f24 100644 --- a/src/cli/cli_mdns.cpp +++ b/src/cli/cli_mdns.cpp @@ -86,6 +86,11 @@ template <> otError Mdns::Process(Arg aArgs[]) return ProcessEnableDisable(aArgs, otMdnsIsQuestionUnicastAllowed, otMdnsSetQuestionUnicastAllowed); } +template <> otError Mdns::Process(Arg aArgs[]) +{ + return ProcessGetSet(aArgs, otMdnsGetLocalHostName, otMdnsSetLocalHostName); +} + void Mdns::OutputHost(const otMdnsHost &aHost) { OutputLine("Host %s", aHost.mHostName); @@ -1209,6 +1214,7 @@ otError Mdns::Process(Arg aArgs[]) CmdEntry("ip6resolvers"), CmdEntry("keys"), #endif + CmdEntry("localhostname"), CmdEntry("recordquerier"), #if OPENTHREAD_CONFIG_MULTICAST_DNS_ENTRY_ITERATION_API_ENABLE CmdEntry("recordqueriers"), diff --git a/src/core/api/mdns_api.cpp b/src/core/api/mdns_api.cpp index 0085fc750..596265cf3 100644 --- a/src/core/api/mdns_api.cpp +++ b/src/core/api/mdns_api.cpp @@ -61,6 +61,16 @@ void otMdnsSetConflictCallback(otInstance *aInstance, otMdnsConflictCallback aCa AsCoreType(aInstance).Get().SetConflictCallback(aCallback); } +const char *otMdnsGetLocalHostName(otInstance *aInstance) +{ + return AsCoreType(aInstance).Get().GetLocalHostName(); +} + +otError otMdnsSetLocalHostName(otInstance *aInstance, const char *aName) +{ + return AsCoreType(aInstance).Get().SetLocalHostName(aName); +} + otError otMdnsRegisterHost(otInstance *aInstance, const otMdnsHost *aHost, otMdnsRequestId aRequestId, diff --git a/src/core/instance/instance.cpp b/src/core/instance/instance.cpp index 48f341526..4b097bda6 100644 --- a/src/core/instance/instance.cpp +++ b/src/core/instance/instance.cpp @@ -405,6 +405,10 @@ void Instance::AfterInit(void) Get().AfterInit(); #endif +#if OPENTHREAD_CONFIG_MULTICAST_DNS_ENABLE + Get().AfterInstanceInit(); +#endif + #endif // OPENTHREAD_MTD || OPENTHREAD_FTD #if OPENTHREAD_ENABLE_VENDOR_EXTENSION diff --git a/src/core/net/mdns.cpp b/src/core/net/mdns.cpp index 2d0303a4e..27770db08 100644 --- a/src/core/net/mdns.cpp +++ b/src/core/net/mdns.cpp @@ -55,6 +55,14 @@ extern "C" void otPlatMdnsHandleReceive(otInstance *aInstance, AsCoreType(aInstance).Get().HandleMessage(AsCoreType(aMessage), aIsUnicast, AsCoreType(aAddress)); } +extern "C" void otPlatMdnsHandleHostAddressEvent(otInstance *aInstance, + const otIp6Address *aAddress, + bool aAdded, + uint32_t aInfraIfIndex) +{ + AsCoreType(aInstance).Get().HandleHostAddressEvent(AsCoreType(aAddress), aAdded, aInfraIfIndex); +} + //---------------------------------------------------------------------------------------------------------------------- // Core @@ -70,6 +78,7 @@ Core::Core(Instance &aInstance) , mIsQuestionUnicastAllowed(kDefaultQuAllowed) , mMaxMessageSize(kMaxMessageSize) , mInfraIfIndex(0) + , mLocalHost(aInstance) , mMultiPacketRxMessages(aInstance) , mNextProbeTxTime(TimerMilli::GetNow() - 1) , mEntryTimer(aInstance) @@ -82,6 +91,19 @@ Core::Core(Instance &aInstance) { } +void Core::AfterInstanceInit(void) +{ + // This is called immediately after the OpenThread `Instance` is + // initialized (i.e., after all constructors are called and saved + // information from `Settings` is restored). This call triggers + // the generation of the local host name, which is derived from + // the device's extended MAC address. This ensures that the MAC + // address is restored from the non-volatile settings, and the + // generated name remains consistent across device reboots. + + mLocalHost.GenerateName(); +} + Error Core::SetEnabled(bool aEnable, uint32_t aInfraIfIndex) { Error error = kErrorNone; @@ -99,10 +121,8 @@ Error Core::SetEnabled(bool aEnable, uint32_t aInfraIfIndex) else { LogInfo("Disabling"); - } - if (!mIsEnabled) - { + mLocalHost.ClearAddresses(); mHostEntries.Clear(); mServiceEntries.Clear(); mServiceTypes.Clear(); @@ -258,6 +278,12 @@ void Core::InvokeConflictCallback(const char *aName, const char *aServiceType) mConflictCallback(&GetInstance(), aName, aServiceType); } } + +void Core::HandleHostAddressEvent(const Ip6::Address &aAddress, bool aAdded, uint32_t aInfraIfIndex) +{ + mLocalHost.HandleAddressEvent(aAddress, aAdded, aInfraIfIndex); +} + void Core::HandleMessage(Message &aMessage, bool aIsUnicast, const AddressInfo &aSenderAddress) { OwnedPtr messagePtr(&aMessage); @@ -540,6 +566,11 @@ exit: return matches; } +bool Core::AddressArray::Matches(const AddressArray &aOther) const +{ + return Matches(aOther.AsCArray(), aOther.GetLength()); +} + void Core::AddressArray::SetFrom(const Ip6::Address *aAddresses, uint16_t aNumAddresses) { Free(); @@ -1198,7 +1229,7 @@ bool Core::Entry::ShouldAnswerNsec(TimeMilli aNow) const return mMulticastNsecPending && (GetNsecAnswerTime() <= aNow); } -void Core::Entry::AnswerNonProbe(const AnswerInfo &aInfo, RecordAndType *aRecords, uint16_t aRecordsLength) +void Core::Entry::AnswerNonProbe(const AnswerInfo &aInfo, RecordAndTypeArray &aRecordAndTypes) { // Schedule answers for all matching records in `aRecords` array // to a given non-probe question. @@ -1206,9 +1237,9 @@ void Core::Entry::AnswerNonProbe(const AnswerInfo &aInfo, RecordAndType *aRecord bool allEmptyOrZeroTtl = true; bool answerNsec = true; - for (uint16_t index = 0; index < aRecordsLength; index++) + for (RecordAndType &recordAndType : aRecordAndTypes) { - RecordInfo &record = aRecords[index].mRecord; + RecordInfo &record = *recordAndType.mRecord; if (!record.CanAnswer()) { @@ -1218,7 +1249,7 @@ void Core::Entry::AnswerNonProbe(const AnswerInfo &aInfo, RecordAndType *aRecord allEmptyOrZeroTtl = false; - if (QuestionMatches(aInfo.mQuestionRrType, aRecords[index].mType)) + if (QuestionMatches(aInfo.mQuestionRrType, recordAndType.mType)) { answerNsec = false; record.ScheduleAnswer(aInfo); @@ -1235,7 +1266,7 @@ void Core::Entry::AnswerNonProbe(const AnswerInfo &aInfo, RecordAndType *aRecord } } -void Core::Entry::AnswerProbe(const AnswerInfo &aInfo, RecordAndType *aRecords, uint16_t aRecordsLength) +void Core::Entry::AnswerProbe(const AnswerInfo &aInfo, RecordAndTypeArray &aRecordAndTypes) { bool allEmptyOrZeroTtl = true; bool shouldDelay = false; @@ -1246,9 +1277,9 @@ void Core::Entry::AnswerProbe(const AnswerInfo &aInfo, RecordAndType *aRecords, OT_ASSERT(info.mIsProbe); - for (uint16_t index = 0; index < aRecordsLength; index++) + for (RecordAndType &recordAndType : aRecordAndTypes) { - RecordInfo &record = aRecords[index].mRecord; + RecordInfo &record = *recordAndType.mRecord; TimeMilli lastMulticastTime; if (!record.CanAnswer()) @@ -1294,9 +1325,9 @@ void Core::Entry::AnswerProbe(const AnswerInfo &aInfo, RecordAndType *aRecords, info.mAnswerDelay = 0; } - for (uint16_t index = 0; index < aRecordsLength; index++) + for (RecordAndType &recordAndType : aRecordAndTypes) { - aRecords[index].mRecord.ScheduleAnswer(info); + recordAndType.mRecord->ScheduleAnswer(info); } exit: @@ -1469,6 +1500,180 @@ exit: return error; } +//---------------------------------------------------------------------------------------------------------------------- +// Core::Entry::RecordAndTypeArray + +void Core::Entry::RecordAndTypeArray::Add(RecordInfo &aRecord, uint16_t aType) +{ + RecordAndType *entry = PushBack(); + + OT_ASSERT(entry != nullptr); + entry->mRecord = &aRecord; + entry->mType = aType; +} + +//---------------------------------------------------------------------------------------------------------------------- +// Core::LocalHost + +Core::LocalHost::LocalHost(Instance &aInstance) + : InstanceLocator(aInstance) + , mEventTimer(aInstance) +{ + GenerateName(); +} + +Error Core::LocalHost::SetName(const char *aName) +{ + Error error = kErrorNone; + + VerifyOrExit(!Get().mIsEnabled, error = kErrorInvalidState); + + if (aName == nullptr) + { + GenerateName(); + } + else + { + SuccessOrAssert(mName.Set(aName)); + } + +exit: + return error; +} + +void Core::LocalHost::GenerateName(void) +{ + Name::LabelBuffer name; + StringWriter writer(name, sizeof(name)); + + writer.Append("ot%s", Get().GetExtAddress().ToString().AsCString()); + + SuccessOrAssert(mName.Set(name)); +} + +void Core::LocalHost::ClearAddresses(void) +{ + mIp4Addresses.Free(); + mIp6Addresses.Free(); + mAddrEvents.Clear(); + mEventTimer.Stop(); +} + +void Core::LocalHost::HandleAddressEvent(const Ip6::Address &aAddress, bool aAdded, uint32_t aInfraIfIndex) +{ + AddrEvent *addrEvent; + + VerifyOrExit(Get().mIsEnabled); + VerifyOrExit(aInfraIfIndex == Get().mInfraIfIndex); + + LogInfo("Host address %s event: %s", aAddress.ToString().AsCString(), aAdded ? "added" : "removed"); + + addrEvent = AddrEvent::Allocate(aAddress, aAdded); + OT_ASSERT(addrEvent != nullptr); + + // Before we add the new event, we remove any previous events in the + // list that match the same address. This way we always track the + // latest event for each address. This handles the case where + // a "removed" address event is quickly followed by an "added" event + // for the same address. + // + // The events are processed after a short guard delay time + // `kGuardTimeToProcessAddrEvents`. This ensures multiple changes + // to be grouped and announced together. + + mAddrEvents.RemoveAndFreeAllMatching(aAddress); + mAddrEvents.Push(*addrEvent); + + if (!mEventTimer.IsRunning()) + { + mEventTimer.Start(kGuardTimeToProcessAddrEvents); + } + +exit: + return; +} + +void Core::LocalHost::HandleEventTimer(void) +{ + // Process all saved `AddrEvents` and update IPv4 and IPv6 + // address lists. + + static const AddrType kAddrTypes[] = {kIp4AddrType, kIp6AddrType}; + + VerifyOrExit(Get().mIsEnabled); + + for (AddrType addrType : kAddrTypes) + { + AddressArray &addresses = (addrType == kIp4AddrType) ? mIp4Addresses : mIp6Addresses; + AddressArray oldAddresses; + + oldAddresses.TakeFrom(static_cast(addresses)); + addresses.Clear(); + + // First, add existing addresses (from old list) that did not + // change (there is no "removed" event). + + for (const Ip6::Address &address : oldAddresses) + { + const AddrEvent *addrEvent = mAddrEvents.FindMatching(address); + + if ((addrEvent == nullptr) || addrEvent->mAdded) + { + SuccessOrAssert(addresses.PushBack(address)); + } + } + + // Next, add any new addresses for which we got an "added" + // event. + + for (const AddrEvent &addrEvent : mAddrEvents) + { + if (!addrEvent.Matches(addrType)) + { + continue; + } + + if (addrEvent.mAdded && !addresses.Contains(addrEvent.mAddress)) + { + SuccessOrAssert(addresses.PushBack(addrEvent.mAddress)); + } + } + } + + IgnoreError(Get().Register(*this, /* aRequestId */ 0, /* aCallback */ nullptr)); + +exit: + mAddrEvents.Clear(); +} + +//---------------------------------------------------------------------------------------------------------------------- +// Core::LocalHost::AddrEvent + +Core::LocalHost::AddrEvent::AddrEvent(const Ip6::Address &aAddress, bool aAdded) + : mNext(nullptr) + , mAddress(aAddress) + , mAdded(aAdded) +{ +} + +bool Core::LocalHost::AddrEvent::Matches(AddrType aType) const +{ + bool matches = false; + bool isIp4 = mAddress.IsIp4Mapped(); + + switch (aType) + { + case kIp4AddrType: + matches = isIp4; + break; + case kIp6AddrType: + matches = !isIp4; + break; + } + + return matches; +} + //---------------------------------------------------------------------------------------------------------------------- // Core::HostEntry @@ -1492,11 +1697,24 @@ bool Core::HostEntry::Matches(const Name &aName) const bool Core::HostEntry::Matches(const Host &aHost) const { return NameMatch(mName, aHost.mHostName); } +bool Core::HostEntry::Matches(const LocalHost &aLocalHost) const { return NameMatch(mName, aLocalHost.GetName()); } + bool Core::HostEntry::Matches(const Key &aKey) const { return !IsKeyForService(aKey) && NameMatch(mName, aKey.mName); } bool Core::HostEntry::Matches(const Heap::String &aName) const { return NameMatch(mName, aName); } -bool Core::HostEntry::IsEmpty(void) const { return !mAddrRecord.IsPresent() && !mKeyRecord.IsPresent(); } +bool Core::HostEntry::IsEmpty(void) const +{ + bool isEmpty = false; + + VerifyOrExit(!mKeyRecord.IsPresent() && !mIp6AddrRecord.IsPresent()); + VerifyOrExit((mIp4AddrRecord == nullptr) || !mIp4AddrRecord->IsPresent()); + + isEmpty = true; + +exit: + return isEmpty; +} void Core::HostEntry::Register(const Host &aHost, const Callback &aCallback) { @@ -1525,8 +1743,8 @@ void Core::HostEntry::Register(const Host &aHost, const Callback &aCallback) ExitNow(); } - mAddrRecord.UpdateTtl(DetermineTtl(aHost.mTtl, kDefaultTtl)); - mAddrRecord.UpdateProperty(mAddresses, AsCoreTypePtr(aHost.mAddresses), aHost.mAddressesLength); + mIp6AddrRecord.UpdateTtl(DetermineTtl(aHost.mTtl, kDefaultTtl)); + mIp6AddrRecord.UpdateAddresses(aHost); DetermineNextFireTime(); ScheduleTimer(); @@ -1535,6 +1753,46 @@ exit: return; } +void Core::HostEntry::Register(const LocalHost &aLocalHost, const Callback &aCallback) +{ + SetCallback(aCallback); + + if (aLocalHost.GetIp6Addresses().IsEmpty()) + { + if (mIp6AddrRecord.IsPresent()) + { + mIp6AddrRecord.UpdateTtl(0); + } + } + else + { + mIp6AddrRecord.UpdateTtl(kDefaultTtl); + mIp6AddrRecord.UpdateAddresses(aLocalHost.GetIp6Addresses()); + } + + if (aLocalHost.GetIp4Addresses().IsEmpty()) + { + if ((mIp4AddrRecord != nullptr) && mIp4AddrRecord->IsPresent()) + { + mIp4AddrRecord->UpdateTtl(0); + } + } + else + { + if (mIp4AddrRecord == nullptr) + { + mIp4AddrRecord.Reset(AddrRecord::Allocate()); + OT_ASSERT(mIp4AddrRecord != nullptr); + } + + mIp4AddrRecord->UpdateTtl(kDefaultTtl); + mIp4AddrRecord->UpdateAddresses(aLocalHost.GetIp4Addresses()); + } + + DetermineNextFireTime(); + ScheduleTimer(); +} + void Core::HostEntry::Register(const Key &aKey, const Callback &aCallback) { Entry::Register(aKey, aCallback); @@ -1547,14 +1805,14 @@ void Core::HostEntry::Unregister(const Host &aHost) { OT_UNUSED_VARIABLE(aHost); - VerifyOrExit(mAddrRecord.IsPresent()); + VerifyOrExit(mIp6AddrRecord.IsPresent()); ClearCallback(); switch (GetState()) { case kRegistered: - mAddrRecord.UpdateTtl(0); + mIp6AddrRecord.UpdateTtl(0); DetermineNextFireTime(); ScheduleTimer(); break; @@ -1585,8 +1843,12 @@ void Core::HostEntry::Unregister(const Key &aKey) void Core::HostEntry::ClearHost(void) { - mAddrRecord.Clear(); - mAddresses.Free(); + mIp6AddrRecord.Clear(); + + if (mIp4AddrRecord != nullptr) + { + mIp4AddrRecord->Clear(); + } } void Core::HostEntry::ScheduleToRemoveIfEmpty(void) @@ -1612,20 +1874,25 @@ exit: void Core::HostEntry::AnswerQuestion(const AnswerInfo &aInfo) { - RecordAndType records[] = { - {mAddrRecord, ResourceRecord::kTypeAaaa}, - {mKeyRecord, ResourceRecord::kTypeKey}, - }; + RecordAndTypeArray recordAndTypes; VerifyOrExit(GetState() == kRegistered); + recordAndTypes.Add(mIp6AddrRecord, ResourceRecord::kTypeAaaa); + recordAndTypes.Add(mKeyRecord, ResourceRecord::kTypeKey); + + if (mIp4AddrRecord != nullptr) + { + recordAndTypes.Add(*mIp4AddrRecord, ResourceRecord::kTypeA); + } + if (aInfo.mIsProbe) { - AnswerProbe(aInfo, records, GetArrayLength(records)); + AnswerProbe(aInfo, recordAndTypes); } else { - AnswerNonProbe(aInfo, records, GetArrayLength(records)); + AnswerNonProbe(aInfo, recordAndTypes); } DetermineNextFireTime(); @@ -1644,7 +1911,12 @@ void Core::HostEntry::ClearAppendState(void) Entry::ClearAppendState(); - mAddrRecord.MarkAsNotAppended(); + mIp6AddrRecord.MarkAsNotAppended(); + + if (mIp4AddrRecord != nullptr) + { + mIp4AddrRecord->MarkAsNotAppended(); + } mNameOffset = kUnspecifiedOffset; } @@ -1660,7 +1932,8 @@ void Core::HostEntry::PrepareProbe(TxMessage &aProbe) AppendNameTo(aProbe, kQuestionSection); AppendQuestionTo(aProbe); - AppendAddressRecordsTo(aProbe, kAuthoritySection); + AppendIp6AddressRecordsTo(aProbe, kAuthoritySection); + AppendIp4AddressRecordsTo(aProbe, kAuthoritySection); AppendKeyRecordTo(aProbe, kAuthoritySection); aProbe.CheckSizeLimitToPrepareAgain(prepareAgain); @@ -1670,7 +1943,13 @@ void Core::HostEntry::PrepareProbe(TxMessage &aProbe) void Core::HostEntry::StartAnnouncing(void) { - mAddrRecord.StartAnnouncing(); + mIp6AddrRecord.StartAnnouncing(); + + if (mIp4AddrRecord != nullptr) + { + mIp4AddrRecord->StartAnnouncing(); + } + mKeyRecord.StartAnnouncing(); } @@ -1695,9 +1974,15 @@ void Core::HostEntry::PrepareResponseRecords(EntryContext &aContext) bool appendNsec = false; TxMessage &response = aContext.mResponseMessage; - if (mAddrRecord.ShouldAppendTo(aContext)) + if (mIp6AddrRecord.ShouldAppendTo(aContext)) { - AppendAddressRecordsTo(response, kAnswerSection); + AppendIp6AddressRecordsTo(response, kAnswerSection); + appendNsec = true; + } + + if ((mIp4AddrRecord != nullptr) && mIp4AddrRecord->ShouldAppendTo(aContext)) + { + AppendIp4AddressRecordsTo(response, kAnswerSection); appendNsec = true; } @@ -1718,7 +2003,12 @@ void Core::HostEntry::UpdateRecordsState(const TxMessage &aResponse) // Updates state after a response is prepared. Entry::UpdateRecordsState(aResponse); - mAddrRecord.UpdateStateAfterAnswer(aResponse); + mIp6AddrRecord.UpdateStateAfterAnswer(aResponse); + + if (mIp4AddrRecord != nullptr) + { + mIp4AddrRecord->UpdateStateAfterAnswer(aResponse); + } if (IsEmpty()) { @@ -1731,7 +2021,12 @@ void Core::HostEntry::DetermineNextFireTime(void) VerifyOrExit(GetState() == kRegistered); Entry::DetermineNextFireTime(); - mAddrRecord.UpdateFireTimeOn(*this); + mIp6AddrRecord.UpdateFireTimeOn(*this); + + if (mIp4AddrRecord != nullptr) + { + mIp4AddrRecord->UpdateFireTimeOn(*this); + } exit: return; @@ -1742,33 +2037,72 @@ void Core::HostEntry::DetermineNextAggrTxTime(NextFireTime &aNextAggrTxTime) con VerifyOrExit(GetState() == kRegistered); Entry::DetermineNextAggrTxTime(aNextAggrTxTime); - mAddrRecord.DetermineNextAggrTxTime(aNextAggrTxTime); + mIp6AddrRecord.DetermineNextAggrTxTime(aNextAggrTxTime); + + if (mIp4AddrRecord != nullptr) + { + mIp4AddrRecord->DetermineNextAggrTxTime(aNextAggrTxTime); + } exit: return; } -void Core::HostEntry::AppendAddressRecordsTo(TxMessage &aTxMessage, Section aSection) +void Core::HostEntry::AppendIp6AddressRecordsTo(TxMessage &aTxMessage, Section aSection) +{ + AppendAddressRecordsTo(aTxMessage, aSection, mIp6AddrRecord, /* aIp6 */ true); +} + +void Core::HostEntry::AppendIp4AddressRecordsTo(TxMessage &aTxMessage, Section aSection) +{ + if (mIp4AddrRecord != nullptr) + { + AppendAddressRecordsTo(aTxMessage, aSection, *mIp4AddrRecord, /* aIp6 */ false); + } +} + +void Core::HostEntry::AppendAddressRecordsTo(TxMessage &aTxMessage, + Section aSection, + AddrRecord &aAddrRecord, + bool aIp6) { Message *message; bool isLegacyUnicast = (aTxMessage.GetType() == TxMessage::kLegacyUnicastResponse); - VerifyOrExit(mAddrRecord.CanAppend()); - mAddrRecord.MarkAsAppended(aTxMessage, aSection); + VerifyOrExit(aAddrRecord.CanAppend()); + aAddrRecord.MarkAsAppended(aTxMessage, aSection); message = &aTxMessage.SelectMessageFor(aSection); - for (const Ip6::Address &address : mAddresses) + for (const Ip6::Address &address : aAddrRecord.mAddresses) { - AaaaRecord aaaaRecord; - - aaaaRecord.Init(); - aaaaRecord.SetAddress(address); - aaaaRecord.SetTtl(mAddrRecord.GetTtl(isLegacyUnicast)); - UpdateCacheFlushFlagIn(aaaaRecord, aSection, isLegacyUnicast); - AppendNameTo(aTxMessage, aSection); - SuccessOrAssert(message->Append(aaaaRecord)); + + if (aIp6) + { + AaaaRecord aaaaRecord; + + aaaaRecord.Init(); + aaaaRecord.SetAddress(address); + aaaaRecord.SetTtl(aAddrRecord.GetTtl(isLegacyUnicast)); + UpdateCacheFlushFlagIn(aaaaRecord, aSection, isLegacyUnicast); + + SuccessOrAssert(message->Append(aaaaRecord)); + } + else + { + Ip4::Address ip4Address; + ARecord aRecord; + + SuccessOrAssert(ip4Address.ExtractFromIp4MappedIp6Address(address)); + + aRecord.Init(); + aRecord.SetAddress(ip4Address); + aRecord.SetTtl(aAddrRecord.GetTtl(isLegacyUnicast)); + UpdateCacheFlushFlagIn(aRecord, aSection, isLegacyUnicast); + + SuccessOrAssert(message->Append(aRecord)); + } aTxMessage.IncrementRecordCount(aSection); } @@ -1786,11 +2120,16 @@ void Core::HostEntry::AppendNsecRecordTo(TxMessage &aTxMessage, Section aSection { TypeArray types; - if (mAddrRecord.IsPresent() && (mAddrRecord.GetTtl() > 0)) + if (mIp6AddrRecord.IsPresent() && (mIp6AddrRecord.GetTtl() > 0)) { types.Add(ResourceRecord::kTypeAaaa); } + if ((mIp4AddrRecord != nullptr) && mIp4AddrRecord->IsPresent() && (mIp4AddrRecord->GetTtl() > 0)) + { + types.Add(ResourceRecord::kTypeA); + } + if (mKeyRecord.IsPresent() && (mKeyRecord.GetTtl() > 0)) { types.Add(ResourceRecord::kTypeKey); @@ -1820,18 +2159,28 @@ exit: return; } +void Core::HostEntry::MarkToAppendAddrRecordsInAdditionalData(void) +{ + mIp6AddrRecord.MarkToAppendInAdditionalData(); + + if (mIp4AddrRecord != nullptr) + { + mIp4AddrRecord->MarkToAppendInAdditionalData(); + } +} + #if OPENTHREAD_CONFIG_MULTICAST_DNS_ENTRY_ITERATION_API_ENABLE Error Core::HostEntry::CopyInfoTo(Host &aHost, EntryState &aState) const { Error error = kErrorNone; - VerifyOrExit(mAddrRecord.IsPresent(), error = kErrorNotFound); + VerifyOrExit(mIp6AddrRecord.IsPresent(), error = kErrorNotFound); aHost.mHostName = mName.AsCString(); - aHost.mAddresses = mAddresses.AsCArray(); - aHost.mAddressesLength = mAddresses.GetLength(); - aHost.mTtl = mAddrRecord.GetTtl(); + aHost.mAddresses = mIp6AddrRecord.mAddresses.AsCArray(); + aHost.mAddressesLength = mIp6AddrRecord.mAddresses.GetLength(); + aHost.mTtl = mIp6AddrRecord.GetTtl(); aHost.mInfraIfIndex = Get().mInfraIfIndex; aState = static_cast(GetState()); @@ -1854,6 +2203,25 @@ exit: #endif // OPENTHREAD_CONFIG_MULTICAST_DNS_ENTRY_ITERATION_API_ENABLE +//---------------------------------------------------------------------------------------------------------------------- +// Core::HostEntry::AddrRecord + +void Core::HostEntry::AddrRecord::Clear(void) +{ + RecordInfo::Clear(); + mAddresses.Free(); +} + +void Core::HostEntry::AddrRecord::UpdateAddresses(const Host &aHost) +{ + UpdateProperty(mAddresses, AsCoreTypePtr(aHost.mAddresses), aHost.mAddressesLength); +} + +void Core::HostEntry::AddrRecord::UpdateAddresses(const AddressArray &aAddresses) +{ + UpdateProperty(mAddresses, aAddresses.AsCArray(), aAddresses.GetLength()); +} + //---------------------------------------------------------------------------------------------------------------------- // Core::ServiceEntry @@ -2103,21 +2471,21 @@ exit: void Core::ServiceEntry::AnswerServiceNameQuestion(const AnswerInfo &aInfo) { - RecordAndType records[] = { - {mSrvRecord, ResourceRecord::kTypeSrv}, - {mTxtRecord, ResourceRecord::kTypeTxt}, - {mKeyRecord, ResourceRecord::kTypeKey}, - }; + RecordAndTypeArray recordAndTypes; VerifyOrExit(GetState() == kRegistered); + recordAndTypes.Add(mSrvRecord, ResourceRecord::kTypeSrv); + recordAndTypes.Add(mTxtRecord, ResourceRecord::kTypeTxt); + recordAndTypes.Add(mKeyRecord, ResourceRecord::kTypeKey); + if (aInfo.mIsProbe) { - AnswerProbe(aInfo, records, GetArrayLength(records)); + AnswerProbe(aInfo, recordAndTypes); } else { - AnswerNonProbe(aInfo, records, GetArrayLength(records)); + AnswerNonProbe(aInfo, recordAndTypes); } DetermineNextFireTime(); @@ -2313,7 +2681,7 @@ void Core::ServiceEntry::PrepareResponseRecords(EntryContext &aContext) if (hostEntry != nullptr) { - hostEntry->mAddrRecord.MarkToAppendInAdditionalData(); + hostEntry->MarkToAppendAddrRecordsInAdditionalData(); } } @@ -2324,7 +2692,7 @@ void Core::ServiceEntry::PrepareResponseRecords(EntryContext &aContext) if ((mSrvRecord.GetTtl() > 0) && (hostEntry != nullptr)) { - hostEntry->mAddrRecord.MarkToAppendInAdditionalData(); + hostEntry->MarkToAppendAddrRecordsInAdditionalData(); } } @@ -2352,9 +2720,17 @@ void Core::ServiceEntry::PrepareResponseRecords(EntryContext &aContext) AppendTxtRecordTo(response, kAdditionalDataSection); } - if ((hostEntry != nullptr) && (hostEntry->mAddrRecord.ShouldAppendInAdditionalDataSection())) + if (hostEntry != nullptr) { - hostEntry->AppendAddressRecordsTo(response, kAdditionalDataSection); + if (hostEntry->mIp6AddrRecord.ShouldAppendInAdditionalDataSection()) + { + hostEntry->AppendIp6AddressRecordsTo(response, kAdditionalDataSection); + } + + if ((hostEntry->mIp4AddrRecord != nullptr) && hostEntry->mIp4AddrRecord->ShouldAppendInAdditionalDataSection()) + { + hostEntry->AppendIp4AddressRecordsTo(response, kAdditionalDataSection); + } } if (appendNsec || ShouldAnswerNsec(aContext.GetNow())) @@ -2428,6 +2804,8 @@ void Core::ServiceEntry::DiscoverOffsetsAndHost(HostEntry *&aHostEntry) // and name compression offsets from the previously appended // entries. + // TODO: Need to handle name matching host name + aHostEntry = Get().mHostEntries.FindMatching(mHostName); if ((aHostEntry != nullptr) && (aHostEntry->GetState() != GetState())) @@ -3709,7 +4087,7 @@ void Core::RxMessage::ProcessQuestion(Question &aQuestion) ExitNow(); } - // Check if question name matches a `HostEntry` or a `ServiceEntry` + // Check if question name matches a `HostEntry` or a `ServiceEntry`. aQuestion.mEntry = Get().mHostEntries.FindMatching(name); diff --git a/src/core/net/mdns.hpp b/src/core/net/mdns.hpp index 7c54290b0..2950f6b17 100644 --- a/src/core/net/mdns.hpp +++ b/src/core/net/mdns.hpp @@ -77,16 +77,28 @@ extern "C" void otPlatMdnsHandleReceive(otInstance *aInstance, bool aIsUnicast, const otPlatMdnsAddressInfo *aAddress); +extern "C" void otPlatMdnsHandleHostAddressEvent(otInstance *aInstance, + const otIp6Address *aAddress, + bool aAdded, + uint32_t aInfraIfIndex); + /** * Implements Multicast DNS (mDNS) core. */ class Core : public InstanceLocator, private NonCopyable { + friend class ot::Instance; + friend void otPlatMdnsHandleReceive(otInstance *aInstance, otMessage *aMessage, bool aIsUnicast, const otPlatMdnsAddressInfo *aAddress); + friend void otPlatMdnsHandleHostAddressEvent(otInstance *aInstance, + const otIp6Address *aAddress, + bool aAdded, + uint32_t aInfraIfIndex); + public: /** * Initializes a `Core` instance. @@ -165,6 +177,26 @@ public: */ bool IsEnabled(void) const { return mIsEnabled; } + /** + * Gets the local host name. + * + * @returns The local host name. + */ + const char *GetLocalHostName(void) { return mLocalHost.GetName(); } + + /** + * Sets the local host name. + * + * The local host name can be set only when the mDNS module is disabled. If not set the mDNS module itself will + * generate the local host name. + * + * @param[in] aName The local host name to use, can be to `nullptr` to allow the mDNS module to choose the name. + * + * @retval kErrorNone The local host name was successfully set. + * @retval kErrorInvalidState mDNS module is already enabled. + */ + Error SetLocalHostName(const char *aName) { return mLocalHost.SetName(aName); } + #if OPENTHREAD_CONFIG_MULTICAST_DNS_AUTO_ENABLE_ON_INFRA_IF /** * Notifies `AdvertisingProxy` that `InfraIf` state changed. @@ -817,6 +849,12 @@ private: kAppendedLabels, }; + enum AddrType : uint8_t + { + kIp4AddrType, + kIp6AddrType, + }; + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Forward declarations @@ -897,7 +935,9 @@ private: class AddressArray : public Heap::Array { public: + bool IsEmpty(void) const { return (GetLength() == 0); } bool Matches(const Ip6::Address *aAddresses, uint16_t aNumAddresses) const; + bool Matches(const AddressArray &aOther) const; void SetFrom(const Ip6::Address *aAddresses, uint16_t aNumAddresses); }; @@ -1031,10 +1071,15 @@ private: struct RecordAndType { - RecordInfo &mRecord; + RecordInfo *mRecord; uint16_t mType; }; + struct RecordAndTypeArray : public Array + { + void Add(RecordInfo &aRecord, uint16_t aType); + }; + typedef void (*NameAppender)(Entry &aEntry, TxMessage &aTxMessage, Section aSection); Entry(void); @@ -1056,8 +1101,8 @@ private: void DetermineNextFireTime(void); void DetermineNextAggrTxTime(NextFireTime &aNextAggrTxTime) const; void ScheduleTimer(void); - void AnswerProbe(const AnswerInfo &aInfo, RecordAndType *aRecords, uint16_t aRecordsLength); - void AnswerNonProbe(const AnswerInfo &aInfo, RecordAndType *aRecords, uint16_t aRecordsLength); + void AnswerProbe(const AnswerInfo &aInfo, RecordAndTypeArray &aRecordAndTypes); + void AnswerNonProbe(const AnswerInfo &aInfo, RecordAndTypeArray &aRecordAndTypes); void ScheduleNsecAnswer(const AnswerInfo &aInfo); template void HandleTimer(EntryContext &aContext); @@ -1086,6 +1131,47 @@ private: // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + void HandleLocalHostEventTimer(void) { mLocalHost.HandleEventTimer(); } + + class LocalHost : public InstanceLocator + { + public: + explicit LocalHost(Instance &aInstance); + + const char *GetName(void) const { return mName.AsCString(); } + Error SetName(const char *aName); + void GenerateName(void); + const AddressArray &GetIp4Addresses(void) const { return mIp4Addresses; } + const AddressArray &GetIp6Addresses(void) const { return mIp6Addresses; } + void HandleAddressEvent(const Ip6::Address &aAddress, bool aAdded, uint32_t aInfraIfIndex); + void HandleEventTimer(void); + void ClearAddresses(void); + + private: + static constexpr uint32_t kGuardTimeToProcessAddrEvents = 4; // msec + + struct AddrEvent : public LinkedListEntry, public Heap::Allocatable + { + AddrEvent(const Ip6::Address &aAddress, bool aAdded); + bool Matches(const Ip6::Address &aAddress) const { return mAddress == aAddress; } + bool Matches(AddrType aType) const; + + AddrEvent *mNext; + Ip6::Address mAddress; + bool mAdded; + }; + + using EventTimer = TimerMilliIn; + + Heap::String mName; + AddressArray mIp4Addresses; + AddressArray mIp6Addresses; + OwningList mAddrEvents; + EventTimer mEventTimer; + }; + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + class HostEntry : public Entry, public LinkedListEntry, public Heap::Allocatable { friend class LinkedListEntry; @@ -1095,15 +1181,18 @@ private: public: HostEntry(void); Error Init(Instance &aInstance, const Host &aHost) { return Init(aInstance, aHost.mHostName); } + Error Init(Instance &aInstance, const LocalHost &aLocalHost) { return Init(aInstance, aLocalHost.GetName()); } Error Init(Instance &aInstance, const Key &aKey) { return Init(aInstance, aKey.mName); } bool IsEmpty(void) const; bool Matches(const Name &aName) const; bool Matches(const Host &aHost) const; + bool Matches(const LocalHost &aLocalHost) const; bool Matches(const Key &aKey) const; bool Matches(const Heap::String &aName) const; bool Matches(State aState) const { return GetState() == aState; } bool Matches(const HostEntry &aEntry) const { return (this == &aEntry); } void Register(const Host &aHost, const Callback &aCallback); + void Register(const LocalHost &aLocalHost, const Callback &aCallback); void Register(const Key &aKey, const Callback &aCallback); void Unregister(const Host &aHost); void Unregister(const Key &aKey); @@ -1119,6 +1208,15 @@ private: #endif private: + struct AddrRecord : public RecordInfo, public Heap::Allocatable + { + void Clear(void); + void UpdateAddresses(const Host &aHost); + void UpdateAddresses(const AddressArray &aAddresses); + + AddressArray mAddresses; + }; + Error Init(Instance &aInstance, const char *aName); void ClearHost(void); void ScheduleToRemoveIfEmpty(void); @@ -1127,18 +1225,21 @@ private: void PrepareResponseRecords(EntryContext &aContext); void UpdateRecordsState(const TxMessage &aResponse); void DetermineNextFireTime(void); - void AppendAddressRecordsTo(TxMessage &aTxMessage, Section aSection); + void AppendIp6AddressRecordsTo(TxMessage &aTxMessage, Section aSection); + void AppendIp4AddressRecordsTo(TxMessage &aTxMessage, Section aSection); + void AppendAddressRecordsTo(TxMessage &aTxMessage, Section aSection, AddrRecord &aAddrRecord, bool aIp6); void AppendKeyRecordTo(TxMessage &aTxMessage, Section aSection); void AppendNsecRecordTo(TxMessage &aTxMessage, Section aSection); void AppendNameTo(TxMessage &aTxMessage, Section aSection); + void MarkToAppendAddrRecordsInAdditionalData(void); static void AppendEntryName(Entry &aEntry, TxMessage &aTxMessage, Section aSection); - HostEntry *mNext; - Heap::String mName; - RecordInfo mAddrRecord; - AddressArray mAddresses; - uint16_t mNameOffset; + HostEntry *mNext; + Heap::String mName; + AddrRecord mIp6AddrRecord; + OwnedPtr mIp4AddrRecord; + uint16_t mNameOffset; }; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -2101,6 +2202,8 @@ private: template Error Stop(const BrowserResolverType &aBrowserOrResolver); + void AfterInstanceInit(void); + void HandleHostAddressEvent(const Ip6::Address &aAddress, bool aAdded, uint32_t aInfraIfIndex); void InvokeConflictCallback(const char *aName, const char *aServiceType); void HandleMessage(Message &aMessage, bool aIsUnicast, const AddressInfo &aSenderAddress); void AddPassiveSrvTxtCache(const char *aServiceInstance, const char *aServiceType); @@ -2140,6 +2243,7 @@ private: bool mIsQuestionUnicastAllowed; uint16_t mMaxMessageSize; uint32_t mInfraIfIndex; + LocalHost mLocalHost; OwningList mHostEntries; OwningList mServiceEntries; OwningList mServiceTypes; diff --git a/tests/unit/test_mdns.cpp b/tests/unit/test_mdns.cpp index 70702cf78..4fe670ddb 100644 --- a/tests/unit/test_mdns.cpp +++ b/tests/unit/test_mdns.cpp @@ -245,7 +245,8 @@ struct DnsRecord : public Allocatable, public LinkedListEntry mData; // For TXT or KEY DnsName mPtrName; // For PTR @@ -285,6 +286,12 @@ struct DnsRecord : public Allocatable, public LinkedListEntry return contains; } + bool ContainsA(const DnsNameString &aFullName, + const Ip4::Address &aAddress, + bool aCacheFlush, + TtlCheckMode aTtlCheckMode, + uint32_t aTtl = 0) const + { + bool contains = false; + + for (const DnsRecord &record : *this) + { + if (record.Matches(aFullName.AsCString()) && (record.mType == ResourceRecord::kTypeA) && + (record.mData.mIp4Address == aAddress)) + { + VerifyOrExit(record.mClass == ResourceRecord::kClassInternet); + VerifyOrExit(record.mCacheFlush == aCacheFlush); + VerifyOrExit(record.MatchesTtl(aTtlCheckMode, aTtl)); + contains = true; + ExitNow(); + } + } + + exit: + return contains; + } + bool ContainsKey(const DnsNameString &aFullName, const Data &aKeyData, bool aCacheFlush, @@ -556,7 +588,7 @@ struct DnsRecords : public OwningList } }; -// Bit-flags used in `Validate()` with a `Service` +// Bit-flags used in `Validate()` with a `Service` or `LocalHost` // to specify which records should be checked in the announce // message. @@ -566,6 +598,8 @@ static constexpr uint8_t kCheckSrv = (1 << 0); static constexpr uint8_t kCheckTxt = (1 << 1); static constexpr uint8_t kCheckPtr = (1 << 2); static constexpr uint8_t kCheckServicesPtr = (1 << 3); +static constexpr uint8_t kCheckAaaa = (1 << 4); +static constexpr uint8_t kCheckA = (1 << 5); enum GoodBye : bool // Used to indicate "goodbye" records (with zero TTL) { @@ -581,6 +615,16 @@ enum DnsMessageType : uint8_t kLegacyUnicastResponse, }; +struct LocalHost +{ + static constexpr uint32_t kTtl = 120; + static constexpr uint16_t kMaxAddrs = 16; + + char mName[Name::kMaxNameSize]; + Array mIp6Addrs; + Array mIp4Addrs; +}; + struct DnsMessage : public Allocatable, public LinkedListEntry { DnsMessage *mNext; @@ -743,6 +787,27 @@ struct DnsMessage : public Allocatable, public LinkedListEntry, public LinkedListEntry, public LinkedListEntrySetEnabled(true, kInfraIfIndex)); + + SuccessOrQuit(StringCopy(localHost.mName, mdns->GetLocalHostName())); + Log("Local host name is \"%s\"", localHost.mName); + + hostFullName.Append("%s.local.", localHost.mName); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + Log("Add an IP6 address and IP4 address for local host, check probes and announcements"); + + SuccessOrQuit(ip6Address.FromString("fd00:cafe::1")); + SuccessOrQuit(localHost.mIp6Addrs.PushBack(ip6Address)); + otPlatMdnsHandleHostAddressEvent(sInstance, &ip6Address, /* aAdded */ true, kInfraIfIndex); + + SuccessOrQuit(ip4Address.FromString("200.1.5.6")); + SuccessOrQuit(localHost.mIp4Addrs.PushBack(ip4Address)); + ip6Address.SetToIp4Mapped(ip4Address); + otPlatMdnsHandleHostAddressEvent(sInstance, &ip6Address, /* aAdded */ true, kInfraIfIndex); + + AdvanceTime(4); + + sDnsMessages.Clear(); + + for (uint8_t probeCount = 0; probeCount < 3; probeCount++) + { + sDnsMessages.Clear(); + + AdvanceTime(250); + + VerifyOrQuit(!sDnsMessages.IsEmpty()); + dnsMsg = sDnsMessages.GetHead(); + dnsMsg->ValidateHeader(kMulticastQuery, /* Q */ 1, /* Ans */ 0, /* Auth */ 2, /* Addnl */ 0); + dnsMsg->ValidateAsProbeFor(localHost, /* aUnicastRequest */ (probeCount == 0)); + VerifyOrQuit(dnsMsg->GetNext() == nullptr); + } + + for (uint8_t anncCount = 0; anncCount < kNumAnnounces; anncCount++) + { + sDnsMessages.Clear(); + + AdvanceTime((anncCount == 0) ? 250 : (1U << (anncCount - 1)) * 1000); + + VerifyOrQuit(!sDnsMessages.IsEmpty()); + dnsMsg = sDnsMessages.GetHead(); + dnsMsg->ValidateHeader(kMulticastResponse, /* Q */ 0, /* Ans */ 2, /* Auth */ 0, /* Addnl */ 1); + dnsMsg->Validate(localHost, kInAnswerSection, kCheckAaaa | kCheckA); + VerifyOrQuit(dnsMsg->GetNext() == nullptr); + } + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + Log("Send a query for AAAA record and validate the response"); + + AdvanceTime(2000); + + sDnsMessages.Clear(); + SendQuery(hostFullName.AsCString(), ResourceRecord::kTypeAaaa); + + AdvanceTime(1000); + + dnsMsg = sDnsMessages.GetHead(); + VerifyOrQuit(dnsMsg != nullptr); + dnsMsg->ValidateHeader(kMulticastResponse, /* Q */ 0, /* Ans */ 1, /* Auth */ 0, /* Addnl */ 1); + dnsMsg->Validate(localHost, kInAnswerSection, kCheckAaaa); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + Log("Send a query for A record and validate the response"); + + AdvanceTime(2000); + + sDnsMessages.Clear(); + SendQuery(hostFullName.AsCString(), ResourceRecord::kTypeA); + + AdvanceTime(1000); + + dnsMsg = sDnsMessages.GetHead(); + VerifyOrQuit(dnsMsg != nullptr); + dnsMsg->ValidateHeader(kMulticastResponse, /* Q */ 0, /* Ans */ 1, /* Auth */ 0, /* Addnl */ 1); + dnsMsg->Validate(localHost, kInAnswerSection, kCheckA); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + Log("Send a query for ANY record and validate the response"); + + AdvanceTime(2000); + + sDnsMessages.Clear(); + SendQuery(hostFullName.AsCString(), ResourceRecord::kTypeAny); + + AdvanceTime(1000); + + dnsMsg = sDnsMessages.GetHead(); + VerifyOrQuit(dnsMsg != nullptr); + dnsMsg->ValidateHeader(kMulticastResponse, /* Q */ 0, /* Ans */ 2, /* Auth */ 0, /* Addnl */ 1); + dnsMsg->Validate(localHost, kInAnswerSection, kCheckAaaa | kCheckA); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + Log("Send a query for non-existing record and validate the response with NSEC"); + + AdvanceTime(2000); + + sDnsMessages.Clear(); + SendQuery(hostFullName.AsCString(), ResourceRecord::kTypeKey); + + AdvanceTime(1000); + + dnsMsg = sDnsMessages.GetHead(); + VerifyOrQuit(dnsMsg != nullptr); + dnsMsg->ValidateHeader(kMulticastResponse, /* Q */ 0, /* Ans */ 0, /* Auth */ 0, /* Addnl */ 1); + VerifyOrQuit(dnsMsg->mAdditionalRecords.ContainsNsec(hostFullName, ResourceRecord::kTypeAaaa)); + VerifyOrQuit(dnsMsg->mAdditionalRecords.ContainsNsec(hostFullName, ResourceRecord::kTypeA)); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + Log("Signal a new host IPv6 address is added and validate new announcements"); + + SuccessOrQuit(ip6Address.FromString("fd00:cafe::22")); + SuccessOrQuit(localHost.mIp6Addrs.PushBack(ip6Address)); + otPlatMdnsHandleHostAddressEvent(sInstance, &ip6Address, /* aAdded */ true, kInfraIfIndex); + + sDnsMessages.Clear(); + + AdvanceTime(5); + + for (uint8_t anncCount = 0; anncCount < kNumAnnounces; anncCount++) + { + AdvanceTime((anncCount == 0) ? 0 : (1U << (anncCount - 1)) * 1000); + + VerifyOrQuit(!sDnsMessages.IsEmpty()); + dnsMsg = sDnsMessages.GetHead(); + dnsMsg->ValidateHeader(kMulticastResponse, /* Q */ 0, /* Ans */ 2, /* Auth */ 0, /* Addnl */ 1); + dnsMsg->Validate(localHost, kInAnswerSection, kCheckAaaa); + VerifyOrQuit(dnsMsg->GetNext() == nullptr); + sDnsMessages.Clear(); + } + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + Log("Signal new host IPv6 addresses added and removed"); + + SuccessOrQuit(ip6Address.FromString("fd00:cafe::22")); + localHost.mIp6Addrs.Remove(ip6Address); + otPlatMdnsHandleHostAddressEvent(sInstance, &ip6Address, /* aAdded */ false, kInfraIfIndex); + + // Add and then remove the same address quickly + // It should not be included in the announcements. + + SuccessOrQuit(ip6Address.FromString("fd00:cafe::333")); + otPlatMdnsHandleHostAddressEvent(sInstance, &ip6Address, /* aAdded */ true, kInfraIfIndex); + AdvanceTime(1); + otPlatMdnsHandleHostAddressEvent(sInstance, &ip6Address, /* aAdded */ false, kInfraIfIndex); + + SuccessOrQuit(ip6Address.FromString("fd00:cafe::4444")); + SuccessOrQuit(localHost.mIp6Addrs.PushBack(ip6Address)); + otPlatMdnsHandleHostAddressEvent(sInstance, &ip6Address, /* aAdded */ true, kInfraIfIndex); + + Log("Validate the announcements"); + + sDnsMessages.Clear(); + + AdvanceTime(4); + + for (uint8_t anncCount = 0; anncCount < kNumAnnounces; anncCount++) + { + AdvanceTime((anncCount == 0) ? 0 : (1U << (anncCount - 1)) * 1000); + + VerifyOrQuit(!sDnsMessages.IsEmpty()); + dnsMsg = sDnsMessages.GetHead(); + dnsMsg->ValidateHeader(kMulticastResponse, /* Q */ 0, /* Ans */ 2, /* Auth */ 0, /* Addnl */ 1); + dnsMsg->Validate(localHost, kInAnswerSection, kCheckAaaa); + VerifyOrQuit(dnsMsg->GetNext() == nullptr); + sDnsMessages.Clear(); + } + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + Log("Signal three new host IPv4 addresses added"); + + SuccessOrQuit(ip4Address.FromString("200.1.5.7")); + SuccessOrQuit(localHost.mIp4Addrs.PushBack(ip4Address)); + ip6Address.SetToIp4Mapped(ip4Address); + otPlatMdnsHandleHostAddressEvent(sInstance, &ip6Address, /* aAdded */ true, kInfraIfIndex); + + SuccessOrQuit(ip4Address.FromString("200.1.2.100")); + SuccessOrQuit(localHost.mIp4Addrs.PushBack(ip4Address)); + ip6Address.SetToIp4Mapped(ip4Address); + otPlatMdnsHandleHostAddressEvent(sInstance, &ip6Address, /* aAdded */ true, kInfraIfIndex); + + SuccessOrQuit(ip4Address.FromString("200.1.4.0")); + SuccessOrQuit(localHost.mIp4Addrs.PushBack(ip4Address)); + ip6Address.SetToIp4Mapped(ip4Address); + otPlatMdnsHandleHostAddressEvent(sInstance, &ip6Address, /* aAdded */ true, kInfraIfIndex); + + Log("Validate the announcements"); + + sDnsMessages.Clear(); + + AdvanceTime(5); + + for (uint8_t anncCount = 0; anncCount < kNumAnnounces; anncCount++) + { + AdvanceTime((anncCount == 0) ? 0 : (1U << (anncCount - 1)) * 1000); + + VerifyOrQuit(!sDnsMessages.IsEmpty()); + dnsMsg = sDnsMessages.GetHead(); + dnsMsg->ValidateHeader(kMulticastResponse, /* Q */ 0, /* Ans */ 4, /* Auth */ 0, /* Addnl */ 1); + dnsMsg->Validate(localHost, kInAnswerSection, kCheckA); + VerifyOrQuit(dnsMsg->GetNext() == nullptr); + sDnsMessages.Clear(); + } + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + Log("Signal all host IPv4 addresses are removed, validate goodbye announcements"); + + for (const Ip4::Address &ip4Addr : localHost.mIp4Addrs) + { + ip6Address.SetToIp4Mapped(ip4Addr); + otPlatMdnsHandleHostAddressEvent(sInstance, &ip6Address, /* aAdded */ false, kInfraIfIndex); + } + + localHost.mIp4Addrs.Clear(); + + sDnsMessages.Clear(); + + AdvanceTime(5); + + for (uint8_t anncCount = 0; anncCount < kNumAnnounces; anncCount++) + { + AdvanceTime((anncCount == 0) ? 0 : (1U << (anncCount - 1)) * 1000); + + VerifyOrQuit(!sDnsMessages.IsEmpty()); + dnsMsg = sDnsMessages.GetHead(); + dnsMsg->ValidateHeader(kMulticastResponse, /* Q */ 0, /* Ans */ 4, /* Auth */ 0, /* Addnl */ 1); + dnsMsg->Validate(localHost, kInAnswerSection, kCheckA, kGoodBye); + VerifyOrQuit(dnsMsg->GetNext() == nullptr); + sDnsMessages.Clear(); + } + + AdvanceTime(10 * 1000); + VerifyOrQuit(sDnsMessages.IsEmpty()); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + Log("Signal removal of an IPv6 host address which was not added earlier"); + + SuccessOrQuit(ip6Address.FromString("fd00:cafe::beef")); + otPlatMdnsHandleHostAddressEvent(sInstance, &ip6Address, /* aAdded */ false, kInfraIfIndex); + + Log("Validate that there are no announcements"); + + sDnsMessages.Clear(); + + AdvanceTime(10 * 1000); + VerifyOrQuit(sDnsMessages.IsEmpty()); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + Log("Signal remove and re-add of the same host IPv6 address quickly"); + + ip6Address = localHost.mIp6Addrs[0]; + otPlatMdnsHandleHostAddressEvent(sInstance, &ip6Address, /* aAdded */ false, kInfraIfIndex); + AdvanceTime(1); + otPlatMdnsHandleHostAddressEvent(sInstance, &ip6Address, /* aAdded */ true, kInfraIfIndex); + + Log("Validate that there are no announcements"); + + sDnsMessages.Clear(); + + AdvanceTime(10 * 1000); + VerifyOrQuit(sDnsMessages.IsEmpty()); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + Log("Validate `SetLocalHostName()`"); + + VerifyOrQuit(mdns->SetLocalHostName("itsme") == kErrorInvalidState); + + SuccessOrQuit(mdns->SetEnabled(false, kInfraIfIndex)); + + SuccessOrQuit(mdns->SetLocalHostName("itsme")); + VerifyOrQuit(StringMatch(mdns->GetLocalHostName(), "itsme")); + + localHost.mIp4Addrs.Clear(); + localHost.mIp6Addrs.Clear(); + + SuccessOrQuit(StringCopy(localHost.mName, mdns->GetLocalHostName())); + Log("Local host name is \"%s\"", localHost.mName); + + hostFullName.Append("%s.local.", localHost.mName); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + Log("Re-enable mDNS module, add 4 new host IPv6 address and validate probe and announcements"); + + SuccessOrQuit(mdns->SetEnabled(true, kInfraIfIndex)); + + SuccessOrQuit(ip6Address.FromString("fd00:beef::a")); + SuccessOrQuit(localHost.mIp6Addrs.PushBack(ip6Address)); + otPlatMdnsHandleHostAddressEvent(sInstance, &ip6Address, /* aAdded */ true, kInfraIfIndex); + + SuccessOrQuit(ip6Address.FromString("fd00:beef::b")); + SuccessOrQuit(localHost.mIp6Addrs.PushBack(ip6Address)); + otPlatMdnsHandleHostAddressEvent(sInstance, &ip6Address, /* aAdded */ true, kInfraIfIndex); + + SuccessOrQuit(ip6Address.FromString("fd00:beef::c")); + SuccessOrQuit(localHost.mIp6Addrs.PushBack(ip6Address)); + otPlatMdnsHandleHostAddressEvent(sInstance, &ip6Address, /* aAdded */ true, kInfraIfIndex); + + SuccessOrQuit(ip6Address.FromString("fd00:beef::d")); + SuccessOrQuit(localHost.mIp6Addrs.PushBack(ip6Address)); + otPlatMdnsHandleHostAddressEvent(sInstance, &ip6Address, /* aAdded */ true, kInfraIfIndex); + + AdvanceTime(4); + + sDnsMessages.Clear(); + + for (uint8_t probeCount = 0; probeCount < 3; probeCount++) + { + sDnsMessages.Clear(); + + AdvanceTime(250); + + VerifyOrQuit(!sDnsMessages.IsEmpty()); + dnsMsg = sDnsMessages.GetHead(); + dnsMsg->ValidateHeader(kMulticastQuery, /* Q */ 1, /* Ans */ 0, /* Auth */ 4, /* Addnl */ 0); + dnsMsg->ValidateAsProbeFor(localHost, /* aUnicastRequest */ (probeCount == 0)); + VerifyOrQuit(dnsMsg->GetNext() == nullptr); + } + + for (uint8_t anncCount = 0; anncCount < kNumAnnounces; anncCount++) + { + sDnsMessages.Clear(); + + AdvanceTime((anncCount == 0) ? 250 : (1U << (anncCount - 1)) * 1000); + + VerifyOrQuit(!sDnsMessages.IsEmpty()); + dnsMsg = sDnsMessages.GetHead(); + dnsMsg->ValidateHeader(kMulticastResponse, /* Q */ 0, /* Ans */ 4, /* Auth */ 0, /* Addnl */ 1); + dnsMsg->Validate(localHost, kInAnswerSection, kCheckAaaa | kCheckA); + VerifyOrQuit(dnsMsg->GetNext() == nullptr); + } + + AdvanceTime(1000); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + + SuccessOrQuit(mdns->SetEnabled(false, kInfraIfIndex)); + VerifyOrQuit(sHeapAllocatedPtrs.GetLength() <= heapAllocations); + + Log("End of test"); + + testFreeInstance(sInstance); +} + +//--------------------------------------------------------------------------------------------------------------------- + void TestKeyReg(void) { Core *mdns = InitTest(); @@ -8182,6 +8653,7 @@ int main(void) { #if OPENTHREAD_CONFIG_MULTICAST_DNS_ENABLE ot::Dns::Multicast::TestHostReg(); + ot::Dns::Multicast::TestLocalHost(); ot::Dns::Multicast::TestKeyReg(); ot::Dns::Multicast::TestServiceReg(); ot::Dns::Multicast::TestUnregisterBeforeProbeFinished();