The FreeRADIUS server $Id: a4e7f8c94e8038b7627bcf52356b7ed15955c1d7 $
Loading...
Searching...
No Matches
queue.c
Go to the documentation of this file.
1/*
2 * This program 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: 701fffb1eae1e86ca1509167648cbf11f736bf59 $
19 * @file lib/bio/queue.c
20 * @brief Binary IO abstractions for queues of raw packets
21 *
22 * @copyright 2024 Network RADIUS SAS (legal@networkradius.com)
23 */
24
25#include <freeradius-devel/bio/bio_priv.h>
26#include <freeradius-devel/bio/queue.h>
27#include <freeradius-devel/bio/null.h>
28
29typedef struct fr_bio_queue_list_s fr_bio_queue_list_t;
31
32/*
33 * Define type-safe wrappers for head and entry definitions.
34 */
35FR_DLIST_TYPES(fr_bio_queue_list)
36
37/*
38 * For delayed writes.
39 *
40 * @todo - we can remove the "cancelled" field by setting packet_ctx == my?
41 */
44 void const *buffer;
45 size_t size;
48
50
51 FR_DLIST_ENTRY(fr_bio_queue_list) entry; //!< List entry.
52};
53
54FR_DLIST_FUNCS(fr_bio_queue_list, fr_bio_queue_entry_t, entry)
55
70
71static ssize_t fr_bio_queue_write_buffer(fr_bio_t *bio, void *packet_ctx, void const *buffer, size_t size);
72
73/** Forcibly cancel all outstanding packets.
74 *
75 * The cancel includes partially written packets. The shutdown and
76 * destructor functions call us. If there is no cancel callback,
77 * the packets are left on the pending list.
78 *
79 * This function does not change the read and write routines.
80 * fr_bio_shutdown() owns the read and write routines, and may leave
81 * the read routine in place so that the application can drain data
82 * which has already been received.
83 */
85{
87
88 if (!my->cancel) return;
89
90 if (fr_bio_queue_list_num_elements(&my->pending) == 0) return;
91
92 /*
93 * Cancel any remaining saved items.
94 */
95 while ((item = fr_bio_queue_list_pop_head(&my->pending)) != NULL) {
96 my->cancel(&my->bio, item->packet_ctx, item->buffer, item->size);
97 item->cancelled = true;
98 fr_bio_queue_list_insert_head(&my->free, item);
99 }
100}
101
103{
105
106 fr_assert(my->cancel); /* otherwise it would be fr_bio_destructor */
107
109
110 return 0;
111}
112
113/** Push a packet onto a list.
114 *
115 */
116static ssize_t fr_bio_queue_list_push(fr_bio_queue_t *my, void *packet_ctx, const void *buffer, size_t size, size_t already_written)
117{
119
120 item = fr_bio_queue_list_pop_head(&my->free);
121 if (!item) return fr_bio_error(IO_WOULD_BLOCK);
122
123 /*
124 * If we're the first entry in the saved list, we can have a partially written packet.
125 *
126 * Otherwise, we're a subsequent entry, and we cannot have any data which is partially written.
127 */
128 fr_assert((fr_bio_queue_list_num_elements(&my->pending) == 0) ||
129 (already_written == 0));
130
131 item->packet_ctx = packet_ctx;
132 item->buffer = buffer;
133 item->size = size;
134 item->already_written = already_written;
135 item->cancelled = false;
136
137 fr_bio_queue_list_insert_tail(&my->pending, item);
138
139 if (my->saved) my->saved(&my->bio, packet_ctx, buffer, size, item);
140
141 return size;
142}
143
144/** Write one packet to the next bio.
145 *
146 * If it blocks, save the packet and return OK to the caller.
147 */
148static ssize_t fr_bio_queue_write_next(fr_bio_t *bio, void *packet_ctx, void const *buffer, size_t size)
149{
150 ssize_t rcode;
151 fr_bio_queue_t *my = talloc_get_type_abort(bio, fr_bio_queue_t);
152 fr_bio_t *next;
153
154 /*
155 * We can't call the next bio if there's still cached data to flush.
156 */
157 fr_assert(fr_bio_queue_list_num_elements(&my->pending) == 0);
158
159 next = fr_bio_next(&my->bio);
160 fr_assert(next != NULL);
161
162 /*
163 * Write the data out. If we write all of it, we're done.
164 */
165 rcode = next->write(next, packet_ctx, buffer, size);
166 if (rcode < 0) return rcode;
167
168 if ((size_t) rcode == size) return rcode;
169
170 /*
171 * We were flushing the next buffer, return any data which was written.
172 */
173 if (!buffer) return rcode;
174
175 /*
176 * The next bio wrote a partial packet. Save the entire packet, and swap the write function to
177 * save all future packets in the saved list.
178 */
180
181 fr_assert(fr_bio_queue_list_num_elements(&my->free) > 0);
182
183 /*
184 * This can only error out if the free list has no more entries.
185 */
186 return fr_bio_queue_list_push(my, packet_ctx, buffer, size, (size_t) rcode);
187}
188
189/** Flush the packet list.
190 *
191 */
193{
194 size_t written;
195 fr_bio_t *next;
196
197 if (fr_bio_queue_list_num_elements(&my->pending) == 0) {
198 my->bio.write = fr_bio_queue_write_next;
199 return 0;
200 }
201
202 next = fr_bio_next(&my->bio);
203 fr_assert(next != NULL);
204
205 /*
206 * Loop over the saved packets, flushing them to the next bio.
207 */
208 written = 0;
209 while (written < size) {
210 ssize_t rcode;
212
213 /*
214 * No more saved packets to write: stop.
215 */
216 item = fr_bio_queue_list_head(&my->pending);
217 if (!item) break;
218
219 /*
220 * A cancelled item must be partially written. A cancelled item which has zero bytes
221 * written should not be in the saved list.
222 */
223 fr_assert(!item->cancelled || (item->already_written > 0));
224
225 /*
226 * Push out however much data we can to the next bio.
227 */
228 rcode = next->write(next, item->packet_ctx, ((uint8_t const *) item->buffer) + item->already_written, item->size - item->already_written);
229 if (rcode == 0) break;
230
231 if (rcode < 0) {
232 if (rcode == fr_bio_error(IO_WOULD_BLOCK)) break;
233
234 /*
235 * The next bio decides whether the error is fatal. See fr_bio_queue_write_next().
236 */
237 return rcode;
238 }
239
240 /*
241 * Update the written count.
242 */
243 written += rcode;
244 item->already_written += rcode;
245
246 if (item->already_written < item->size) break;
247
248 /*
249 * We don't run "sent" callbacks for cancelled items.
250 */
251 if (item->cancelled) {
252 if (my->cancel) my->cancel(&my->bio, item->packet_ctx, item->buffer, item->size);
253 } else {
254 if (my->sent) my->sent(&my->bio, item->packet_ctx, item->buffer, item->size);
255 }
256
257 (void) fr_bio_queue_list_pop_head(&my->pending);
258#ifndef NDEBUG
259 item->buffer = NULL;
260 item->packet_ctx = NULL;
261 item->size = 0;
262 item->already_written = 0;
263#endif
264 item->cancelled = true;
265
266 fr_bio_queue_list_insert_head(&my->free, item);
267 }
268
269 /*
270 * If we've written all of the saved packets, go back to writing to the "next" bio.
271 */
272 if (!fr_bio_queue_list_head(&my->pending)) my->bio.write = fr_bio_queue_write_next;
273
274 return written;
275}
276
277/** Write to the packet list buffer.
278 *
279 * The special buffer pointer of NULL means flush(). On flush, we call next->read(), and if that succeeds,
280 * go back to "pass through" mode for the buffers.
281 */
282static ssize_t fr_bio_queue_write_buffer(fr_bio_t *bio, void *packet_ctx, void const *buffer, size_t size)
283{
284 fr_bio_queue_t *my = talloc_get_type_abort(bio, fr_bio_queue_t);
285
286 if (!buffer) return fr_bio_queue_write_flush(my, size);
287
288 /*
289 * This can only error out if the free list has no more entries.
290 */
291 return fr_bio_queue_list_push(my, packet_ctx, buffer, size, 0);
292}
293
294/** Shutdown
295 *
296 * Cancel / close has to be called before re-init.
297 */
299{
300 fr_bio_queue_t *my = talloc_get_type_abort(bio, fr_bio_queue_t);
301
303
304 return 0;
305}
306
307
308/** Allocate a packet-based bio.
309 *
310 * This bio assumes that each call to fr_bio_write() is for one packet, and only one packet. If the next bio
311 * returns a partial write, or WOULD BLOCK, then information about the packet is cached. Subsequent writes
312 * will write the partial data first, and then continue with subsequent writes.
313 *
314 * The caller is responsible for not freeing the packet ctx or the packet buffer until either the write has
315 * been performed, or the write has been cancelled.
316 *
317 * The read() API makes no provisions for reading complete packets. It simply returns whatever the next bio
318 * allows. If instead there is a need to read only complete packets, then the next bio should be
319 * fr_bio_mem_alloc() with a fr_bio_mem_set_verify()
320 *
321 * The read() API may return 0. There may have been data read from an underlying FD, but that data did not
322 * make it through the filters of the "next" bios. e.g. Any underlying FD should be put into a "wait for
323 * readable" state.
324 *
325 * The write() API will return a full write, even if the next layer is blocked. Any underlying FD
326 * should be put into a "wait for writeable" state. The packet which was supposed to be written has been
327 * cached, and cannot be cancelled as it is partially written. The caller should likely start using another
328 * bio for writes. If the caller continues to use the bio, then any subsequent writes will *always* cache
329 * the packets. @todo - we need to mark up the bio as "blocked", and then have a write_blocked() API? ugh.
330 * or just add `bool blocked` and `bool eof` to both read/write bios
331 *
332 * Once the underlying FD has become writeable, the caller should call fr_bio_write(bio, NULL, NULL, SIZE_MAX);
333 * That will cause the pending packets to be flushed.
334 *
335 * The write() API may return that it's written a full packet, in which case it's either completely written to
336 * the next bio, or to the pending queue.
337 *
338 * The read / write APIs can return WOULD_BLOCK, in which case nothing was done. Any underlying FD should be
339 * put into a "wait for writeable" state. Other errors from bios "further down" the chain can also be
340 * returned.
341 *
342 * @param ctx the talloc ctx
343 * @param max_saved Maximum number of packets to cache. Must be 1..1^17
344 * @param saved callback to run when a packet is saved in the pending queue
345 * @param sent callback to run when a packet is sent.
346 * @param cancel callback to run when a packet is cancelled.
347 * @param next the next bio which will perform the underlying reads and writes.
348 * - NULL on error, memory allocation failed
349 * - !NULL the bio
350 */
351fr_bio_t *fr_bio_queue_alloc(TALLOC_CTX *ctx, size_t max_saved,
355 fr_bio_t *next)
356{
357 size_t i;
358 fr_bio_queue_t *my;
359
360 if (!max_saved) max_saved = 1;
361 if (max_saved > (1 << 17)) max_saved = 1 << 17;
362
363 my = (fr_bio_queue_t *) talloc_zero_array(ctx, uint8_t, sizeof(fr_bio_queue_t) +
365 if (!my) return NULL;
366
367 talloc_set_type(my, fr_bio_queue_t);
368
369 my->max_saved = max_saved;
370
371 fr_bio_queue_list_init(&my->pending);
372 fr_bio_queue_list_init(&my->free);
373
374 my->saved = saved;
375 my->sent = sent;
376 my->cancel = cancel;
377
378 for (i = 0; i < max_saved; i++) {
379 my->array[i].my = my;
380 my->array[i].cancelled = true;
381 fr_bio_queue_list_insert_tail(&my->free, &my->array[i]);
382 }
383
384 my->bio.read = fr_bio_next_read; /* the queue bio only changes the write path */
385 my->bio.write = fr_bio_queue_write_next;
386 my->priv_cb.shutdown = fr_bio_queue_shutdown;
387
388 fr_bio_chain(&my->bio, next);
389
390 if (my->cancel) {
391 talloc_set_destructor(my, fr_bio_queue_destructor);
392 } else {
393 talloc_set_destructor((fr_bio_t *) my, fr_bio_destructor);
394 }
395
396 return (fr_bio_t *) my;
397}
398
399/** Cancel the write for a packet.
400 *
401 * Cancel one a saved packets, and call the cancel() routine if it exists.
402 *
403 * There is no way to cancel all packets. The caller must find the lowest bio in the chain, and shutdown it.
404 * e.g. by closing the socket via fr_bio_fd_close(). That function will take care of walking back up the
405 * chain, and shutting down each bio.
406 *
407 * @param bio the #fr_bio_queue_t
408 * @param item The context returned from #fr_bio_queue_saved_t
409 * @return
410 * - <0 no such packet was found in the list of saved packets, OR the packet cannot be cancelled.
411 * - 0 the packet was cancelled.
412 */
414{
415 fr_bio_queue_t *my = talloc_get_type_abort(bio, fr_bio_queue_t);
416
417 if (!((item >= &my->array[0]) && (item < &my->array[my->max_saved]))) {
418 return -1;
419 }
420
421 /*
422 * Already cancelled, that's a NOOP.
423 */
424 if (item->cancelled) return 0;
425
426 /*
427 * If the item has been partially written, AND we have a working write function, see if we can
428 * cancel it.
429 */
430 if (item->already_written && (my->bio.write != fr_bio_shutdown_write)) {
431 ssize_t rcode;
432 fr_bio_t *next;
433
434 next = fr_bio_next(bio);
435 fr_assert(next != NULL);
436
437 /*
438 * If the write fails or returns nothing, the item can't be cancelled.
439 */
440 rcode = next->write(next, item->packet_ctx, ((uint8_t const *) item->buffer) + item->already_written, item->size - item->already_written);
441 if (rcode <= 0) return -1;
442
443 /*
444 * If we haven't written the full item, it can't be cancelled.
445 */
446 item->already_written += rcode;
447 if (item->already_written < item->size) return -1;
448
449 /*
450 * Else the item has been fully written, it can be safely cancelled.
451 */
452 }
453
454 /*
455 * Remove it from the saved list, and run the cancellation callback.
456 */
457 (void) fr_bio_queue_list_remove(&my->pending, item);
458 fr_bio_queue_list_insert_head(&my->free, item);
459 item->cancelled = true;
460
461 if (my->cancel) my->cancel(bio, item->packet_ctx, item->buffer, item->size);
462
463 return 0;
464}
static int const char char buffer[256]
Definition acutest.h:641
fr_bio_write_t _CONST write
write to the underlying bio
Definition base.h:117
static fr_bio_t * fr_bio_next(fr_bio_t *bio)
Definition base.h:131
#define fr_bio_error(_x)
Definition base.h:200
fr_bio_t * fr_bio_queue_alloc(TALLOC_CTX *ctx, size_t max_saved, fr_bio_queue_saved_t saved, fr_bio_queue_callback_t sent, fr_bio_queue_callback_t cancel, fr_bio_t *next)
Allocate a packet-based bio.
Definition queue.c:351
fr_bio_queue_callback_t cancel
Definition queue.c:63
static int fr_bio_queue_destructor(fr_bio_queue_t *my)
Definition queue.c:102
static ssize_t fr_bio_queue_list_push(fr_bio_queue_t *my, void *packet_ctx, const void *buffer, size_t size, size_t already_written)
Push a packet onto a list.
Definition queue.c:116
int fr_bio_queue_cancel(fr_bio_t *bio, fr_bio_queue_entry_t *item)
Cancel the write for a packet.
Definition queue.c:413
void const * buffer
Definition queue.c:44
fr_bio_queue_callback_t sent
Definition queue.c:62
static ssize_t fr_bio_queue_write_flush(fr_bio_queue_t *my, size_t size)
Flush the packet list.
Definition queue.c:192
struct fr_bio_queue_list_s fr_bio_queue_list_t
Definition queue.c:29
size_t max_saved
Definition queue.c:59
fr_bio_queue_t * my
Definition queue.c:49
void * packet_ctx
Definition queue.c:43
struct fr_bio_queue_s fr_bio_queue_t
Definition queue.c:30
fr_bio_queue_saved_t saved
Definition queue.c:61
size_t already_written
Definition queue.c:46
static int fr_bio_queue_shutdown(fr_bio_t *bio)
Shutdown.
Definition queue.c:298
static ssize_t fr_bio_queue_write_next(fr_bio_t *bio, void *packet_ctx, void const *buffer, size_t size)
Write one packet to the next bio.
Definition queue.c:148
static ssize_t fr_bio_queue_write_buffer(fr_bio_t *bio, void *packet_ctx, void const *buffer, size_t size)
Write to the packet list buffer.
Definition queue.c:282
static void fr_bio_queue_list_cancel(fr_bio_queue_t *my)
Forcibly cancel all outstanding packets.
Definition queue.c:84
Definition queue.c:42
void(* fr_bio_queue_callback_t)(fr_bio_t *bio, void *packet_ctx, const void *buffer, size_t size)
Definition queue.h:32
struct fr_bio_queue_entry_s fr_bio_queue_entry_t
Definition queue.h:30
void(* fr_bio_queue_saved_t)(fr_bio_t *bio, void *packet_ctx, const void *buffer, size_t size, fr_bio_queue_entry_t *queue_ctx)
Definition queue.h:33
static int packet_ctx
the tests pass only the address of packet_ctx
static void fr_bio_chain(fr_bio_t *first, fr_bio_t *second)
Chain one bio after another.
Definition bio_priv.h:86
#define FR_BIO_DESTRUCTOR_COMMON
Define a common destructor pattern.
Definition bio_priv.h:65
#define FR_DLIST_TYPES(_name)
Define type specific wrapper structs for dlists.
Definition dlist.h:1150
#define FR_DLIST_ENTRY(_name)
Expands to the type name used for the entry wrapper structure.
Definition dlist.h:1136
#define FR_DLIST_FUNCS(_name, _element_type, _element_entry)
Define type specific wrapper functions for dlists.
Definition dlist.h:1173
#define FR_DLIST_HEAD(_name)
Expands to the type name used for the head wrapper structure.
Definition dlist.h:1143
free(array)
int fr_bio_destructor(fr_bio_t *bio)
Free this bio.
Definition base.c:35
ssize_t fr_bio_shutdown_write(UNUSED fr_bio_t *bio, UNUSED void *packet_ctx, UNUSED void const *buffer, UNUSED size_t size)
Definition base.c:79
ssize_t fr_bio_next_read(fr_bio_t *bio, void *packet_ctx, void *buffer, size_t size)
Internal bio function which just reads from the "next" bio.
Definition base.c:49
static void * item(fr_lst_t const *lst, fr_lst_index_t idx)
Definition lst.c:121
long int ssize_t
unsigned char uint8_t
#define fr_assert(_expr)
Definition rad_assert.h:37