The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
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  */
25 RCSID("$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
36 DIAG_OFF(documentation)
37 #endif
38 #include <unbound-event.h>
39 #ifdef HAVE_WDOCUMENTATION
40 DIAG_ON(documentation)
41 #endif
42 
43 /** Definition for libunbound's event callback
44  *
45  * Here because they don't provide one.
46  */
47 typedef 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  */
55 typedef 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  */
89 static 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  */
105 static 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  */
121 static 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  */
137 static 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  */
168 static 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);
183  if (fr_event_fd_delete(el, ev->fd, FR_EVENT_FILTER_IO) < 0) {
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  */
192 static 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);
207  if (fr_event_fd_delete(el, ev->fd, FR_EVENT_FILTER_IO) < 0) {
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  */
246 static 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,
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  */
330 static 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  */
365 static 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;
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 
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,
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  */
408 static 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  */
429 static 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);
433  unbound_io_event_t *ev;
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,
439  .free = _unbound_io_event_free,
442  .add_timer = _unbound_io_timer_modify,
443  .del_timer = _unbound_io_timer_deactivate
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  */
482 int unbound_io_init(TALLOC_CTX *ctx, unbound_io_event_base_t **ev_b_out, fr_event_list_t *el)
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:444
#define DIAG_ON(_x)
Definition: build.h:419
#define UNUSED
Definition: build.h:313
#define DIAG_OFF(_x)
Definition: build.h:418
#define ERROR(fmt,...)
Definition: dhcpclient.c:41
static fr_time_delta_t timeout
Definition: dhcpclient.c:54
#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:1604
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:1253
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
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
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
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
fr_assert(0)
MEM(pair_append_request(&vp, attr_eap_aka_sim_identity) >=0)
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:595
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:336