/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); |