tangled
alpha
login
or
join now
jcs.org
/
openbsd-src
0
fork
atom
jcs's openbsd hax
openbsd
0
fork
atom
overview
issues
pulls
pipelines
add infrastructure for ratelimited logging; feedback/ok dtucker
djm
1 year ago
cc7fda5d
28676b80
+193
-3
2 changed files
expand all
collapse all
unified
split
usr.bin
ssh
log.c
log.h
+168
-2
usr.bin/ssh/log.c
reviewed
···
1
1
-
/* $OpenBSD: log.c,v 1.63 2024/10/14 01:57:50 djm Exp $ */
1
1
+
/* $OpenBSD: log.c,v 1.64 2024/12/07 10:05:36 djm Exp $ */
2
2
/*
3
3
* Author: Tatu Ylonen <ylo@cs.hut.fi>
4
4
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
···
36
36
37
37
#include <sys/types.h>
38
38
39
39
+
#include <errno.h>
39
40
#include <fcntl.h>
41
41
+
#include <limits.h>
40
42
#include <stdarg.h>
41
43
#include <stdio.h>
42
44
#include <stdlib.h>
43
45
#include <string.h>
44
46
#include <syslog.h>
47
47
+
#include <time.h>
45
48
#include <unistd.h>
46
46
-
#include <errno.h>
47
49
#include <vis.h>
48
50
49
51
#include "log.h"
···
447
449
do_log(level, forced, NULL, fmt, args);
448
450
va_end(args);
449
451
}
452
452
+
453
453
+
454
454
+
/*
455
455
+
* A simple system for ratelimiting aperiodic events such as logs, without
456
456
+
* needing to be hooked into a mainloop/timer. A running total of events is
457
457
+
* maintained and when it exceeds a threshold further events are dropped
458
458
+
* until the rate falls back below that threshold.
459
459
+
*
460
460
+
* To prevent flipping in and out of rate-limiting, there is a hysteresis
461
461
+
* timer that delays leaving the rate-limited state.
462
462
+
*
463
463
+
* While in the rate-limited state, events can be periodically allowed though
464
464
+
* and the number of dropped events since the last log obtained.
465
465
+
*
466
466
+
* XXX a moving average rate of events might be a better approach here rather
467
467
+
* than linear decay, which can suppress events for a while after large
468
468
+
* bursts.
469
469
+
*/
470
470
+
471
471
+
/* #define RATELIMIT_DEBUG 1 */
472
472
+
473
473
+
#ifdef RATELIMIT_DEBUG
474
474
+
# define RLDBG(x) do { \
475
475
+
printf("%s:%d %s: ", __FILE__, __LINE__, __func__); \
476
476
+
printf x; \
477
477
+
printf("\n"); \
478
478
+
fflush(stdout); \
479
479
+
} while (0)
480
480
+
#else
481
481
+
# define RLDBG(x)
482
482
+
#endif
483
483
+
484
484
+
/* set up a ratelimit */
485
485
+
void
486
486
+
log_ratelimit_init(struct log_ratelimit_ctx *rl, u_int threshold,
487
487
+
u_int max_accum, u_int hysteresis, u_int log_every)
488
488
+
{
489
489
+
memset(rl, 0, sizeof(*rl));
490
490
+
rl->threshold = threshold;
491
491
+
rl->max_accum = max_accum;
492
492
+
rl->hysteresis = hysteresis;
493
493
+
rl->log_every = log_every;
494
494
+
RLDBG(("called: rl=%p thresh=%u max=%u hys=%u log_every=%u",
495
495
+
rl, rl->threshold, rl->max_accum, rl->hysteresis, rl->log_every));
496
496
+
}
497
497
+
498
498
+
/*
499
499
+
* check whether a log event should be dropped because of rate-limiting.
500
500
+
* returns non-zero if the event should be dropped. If events_since_last
501
501
+
* is supplied then, for periodic logs, it will be set to the number of
502
502
+
* dropped events since the last message.
503
503
+
*/
504
504
+
int
505
505
+
log_ratelimit(struct log_ratelimit_ctx *rl, time_t now, int *active,
506
506
+
u_int *events_dropped)
507
507
+
{
508
508
+
time_t olast_event;
509
509
+
510
510
+
RLDBG(("called: rl=%p thresh=%u max=%u hys=%u log_every=%u "
511
511
+
"accum=%u since=%ld since_last=%u", rl, rl->threshold,
512
512
+
rl->max_accum, rl->hysteresis,
513
513
+
rl->log_every, rl->accumulated_events,
514
514
+
rl->last_event == 0 ? -1 : (long)(now - rl->last_event),
515
515
+
rl->ratelimited_events));
516
516
+
517
517
+
if (now < 0)
518
518
+
return 0;
519
519
+
if (events_dropped != NULL)
520
520
+
*events_dropped = 0;
521
521
+
if (active != NULL)
522
522
+
*active = rl->ratelimit_active;
523
523
+
524
524
+
/* First, decay accumulated events */
525
525
+
if (rl->last_event <= 0)
526
526
+
rl->last_event = now;
527
527
+
if (now > rl->last_event) {
528
528
+
uint64_t n = now - rl->last_event;
529
529
+
530
530
+
if (n > UINT_MAX)
531
531
+
n = UINT_MAX;
532
532
+
if (rl->accumulated_events < (u_int)n)
533
533
+
rl->accumulated_events = 0;
534
534
+
else
535
535
+
rl->accumulated_events -= (u_int)n;
536
536
+
RLDBG(("decay: accum=%u", rl->accumulated_events));
537
537
+
}
538
538
+
rl->accumulated_events++; /* add this event */
539
539
+
if (rl->accumulated_events > rl->max_accum)
540
540
+
rl->accumulated_events = rl->max_accum;
541
541
+
olast_event = rl->last_event;
542
542
+
rl->last_event = now;
543
543
+
RLDBG(("check threshold: accum=%u vs thresh=%u",
544
544
+
rl->accumulated_events, rl->threshold));
545
545
+
546
546
+
/* Are we under threshold? */
547
547
+
if (rl->accumulated_events < rl->threshold) {
548
548
+
if (!rl->ratelimit_active)
549
549
+
return 0;
550
550
+
RLDBG(("under threshold: hys=%u since_hys=%ld since_last=%ld",
551
551
+
rl->hysteresis, rl->hysteresis_start == 0 ? -1 :
552
552
+
(long)(now - rl->hysteresis_start),
553
553
+
olast_event == 0 ? -1 : (long)(now - olast_event)));
554
554
+
if (rl->hysteresis_start == 0) {
555
555
+
/* active, but under threshold; hysteresis */
556
556
+
if (olast_event + rl->hysteresis < now) {
557
557
+
/* hysteresis expired before this event */
558
558
+
RLDBG(("hysteresis preexpired"));
559
559
+
goto inactive;
560
560
+
}
561
561
+
RLDBG(("start hysteresis"));
562
562
+
rl->hysteresis_start = now;
563
563
+
} else if (rl->hysteresis_start + rl->hysteresis < now) {
564
564
+
/* Hysteresis period expired, transition to inactive */
565
565
+
RLDBG(("complete hysteresis"));
566
566
+
inactive:
567
567
+
if (events_dropped != NULL)
568
568
+
*events_dropped = rl->ratelimited_events;
569
569
+
if (active != NULL)
570
570
+
*active = 0;
571
571
+
rl->ratelimit_active = 0;
572
572
+
rl->ratelimit_start = 0;
573
573
+
rl->last_log = 0;
574
574
+
rl->hysteresis_start = 0;
575
575
+
rl->ratelimited_events = 0;
576
576
+
return 0;
577
577
+
}
578
578
+
/* ratelimiting active, but in hysteresis period */
579
579
+
} else if (!rl->ratelimit_active) {
580
580
+
/* Transition to rate-limiting */
581
581
+
RLDBG(("start ratelimit"));
582
582
+
rl->ratelimit_active = 1;
583
583
+
rl->ratelimit_start = now;
584
584
+
rl->last_log = now;
585
585
+
rl->hysteresis_start = 0;
586
586
+
rl->ratelimited_events = 1;
587
587
+
if (active != NULL)
588
588
+
*active = 1;
589
589
+
return 1;
590
590
+
} else if (rl->hysteresis_start != 0) {
591
591
+
/* active and over threshold; reset hysteresis timer */
592
592
+
RLDBG(("clear hysteresis"));
593
593
+
rl->hysteresis_start = 0;
594
594
+
}
595
595
+
596
596
+
/* over threshold or in hysteresis period; log periodically */
597
597
+
if (active != NULL)
598
598
+
*active = 1;
599
599
+
RLDBG(("log_every=%u since_log=%ld", rl->log_every,
600
600
+
(long)(now - rl->last_log)));
601
601
+
if (rl->log_every > 0 && now >= rl->last_log + rl->log_every) {
602
602
+
RLDBG(("periodic: since_last=%u", rl->ratelimited_events));
603
603
+
rl->last_log = now;
604
604
+
if (events_dropped != NULL) {
605
605
+
*events_dropped = rl->ratelimited_events;
606
606
+
rl->ratelimited_events = 0;
607
607
+
}
608
608
+
return 0;
609
609
+
}
610
610
+
611
611
+
/* drop event */
612
612
+
rl->ratelimited_events++;
613
613
+
RLDBG(("drop: ratelimited_events=%u", rl->ratelimited_events));
614
614
+
return 1;
615
615
+
}
+25
-1
usr.bin/ssh/log.h
reviewed
···
1
1
-
/* $OpenBSD: log.h,v 1.34 2024/06/27 22:36:44 djm Exp $ */
1
1
+
/* $OpenBSD: log.h,v 1.35 2024/12/07 10:05:37 djm Exp $ */
2
2
3
3
/*
4
4
* Author: Tatu Ylonen <ylo@cs.hut.fi>
···
77
77
__attribute__((format(printf, 7, 8)));
78
78
void sshlogdirect(LogLevel, int, const char *, ...)
79
79
__attribute__((format(printf, 3, 4)));
80
80
+
81
81
+
struct log_ratelimit_ctx {
82
82
+
/* configuration */
83
83
+
u_int threshold; /* events per second */
84
84
+
u_int max_accum; /* max events to accumulate */
85
85
+
u_int hysteresis; /* seconds */
86
86
+
u_int log_every; /* seconds */
87
87
+
88
88
+
/* state */
89
89
+
time_t last_event;
90
90
+
u_int accumulated_events; /* used for threshold comparisons */
91
91
+
92
92
+
/* state while actively rate-limiting */
93
93
+
int ratelimit_active;
94
94
+
time_t ratelimit_start;
95
95
+
time_t last_log;
96
96
+
time_t hysteresis_start;
97
97
+
u_int ratelimited_events;
98
98
+
};
99
99
+
100
100
+
void log_ratelimit_init(struct log_ratelimit_ctx *rl, u_int threshold,
101
101
+
u_int max_accum, u_int hysteresis, u_int log_every);
102
102
+
int log_ratelimit(struct log_ratelimit_ctx *rl, time_t now, int *active,
103
103
+
u_int *events_dropped);
80
104
81
105
#define do_log2(level, ...) sshlog(__FILE__, __func__, __LINE__, 0, level, NULL, __VA_ARGS__)
82
106
#define debug3(...) sshlog(__FILE__, __func__, __LINE__, 0, SYSLOG_LEVEL_DEBUG3, NULL, __VA_ARGS__)