1649Sjoehw/*	$NetBSD: ratelimiter.c,v 1.6 2020/05/25 20:47:20 christos Exp $	*/
2963Sjoehw
3649Sjoehw/*
4281SN/A * Copyright (C) 2004, 2005, 2007  Internet Systems Consortium, Inc. ("ISC")
5635Sjoehw * Copyright (C) 1999-2002  Internet Software Consortium.
6635Sjoehw *
7635Sjoehw * Permission to use, copy, modify, and/or distribute this software for any
8635Sjoehw * purpose with or without fee is hereby granted, provided that the above
9635Sjoehw * copyright notice and this permission notice appear in all copies.
10635Sjoehw *
11281SN/A * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
12281SN/A * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
13281SN/A * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
14281SN/A * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
15281SN/A * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
16281SN/A * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17281SN/A * PERFORMANCE OF THIS SOFTWARE.
18281SN/A */
19281SN/A
20281SN/A/* Id: ratelimiter.c,v 1.25 2007/06/19 23:47:17 tbox Exp  */
21281SN/A
22281SN/A/*! \file */
23281SN/A
24281SN/A#include <config.h>
25281SN/A
26281SN/A#include <isc/mem.h>
27281SN/A#include <isc/ratelimiter.h>
28281SN/A#include <isc/task.h>
29281SN/A#include <isc/time.h>
30281SN/A#include <isc/timer.h>
31649Sjoehw#include <isc/util.h>
32635Sjoehw
33281SN/Atypedef enum {
34281SN/A	isc_ratelimiter_stalled = 0,
35281SN/A	isc_ratelimiter_ratelimited = 1,
36281SN/A	isc_ratelimiter_idle = 2,
37281SN/A	isc_ratelimiter_shuttingdown = 3
38281SN/A} isc_ratelimiter_state_t;
39281SN/A
40281SN/Astruct isc_ratelimiter {
41281SN/A	isc_mem_t *		mctx;
42281SN/A	isc_mutex_t		lock;
43281SN/A	int			refs;
44281SN/A	isc_task_t *		task;
45281SN/A	isc_timer_t *		timer;
46649Sjoehw	isc_interval_t		interval;
47798Sjoehw	isc_uint32_t		pertic;
48281SN/A	isc_ratelimiter_state_t	state;
49281SN/A	isc_event_t		shutdownevent;
50281SN/A	ISC_LIST(isc_event_t)	pending;
51798Sjoehw};
52281SN/A
53281SN/A#define ISC_RATELIMITEREVENT_SHUTDOWN (ISC_EVENTCLASS_RATELIMITER + 1)
54281SN/A
55281SN/Astatic void
56281SN/Aratelimiter_tick(isc_task_t *task, isc_event_t *event);
57281SN/A
58281SN/Astatic void
59281SN/Aratelimiter_shutdowncomplete(isc_task_t *task, isc_event_t *event);
60281SN/A
61281SN/Aisc_result_t
62281SN/Aisc_ratelimiter_create(isc_mem_t *mctx, isc_timermgr_t *timermgr,
63281SN/A		       isc_task_t *task, isc_ratelimiter_t **ratelimiterp)
64281SN/A{
65281SN/A	isc_result_t result;
66281SN/A	isc_ratelimiter_t *rl;
67281SN/A	INSIST(ratelimiterp != NULL && *ratelimiterp == NULL);
68281SN/A
69281SN/A	rl = isc_mem_get(mctx, sizeof(*rl));
70281SN/A	if (rl == NULL)
71281SN/A		return ISC_R_NOMEMORY;
72281SN/A	rl->mctx = mctx;
73281SN/A	rl->refs = 1;
74281SN/A	rl->task = task;
75281SN/A	isc_interval_set(&rl->interval, 0, 0);
76281SN/A	rl->timer = NULL;
77281SN/A	rl->pertic = 1;
78281SN/A	rl->state = isc_ratelimiter_idle;
79281SN/A	ISC_LIST_INIT(rl->pending);
80798Sjoehw
81798Sjoehw	result = isc_mutex_init(&rl->lock);
82798Sjoehw	if (result != ISC_R_SUCCESS)
83798Sjoehw		goto free_mem;
84798Sjoehw	result = isc_timer_create(timermgr, isc_timertype_inactive,
85798Sjoehw				  NULL, NULL, rl->task, ratelimiter_tick,
86798Sjoehw				  rl, &rl->timer);
87798Sjoehw	if (result != ISC_R_SUCCESS)
88798Sjoehw		goto free_mutex;
89963Sjoehw
90963Sjoehw	/*
91281SN/A	 * Increment the reference count to indicate that we may
92281SN/A	 * (soon) have events outstanding.
93281SN/A	 */
94281SN/A	rl->refs++;
95281SN/A
96281SN/A	ISC_EVENT_INIT(&rl->shutdownevent,
97281SN/A		       sizeof(isc_event_t),
98281SN/A		       0, NULL, ISC_RATELIMITEREVENT_SHUTDOWN,
99281SN/A		       ratelimiter_shutdowncomplete, rl, rl, NULL, NULL);
100281SN/A
101281SN/A	*ratelimiterp = rl;
102281SN/A	return (ISC_R_SUCCESS);
103281SN/A
104281SN/Afree_mutex:
105281SN/A	DESTROYLOCK(&rl->lock);
106281SN/Afree_mem:
107281SN/A	isc_mem_put(mctx, rl, sizeof(*rl));
108281SN/A	return (result);
109281SN/A}
110281SN/A
111281SN/Aisc_result_t
112281SN/Aisc_ratelimiter_setinterval(isc_ratelimiter_t *rl, isc_interval_t *interval) {
113281SN/A	isc_result_t result = ISC_R_SUCCESS;
114281SN/A	LOCK(&rl->lock);
115281SN/A	rl->interval = *interval;
116649Sjoehw	/*
117281SN/A	 * If the timer is currently running, change its rate.
118281SN/A	 */
119281SN/A        if (rl->state == isc_ratelimiter_ratelimited) {
120281SN/A		result = isc_timer_reset(rl->timer, isc_timertype_ticker, NULL,
121281SN/A					 &rl->interval, ISC_FALSE);
122281SN/A	}
123281SN/A	UNLOCK(&rl->lock);
124281SN/A	return (result);
125281SN/A}
126281SN/A
127281SN/Avoid
128281SN/Aisc_ratelimiter_setpertic(isc_ratelimiter_t *rl, isc_uint32_t pertic) {
129281SN/A	if (pertic == 0)
130281SN/A		pertic = 1;
131281SN/A	rl->pertic = pertic;
132281SN/A}
133281SN/A
134281SN/Aisc_result_t
135281SN/Aisc_ratelimiter_enqueue(isc_ratelimiter_t *rl, isc_task_t *task,
136281SN/A			isc_event_t **eventp)
137281SN/A{
138281SN/A	isc_result_t result = ISC_R_SUCCESS;
139281SN/A	isc_event_t *ev;
140281SN/A
141281SN/A	REQUIRE(eventp != NULL && *eventp != NULL);
142281SN/A	REQUIRE(task != NULL);
143281SN/A	ev = *eventp;
144281SN/A	REQUIRE(ev->ev_sender == NULL);
145281SN/A
146281SN/A	LOCK(&rl->lock);
147281SN/A        if (rl->state == isc_ratelimiter_ratelimited ||
148281SN/A	    rl->state == isc_ratelimiter_stalled) {
149281SN/A		isc_event_t *ev = *eventp;
150281SN/A		ev->ev_sender = task;
151281SN/A                ISC_LIST_APPEND(rl->pending, ev, ev_link);
152281SN/A		*eventp = NULL;
153281SN/A        } else if (rl->state == isc_ratelimiter_idle) {
154281SN/A		result = isc_timer_reset(rl->timer, isc_timertype_ticker, NULL,
155281SN/A					 &rl->interval, ISC_FALSE);
156281SN/A		if (result == ISC_R_SUCCESS) {
157281SN/A			ev->ev_sender = task;
158281SN/A			rl->state = isc_ratelimiter_ratelimited;
159281SN/A		}
160281SN/A	} else {
161281SN/A		INSIST(rl->state == isc_ratelimiter_shuttingdown);
162281SN/A		result = ISC_R_SHUTTINGDOWN;
163281SN/A	}
164281SN/A	UNLOCK(&rl->lock);
165281SN/A	if (*eventp != NULL && result == ISC_R_SUCCESS)
166281SN/A		isc_task_send(task, eventp);
167281SN/A	return (result);
168281SN/A}
169281SN/A
170281SN/Astatic void
171281SN/Aratelimiter_tick(isc_task_t *task, isc_event_t *event) {
172281SN/A	isc_result_t result = ISC_R_SUCCESS;
173281SN/A	isc_ratelimiter_t *rl = (isc_ratelimiter_t *)event->ev_arg;
174281SN/A	isc_event_t *p;
175281SN/A	isc_uint32_t pertic;
176281SN/A
177281SN/A	UNUSED(task);
178281SN/A
179281SN/A	isc_event_free(&event);
180281SN/A
181281SN/A	pertic = rl->pertic;
182281SN/A        while (pertic != 0) {
183281SN/A		pertic--;
184281SN/A		LOCK(&rl->lock);
185281SN/A		p = ISC_LIST_HEAD(rl->pending);
186281SN/A		if (p != NULL) {
187281SN/A			/*
188281SN/A			 * There is work to do.  Let's do it after unlocking.
189281SN/A			 */
190281SN/A			ISC_LIST_UNLINK(rl->pending, p, ev_link);
191281SN/A		} else {
192635Sjoehw			/*
193635Sjoehw			 * No work left to do.  Stop the timer so that we don't
194635Sjoehw			 * waste resources by having it fire periodically.
195635Sjoehw			 */
196635Sjoehw			result = isc_timer_reset(rl->timer,
197635Sjoehw						 isc_timertype_inactive,
198635Sjoehw						 NULL, NULL, ISC_FALSE);
199635Sjoehw			RUNTIME_CHECK(result == ISC_R_SUCCESS);
200635Sjoehw			rl->state = isc_ratelimiter_idle;
201635Sjoehw			pertic = 0;	/* Force the loop to exit. */
202635Sjoehw		}
203635Sjoehw		UNLOCK(&rl->lock);
204635Sjoehw		if (p != NULL) {
205635Sjoehw			isc_task_t *evtask = p->ev_sender;
206635Sjoehw			isc_task_send(evtask, &p);
207635Sjoehw		}
208281SN/A		INSIST(p == NULL);
209281SN/A	}
210281SN/A}
211281SN/A
212281SN/Avoid
213281SN/Aisc_ratelimiter_shutdown(isc_ratelimiter_t *rl) {
214281SN/A	isc_event_t *ev;
215281SN/A	isc_task_t *task;
216281SN/A	LOCK(&rl->lock);
217281SN/A	rl->state = isc_ratelimiter_shuttingdown;
218281SN/A	(void)isc_timer_reset(rl->timer, isc_timertype_inactive,
219281SN/A			      NULL, NULL, ISC_FALSE);
220281SN/A	while ((ev = ISC_LIST_HEAD(rl->pending)) != NULL) {
221281SN/A		ISC_LIST_UNLINK(rl->pending, ev, ev_link);
222281SN/A		ev->ev_attributes |= ISC_EVENTATTR_CANCELED;
223281SN/A		task = ev->ev_sender;
224281SN/A		isc_task_send(task, &ev);
225281SN/A	}
226281SN/A	isc_timer_detach(&rl->timer);
227281SN/A	/*
228281SN/A	 * Send an event to our task.  The delivery of this event
229281SN/A	 * indicates that no more timer events will be delivered.
230281SN/A	 */
231281SN/A	ev = &rl->shutdownevent;
232281SN/A	isc_task_send(rl->task, &ev);
233281SN/A
234281SN/A	UNLOCK(&rl->lock);
235281SN/A}
236281SN/A
237281SN/Astatic void
238281SN/Aratelimiter_shutdowncomplete(isc_task_t *task, isc_event_t *event) {
239281SN/A	isc_ratelimiter_t *rl = (isc_ratelimiter_t *)event->ev_arg;
240281SN/A
241281SN/A	UNUSED(task);
242281SN/A
243281SN/A	isc_ratelimiter_detach(&rl);
244281SN/A}
245281SN/A
246281SN/Astatic void
247281SN/Aratelimiter_free(isc_ratelimiter_t *rl) {
248281SN/A	DESTROYLOCK(&rl->lock);
249281SN/A	isc_mem_put(rl->mctx, rl, sizeof(*rl));
250281SN/A}
251635Sjoehw
252635Sjoehwvoid
253635Sjoehwisc_ratelimiter_attach(isc_ratelimiter_t *source, isc_ratelimiter_t **target) {
254635Sjoehw	REQUIRE(source != NULL);
255649Sjoehw	REQUIRE(target != NULL && *target == NULL);
256649Sjoehw
257649Sjoehw	LOCK(&source->lock);
258649Sjoehw	REQUIRE(source->refs > 0);
259281SN/A	source->refs++;
260281SN/A	INSIST(source->refs > 0);
261281SN/A	UNLOCK(&source->lock);
262281SN/A	*target = source;
263464SN/A}
264464SN/A
265464SN/Avoid
266418SN/Aisc_ratelimiter_detach(isc_ratelimiter_t **rlp) {
267293SN/A	isc_ratelimiter_t *rl = *rlp;
268293SN/A	isc_boolean_t free_now = ISC_FALSE;
269963Sjoehw
270963Sjoehw	LOCK(&rl->lock);
271281SN/A	REQUIRE(rl->refs > 0);
272281SN/A	rl->refs--;
273281SN/A	if (rl->refs == 0)
274281SN/A		free_now = ISC_TRUE;
275281SN/A	UNLOCK(&rl->lock);
276281SN/A
277281SN/A	if (free_now)
278281SN/A		ratelimiter_free(rl);
279281SN/A
280281SN/A	*rlp = NULL;
281281SN/A}
282281SN/A
283281SN/Aisc_result_t
284281SN/Aisc_ratelimiter_stall(isc_ratelimiter_t *rl) {
285281SN/A	isc_result_t result = ISC_R_SUCCESS;
286281SN/A
287635Sjoehw	LOCK(&rl->lock);
288635Sjoehw	switch (rl->state) {
289635Sjoehw	case isc_ratelimiter_shuttingdown:
290635Sjoehw		result = ISC_R_SHUTTINGDOWN;
291281SN/A		break;
292293SN/A	case isc_ratelimiter_ratelimited:
293963Sjoehw		result = isc_timer_reset(rl->timer, isc_timertype_inactive,
294963Sjoehw				 	 NULL, NULL, ISC_FALSE);
295649Sjoehw		RUNTIME_CHECK(result == ISC_R_SUCCESS);
296281SN/A	case isc_ratelimiter_idle:
297281SN/A	case isc_ratelimiter_stalled:
298281SN/A		rl->state = isc_ratelimiter_stalled;
299281SN/A		break;
300281SN/A	}
301281SN/A	UNLOCK(&rl->lock);
302281SN/A	return (result);
303281SN/A}
304281SN/A
305635Sjoehwisc_result_t
306635Sjoehwisc_ratelimiter_release(isc_ratelimiter_t *rl) {
307635Sjoehw	isc_result_t result = ISC_R_SUCCESS;
308635Sjoehw
309281SN/A	LOCK(&rl->lock);
310281SN/A	switch (rl->state) {
311281SN/A	case isc_ratelimiter_shuttingdown:
312281SN/A		result = ISC_R_SHUTTINGDOWN;
313281SN/A		break;
314281SN/A	case isc_ratelimiter_stalled:
315281SN/A		if (!ISC_LIST_EMPTY(rl->pending)) {
316281SN/A			result = isc_timer_reset(rl->timer,
317281SN/A						 isc_timertype_ticker, NULL,
318281SN/A						 &rl->interval, ISC_FALSE);
319281SN/A			if (result == ISC_R_SUCCESS)
320293SN/A				rl->state = isc_ratelimiter_ratelimited;
321635Sjoehw		} else
322635Sjoehw			rl->state = isc_ratelimiter_idle;
323635Sjoehw		break;
324635Sjoehw	case isc_ratelimiter_ratelimited:
325963Sjoehw	case isc_ratelimiter_idle:
326963Sjoehw		break;
327281SN/A	}
328281SN/A	UNLOCK(&rl->lock);
329281SN/A	return (result);
330281SN/A}
331281SN/A