Skip to content

Commit 6bff231

Browse files
authored
Throttle NodeInfo direct responses (#11104)
1 parent 077c6e7 commit 6bff231

2 files changed

Lines changed: 56 additions & 0 deletions

File tree

src/modules/TrafficManagementModule.cpp

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1092,6 +1092,12 @@ bool TrafficManagementModule::shouldRespondToNodeInfo(const meshtastic_MeshPacke
10921092
if (!sendResponse)
10931093
return true;
10941094

1095+
// Checked here, once a reply would actually go out, so declined requests do not consume the budget.
1096+
if (!directResponseAllowed(getFrom(p), clockMs())) {
1097+
TM_LOG_DEBUG("NodeInfo direct response throttled for 0x%08x", getFrom(p));
1098+
return false;
1099+
}
1100+
10951101
meshtastic_MeshPacket *reply = router->allocForSending();
10961102
if (!reply) {
10971103
TM_LOG_WARN("NodeInfo direct response dropped: no packet buffer");
@@ -1144,6 +1150,43 @@ bool TrafficManagementModule::shouldRespondToNodeInfo(const meshtastic_MeshPacke
11441150
return true;
11451151
}
11461152

1153+
bool TrafficManagementModule::directResponseAllowed(NodeNum requestor, uint32_t nowMs)
1154+
{
1155+
// Reached from the packet path and from runOnce, so the throttle state needs the same lock as the cache.
1156+
concurrency::LockGuard guard(&cacheLock);
1157+
1158+
if (lastDirectResponseMs != 0 && (nowMs - lastDirectResponseMs) < kDirectResponseGlobalMs)
1159+
return false;
1160+
1161+
DirectResponseThrottleEntry *slot = nullptr;
1162+
for (size_t i = 0; i < kDirectResponseTrackedRequestors; i++) {
1163+
if (directResponseSeen[i].requestor == requestor) {
1164+
if ((nowMs - directResponseSeen[i].lastReplyMs) < kDirectResponsePerRequestorMs)
1165+
return false;
1166+
slot = &directResponseSeen[i];
1167+
break;
1168+
}
1169+
}
1170+
1171+
if (slot == nullptr) {
1172+
// Unseen requester: take a free slot, otherwise reuse the least recently used one.
1173+
slot = &directResponseSeen[0];
1174+
for (size_t i = 0; i < kDirectResponseTrackedRequestors; i++) {
1175+
if (directResponseSeen[i].requestor == 0) {
1176+
slot = &directResponseSeen[i];
1177+
break;
1178+
}
1179+
if (directResponseSeen[i].lastReplyMs < slot->lastReplyMs)
1180+
slot = &directResponseSeen[i];
1181+
}
1182+
}
1183+
1184+
slot->requestor = requestor;
1185+
slot->lastReplyMs = nowMs;
1186+
lastDirectResponseMs = nowMs;
1187+
return true;
1188+
}
1189+
11471190
bool TrafficManagementModule::isMinHopsFromRequestor(const meshtastic_MeshPacket *p) const
11481191
{
11491192
int8_t hopsAway = getHopsAway(*p, -1);

src/modules/TrafficManagementModule.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,19 @@ class TrafficManagementModule : public MeshModule, private concurrency::OSThread
293293

294294
bool shouldDropPosition(const meshtastic_MeshPacket *p, const meshtastic_Position *pos, uint32_t nowMs);
295295
bool shouldRespondToNodeInfo(const meshtastic_MeshPacket *p, bool sendResponse);
296+
297+
// Replies go to the requesting packet's unauthenticated `from`, so space them per requester to bound
298+
// what any one node can be made to receive, plus a global floor on airtime.
299+
static constexpr uint32_t kDirectResponsePerRequestorMs = 60'000UL;
300+
static constexpr uint32_t kDirectResponseGlobalMs = 1'000UL;
301+
static constexpr size_t kDirectResponseTrackedRequestors = 8;
302+
struct DirectResponseThrottleEntry {
303+
NodeNum requestor;
304+
uint32_t lastReplyMs;
305+
};
306+
DirectResponseThrottleEntry directResponseSeen[kDirectResponseTrackedRequestors] = {};
307+
uint32_t lastDirectResponseMs = 0;
308+
bool directResponseAllowed(NodeNum requestor, uint32_t nowMs);
296309
bool isMinHopsFromRequestor(const meshtastic_MeshPacket *p) const;
297310
bool isRateLimited(NodeNum from, uint32_t nowMs);
298311
bool shouldDropUnknown(const meshtastic_MeshPacket *p, uint32_t nowMs);

0 commit comments

Comments
 (0)