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