diff --git a/src/core/net/mdns.cpp b/src/core/net/mdns.cpp index 5b9f99d57..d2a3002b8 100644 --- a/src/core/net/mdns.cpp +++ b/src/core/net/mdns.cpp @@ -851,8 +851,19 @@ void Core::RecordInfo::ScheduleAnswer(const AnswerInfo &aInfo) if (aInfo.mUnicastResponse || aInfo.mLegacyUnicastResponse) { - mUnicastAnswerPending = true; - ExitNow(); + VerifyOrExit(!mUnicastAnswerPending); + + if (aInfo.mIsProbe || aInfo.mLegacyUnicastResponse || + (GetDurationSinceLastUnicast(aInfo.mQueryRxTime) >= kMinIntervalBetweenUnicast)) + { + mUnicastAnswerPending = true; + ExitNow(); + } + + // If the unicast rate limit (`kMinIntervalBetweenUnicast`) is + // reached on a `QU` query, fall through to schedule a multicast + // response instead (which has its own rate-limiting logic) so + // other queriers on the network can still receive the answer. } if (!aInfo.mIsProbe) @@ -1052,6 +1063,20 @@ void Core::RecordInfo::UpdateFireTimeOn(FireTime &aFireTime) } } + if (mIsLastUnicastValid) + { + TimeMilli lastUnicastAgeTime = mLastUnicastTime + kLastUnicastTimeAge; + + if (lastUnicastAgeTime <= TimerMilli::GetNow()) + { + mIsLastUnicastValid = false; + } + else + { + aFireTime.SetFireTime(lastUnicastAgeTime); + } + } + exit: return; } @@ -1094,6 +1119,11 @@ void Core::RecordInfo::MarkAsAppended(TxMessage &aTxMessage, Section aSection) break; case TxMessage::kUnicastResponse: + mAppendState = kAppendedInUnicastMsg; + mLastUnicastTime = TimerMilli::GetNow(); + mIsLastUnicastValid = true; + break; + case TxMessage::kLegacyUnicastResponse: mAppendState = kAppendedInUnicastMsg; break; @@ -1156,6 +1186,20 @@ exit: return duration; } +uint32_t Core::RecordInfo::GetDurationSinceLastUnicast(TimeMilli aTime) const +{ + uint32_t duration; + + SetToUintMax(duration); + + VerifyOrExit(mIsPresent && mIsLastUnicastValid); + VerifyOrExit(aTime > mLastUnicastTime, duration = 0); + duration = aTime - mLastUnicastTime; + +exit: + return duration; +} + //---------------------------------------------------------------------------------------------------------------------- // Core::FireTime diff --git a/src/core/net/mdns.hpp b/src/core/net/mdns.hpp index 211bd2e76..fd7e08906 100644 --- a/src/core/net/mdns.hpp +++ b/src/core/net/mdns.hpp @@ -1091,6 +1091,7 @@ private: void UpdateFireTimeOn(FireTime &aFireTime); void DetermineNextAggrTxTime(NextFireTime &aNextAggrTxTime) const; uint32_t GetDurationSinceLastMulticast(TimeMilli aTime) const; + uint32_t GetDurationSinceLastUnicast(TimeMilli aTime) const; Error GetLastMulticastTime(TimeMilli &aLastMulticastTime) const; // `AppendState` methods: Used to track whether the record @@ -1116,7 +1117,9 @@ private: TimeMilli GetAnswerTime(void) const { return mQueryRxTime + mAnswerDelay; } static constexpr uint32_t kMinIntervalBetweenMulticast = 1000; // msec + static constexpr uint32_t kMinIntervalBetweenUnicast = 1000; // msec static constexpr uint32_t kLastMulticastTimeAge = 10 * Time::kOneHourInMsec; + static constexpr uint32_t kLastUnicastTimeAge = 10 * Time::kOneHourInMsec; static_assert(kNotAppended == 0, "kNotAppended MUST be zero, so `Clear()` works correctly"); @@ -1124,6 +1127,7 @@ private: bool mMulticastAnswerPending : 1; bool mUnicastAnswerPending : 1; bool mIsLastMulticastValid : 1; + bool mIsLastUnicastValid : 1; bool mCanExtendAnswerDelay : 1; uint8_t mAnnounceCounter; AppendState mAppendState; @@ -1133,6 +1137,7 @@ private: TimeMilli mAnnounceTime; TimeMilli mQueryRxTime; TimeMilli mLastMulticastTime; + TimeMilli mLastUnicastTime; }; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/unit/test_mdns.cpp b/tests/unit/test_mdns.cpp index 65676bc5f..15c28ba30 100644 --- a/tests/unit/test_mdns.cpp +++ b/tests/unit/test_mdns.cpp @@ -4368,6 +4368,15 @@ void TestQuery(void) dnsMsg->Validate(host1, kInAdditionalSection); dnsMsg->Validate(host2, kInAdditionalSection); + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + Log("Resend the same unicast-response (QU) query and validate that response is not emitted (rate limit)"); + + sDnsMessages.Clear(); + SendQuery("_srv._udp.local.", ResourceRecord::kTypePtr, ResourceRecord::kClassInternet | kClassQueryUnicastFlag); + + AdvanceTime(200); + VerifyOrQuit(sDnsMessages.IsEmpty()); + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); Log("Resend the same multicast query and validate that response is not emitted (rate limit)"); @@ -4397,6 +4406,51 @@ void TestQuery(void) dnsMsg->Validate(host1, kInAdditionalSection); dnsMsg->Validate(host2, kInAdditionalSection); + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + Log("Resend the unicast-response (QU) query after > 1 second and validate the response"); + + sDnsMessages.Clear(); + SendQuery("_srv._udp.local.", ResourceRecord::kTypePtr, ResourceRecord::kClassInternet | kClassQueryUnicastFlag); + + AdvanceTime(200); + + dnsMsg = sDnsMessages.GetHead(); + VerifyOrQuit(dnsMsg != nullptr); + VerifyOrQuit(dnsMsg->GetNext() == nullptr); + + dnsMsg->ValidateHeader(kUnicastResponse, /* Q */ 0, /* Ans */ 2, /* Auth */ 0, /* Addnl */ 9); + + dnsMsg->Validate(service1, kInAnswerSection, kCheckPtr); + dnsMsg->Validate(service3, kInAnswerSection, kCheckPtr); + dnsMsg->Validate(service1, kInAdditionalSection, kCheckSrv | kCheckTxt); + dnsMsg->Validate(service3, kInAdditionalSection, kCheckSrv | kCheckTxt); + dnsMsg->Validate(host1, kInAdditionalSection); + dnsMsg->Validate(host2, kInAdditionalSection); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + Log("Resend the QU query before 1s unicast interval (when > 1s since last multicast) and validate multicast " + "response"); + + AdvanceTime(750); + + sDnsMessages.Clear(); + SendQuery("_srv._udp.local.", ResourceRecord::kTypePtr, ResourceRecord::kClassInternet | kClassQueryUnicastFlag); + + AdvanceTime(200); + + dnsMsg = sDnsMessages.GetHead(); + VerifyOrQuit(dnsMsg != nullptr); + VerifyOrQuit(dnsMsg->GetNext() == nullptr); + + dnsMsg->ValidateHeader(kMulticastResponse, /* Q */ 0, /* Ans */ 2, /* Auth */ 0, /* Addnl */ 9); + + dnsMsg->Validate(service1, kInAnswerSection, kCheckPtr); + dnsMsg->Validate(service3, kInAnswerSection, kCheckPtr); + dnsMsg->Validate(service1, kInAdditionalSection, kCheckSrv | kCheckTxt); + dnsMsg->Validate(service3, kInAdditionalSection, kCheckSrv | kCheckTxt); + dnsMsg->Validate(host1, kInAdditionalSection); + dnsMsg->Validate(host2, kInAdditionalSection); + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); Log("Browse for sub-type `_s._sub._srv._udp` and validate two answers");