Coverage Report

Created: 2026-07-16 07:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/bind9/lib/isc/ratelimiter.c
Line
Count
Source
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
223
0
  REQUIRE(VALID_RATELIMITER(rl));
224
225
0
  LOCK(&rl->lock);
226
227
0
  REQUIRE(rl->timer != NULL);
228
229
0
  if (rl->state == isc_ratelimiter_shuttingdown) {
230
0
    INSIST(ISC_LIST_EMPTY(rl->pending));
231
0
    goto unlock;
232
0
  }
233
234
0
  pertic = rl->pertic;
235
0
  while (pertic != 0) {
236
0
    isc_rlevent_t *rle = ISC_LIST_HEAD(rl->pending);
237
0
    if (rle == NULL) {
238
      /*
239
       * We processed all the scheduled work, but there's a
240
       * room for at least one more event (we haven't consumed
241
       * all of the "pertick"), so we can stop the ratelimiter
242
       * now, and don't worry about isc_ratelimiter_enqueue()
243
       * sending an extra event immediately.
244
       */
245
0
      rl->state = isc_ratelimiter_idle;
246
0
      break;
247
0
    }
248
    /*
249
     * Unlink and dispatch under the lock: isc_async_run() is a
250
     * non-blocking enqueue, so this stays cheap, and once the
251
     * link is TOMBSTONEd a concurrent isc_ratelimiter_dequeue()
252
     * sees ISC_LINK_LINKED == false and returns ISC_R_NOTFOUND
253
     * cleanly instead of racing with our dispatch.
254
     */
255
0
    ISC_LIST_UNLINK(rl->pending, rle, link);
256
0
    isc_async_run(rle->loop, rle->cb, rle->arg);
257
0
    pertic--;
258
0
  }
259
260
0
  if (rl->state != isc_ratelimiter_idle) {
261
    /* Reschedule the timer */
262
0
    isc_timer_start(rl->timer, isc_timertype_once, &rl->interval);
263
0
  }
264
0
unlock:
265
0
  UNLOCK(&rl->lock);
266
0
}
267
268
void
269
0
isc__ratelimiter_doshutdown(void *arg) {
270
0
  isc_ratelimiter_t *rl = arg;
271
272
0
  REQUIRE(VALID_RATELIMITER(rl));
273
274
0
  LOCK(&rl->lock);
275
0
  INSIST(rl->state == isc_ratelimiter_shuttingdown);
276
0
  INSIST(ISC_LIST_EMPTY(rl->pending));
277
278
0
  isc_timer_stop(rl->timer);
279
0
  isc_timer_destroy(&rl->timer);
280
0
  isc_loop_detach(&rl->loop);
281
0
  UNLOCK(&rl->lock);
282
0
  isc_ratelimiter_detach(&rl);
283
0
}
284
285
void
286
0
isc_ratelimiter_shutdown(isc_ratelimiter_t *restrict rl) {
287
0
  REQUIRE(VALID_RATELIMITER(rl));
288
289
0
  LOCK(&rl->lock);
290
0
  if (rl->state != isc_ratelimiter_shuttingdown) {
291
0
    rl->state = isc_ratelimiter_shuttingdown;
292
0
    ISC_LIST_FOREACH(rl->pending, rle, link) {
293
0
      ISC_LIST_UNLINK(rl->pending, rle, link);
294
0
      rle->canceled = true;
295
0
      isc_async_run(rl->loop, rle->cb, rle->arg);
296
0
    }
297
0
    isc_ratelimiter_ref(rl);
298
0
    isc_async_run(rl->loop, isc__ratelimiter_doshutdown, rl);
299
0
  }
300
0
  UNLOCK(&rl->lock);
301
0
}
302
303
static void
304
0
ratelimiter_destroy(isc_ratelimiter_t *restrict rl) {
305
0
  LOCK(&rl->lock);
306
0
  REQUIRE(rl->state == isc_ratelimiter_shuttingdown);
307
0
  UNLOCK(&rl->lock);
308
309
0
  isc_mutex_destroy(&rl->lock);
310
0
  isc_mem_putanddetach(&rl->mctx, rl, sizeof(*rl));
311
0
}
312
313
void
314
0
isc_rlevent_free(isc_rlevent_t **rlep) {
315
0
  REQUIRE(rlep != NULL && *rlep != NULL);
316
317
0
  isc_rlevent_t *rle = *rlep;
318
0
  isc_mem_t *mctx = isc_loop_getmctx(rle->loop);
319
320
0
  *rlep = NULL;
321
322
0
  isc_loop_detach(&rle->loop);
323
0
  isc_ratelimiter_detach(&rle->rl);
324
0
  isc_mem_put(mctx, rle, sizeof(*rle));
325
0
}
326
327
ISC_REFCOUNT_IMPL(isc_ratelimiter, ratelimiter_destroy);
Unexecuted instantiation: isc_ratelimiter_ref
Unexecuted instantiation: isc_ratelimiter_unref
Unexecuted instantiation: isc_ratelimiter_detach