The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
io.c
Go to the documentation of this file.
1/*
2 * This program is is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or (at
5 * your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17/**
18 * $Id: 71cdf304bfeecaf53b6ca8e14697c8808cbab71f $
19 * @file rlm_unbound/io.c
20 * @brief Provides interface between libunbound and the FreeRADIUS event loop
21 *
22 * @copyright 2019 The FreeRADIUS server project
23 * @copyright 2019 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
24 */
25RCSID("$Id: 71cdf304bfeecaf53b6ca8e14697c8808cbab71f $")
26
27#define LOG_PREFIX "unbound"
28
29#include <freeradius-devel/server/log.h>
30#include <freeradius-devel/util/debug.h>
31#include <freeradius-devel/util/event.h>
32#include <freeradius-devel/util/syserror.h>
33
34#include "io.h"
35
36#ifdef HAVE_WDOCUMENTATION
37DIAG_OFF(documentation)
38#endif
39#include <unbound-event.h>
40#ifdef HAVE_WDOCUMENTATION
41DIAG_ON(documentation)
42#endif
43
44/** Definition for libunbound's event callback
45 *
46 * Here because they don't provide one.
47 */
48typedef void(*unbound_cb_t)(int, short flags, void *uctx);
49
50/** Wrapper around event handle for our event loop
51 *
52 * This stores libunbound specific information for an event in our event loop.
53 *
54 * Lifetime should be bound to the event base.
55 */
56typedef struct {
57 struct ub_event base; //!< Unbound event base, which we populate with
58 ///< callback functions for adding events for FDs
59 ///< setting timers etc...
60 ///< MUST BE LISTED FIRST.
61
62 unbound_io_event_base_t *ev_b; //!< Event base this handle was created for.
63
64 fr_timer_t *timer_ev; //!< Stores the pointer to the enabled timer for
65 ///< this event handled. libunbound uses a single
66 ///< handle for managing related FD events and
67 ///< timers, which is weird, but ok...
68
69 short events; //!< The events this event handle should receive
70 ///< when activated.
71
72 int fd; //!< File descriptor this event handle relates to.
73
74 unbound_cb_t cb; //!< The callback we need to call when a specified
75 ///< event happens.
76
77 void *uctx; //!< This is the argument libunbound wants passed to
78 ///< the callback when it's called. It usually
79 ///< contains libunbound's internal connection handled.
80 ///< We don't have any visibility, it just remains
81 ///< an opaque blob to us.
82
83 bool active; //!< Whether this event is considered active.
85
86/** Alter the enabled flags associated with the event
87 *
88 * Event *MUST* be disabled before these flags are changed.
89 */
90static void _unbound_io_event_flags_add(struct ub_event *ub_ev, short flags)
91{
92 unbound_io_event_t *ev = talloc_get_type_abort(ub_ev, unbound_io_event_t);
93 short new = ev->events | flags;
94
95 DEBUG4("unbound event %p - Adding flags %i (current %i, new %i)", ev, flags, ev->events, new);
96
97 fr_assert(!ev->active); /* must not be active */
98
99 ev->events = new;
100}
101
102/** Alter the enabled flags associated with the event
103 *
104 * Event *MUST* be disabled before these flags are changed.
105 */
106static void _unbound_io_event_flags_del(struct ub_event *ub_ev, short flags)
107{
108 unbound_io_event_t *ev = talloc_get_type_abort(ub_ev, unbound_io_event_t);
109 short new = ev->events & ~flags;
110
111 fr_assert(!ev->active); /* must not be active */
112
113 DEBUG4("unbound event %p - Removing flags %i (current %i, new %i)", ev, flags, ev->events, new);
114
115 ev->events = new;
116}
117
118/** Change the file descriptor associated with an event
119 *
120 * Event *MUST* be disabled before changing the fd.
121 */
122static void _unbound_io_event_fd_set(struct ub_event *ub_ev, int fd)
123{
124 unbound_io_event_t *ev = talloc_get_type_abort(ub_ev, unbound_io_event_t);
125
126 fr_assert(!ev->active); /* must not be active */
127
128 if (fd == ev->fd) return;
129
130 DEBUG4("unbound event %p - Changed FD from %i to %i", ev, ev->fd, fd);
131
132 ev->fd = fd;
133}
134
135/** Free an event, and, by the magic of talloc, any timers or fd events
136 *
137 */
138static void _unbound_io_event_free(struct ub_event *ub_ev)
139{
140 unbound_io_event_t *ev = talloc_get_type_abort(ub_ev, unbound_io_event_t);
141
142 DEBUG4("unbound event %p - Freed", ev);
143
144 talloc_free(ev);
145}
146
147/** Timeout fired
148 *
149 * Unbound uses these timeouts as part of its mechanism to measure rtt from
150 * candidate DNS servers, working out which is the fastest to use for any
151 * given query. The timeout happening causes the timeout against the server
152 * to be increased for any subsequent queries sent to it.
153 */
155{
156 unbound_io_event_t *ev = talloc_get_type_abort(uctx, unbound_io_event_t);
157
158 DEBUG4("unbound event %p - Timeout", ev);
159
160 ev->cb(-1, UB_EV_TIMEOUT, ev->uctx); /* Inform libunbound */
161}
162
163/** Unbound FD became readable
164 *
165 * Because we don't have the separation between the IO event loop
166 * and the event loop processing results, we call ub_process
167 * immediately after calling the IO callback.
168 */
169static void _unbound_io_service_readable(fr_event_list_t *el, int fd, UNUSED int flags, void *uctx)
170{
171 unbound_io_event_t *ev = talloc_get_type_abort(uctx, unbound_io_event_t);
172
173 fr_assert(ev->active); /* must be active */
174
175 DEBUG4("unbound event %p - FD %i now readable", ev, fd);
176
177 ev->cb(fd, UB_EV_READ, ev->uctx); /* Inform libunbound */
178
179 /*
180 * Remove IO events
181 */
182 if (!(ev->events & UB_EV_PERSIST)) {
183 DEBUG4("unbound event %p - UB_EV_PERSIST not set - Removing events for FD %i", ev, ev->fd);
185 PERROR("unbound event %p - De-registration failed for FD %i", ev, ev->fd);
186 }
187 }
188}
189
190/** Unbound FD became writable
191 *
192 */
193static void _unbound_io_service_writable(fr_event_list_t *el, int fd, UNUSED int flags, void *uctx)
194{
195 unbound_io_event_t *ev = talloc_get_type_abort(uctx, unbound_io_event_t);
196
197 fr_assert(ev->active); /* must be active */
198
199 DEBUG4("unbound event %p - FD %i now writable", ev, fd);
200
201 ev->cb(fd, UB_EV_WRITE, ev->uctx); /* Inform libunbound */
202
203 /*
204 * Remove IO events
205 */
206 if (!(ev->events & UB_EV_PERSIST)) {
207 DEBUG4("unbound event %p - UB_EV_PERSIST not set - Removing events for FD %i", ev, ev->fd);
209 PERROR("unbound event %p - De-registration failed for FD %i", ev, ev->fd);
210 }
211 }
212}
213
214/** Unbound FD errored
215 *
216 * libunbound doesn't request errors, so tell it a timeout occurred
217 *
218 * Because we don't have the separation between the IO event loop
219 * and the event loop processing results, we call ub_process
220 * immediately after calling the IO callback.
221 */
223 int fd, UNUSED int flags, int fd_errno, void *uctx)
224{
225 unbound_io_event_t *ev = talloc_get_type_abort(uctx, unbound_io_event_t);
226
227 fr_assert(ev->active); /* must be active */
228
229 DEBUG4("unbound event %p - FD %i errored: %s", ev, fd, fr_syserror(fd_errno));
230
231 /*
232 * Delete the timer as we're telling libunbound
233 * that it fired. This is imperfect but unbound
234 * doesn't have a callback for receiving errors.
235 */
237
238 ev->cb(-1, UB_EV_TIMEOUT, ev->uctx); /* Call libunbound - pretend this is a timeout */
239}
240
241
242/** Activate FD events and set a timer for a timeout
243 *
244 */
245static int _unbound_io_event_activate(struct ub_event *ub_ev, struct timeval *tv)
246{
247 unbound_io_event_t *ev = talloc_get_type_abort(ub_ev, unbound_io_event_t);
248
249 fr_assert(!ev->active); /* must not be active */
250
251 /*
252 * File descriptor event
253 */
254 if ((ev->events & UB_EV_READ) && (ev->events & UB_EV_WRITE)) {
255 fr_assert(ev->fd >= 0); /* File descriptor must valid */
256
257 DEBUG4("unbound event %p - Registered for read+write events on FD %i", ev, ev->fd);
258
259 if (fr_event_fd_insert(ev, NULL, ev->ev_b->el, ev->fd,
263 ev) < 0) {
264 PERROR("unbound event %p - Registration failed for read+write+error events on FD %i",
265 ev, ev->fd);
266
267 return -1;
268 }
269 } else if (ev->events & UB_EV_READ) {
270 fr_assert(ev->fd >= 0); /* File descriptor must valid */
271
272 DEBUG4("unbound event %p - Registered for read+error events on FD %i", ev, ev->fd);
273
274 if (fr_event_fd_insert(ev, NULL, ev->ev_b->el, ev->fd,
276 NULL,
278 ev) < 0) {
279 PERROR("unbound event %p - Registration failed for read+error events on FD %i",
280 ev, ev->fd);
281
282 return -1;
283 }
284 } else if (ev->events & UB_EV_WRITE) {
285 fr_assert(ev->fd >= 0); /* File descriptor must valid */
286
287 DEBUG4("unbound event %p - Registered for write+error events on FD %i", ev, ev->fd);
288
289 if (fr_event_fd_insert(ev, NULL, ev->ev_b->el, ev->fd,
290 NULL,
293 ev) < 0) {
294 PERROR("unbound event %p - Registration failed for write+error events on FD %i",
295 ev, ev->fd);
296
297 return -1;
298 }
299 }
300
301 /*
302 * Add a timeout event
303 */
304 if (ev->events & UB_EV_TIMEOUT) {
306
307 DEBUG4("unbound event %p - Timeout in %pV seconds", ev, fr_box_time_delta(timeout));
308
309 if (fr_timer_in(ev, ev->ev_b->el->tl, &ev->timer_ev,
310 timeout, false, _unbound_io_service_timer_expired, ev) < 0) {
311 PERROR("unbound event %p - Failed adding timeout", ev);
312
313 if (ev->events & (UB_EV_READ | UB_EV_WRITE)) {
315 }
316
317 return -1;
318 }
319 }
320
321 ev->active = true; /* Event is now active! */
322
323 return 0;
324}
325
326/* Deactivate FD events and disarm the timeout
327 *
328 */
329static int _unbound_io_event_deactivate(struct ub_event *ub_ev)
330{
331 unbound_io_event_t *ev = talloc_get_type_abort(ub_ev, unbound_io_event_t);
332 int ret = 0;
333
334 if (!ev->active) return 0; /* Allow this to be called multiple times */
335
336 if (ev->events & (UB_EV_READ | UB_EV_WRITE)) {
337 DEBUG4("unbound event %p - De-registering FD %i", ev, ev->fd);
338
339 if (fr_event_fd_delete(ev->ev_b->el, ev->fd, FR_EVENT_FILTER_IO) < 0) {
340 PERROR("unbound event %p - De-registration failed for FD %i", ev, ev->fd);
341
342 ret = -1;
343 }
344 }
345
346 if (ev->events & UB_EV_TIMEOUT) {
347 DEBUG4("unbound event %p - Disarming timeout", ev);
348
350 }
351
352 ev->active = false; /* Event is now inactive and can be modified */
353
354 return ret;
355}
356
357/** Modify an existing timeout
358 *
359 */
360static int _unbound_io_timer_modify(struct ub_event *ub_ev, UNUSED struct ub_event_base *ev_b,
361 void (*cb)(int, short, void*),
362 void *uctx, struct timeval *tv)
363{
364 unbound_io_event_t *ev = talloc_get_type_abort(ub_ev, unbound_io_event_t);
365 int ret = 0;
366 fr_time_delta_t timeout;
367
368 fr_assert(ev->events & UB_EV_TIMEOUT);
369
370 if (ev->cb != cb) {
371 DEBUG4("unbound event %p - New callback %p (old callback was %p)",
372 ev, cb, ev->cb);
373 ev->cb = cb;
374 }
375 if (ev->uctx != uctx) {
376 DEBUG4("unbound event %p - New uctx %p (old uctx was %p)",
377 ev, uctx, ev->uctx);
378 ev->uctx = uctx;
379 }
381
382 timeout = fr_time_delta_from_timeval(tv);
383
384 DEBUG4("unbound event %p - Timeout in %pV seconds", ev, fr_box_time_delta(timeout));
385
386 if (fr_timer_in(ev, ev->ev_b->el->tl, &ev->timer_ev,
387 timeout,
388 false, _unbound_io_service_timer_expired, ev) < 0) {
389 PERROR("unbound event %p - Failed adding timeout", ev);
390
391 ret = -1;
392 }
393
394 return ret;
395}
396
397/** Deactivate a timeout
398 *
399 */
400static int _unbound_io_timer_deactivate(struct ub_event *ub_ev)
401{
402 unbound_io_event_t *ev = talloc_get_type_abort(ub_ev, unbound_io_event_t);
403
404 fr_assert(ev->events & UB_EV_TIMEOUT);
405
406 DEBUG4("unbound event %p - Disarming timeout", ev);
407
409
410 return 0;
411}
412
413/** Returns a new libunbound event handle
414 *
415 * This handle is used by libunbound to interface with the worker's event loop
416 */
417static struct ub_event *_unbound_io_event_new(struct ub_event_base* base, int fd, short flags,
418 void (*cb)(int, short, void*), void *uctx)
419{
420 unbound_io_event_base_t *ev_b = talloc_get_type_abort(base, unbound_io_event_base_t);
422
423 static struct ub_event_vmt vmt = {
424 .add_bits = _unbound_io_event_flags_add,
425 .del_bits = _unbound_io_event_flags_del,
426 .set_fd = _unbound_io_event_fd_set,
430 .add_timer = _unbound_io_timer_modify,
432 };
433
434 MEM(ev = talloc_zero(ev_b, unbound_io_event_t));
435 ev->base.magic = UB_EVENT_MAGIC; /* Magic value libunbound requires */
436 ev->base.vmt = &vmt; /* Callbacks for adding/removing timers/fd events */
437 ev->ev_b = ev_b; /* Our event base (containing the el ) */
438 ev->events = flags; /* When this event should fire */
439 ev->fd = fd; /* File descriptor to register events for */
440 ev->cb = cb; /* Callback to execute on event */
441 ev->uctx = uctx; /* Lib unbound's arg to pass to the cb */
442 ev->active = false; /* Event is not currently active */
443
444 DEBUG4("unbound event %p - Allocated - Events %i, FD %i, callback %p, uctx %p", ev, flags, fd, cb, uctx);
445
446 return (struct ub_event *)ev;
447}
448
450{
451 if (ev_b->ub) ub_ctx_delete(ev_b->ub);
452
453 return 0;
454}
455
456/** Alloc a new event base, and unbound ctx initialised from that event base
457 *
458 * The ub_ctx is configured to use the el specified.
459 *
460 * When the thread ctx is freed, unbound_io_free should be called to gracefully
461 * free the ub_ctx, and then the event base structure it depends on.
462 *
463 * @param[in] ctx Talloc ctx to allocate even base in.
464 * @param[out] ev_b_out Event base. Free with talloc_free.
465 * @param[in] el To use to run the unbound event loop.
466 * @return
467 * - 0 on success.
468 * - -1 on failure.
469 */
471{
473
474 static struct ub_event_base_vmt vmt = {
475 .new_event = _unbound_io_event_new
476 };
477
478 /*
479 * Should be manually freed *AFTER* t->ub
480 * is freed. So must be parented from the NULL
481 * ctx.
482 */
483 MEM(ev_b = talloc_zero(ctx, unbound_io_event_base_t));
484 ev_b->base.magic = UB_EVENT_MAGIC;
485 ev_b->base.vmt = &vmt;
486 ev_b->el = el;
487
488 /*
489 * Create the main ub_ctx using our event base
490 * which specifies how libunbound integrates
491 * with our event loop.
492 */
493 ev_b->ub = ub_ctx_create_ub_event((struct ub_event_base *)ev_b);
494 if (!ev_b->ub) {
495 ERROR("Failed creating ub_ctx");
496 TALLOC_FREE(ev_b);
497 return -1;
498 }
499 talloc_set_destructor(ev_b, _event_base_free);
500
501 *ev_b_out = ev_b;
502
503 return 0;
504}
#define RCSID(id)
Definition build.h:485
#define DIAG_ON(_x)
Definition build.h:460
#define UNUSED
Definition build.h:317
#define DIAG_OFF(_x)
Definition build.h:459
#define MEM(x)
Definition debug.h:36
#define ERROR(fmt,...)
Definition dhcpclient.c:41
#define fr_event_fd_insert(...)
Definition event.h:248
@ FR_EVENT_FILTER_IO
Combined filter for read/write functions/.
Definition event.h:84
#define PERROR(_fmt,...)
Definition log.h:228
#define DEBUG4(_fmt,...)
Definition log.h:267
talloc_free(reap)
int fr_event_fd_delete(fr_event_list_t *el, int fd, fr_event_filter_t filter)
Remove a file descriptor from the event loop.
Definition event.c:1203
Stores all information relating to an event list.
Definition event.c:377
static void _unbound_io_service_errored(UNUSED fr_event_list_t *el, int fd, UNUSED int flags, int fd_errno, void *uctx)
Unbound FD errored.
Definition io.c:222
static void _unbound_io_event_flags_del(struct ub_event *ub_ev, short flags)
Alter the enabled flags associated with the event.
Definition io.c:106
int fd
File descriptor this event handle relates to.
Definition io.c:72
static void _unbound_io_service_timer_expired(UNUSED fr_timer_list_t *tl, UNUSED fr_time_t now, void *uctx)
Timeout fired.
Definition io.c:154
static int _unbound_io_timer_deactivate(struct ub_event *ub_ev)
Deactivate a timeout.
Definition io.c:400
short events
The events this event handle should receive when activated.
Definition io.c:69
struct ub_event base
Unbound event base, which we populate with callback functions for adding events for FDs setting timer...
Definition io.c:57
static void _unbound_io_service_writable(fr_event_list_t *el, int fd, UNUSED int flags, void *uctx)
Unbound FD became writable.
Definition io.c:193
int unbound_io_init(TALLOC_CTX *ctx, unbound_io_event_base_t **ev_b_out, fr_event_list_t *el)
Alloc a new event base, and unbound ctx initialised from that event base.
Definition io.c:470
void(* unbound_cb_t)(int, short flags, void *uctx)
Definition for libunbound's event callback.
Definition io.c:48
unbound_cb_t cb
The callback we need to call when a specified event happens.
Definition io.c:74
void * uctx
This is the argument libunbound wants passed to the callback when it's called.
Definition io.c:77
static void _unbound_io_service_readable(fr_event_list_t *el, int fd, UNUSED int flags, void *uctx)
Unbound FD became readable.
Definition io.c:169
static void _unbound_io_event_free(struct ub_event *ub_ev)
Free an event, and, by the magic of talloc, any timers or fd events.
Definition io.c:138
unbound_io_event_base_t * ev_b
Event base this handle was created for.
Definition io.c:62
static int _unbound_io_timer_modify(struct ub_event *ub_ev, UNUSED struct ub_event_base *ev_b, void(*cb)(int, short, void *), void *uctx, struct timeval *tv)
Modify an existing timeout.
Definition io.c:360
static int _unbound_io_event_deactivate(struct ub_event *ub_ev)
Definition io.c:329
static int _unbound_io_event_activate(struct ub_event *ub_ev, struct timeval *tv)
Activate FD events and set a timer for a timeout.
Definition io.c:245
static void _unbound_io_event_flags_add(struct ub_event *ub_ev, short flags)
Alter the enabled flags associated with the event.
Definition io.c:90
fr_timer_t * timer_ev
Stores the pointer to the enabled timer for this event handled.
Definition io.c:64
static int _event_base_free(unbound_io_event_base_t *ev_b)
Definition io.c:449
static struct ub_event * _unbound_io_event_new(struct ub_event_base *base, int fd, short flags, void(*cb)(int, short, void *), void *uctx)
Returns a new libunbound event handle.
Definition io.c:417
static void _unbound_io_event_fd_set(struct ub_event *ub_ev, int fd)
Change the file descriptor associated with an event.
Definition io.c:122
bool active
Whether this event is considered active.
Definition io.c:83
Wrapper around event handle for our event loop.
Definition io.c:56
Function prototypes and datatypes for the REST (HTTP) transport.
struct ub_ctx * ub
Unbound ctx instantiated from this event base.
Definition io.h:51
struct ub_event_base base
Interface structure for libunbound.
Definition io.h:49
fr_event_list_t * el
Event loop events should be inserted into.
Definition io.h:53
Wrapper around our event loop specifying callbacks for creating new event handles.
Definition io.h:48
#define fr_assert(_expr)
Definition rad_assert.h:38
char const * fr_syserror(int num)
Guaranteed to be thread-safe version of strerror.
Definition syserror.c:243
static fr_time_delta_t fr_time_delta_from_timeval(struct timeval const *tv)
Definition time.h:597
A time delta, a difference in time measured in nanoseconds.
Definition time.h:80
"server local" time.
Definition time.h:69
An event timer list.
Definition timer.c:49
A timer event.
Definition timer.c:75
#define FR_TIMER_DISARM_RETURN(_ev)
Definition timer.h:97
#define fr_timer_in(...)
Definition timer.h:86
#define FR_TIMER_DISARM(_ev)
Definition timer.h:90
static fr_event_list_t * el
#define fr_box_time_delta(_val)
Definition value.h:354