Throttle NodeInfo direct responses (#11104)

This commit is contained in:
Thomas Göttgens
2026-07-21 09:12:02 +02:00
committed by GitHub
co-authored by GitHub
parent 077c6e72f4
commit 6bff231d53
2 changed files with 56 additions and 0 deletions
+43
View File
@@ -1092,6 +1092,12 @@ bool TrafficManagementModule::shouldRespondToNodeInfo(const meshtastic_MeshPacke
if (!sendResponse)
return true;
// Checked here, once a reply would actually go out, so declined requests do not consume the budget.
if (!directResponseAllowed(getFrom(p), clockMs())) {
TM_LOG_DEBUG("NodeInfo direct response throttled for 0x%08x", getFrom(p));
return false;
}
meshtastic_MeshPacket *reply = router->allocForSending();
if (!reply) {
TM_LOG_WARN("NodeInfo direct response dropped: no packet buffer");
@@ -1144,6 +1150,43 @@ bool TrafficManagementModule::shouldRespondToNodeInfo(const meshtastic_MeshPacke
return true;
}
bool TrafficManagementModule::directResponseAllowed(NodeNum requestor, uint32_t nowMs)
{
// Reached from the packet path and from runOnce, so the throttle state needs the same lock as the cache.
concurrency::LockGuard guard(&cacheLock);
if (lastDirectResponseMs != 0 && (nowMs - lastDirectResponseMs) < kDirectResponseGlobalMs)
return false;
DirectResponseThrottleEntry *slot = nullptr;
for (size_t i = 0; i < kDirectResponseTrackedRequestors; i++) {
if (directResponseSeen[i].requestor == requestor) {
if ((nowMs - directResponseSeen[i].lastReplyMs) < kDirectResponsePerRequestorMs)
return false;
slot = &directResponseSeen[i];
break;
}
}
if (slot == nullptr) {
// Unseen requester: take a free slot, otherwise reuse the least recently used one.
slot = &directResponseSeen[0];
for (size_t i = 0; i < kDirectResponseTrackedRequestors; i++) {
if (directResponseSeen[i].requestor == 0) {
slot = &directResponseSeen[i];
break;
}
if (directResponseSeen[i].lastReplyMs < slot->lastReplyMs)
slot = &directResponseSeen[i];
}
}
slot->requestor = requestor;
slot->lastReplyMs = nowMs;
lastDirectResponseMs = nowMs;
return true;
}
bool TrafficManagementModule::isMinHopsFromRequestor(const meshtastic_MeshPacket *p) const
{
int8_t hopsAway = getHopsAway(*p, -1);
+13
View File
@@ -293,6 +293,19 @@ class TrafficManagementModule : public MeshModule, private concurrency::OSThread
bool shouldDropPosition(const meshtastic_MeshPacket *p, const meshtastic_Position *pos, uint32_t nowMs);
bool shouldRespondToNodeInfo(const meshtastic_MeshPacket *p, bool sendResponse);
// Replies go to the requesting packet's unauthenticated `from`, so space them per requester to bound
// what any one node can be made to receive, plus a global floor on airtime.
static constexpr uint32_t kDirectResponsePerRequestorMs = 60'000UL;
static constexpr uint32_t kDirectResponseGlobalMs = 1'000UL;
static constexpr size_t kDirectResponseTrackedRequestors = 8;
struct DirectResponseThrottleEntry {
NodeNum requestor;
uint32_t lastReplyMs;
};
DirectResponseThrottleEntry directResponseSeen[kDirectResponseTrackedRequestors] = {};
uint32_t lastDirectResponseMs = 0;
bool directResponseAllowed(NodeNum requestor, uint32_t nowMs);
bool isMinHopsFromRequestor(const meshtastic_MeshPacket *p) const;
bool isRateLimited(NodeNum from, uint32_t nowMs);
bool shouldDropUnknown(const meshtastic_MeshPacket *p, uint32_t nowMs);