mirror of
https://github.com/openbsd/src.git
synced 2024-12-22 07:27:59 -08:00
add infrastructure for ratelimited logging; feedback/ok dtucker
This commit is contained in:
parent
28676b808b
commit
cc7fda5dc1
@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: log.c,v 1.63 2024/10/14 01:57:50 djm Exp $ */
|
||||
/* $OpenBSD: log.c,v 1.64 2024/12/07 10:05:36 djm Exp $ */
|
||||
/*
|
||||
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
||||
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
||||
@ -36,14 +36,16 @@
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <syslog.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <vis.h>
|
||||
|
||||
#include "log.h"
|
||||
@ -447,3 +449,167 @@ sshlogdirect(LogLevel level, int forced, const char *fmt, ...)
|
||||
do_log(level, forced, NULL, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* A simple system for ratelimiting aperiodic events such as logs, without
|
||||
* needing to be hooked into a mainloop/timer. A running total of events is
|
||||
* maintained and when it exceeds a threshold further events are dropped
|
||||
* until the rate falls back below that threshold.
|
||||
*
|
||||
* To prevent flipping in and out of rate-limiting, there is a hysteresis
|
||||
* timer that delays leaving the rate-limited state.
|
||||
*
|
||||
* While in the rate-limited state, events can be periodically allowed though
|
||||
* and the number of dropped events since the last log obtained.
|
||||
*
|
||||
* XXX a moving average rate of events might be a better approach here rather
|
||||
* than linear decay, which can suppress events for a while after large
|
||||
* bursts.
|
||||
*/
|
||||
|
||||
/* #define RATELIMIT_DEBUG 1 */
|
||||
|
||||
#ifdef RATELIMIT_DEBUG
|
||||
# define RLDBG(x) do { \
|
||||
printf("%s:%d %s: ", __FILE__, __LINE__, __func__); \
|
||||
printf x; \
|
||||
printf("\n"); \
|
||||
fflush(stdout); \
|
||||
} while (0)
|
||||
#else
|
||||
# define RLDBG(x)
|
||||
#endif
|
||||
|
||||
/* set up a ratelimit */
|
||||
void
|
||||
log_ratelimit_init(struct log_ratelimit_ctx *rl, u_int threshold,
|
||||
u_int max_accum, u_int hysteresis, u_int log_every)
|
||||
{
|
||||
memset(rl, 0, sizeof(*rl));
|
||||
rl->threshold = threshold;
|
||||
rl->max_accum = max_accum;
|
||||
rl->hysteresis = hysteresis;
|
||||
rl->log_every = log_every;
|
||||
RLDBG(("called: rl=%p thresh=%u max=%u hys=%u log_every=%u",
|
||||
rl, rl->threshold, rl->max_accum, rl->hysteresis, rl->log_every));
|
||||
}
|
||||
|
||||
/*
|
||||
* check whether a log event should be dropped because of rate-limiting.
|
||||
* returns non-zero if the event should be dropped. If events_since_last
|
||||
* is supplied then, for periodic logs, it will be set to the number of
|
||||
* dropped events since the last message.
|
||||
*/
|
||||
int
|
||||
log_ratelimit(struct log_ratelimit_ctx *rl, time_t now, int *active,
|
||||
u_int *events_dropped)
|
||||
{
|
||||
time_t olast_event;
|
||||
|
||||
RLDBG(("called: rl=%p thresh=%u max=%u hys=%u log_every=%u "
|
||||
"accum=%u since=%ld since_last=%u", rl, rl->threshold,
|
||||
rl->max_accum, rl->hysteresis,
|
||||
rl->log_every, rl->accumulated_events,
|
||||
rl->last_event == 0 ? -1 : (long)(now - rl->last_event),
|
||||
rl->ratelimited_events));
|
||||
|
||||
if (now < 0)
|
||||
return 0;
|
||||
if (events_dropped != NULL)
|
||||
*events_dropped = 0;
|
||||
if (active != NULL)
|
||||
*active = rl->ratelimit_active;
|
||||
|
||||
/* First, decay accumulated events */
|
||||
if (rl->last_event <= 0)
|
||||
rl->last_event = now;
|
||||
if (now > rl->last_event) {
|
||||
uint64_t n = now - rl->last_event;
|
||||
|
||||
if (n > UINT_MAX)
|
||||
n = UINT_MAX;
|
||||
if (rl->accumulated_events < (u_int)n)
|
||||
rl->accumulated_events = 0;
|
||||
else
|
||||
rl->accumulated_events -= (u_int)n;
|
||||
RLDBG(("decay: accum=%u", rl->accumulated_events));
|
||||
}
|
||||
rl->accumulated_events++; /* add this event */
|
||||
if (rl->accumulated_events > rl->max_accum)
|
||||
rl->accumulated_events = rl->max_accum;
|
||||
olast_event = rl->last_event;
|
||||
rl->last_event = now;
|
||||
RLDBG(("check threshold: accum=%u vs thresh=%u",
|
||||
rl->accumulated_events, rl->threshold));
|
||||
|
||||
/* Are we under threshold? */
|
||||
if (rl->accumulated_events < rl->threshold) {
|
||||
if (!rl->ratelimit_active)
|
||||
return 0;
|
||||
RLDBG(("under threshold: hys=%u since_hys=%ld since_last=%ld",
|
||||
rl->hysteresis, rl->hysteresis_start == 0 ? -1 :
|
||||
(long)(now - rl->hysteresis_start),
|
||||
olast_event == 0 ? -1 : (long)(now - olast_event)));
|
||||
if (rl->hysteresis_start == 0) {
|
||||
/* active, but under threshold; hysteresis */
|
||||
if (olast_event + rl->hysteresis < now) {
|
||||
/* hysteresis expired before this event */
|
||||
RLDBG(("hysteresis preexpired"));
|
||||
goto inactive;
|
||||
}
|
||||
RLDBG(("start hysteresis"));
|
||||
rl->hysteresis_start = now;
|
||||
} else if (rl->hysteresis_start + rl->hysteresis < now) {
|
||||
/* Hysteresis period expired, transition to inactive */
|
||||
RLDBG(("complete hysteresis"));
|
||||
inactive:
|
||||
if (events_dropped != NULL)
|
||||
*events_dropped = rl->ratelimited_events;
|
||||
if (active != NULL)
|
||||
*active = 0;
|
||||
rl->ratelimit_active = 0;
|
||||
rl->ratelimit_start = 0;
|
||||
rl->last_log = 0;
|
||||
rl->hysteresis_start = 0;
|
||||
rl->ratelimited_events = 0;
|
||||
return 0;
|
||||
}
|
||||
/* ratelimiting active, but in hysteresis period */
|
||||
} else if (!rl->ratelimit_active) {
|
||||
/* Transition to rate-limiting */
|
||||
RLDBG(("start ratelimit"));
|
||||
rl->ratelimit_active = 1;
|
||||
rl->ratelimit_start = now;
|
||||
rl->last_log = now;
|
||||
rl->hysteresis_start = 0;
|
||||
rl->ratelimited_events = 1;
|
||||
if (active != NULL)
|
||||
*active = 1;
|
||||
return 1;
|
||||
} else if (rl->hysteresis_start != 0) {
|
||||
/* active and over threshold; reset hysteresis timer */
|
||||
RLDBG(("clear hysteresis"));
|
||||
rl->hysteresis_start = 0;
|
||||
}
|
||||
|
||||
/* over threshold or in hysteresis period; log periodically */
|
||||
if (active != NULL)
|
||||
*active = 1;
|
||||
RLDBG(("log_every=%u since_log=%ld", rl->log_every,
|
||||
(long)(now - rl->last_log)));
|
||||
if (rl->log_every > 0 && now >= rl->last_log + rl->log_every) {
|
||||
RLDBG(("periodic: since_last=%u", rl->ratelimited_events));
|
||||
rl->last_log = now;
|
||||
if (events_dropped != NULL) {
|
||||
*events_dropped = rl->ratelimited_events;
|
||||
rl->ratelimited_events = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* drop event */
|
||||
rl->ratelimited_events++;
|
||||
RLDBG(("drop: ratelimited_events=%u", rl->ratelimited_events));
|
||||
return 1;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: log.h,v 1.34 2024/06/27 22:36:44 djm Exp $ */
|
||||
/* $OpenBSD: log.h,v 1.35 2024/12/07 10:05:37 djm Exp $ */
|
||||
|
||||
/*
|
||||
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
||||
@ -78,6 +78,30 @@ void sshfatal(const char *, const char *, int, int,
|
||||
void sshlogdirect(LogLevel, int, const char *, ...)
|
||||
__attribute__((format(printf, 3, 4)));
|
||||
|
||||
struct log_ratelimit_ctx {
|
||||
/* configuration */
|
||||
u_int threshold; /* events per second */
|
||||
u_int max_accum; /* max events to accumulate */
|
||||
u_int hysteresis; /* seconds */
|
||||
u_int log_every; /* seconds */
|
||||
|
||||
/* state */
|
||||
time_t last_event;
|
||||
u_int accumulated_events; /* used for threshold comparisons */
|
||||
|
||||
/* state while actively rate-limiting */
|
||||
int ratelimit_active;
|
||||
time_t ratelimit_start;
|
||||
time_t last_log;
|
||||
time_t hysteresis_start;
|
||||
u_int ratelimited_events;
|
||||
};
|
||||
|
||||
void log_ratelimit_init(struct log_ratelimit_ctx *rl, u_int threshold,
|
||||
u_int max_accum, u_int hysteresis, u_int log_every);
|
||||
int log_ratelimit(struct log_ratelimit_ctx *rl, time_t now, int *active,
|
||||
u_int *events_dropped);
|
||||
|
||||
#define do_log2(level, ...) sshlog(__FILE__, __func__, __LINE__, 0, level, NULL, __VA_ARGS__)
|
||||
#define debug3(...) sshlog(__FILE__, __func__, __LINE__, 0, SYSLOG_LEVEL_DEBUG3, NULL, __VA_ARGS__)
|
||||
#define debug2(...) sshlog(__FILE__, __func__, __LINE__, 0, SYSLOG_LEVEL_DEBUG2, NULL, __VA_ARGS__)
|
||||
|
Loading…
Reference in New Issue
Block a user