|
1 |
| -/* $OpenBSD: log.c,v 1.62 2024/06/27 22:36:44 djm Exp $ */ |
| 1 | +/* $OpenBSD: log.c,v 1.64 2024/12/07 10:05:36 djm Exp $ */ |
2 | 2 | /*
|
3 | 3 | * Author: Tatu Ylonen <[email protected]>
|
4 | 4 | * Copyright (c) 1995 Tatu Ylonen <[email protected]>, Espoo, Finland
|
|
38 | 38 |
|
39 | 39 | #include <sys/types.h>
|
40 | 40 |
|
| 41 | +#include <errno.h> |
41 | 42 | #include <fcntl.h>
|
| 43 | +#include <limits.h> |
42 | 44 | #include <stdarg.h>
|
43 | 45 | #include <stdio.h>
|
44 | 46 | #include <stdlib.h>
|
45 | 47 | #include <string.h>
|
46 | 48 | #include <syslog.h>
|
| 49 | +#include <time.h> |
47 | 50 | #include <unistd.h>
|
48 |
| -#include <errno.h> |
49 | 51 | #if defined(HAVE_STRNVIS) && defined(HAVE_VIS_H) && !defined(BROKEN_STRNVIS)
|
50 | 52 | # include <vis.h>
|
51 | 53 | #endif
|
@@ -489,3 +491,167 @@ sshlogdirect(LogLevel level, int forced, const char *fmt, ...)
|
489 | 491 | do_log(level, forced, NULL, fmt, args);
|
490 | 492 | va_end(args);
|
491 | 493 | }
|
| 494 | + |
| 495 | + |
| 496 | +/* |
| 497 | + * A simple system for ratelimiting aperiodic events such as logs, without |
| 498 | + * needing to be hooked into a mainloop/timer. A running total of events is |
| 499 | + * maintained and when it exceeds a threshold further events are dropped |
| 500 | + * until the rate falls back below that threshold. |
| 501 | + * |
| 502 | + * To prevent flipping in and out of rate-limiting, there is a hysteresis |
| 503 | + * timer that delays leaving the rate-limited state. |
| 504 | + * |
| 505 | + * While in the rate-limited state, events can be periodically allowed though |
| 506 | + * and the number of dropped events since the last log obtained. |
| 507 | + * |
| 508 | + * XXX a moving average rate of events might be a better approach here rather |
| 509 | + * than linear decay, which can suppress events for a while after large |
| 510 | + * bursts. |
| 511 | + */ |
| 512 | + |
| 513 | +/* #define RATELIMIT_DEBUG 1 */ |
| 514 | + |
| 515 | +#ifdef RATELIMIT_DEBUG |
| 516 | +# define RLDBG(x) do { \ |
| 517 | + printf("%s:%d %s: ", __FILE__, __LINE__, __func__); \ |
| 518 | + printf x; \ |
| 519 | + printf("\n"); \ |
| 520 | + fflush(stdout); \ |
| 521 | + } while (0) |
| 522 | +#else |
| 523 | +# define RLDBG(x) |
| 524 | +#endif |
| 525 | + |
| 526 | +/* set up a ratelimit */ |
| 527 | +void |
| 528 | +log_ratelimit_init(struct log_ratelimit_ctx *rl, u_int threshold, |
| 529 | + u_int max_accum, u_int hysteresis, u_int log_every) |
| 530 | +{ |
| 531 | + memset(rl, 0, sizeof(*rl)); |
| 532 | + rl->threshold = threshold; |
| 533 | + rl->max_accum = max_accum; |
| 534 | + rl->hysteresis = hysteresis; |
| 535 | + rl->log_every = log_every; |
| 536 | + RLDBG(("called: rl=%p thresh=%u max=%u hys=%u log_every=%u", |
| 537 | + rl, rl->threshold, rl->max_accum, rl->hysteresis, rl->log_every)); |
| 538 | +} |
| 539 | + |
| 540 | +/* |
| 541 | + * check whether a log event should be dropped because of rate-limiting. |
| 542 | + * returns non-zero if the event should be dropped. If events_since_last |
| 543 | + * is supplied then, for periodic logs, it will be set to the number of |
| 544 | + * dropped events since the last message. |
| 545 | + */ |
| 546 | +int |
| 547 | +log_ratelimit(struct log_ratelimit_ctx *rl, time_t now, int *active, |
| 548 | + u_int *events_dropped) |
| 549 | +{ |
| 550 | + time_t olast_event; |
| 551 | + |
| 552 | + RLDBG(("called: rl=%p thresh=%u max=%u hys=%u log_every=%u " |
| 553 | + "accum=%u since=%ld since_last=%u", rl, rl->threshold, |
| 554 | + rl->max_accum, rl->hysteresis, |
| 555 | + rl->log_every, rl->accumulated_events, |
| 556 | + rl->last_event == 0 ? -1 : (long)(now - rl->last_event), |
| 557 | + rl->ratelimited_events)); |
| 558 | + |
| 559 | + if (now < 0) |
| 560 | + return 0; |
| 561 | + if (events_dropped != NULL) |
| 562 | + *events_dropped = 0; |
| 563 | + if (active != NULL) |
| 564 | + *active = rl->ratelimit_active; |
| 565 | + |
| 566 | + /* First, decay accumulated events */ |
| 567 | + if (rl->last_event <= 0) |
| 568 | + rl->last_event = now; |
| 569 | + if (now > rl->last_event) { |
| 570 | + uint64_t n = now - rl->last_event; |
| 571 | + |
| 572 | + if (n > UINT_MAX) |
| 573 | + n = UINT_MAX; |
| 574 | + if (rl->accumulated_events < (u_int)n) |
| 575 | + rl->accumulated_events = 0; |
| 576 | + else |
| 577 | + rl->accumulated_events -= (u_int)n; |
| 578 | + RLDBG(("decay: accum=%u", rl->accumulated_events)); |
| 579 | + } |
| 580 | + rl->accumulated_events++; /* add this event */ |
| 581 | + if (rl->accumulated_events > rl->max_accum) |
| 582 | + rl->accumulated_events = rl->max_accum; |
| 583 | + olast_event = rl->last_event; |
| 584 | + rl->last_event = now; |
| 585 | + RLDBG(("check threshold: accum=%u vs thresh=%u", |
| 586 | + rl->accumulated_events, rl->threshold)); |
| 587 | + |
| 588 | + /* Are we under threshold? */ |
| 589 | + if (rl->accumulated_events < rl->threshold) { |
| 590 | + if (!rl->ratelimit_active) |
| 591 | + return 0; |
| 592 | + RLDBG(("under threshold: hys=%u since_hys=%ld since_last=%ld", |
| 593 | + rl->hysteresis, rl->hysteresis_start == 0 ? -1 : |
| 594 | + (long)(now - rl->hysteresis_start), |
| 595 | + olast_event == 0 ? -1 : (long)(now - olast_event))); |
| 596 | + if (rl->hysteresis_start == 0) { |
| 597 | + /* active, but under threshold; hysteresis */ |
| 598 | + if (olast_event + rl->hysteresis < now) { |
| 599 | + /* hysteresis expired before this event */ |
| 600 | + RLDBG(("hysteresis preexpired")); |
| 601 | + goto inactive; |
| 602 | + } |
| 603 | + RLDBG(("start hysteresis")); |
| 604 | + rl->hysteresis_start = now; |
| 605 | + } else if (rl->hysteresis_start + rl->hysteresis < now) { |
| 606 | + /* Hysteresis period expired, transition to inactive */ |
| 607 | + RLDBG(("complete hysteresis")); |
| 608 | + inactive: |
| 609 | + if (events_dropped != NULL) |
| 610 | + *events_dropped = rl->ratelimited_events; |
| 611 | + if (active != NULL) |
| 612 | + *active = 0; |
| 613 | + rl->ratelimit_active = 0; |
| 614 | + rl->ratelimit_start = 0; |
| 615 | + rl->last_log = 0; |
| 616 | + rl->hysteresis_start = 0; |
| 617 | + rl->ratelimited_events = 0; |
| 618 | + return 0; |
| 619 | + } |
| 620 | + /* ratelimiting active, but in hysteresis period */ |
| 621 | + } else if (!rl->ratelimit_active) { |
| 622 | + /* Transition to rate-limiting */ |
| 623 | + RLDBG(("start ratelimit")); |
| 624 | + rl->ratelimit_active = 1; |
| 625 | + rl->ratelimit_start = now; |
| 626 | + rl->last_log = now; |
| 627 | + rl->hysteresis_start = 0; |
| 628 | + rl->ratelimited_events = 1; |
| 629 | + if (active != NULL) |
| 630 | + *active = 1; |
| 631 | + return 1; |
| 632 | + } else if (rl->hysteresis_start != 0) { |
| 633 | + /* active and over threshold; reset hysteresis timer */ |
| 634 | + RLDBG(("clear hysteresis")); |
| 635 | + rl->hysteresis_start = 0; |
| 636 | + } |
| 637 | + |
| 638 | + /* over threshold or in hysteresis period; log periodically */ |
| 639 | + if (active != NULL) |
| 640 | + *active = 1; |
| 641 | + RLDBG(("log_every=%u since_log=%ld", rl->log_every, |
| 642 | + (long)(now - rl->last_log))); |
| 643 | + if (rl->log_every > 0 && now >= rl->last_log + rl->log_every) { |
| 644 | + RLDBG(("periodic: since_last=%u", rl->ratelimited_events)); |
| 645 | + rl->last_log = now; |
| 646 | + if (events_dropped != NULL) { |
| 647 | + *events_dropped = rl->ratelimited_events; |
| 648 | + rl->ratelimited_events = 0; |
| 649 | + } |
| 650 | + return 0; |
| 651 | + } |
| 652 | + |
| 653 | + /* drop event */ |
| 654 | + rl->ratelimited_events++; |
| 655 | + RLDBG(("drop: ratelimited_events=%u", rl->ratelimited_events)); |
| 656 | + return 1; |
| 657 | +} |
0 commit comments