mirror of
https://github.com/espressif/openthread.git
synced 2026-11-01 03:56:36 +00:00
[mdns] rate-limit responses to repeated QU queries (#13675)
Currently, `RecordInfo::ScheduleAnswer()` enforces a 1-second minimum interval (`kMinIntervalBetweenMulticast`) between multicast responses for the same record, but unconditionally sets `mUnicastAnswerPending = true` for every query with the `QU` (unicast-response) bit set. If a host on the LAN repeatedly sends `QU` queries for a registered name or service, the mDNS responder generates and transmits a unicast response packet for every incoming query without rate limiting, causing excessive packet and CPU load. This commit adds per-record rate limiting for responses to non-probe `QU` queries in `RecordInfo`: - Track `mLastUnicastTime` and `mIsLastUnicastValid` in `RecordInfo` with the same aging mechanism (`kLastUnicastTimeAge`) used for `mLastMulticastTime`. - In `RecordInfo::ScheduleAnswer()`, enforce a 1-second minimum interval (`kMinIntervalBetweenUnicast = 1000` ms) between unicast responses for the same record when answering non-probe `QU` queries. If the unicast rate limit is reached, fall through to schedule a multicast response instead (which is subject to multicast rate limiting) so other queriers on the network can still receive the answer. - Update `TestQuery()` in `test_mdns.cpp` to verify `QU` rate limiting and multicast fallback behavior.
This commit is contained in:
+46
-2
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user