Skip to content

Commit 54c11b0

Browse files
authored
Merge pull request #53 from sy-c/master
flood protection
2 parents 80be056 + 3b9ca51 commit 54c11b0

File tree

4 files changed

+123
-4
lines changed

4 files changed

+123
-4
lines changed

doc/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,8 @@ achieved on CentOS 7 with e.g. (as root):
266266
During development phase, it can be useful to set mode to "stdout", to allow using the infoLogger interface
267267
and printing messages without infoLoggerD/infoLoggerServer.
268268
When "infoLoggerD" mode is selected and no infoLoggerD connection can be established, the mode falls back to "stdout".
269+
270+
There is a built-in protection in the logging API to avoid message floods. If a client tries to send more than 500 messages in one second, or more than 1000 messages in one minute, further messages are redirected to a local file in /tmp. When the number of messages in this overflow file exceeds 1000 (or if can't be created), further messages are dropped. Normal behavior resumes when the message rate is reduced below 10 messages per minute. Some warning messages are logged by the library itself when this situation occurs.
269271
270272
* infoLoggerD local socket
271273

doc/releaseNotes.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,6 @@ This file describes the main feature changes for each InfoLogger released versio
5656

5757
## v1.3.15 - 06/10/2020
5858
- increased width of pid column in database, for machines with pid counters exceeding the usual 32765 maximum.
59+
60+
## v1.3.16 - 28/10/2020
61+
- added message flood protection.

src/InfoLogger.cxx

Lines changed: 115 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
#include "InfoLogger/InfoLogger.hxx"
1717
#include "InfoLogger/InfoLogger.h"
18+
#include "InfoLogger/InfoLoggerMacros.hxx"
1819

1920
#include "infoLoggerMessage.h"
2021
#include "InfoLoggerClient.h"
@@ -184,6 +185,8 @@ class InfoLogger::Impl
184185
currentStreamMessage.clear();
185186
currentStreamOptions = undefinedMessageOption;
186187

188+
floodReset();
189+
187190
if (infoLog_proto_init()) {
188191
throw __LINE__;
189192
}
@@ -233,11 +236,13 @@ class InfoLogger::Impl
233236
if (client != nullptr) {
234237
delete client;
235238
}
239+
floodReset();
236240
}
237241

238242
int pushMessage(InfoLogger::Severity severity, const char* msg); // todo: add extra "configurable" fields, e.g. line, etc
239243

240-
int pushMessage(const InfoLoggerMessageOption& options, const InfoLoggerContext& context, const char* msg);
244+
// the noFlood parameter allows to send message even if in flood mode
245+
int pushMessage(const InfoLoggerMessageOption& options, const InfoLoggerContext& context, const char* msg, bool noFlood=0);
241246

242247
friend class InfoLogger; //< give access to this data from InfoLogger class
243248

@@ -293,6 +298,40 @@ class InfoLogger::Impl
293298

294299
bool filterDiscardDebug = false; // when set, messages with debug severity are dropped
295300
int filterDiscardLevel = InfoLogger::undefinedMessageOption.level; // when set, messages with higher level (>=) are dropped
301+
302+
// message flood prevention
303+
// constants
304+
const unsigned int flood_maxmsg_sec=500; // maximum messages in one second to trigger flood
305+
const unsigned int flood_maxmsg_min=1000; // maximum messages in one minute to trigger flood
306+
const unsigned int flood_maxmsg_file=1000; // maximum number of messages in overflow log file
307+
const unsigned int flood_maxmsg_reset=10; // maximum number of messages in one minute to reset
308+
const std::string floodFile_dir="/tmp"; // path where to create overflow files
309+
// runtime vars
310+
unsigned int floodStat_msgs_sec; // number of messages in last second
311+
unsigned int floodStat_msgs_min; // number of messages since minute interval begin
312+
unsigned int floodStat_msgs_lastmin; // number of messages in last minute
313+
double floodStat_time_lastsec; // time counter for last second
314+
double floodStat_time_lastmin; // time counter for last minute
315+
int floodMode; // current flood status = 0: no flood, 1: messages redirected to local file, 2: messages dropped
316+
FILE *floodFile_fp=nullptr; // handle to current flood file to store messages in excess
317+
unsigned int floodFile_msg; // number of messages recorded to flood file
318+
unsigned int floodFile_msg_drop; // number of messages dropped
319+
320+
void floodReset() {
321+
floodStat_msgs_sec=0;
322+
floodStat_msgs_min=0;
323+
floodStat_msgs_lastmin=flood_maxmsg_reset;
324+
floodStat_time_lastsec=time(NULL);
325+
floodStat_time_lastmin=time(NULL);
326+
floodMode=0;
327+
if (floodFile_fp!=nullptr) {
328+
fclose(floodFile_fp);
329+
}
330+
floodFile_fp=nullptr;
331+
floodFile_msg=0;
332+
floodFile_msg_drop=0;
333+
}
334+
296335
};
297336

298337
void InfoLogger::Impl::refreshDefaultMsg()
@@ -353,7 +392,7 @@ int InfoLogger::Impl::pushMessage(InfoLogger::Severity severity, const char* mes
353392
return pushMessage(options, currentContext, messageBody);
354393
}
355394

356-
int InfoLogger::Impl::pushMessage(const InfoLoggerMessageOption& options, const InfoLoggerContext& context, const char* messageBody)
395+
int InfoLogger::Impl::pushMessage(const InfoLoggerMessageOption& options, const InfoLoggerContext& context, const char* messageBody, bool noFlood)
357396
{
358397
// check if message passes local filter criteria, if any
359398
if (filterDiscardDebug && (options.severity == InfoLogger::Severity::Debug)) {
@@ -368,8 +407,9 @@ int InfoLogger::Impl::pushMessage(const InfoLoggerMessageOption& options, const
368407
infoLog_msg_t msg = defaultMsg;
369408

370409
struct timeval tv;
410+
double now = 0;
371411
if (gettimeofday(&tv, NULL) == 0) {
372-
double now = (double)tv.tv_sec + (double)tv.tv_usec / 1000000;
412+
now = (double)tv.tv_sec + (double)tv.tv_usec / 1000000;
373413
InfoLoggerMessageHelperSetValue(msg, msgHelper.ix_timestamp, Double, now);
374414
}
375415

@@ -435,7 +475,78 @@ int InfoLogger::Impl::pushMessage(const InfoLoggerMessageOption& options, const
435475
InfoLoggerMessageHelperSetValue(msg, msgHelper.ix_username, String, context.userName.c_str());
436476
}
437477

438-
// printf("%s\n",buffer);
478+
// update message statistics */
479+
if (now-floodStat_time_lastsec>1) {
480+
floodStat_time_lastsec=now;
481+
floodStat_msgs_sec=1;
482+
} else {
483+
floodStat_msgs_sec++;
484+
}
485+
if (now-floodStat_time_lastmin>60) {
486+
floodStat_time_lastmin=now;
487+
floodStat_msgs_lastmin=floodStat_msgs_min;
488+
floodStat_msgs_min=1;
489+
} else {
490+
floodStat_msgs_min++;
491+
}
492+
493+
// message flood prevention
494+
if (!noFlood) {
495+
switch (floodMode) {
496+
case 0:
497+
if ((floodStat_msgs_sec>flood_maxmsg_sec)||(floodStat_msgs_min>flood_maxmsg_min)) {
498+
floodFile_msg=0;
499+
floodFile_msg_drop=0;
500+
std::string floodFile_path = floodFile_dir + "/infoLogger.flood-" + std::to_string(context.processId) + "@" + context.hostName + "-" + std::to_string((int)now);
501+
floodFile_fp=fopen(floodFile_path.c_str(),"w");
502+
if (floodFile_fp==NULL) {
503+
floodMode=2;
504+
std::string msg = "Message flood detected - further messages will be dropped (failed to create flood file " + floodFile_path + ")";
505+
pushMessage(LogWarningSupport_(1101), currentContext, msg.c_str(),1);
506+
goto case2;
507+
} else {
508+
floodMode=1;
509+
std::string msg = "Message flood detected - further messages will be stored locally in " + floodFile_path;
510+
pushMessage(LogWarningSupport_(1101), currentContext, msg.c_str(),1);
511+
}
512+
} else {
513+
break;
514+
}
515+
case 1:
516+
if (floodStat_msgs_lastmin<flood_maxmsg_reset) {
517+
// reset flood mode
518+
break;
519+
}
520+
if (floodFile_msg>=flood_maxmsg_file) {
521+
if (floodFile_fp!=nullptr) {
522+
fclose(floodFile_fp);
523+
floodFile_fp=nullptr;
524+
}
525+
pushMessage(LogWarningSupport_(1101), currentContext, "Message flood - maximum entries in local file exceeded, further messages will be dropped",1);
526+
floodMode=2;
527+
} else {
528+
// log to flood file
529+
fprintf(floodFile_fp,"%f\t%c\t%s\t%s\n",now,(char)options.severity,context.facility.c_str(),messageBody);
530+
fflush(floodFile_fp);
531+
floodFile_msg++;
532+
return 0;
533+
}
534+
case 2:
535+
case2:
536+
if (floodStat_msgs_lastmin<flood_maxmsg_reset) {
537+
// reset flood mode
538+
break;
539+
}
540+
// drop message
541+
floodFile_msg_drop++;
542+
return 0;
543+
}
544+
if (floodMode) {
545+
std::string msg = "Message flood - resuming normal operation: " + std::to_string(floodFile_msg) + " messages stored locally, " + std::to_string(floodFile_msg_drop) + " messages dropped";
546+
pushMessage(LogWarningSupport_(1102), currentContext, msg.c_str(),1);
547+
floodReset();
548+
}
549+
}
439550

440551
if (client != nullptr) {
441552
char buffer[LOG_MAX_SIZE];

src/InfoLoggerErrorCodes.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ typedef struct {
5252
// Static array defining available error codes
5353
// Terminated by a null entry
5454
static constexpr ErrorCode errorCodes[]={
55+
// infoLogger error codes
56+
{ 1101, "Message flood detected", nullptr},
57+
{ 1102, "End of message flood", nullptr},
5558
{ 0, nullptr, nullptr}
5659
};
5760

0 commit comments

Comments
 (0)