Coverage Report

Created: 2025-08-26 06:58

/src/bind9/lib/isc/ratelimiter.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
3
 *
4
 * SPDX-License-Identifier: MPL-2.0
5
 *
6
 * This Source Code Form is subject to the terms of the Mozilla Public
7
 * License, v. 2.0. If a copy of the MPL was not distributed with this
8
 * file, you can obtain one at https://mozilla.org/MPL/2.0/.
9
 *
10
 * See the COPYRIGHT file distributed with this work for additional
11
 * information regarding copyright ownership.
12
 */
13
14
/*! \file */
15
16
#include <inttypes.h>
17
#include <stdbool.h>
18
19
#include <isc/async.h>
20
#include <isc/loop.h>
21
#include <isc/magic.h>
22
#include <isc/mem.h>
23
#include <isc/ratelimiter.h>
24
#include <isc/refcount.h>
25
#include <isc/time.h>
26
#include <isc/timer.h>
27
#include <isc/util.h>
28
29
typedef enum {
30
  isc_ratelimiter_ratelimited = 0,
31
  isc_ratelimiter_idle = 1,
32
  isc_ratelimiter_shuttingdown = 2
33
} isc_ratelimiter_state_t;
34
35
0
#define RATELIMITER_MAGIC     ISC_MAGIC('R', 't', 'L', 'm')
36
#define VALID_RATELIMITER(rl) ISC_MAGIC_VALID(rl, RATELIMITER_MAGIC)
37
38
struct isc_ratelimiter {
39
  int magic;
40
  isc_mem_t *mctx;
41
  isc_loop_t *loop;
42
  isc_refcount_t references;
43
  isc_mutex_t lock;
44
  isc_timer_t *timer;
45
  isc_interval_t interval;
46
  uint32_t pertic;
47
  bool pushpop;
48
  isc_ratelimiter_state_t state;
49
  ISC_LIST(isc_rlevent_t) pending;
50
};
51
52
static void
53
isc__ratelimiter_tick(void *arg);
54
55
static void
56
isc__ratelimiter_start(void *arg);
57
58
static void
59
isc__ratelimiter_doshutdown(void *arg);
60
61
void
62
0
isc_ratelimiter_create(isc_loop_t *loop, isc_ratelimiter_t **rlp) {
63
0
  isc_ratelimiter_t *rl = NULL;
64
0
  isc_mem_t *mctx;
65
66
0
  REQUIRE(loop != NULL);
67
0
  REQUIRE(rlp != NULL && *rlp == NULL);
68
69
0
  mctx = isc_loop_getmctx(loop);
70
71
0
  rl = isc_mem_get(mctx, sizeof(*rl));
72
0
  *rl = (isc_ratelimiter_t){
73
0
    .pertic = 1,
74
0
    .state = isc_ratelimiter_idle,
75
0
    .magic = RATELIMITER_MAGIC,
76
0
  };
77
78
0
  isc_mem_attach(mctx, &rl->mctx);
79
0
  isc_loop_attach(loop, &rl->loop);
80
0
  isc_refcount_init(&rl->references, 1);
81
0
  isc_interval_set(&rl->interval, 0, 0);
82
0
  ISC_LIST_INIT(rl->pending);
83
84
0
  isc_timer_create(rl->loop, isc__ratelimiter_tick, rl, &rl->timer);
85
86
0
  isc_mutex_init(&rl->lock);
87
88
0
  *rlp = rl;
89
0
}
90
91
void
92
isc_ratelimiter_setinterval(isc_ratelimiter_t *restrict rl,
93
0
          const isc_interval_t *const interval) {
94
0
  REQUIRE(VALID_RATELIMITER(rl));
95
0
  REQUIRE(interval != NULL);
96
97
0
  LOCK(&rl->lock);
98
0
  rl->interval = *interval;
99
  /* The interval will be adjusted on the next tick */
100
0
  UNLOCK(&rl->lock);
101
0
}
102
103
void
104
isc_ratelimiter_setpertic(isc_ratelimiter_t *restrict rl,
105
0
        const uint32_t pertic) {
106
0
  REQUIRE(VALID_RATELIMITER(rl));
107
0
  REQUIRE(pertic > 0);
108
109
0
  LOCK(&rl->lock);
110
0
  rl->pertic = pertic;
111
0
  UNLOCK(&rl->lock);
112
0
}
113
114
void
115
0
isc_ratelimiter_setpushpop(isc_ratelimiter_t *restrict rl, const bool pushpop) {
116
0
  REQUIRE(VALID_RATELIMITER(rl));
117
118
0
  LOCK(&rl->lock);
119
0
  rl->pushpop = pushpop;
120
0
  UNLOCK(&rl->lock);
121
0
}
122
123
static void
124
0
isc__ratelimiter_start(void *arg) {
125
0
  isc_ratelimiter_t *rl = arg;
126
0
  isc_interval_t interval;
127
128
0
  REQUIRE(VALID_RATELIMITER(rl));
129
130
0
  LOCK(&rl->lock);
131
0
  switch (rl->state) {
132
0
  case isc_ratelimiter_ratelimited:
133
    /* The first tick happens immediately */
134
0
    isc_interval_set(&interval, 0, 0);
135
0
    isc_timer_start(rl->timer, isc_timertype_once, &interval);
136
0
    break;
137
0
  case isc_ratelimiter_shuttingdown:
138
    /* The ratelimiter is shutting down */
139
0
    break;
140
0
  case isc_ratelimiter_idle:
141
    /*
142
     * This could happen if we are changing the interval on the
143
     * ratelimiter, but all the events were processed and the timer
144
     * was stopped before the new interval could be applied.
145
     */
146
0
    break;
147
0
  default:
148
0
    UNREACHABLE();
149
0
  }
150
0
  UNLOCK(&rl->lock);
151
0
  isc_ratelimiter_detach(&rl);
152
0
}
153
154
isc_result_t
155
isc_ratelimiter_enqueue(isc_ratelimiter_t *restrict rl,
156
      isc_loop_t *restrict loop, isc_job_cb cb, void *arg,
157
0
      isc_rlevent_t **rlep) {
158
0
  isc_result_t result = ISC_R_SUCCESS;
159
0
  isc_rlevent_t *rle = NULL;
160
161
0
  REQUIRE(VALID_RATELIMITER(rl));
162
0
  REQUIRE(loop != NULL);
163
0
  REQUIRE(rlep != NULL && *rlep == NULL);
164
165
0
  LOCK(&rl->lock);
166
0
  switch (rl->state) {
167
0
  case isc_ratelimiter_shuttingdown:
168
0
    result = ISC_R_SHUTTINGDOWN;
169
0
    break;
170
0
  case isc_ratelimiter_idle:
171
    /* Start the ratelimiter */
172
0
    isc_ratelimiter_ref(rl);
173
0
    isc_async_run(rl->loop, isc__ratelimiter_start, rl);
174
0
    rl->state = isc_ratelimiter_ratelimited;
175
0
    FALLTHROUGH;
176
0
  case isc_ratelimiter_ratelimited:
177
0
    rle = isc_mem_get(isc_loop_getmctx(loop), sizeof(*rle));
178
0
    *rle = (isc_rlevent_t){
179
0
      .cb = cb,
180
0
      .arg = arg,
181
0
      .link = ISC_LINK_INITIALIZER,
182
0
    };
183
0
    isc_loop_attach(loop, &rle->loop);
184
0
    isc_ratelimiter_attach(rl, &rle->rl);
185
186
0
    if (rl->pushpop) {
187
0
      ISC_LIST_PREPEND(rl->pending, rle, link);
188
0
    } else {
189
0
      ISC_LIST_APPEND(rl->pending, rle, link);
190
0
    }
191
0
    *rlep = rle;
192
0
    break;
193
0
  default:
194
0
    UNREACHABLE();
195
0
  }
196
0
  UNLOCK(&rl->lock);
197
0
  return result;
198
0
}
199
200
isc_result_t
201
0
isc_ratelimiter_dequeue(isc_ratelimiter_t *restrict rl, isc_rlevent_t **rlep) {
202
0
  isc_result_t result = ISC_R_SUCCESS;
203
204
0
  REQUIRE(rl != NULL);
205
0
  REQUIRE(rlep != NULL);
206
207
0
  LOCK(&rl->lock);
208
0
  if (ISC_LINK_LINKED(*rlep, link)) {
209
0
    ISC_LIST_UNLINK(rl->pending, *rlep, link);
210
0
    isc_rlevent_free(rlep);
211
0
  } else {
212
0
    result = ISC_R_NOTFOUND;
213
0
  }
214
0
  UNLOCK(&rl->lock);
215
0
  return result;
216
0
}
217
218
static void
219
0
isc__ratelimiter_tick(void *arg) {
220
0
  isc_ratelimiter_t *rl = (isc_ratelimiter_t *)arg;
221
0
  uint32_t pertic;
222
0
  ISC_LIST(isc_rlevent_t) pending;
223
224
0
  REQUIRE(VALID_RATELIMITER(rl));
225
226
0
  ISC_LIST_INIT(pending);
227
228
0
  LOCK(&rl->lock);
229
230
0
  REQUIRE(rl->timer != NULL);
231
232
0
  if (rl->state == isc_ratelimiter_shuttingdown) {
233
0
    INSIST(ISC_LIST_EMPTY(rl->pending));
234
0
    goto unlock;
235
0
  }
236
237
0
  pertic = rl->pertic;
238
0
  while (pertic != 0) {
239
0
    isc_rlevent_t *rle = ISC_LIST_HEAD(rl->pending);
240
0
    if (rle != NULL) {
241
      /* There is work to do.  Let's do it after unlocking. */
242
0
      ISC_LIST_UNLINK(rl->pending, rle, link);
243
0
      ISC_LIST_APPEND(pending, rle, link);
244
0
    } else {
245
      /*
246
       * We processed all the scheduled work, but there's a
247
       * room for at least one more event (we haven't consumed
248
       * all of the "pertick"), so we can stop the ratelimiter
249
       * now, and don't worry about isc_ratelimiter_enqueue()
250
       * sending an extra event immediately.
251
       */
252
0
      rl->state = isc_ratelimiter_idle;
253
0
      break;
254
0
    }
255
0
    pertic--;
256
0
  }
257
258
0
  if (rl->state != isc_ratelimiter_idle) {
259
    /* Reschedule the timer */
260
0
    isc_timer_start(rl->timer, isc_timertype_once, &rl->interval);
261
0
  }
262
0
unlock:
263
0
  UNLOCK(&rl->lock);
264
265
0
  ISC_LIST_FOREACH(pending, rle, link) {
266
0
    ISC_LIST_UNLINK(pending, rle, link);
267
0
    isc_async_run(rle->loop, rle->cb, rle->arg);
268
0
  }
269
0
}
270
271
void
272
0
isc__ratelimiter_doshutdown(void *arg) {
273
0
  isc_ratelimiter_t *rl = arg;
274
275
0
  REQUIRE(VALID_RATELIMITER(rl));
276
277
0
  LOCK(&rl->lock);
278
0
  INSIST(rl->state == isc_ratelimiter_shuttingdown);
279
0
  INSIST(ISC_LIST_EMPTY(rl->pending));
280
281
0
  isc_timer_stop(rl->timer);
282
0
  isc_timer_destroy(&rl->timer);
283
0
  isc_loop_detach(&rl->loop);
284
0
  UNLOCK(&rl->lock);
285
0
  isc_ratelimiter_detach(&rl);
286
0
}
287
288
void
289
0
isc_ratelimiter_shutdown(isc_ratelimiter_t *restrict rl) {
290
0
  ISC_LIST(isc_rlevent_t) pending;
291
292
0
  REQUIRE(VALID_RATELIMITER(rl));
293
294
0
  ISC_LIST_INIT(pending);
295
296
0
  LOCK(&rl->lock);
297
0
  if (rl->state != isc_ratelimiter_shuttingdown) {
298
0
    rl->state = isc_ratelimiter_shuttingdown;
299
0
    ISC_LIST_MOVE(pending, rl->pending);
300
0
    isc_ratelimiter_ref(rl);
301
0
    isc_async_run(rl->loop, isc__ratelimiter_doshutdown, rl);
302
0
  }
303
0
  UNLOCK(&rl->lock);
304
305
0
  ISC_LIST_FOREACH(pending, rle, link) {
306
0
    ISC_LIST_UNLINK(pending, rle, link);
307
0
    rle->canceled = true;
308
0
    isc_async_run(rl->loop, rle->cb, rle->arg);
309
0
  }
310
0
}
311
312
static void
313
0
ratelimiter_destroy(isc_ratelimiter_t *restrict rl) {
314
0
  LOCK(&rl->lock);
315
0
  REQUIRE(rl->state == isc_ratelimiter_shuttingdown);
316
0
  UNLOCK(&rl->lock);
317
318
0
  isc_mutex_destroy(&rl->lock);
319
0
  isc_mem_putanddetach(&rl->mctx, rl, sizeof(*rl));
320
0
}
321
322
void
323
0
isc_rlevent_free(isc_rlevent_t **rlep) {
324
0
  REQUIRE(rlep != NULL && *rlep != NULL);
325
326
0
  isc_rlevent_t *rle = *rlep;
327
0
  isc_mem_t *mctx = isc_loop_getmctx(rle->loop);
328
329
0
  *rlep = NULL;
330
331
0
  isc_loop_detach(&rle->loop);
332
0
  isc_ratelimiter_detach(&rle->rl);
333
0
  isc_mem_put(mctx, rle, sizeof(*rle));
334
0
}
335
336
ISC_REFCOUNT_IMPL(isc_ratelimiter, ratelimiter_destroy);