The FreeRADIUS server $Id: a4e7f8c94e8038b7627bcf52356b7ed15955c1d7 $
Loading...
Searching...
No Matches
dedup.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: 6d2a67b6dc255a7d52a7df1d962d35370be4e444 $
19 * @file lib/bio/dedup.c
20 * @brief Binary IO abstractions for deduping packets.
21 *
22 * The dedup BIO receives packets from the network, and allows for deduplication of requests, so that
23 * duplicate requests are only processed once. In addition, if a reply is available, a duplicate request will
24 * result in a duplicate reply. The actual deduplication tree / table has to be maintained by the calling
25 * application, as packet comparisons for deduplication is very protocol-specific. The purpose of the dedup
26 * BIO is to abstract all of the common support functions around this limitation.
27 *
28 * When packets are read() from the next bio, the #fr_bio_dedup_receive_t callback is run. It tells the BIO
29 * whether or not the packet should be received, and whether or not the packet should be returned to the
30 * caller. The receive callback is also passed a #fr_bio_dedup_entry_t pointer, where the packet_ctx, packet,
31 * and size are already filled out. This entry is used to correlate requests and replies.
32 *
33 * When packets are write() to the network, the #fr_bio_dedup_get_item_t callback is called to get the
34 * previously cached #fr_bio_dedup_entry_t pointer. This is because there is no generic way to get an
35 * additional context to this BIO via the write() routine. i.e. the packet_ctx for write() may include things
36 * like src/dst ip/port, and therefore can't always be an #fr_bio_dedup_entry_t. The caller should associate
37 * the #fr_bio_dedup_entry_t with the packet_ctx for the reply. The get_item() routine can then return the entry.
38 *
39 * For simplicity, the next bio should be a memory one. That way the read() can read more than one packet if
40 * necessary. And the write() can cache a partial packet if it blocks.
41 *
42 * The entry needs to be cached in order to maintain the internal tracking used by the dedup BIO.
43 *
44 * On client retransmit, the #fr_bio_dedup_receive_t callback is run, just as if it is a new packet. The
45 * dedup BIO does not know if the received data is a new packet until the #fr_bio_dedup_receive_t callback
46 * says so. On duplicate client request, the #fr_bio_dedup_receive_t callback can call fr_bio_dedup_respond()
47 * to send a duplicate reply. That call bypasses the normal dedup stack, and writes directly to the next bio.
48 *
49 * The calling application can also call fr_bio_dedup_respond() as soon as it has a reply. i.e. skip the BIO
50 * write() call. That works, and is safe.
51 *
52 * The dedup BIO tracks a number of lists / trees internally. Packets which are received but which have no
53 * reply are in an "active" list. Packets which have a reply are in an "expired" RB tree, where a timer is
54 * set to expire packets. If a write() call results in a partial write, that packet is put into a "partially
55 * written" state. If multiple calls to write() are done when writing is blocked, the replies are put into a
56 * "pending" state.
57 *
58 * The calling application can always call fr_bio_dedup_cancel() to cancel or expire a packet. This call is
59 * safe, and can be made at any time, no matter what state the packet is in.
60 *
61 * @copyright 2024 Network RADIUS SAS (legal@networkradius.com)
62 */
63
64#include <freeradius-devel/bio/bio_priv.h>
65#include <freeradius-devel/bio/null.h>
66#include <freeradius-devel/bio/buf.h>
67
68#define _BIO_DEDUP_PRIVATE
69#include <freeradius-devel/bio/dedup.h>
70
71typedef struct fr_bio_dedup_list_s fr_bio_dedup_list_t;
73
74/*
75 * There is substantial similarity between this code and the
76 * "retry" bio. Any fixes which are done here should be checked
77 * there, and vice versa.
78 */
79
80/*
81 * Define type-safe wrappers for head and entry definitions.
82 */
83FR_DLIST_TYPES(fr_bio_dedup_list)
84
85typedef enum : uint8_t {
87 FR_BIO_DEDUP_STATE_ACTIVE, //!< Received, but not replied.
88 FR_BIO_DEDUP_STATE_PENDING, //!< Have a reply, but we're trying to write it out.
89 FR_BIO_DEDUP_STATE_REPLIED, //!< Replied, and waiting for it to expire.
90 FR_BIO_DEDUP_STATE_PARTIAL, //!< Partially written
91 FR_BIO_DEDUP_STATE_CANCELLED, //!< Partially written, and then cancelled.
93
95 void *uctx;
97 uint8_t *packet; //!< cached packet data for finding duplicates
98 size_t packet_size; //!< size of the cached packet data
99 void *reply_ctx; //!< reply ctx
100 uint8_t *reply; //!< reply cached by the application
101 size_t reply_size; //!< size of the cached reply
102
103 fr_rb_node_t dedup; //!< user managed dedup node
104
105 union {
106 struct {
107 fr_rb_node_t node; //!< for the expiry timers
108 };
109 FR_DLIST_ENTRY(fr_bio_dedup_list) entry; //!< for the free list
110 };
111
112 fr_bio_dedup_t *my; //!< so we can get to it from the event timer callback
113
114 fr_time_t expires; //!< when this entry expires
115 fr_bio_dedup_state_t state; //!< which tree or list this item is in
116};
117
118FR_DLIST_FUNCS(fr_bio_dedup_list, fr_bio_dedup_entry_t, entry)
119
122
124
125 fr_rb_tree_t rb; //!< expire list
126
128
130
131 /*
132 * The "first" entry is cached here so that we can detect when it changes. The insert / delete
133 * code can just do its work without worrying about timers. And then when the tree manipulation
134 * is done, call the fr_bio_dedup_timer_reset() function to reset (or not) the timer.
135 *
136 * The timer is set for is when the first packet expires.
137 */
139
140 /*
141 * Cache a partial write when IO is blocked.
142 *
143 * When the IO is blocked, we can still expire old entries, unlike the "retry" BIOs. This is
144 * because we're not resending packets, we're just cleaning up *sent* packets when they expire.
145 */
147
148 fr_bio_dedup_receive_t receive; //!< called when we receive a potentially new packet
149 fr_bio_dedup_release_t release; //!< called to release a packet
150
151 fr_bio_dedup_get_item_t get_item; //!< turn a packet_ctx into a #fr_bio_dedup_entry_t
152
154
155 FR_DLIST_HEAD(fr_bio_dedup_list) active; //!< received but not yet replied
156 FR_DLIST_HEAD(fr_bio_dedup_list) pending; //!< trying to write when the socket is blocked.
157 FR_DLIST_HEAD(fr_bio_dedup_list) free; //!< free list
158};
159
160static void fr_bio_dedup_timer(UNUSED fr_timer_list_t *el, fr_time_t now, void *uctx);
161static ssize_t fr_bio_dedup_write(fr_bio_t *bio, void *packet_ctx, void const *buffer, size_t size);
165
167{
168 if (my->first != item) return;
169
170 my->first = NULL;
171 (void) fr_bio_dedup_timer_reset(my);
172}
173
174/** Move an item from active to replied.
175 *
176 * Note that we don't update any timers. The caller is responsible for that.
177 */
179{
180 if (item->state == FR_BIO_DEDUP_STATE_REPLIED) return;
181
183
184 (void) fr_bio_dedup_list_remove(&my->active, item);
185
186 /*
187 * Now that we have a reply, set the default expiry time. The caller can always call
188 * fr_bio_dedup_entry_extend() to change the expiry time.
189 */
191
192 /*
193 * This should never fail.
194 */
195 (void) fr_rb_insert(&my->rb, item);
197}
198
199/** Resend a reply when we receive a duplicate request.
200 *
201 * This function should be called by the respond() callback to re-send a duplicate reply.
202 *
203 * It can also be called by the application when it first has a response to the request.
204 *
205 * @param bio the binary IO handler
206 * @param item the dedup context from #fr_bio_dedup_sent_t
207 * @return
208 * - <0 on error
209 * - 0 for "wrote no data"
210 * - >0 for "wrote data".
211 */
213{
214 ssize_t rcode;
215 fr_bio_dedup_t *my = talloc_get_type_abort(bio, fr_bio_dedup_t);
216 fr_bio_t *next;
217
218 if (!item->reply || !item->reply_size) return 0;
219
220 switch (item->state) {
221 /*
222 * Send a first reply if we can.
223 */
225 /*
226 * If we're not writing to the socket, just insert the packet into the pending list.
227 */
228 if (my->bio.write != fr_bio_dedup_write) {
229 (void) fr_bio_dedup_list_remove(&my->active, item);
230 fr_bio_dedup_list_insert_tail(&my->pending, item);
231
234 return item->reply_size;
235 }
236
237 /*
238 * The socket is writable, go do that.
239 */
240 break;
241
242 /*
243 * Send a duplicate reply.
244 */
246 if (my->bio.write == fr_bio_dedup_write) break;
247
248 /*
249 * The socket is blocked. Save the packet in the pending queue.
250 */
251 move_to_pending:
252 fr_rb_remove_by_inline_node(&my->rb, &item->node);
253
254 save_in_pending:
255 /*
256 * We could update the timer for pending packets. However, that's more complicated.
257 *
258 * The packets will be expire when the pending queue is flushed, OR when the application
259 * cancels the pending packet.
260 */
262
263 fr_bio_dedup_list_insert_tail(&my->pending, item);
265 return item->reply_size;
266
267 /*
268 * We're already trying to write this entry, don't do anything else.
269 */
272
274 return fr_bio_error(IO_WOULD_BLOCK);
275
278 fr_assert(0);
279 return fr_bio_error(GENERIC);
280 }
281
282 /*
283 * There must be a next bio.
284 */
285 next = fr_bio_next(&my->bio);
286 fr_assert(next != NULL);
287
288 /*
289 * Write out the packet, if everything is OK, return.
290 */
291 rcode = next->write(next, item->reply_ctx, item->reply, item->reply_size);
292 if ((size_t) rcode == item->reply_size) {
294 (void) fr_bio_dedup_timer_reset(my);
295 return rcode;
296 }
297
298 /*
299 * Can't write anything, be sad.
300 */
301 if ((rcode == 0) || (rcode == fr_bio_error(IO_WOULD_BLOCK))) {
302 if (item->state == FR_BIO_DEDUP_STATE_ACTIVE) {
303 (void) fr_bio_dedup_list_remove(&my->active, item);
305 goto save_in_pending;
306 }
307
309 goto move_to_pending;
310 }
311
312 /*
313 * There's an error writing the packet. Release it, and move the item to the free list.
314 *
315 * Note that we don't bother resetting the timer. There's no point in changing the timer when
316 * the bio is likely dead.
317 */
318 if (rcode < 0) {
320 return rcode;
321 }
322
323 /*
324 * We are writing item->reply, and that's blocked. Save the partial packet for later.
325 */
326 return fr_bio_dedup_blocked(my, item, rcode);
327}
328
329/** Reset the timer after changing the rb tree.
330 *
331 */
333{
335
336 /*
337 * Nothing to do, don't set any timers.
338 */
339 first = fr_rb_first(&my->rb);
340 if (!first) {
342 my->ev = NULL;
343 my->first = NULL;
344 return 0;
345 }
346
347 /*
348 * We don't care about partially written packets. The timer remains set even when we have a
349 * partial outgoing packet, because we can expire entries which aren't partially written.
350 *
351 * However, the partially written packet MUST NOT be in the expiry tree.
352 *
353 * We also don't care about the pending list. The application will either cancel the item, or
354 * the socket will become writable, and the item will get handled then.
355 */
356 fr_assert(first != my->partial);
357
358 /*
359 * The timer is already set correctly, we're done.
360 */
361 if (first == my->first) return 0;
362
363 /*
364 * Update the timer. This should never fail.
365 */
366 if (fr_timer_at(my, my->el->tl, &my->ev, first->expires, false, fr_bio_dedup_timer, my) < 0) return -1;
367
368 my->first = first;
369 return 0;
370}
371
372/** Release an entry back to the free list.
373 *
374 */
376{
377 my->release((fr_bio_t *) my, item, reason);
378
379 switch (item->state) {
380 /*
381 * Cancel an active item, just nuke it.
382 */
384 fr_bio_dedup_list_remove(&my->active, item);
385 break;
386
387 /*
388 * We already replied, remove it from the expiry tree.
389 *
390 * We only update the timer if the caller isn't already expiring the entry.
391 */
393 (void) fr_rb_remove_by_inline_node(&my->rb, &item->node);
394
396 break;
397
398 /*
399 * It was pending another write, so we just discard the write.
400 */
402 fr_bio_dedup_list_remove(&my->pending, item);
403 break;
404
405 /*
406 * Don't free it. Just set its state to cancelled.
407 */
409 fr_assert(my->partial == item);
411 return;
412
415 fr_assert(0);
416 return;
417 }
418
419#ifndef NDEBUG
420 item->packet = NULL;
421#endif
422 item->uctx = NULL;
423 item->packet_ctx = NULL;
424
425 fr_assert(my->first != item);
426 fr_bio_dedup_list_insert_head(&my->free, item);
427}
428
429/** Flush any packets in the pending queue.
430 *
431 */
433{
434 ssize_t rcode, out_rcode;
436 fr_bio_t *next;
437 fr_time_t now;
438
439 /*
440 * We can only flush the pending list when any previous partial packet has been written.
441 */
442 fr_assert(!my->partial);
443
444 /*
445 * Nothing in the list, we're done.
446 */
447 if (fr_bio_dedup_list_num_elements(&my->pending) == 0) return 0;
448
449 /*
450 * There must be a next bio.
451 */
452 next = fr_bio_next(&my->bio);
453 fr_assert(next != NULL);
454
455 now = fr_time();
456 out_rcode = 0;
457
458 /*
459 * Write out any pending packets.
460 */
461 while ((item = fr_bio_dedup_list_pop_head(&my->pending)) != NULL) {
463
464 /*
465 * It's already expired, don't bother replying.
466 */
467 if (fr_time_lteq(item->expires, now)) {
469 continue;
470 }
471
472 /*
473 * Write the entry to the next bio.
474 */
475 rcode = next->write(next, item->reply_ctx, item->reply, item->reply_size);
476
477 /*
478 * The next bio is blocked. Put the entry back at the head of the pending list, so that we write
479 * the entry first when writes resume.
480 */
481 if ((rcode == 0) || (rcode == fr_bio_error(IO_WOULD_BLOCK))) {
482 fr_bio_dedup_list_insert_head(&my->pending, item);
483 out_rcode = fr_bio_error(IO_WOULD_BLOCK);
484 break;
485 }
486
487 /*
488 * The next bio returned an error other than IO_WOULD_BLOCK. Release the entry, and return the
489 * error. The next bio has already decided whether the error is fatal, and has shut the chain
490 * down if the error is fatal.
491 */
492 if (rcode < 0) {
494 out_rcode = rcode;
495 break;
496 }
497
498 /*
499 * We've written the entire packet, move it to the expiry list.
500 */
501 if ((size_t) rcode == item->reply_size) {
502 (void) fr_rb_insert(&my->rb, item);
504 continue;
505 }
506
507 fr_bio_dedup_blocked(my, item, rcode);
508
509 out_rcode = fr_bio_error(IO_WOULD_BLOCK);
510 break;
511 }
512
513 /*
514 * We may need to update the timer if we've removed the first entry from the tree, or added a new
515 * first entry.
516 */
517 if (!my->first || (my->first != fr_rb_first(&my->rb))) {
518 my->first = NULL;
519 (void) fr_bio_dedup_timer_reset(my);
520 }
521
522 return out_rcode;
523}
524
525/** Save partially written data to our local buffer.
526 *
527 */
528static int fr_bio_dedup_buffer_save(fr_bio_dedup_t *my, uint8_t const *buffer, size_t size, ssize_t rcode)
529{
530 /*
531 * (re)-alloc the buffer for partial writes.
532 */
533 if (!my->buffer.start ||
534 (size > fr_bio_buf_size(&my->buffer))) {
535 if (fr_bio_buf_alloc(my, &my->buffer, size) < 0) return -1;
536 }
537
538 fr_assert(fr_bio_buf_used(&my->buffer) == 0);
539 fr_assert(my->buffer.read == my->buffer.start);
540
541 fr_bio_buf_write(&my->buffer, buffer + rcode, size - rcode);
542
543 return 0;
544}
545
546/** Write data from our local buffer to the next bio.
547 *
548 */
550{
551 size_t used;
552 ssize_t rcode;
553 fr_bio_t *next;
554
556
558 fr_assert(used > 0);
559
560 /*
561 * There must be a next bio.
562 */
563 next = fr_bio_next(&my->bio);
564 fr_assert(next != NULL);
565
566 rcode = next->write(next, NULL, my->buffer.read, used);
567 if (rcode <= 0) return rcode;
568
569 my->buffer.read += rcode;
570
571 /*
572 * Still data in the buffer. We can't send more packets until we finish writing this one.
573 */
574 if (fr_bio_buf_used(&my->buffer) > 0) return 0;
575
576 /*
577 * We're done. Reset the buffer and clean up our cached partial packet.
578 */
580
581 return rcode;
582}
583
584/** There's a partial packet written. Write all of that one first, before writing another packet.
585 *
586 * The packet can either be cancelled, or IO blocked. In either case, we must write this packet before
587 * we can write another one.
588 */
589static ssize_t fr_bio_dedup_write_partial(fr_bio_t *bio, void *packet_ctx, const void *buffer, size_t size)
590{
591 ssize_t rcode;
592 fr_bio_dedup_t *my = talloc_get_type_abort(bio, fr_bio_dedup_t);
594
595 fr_assert(my->partial != NULL);
597
600
601 rcode = fr_bio_dedup_buffer_write(my);
602 if (rcode <= 0) return rcode;
603
604 my->partial = NULL;
605
606 /*
607 * Partial writes are removed from the expiry tree until they're fully written. When they're
608 * written, either add it back to the tree if it's still operational, or add it to the free list
609 * if it has been cancelled.
610 */
611 if (item->state == FR_BIO_DEDUP_STATE_PARTIAL) {
612
613 /*
614 * See if we have to clean up this entry. If so, do it now. That avoids another bounce
615 * through the event loop.
616 */
617 if (fr_time_lteq(item->expires, fr_time())) {
619
620 } else {
621 /*
622 * We've changed the tree, so update the timer. fr_bio_dedup_write() only
623 * updates the timer on successful write.
624 */
626 (void) fr_rb_insert(&my->rb, item);
627 }
628 (void) fr_bio_dedup_timer_reset(my);
629
630 } else {
631 /*
632 * The item was cancelled, add it to the free list.
633 */
634#ifndef NDEBUG
635 item->packet = NULL;
636#endif
637 item->uctx = NULL;
638 item->packet_ctx = NULL;
639
641 fr_bio_dedup_list_insert_head(&my->free, item);
642 }
643
644 /*
645 * We have written all of the partial entry, so we restore fr_bio_dedup_write() as the write
646 * function. If the next bio writes only part of a pending reply, fr_bio_dedup_blocked() installs
647 * fr_bio_dedup_write_partial() again.
648 */
649 my->bio.write = fr_bio_dedup_write;
650
651 /*
652 * Flush any packets which were pending during the blocking period.
653 */
654 rcode = fr_bio_dedup_flush_pending(my);
655 if (rcode < 0) return rcode;
656
657 /*
658 * Unlike the retry BIO, we don't retry writes for items in the RB tree. Those packets have already
659 * been written.
660 */
661
662 /*
663 * Try to write the packet which we were given.
664 */
665 return fr_bio_dedup_write(bio, packet_ctx, buffer, size);
666}
667
668/** The write is blocked.
669 *
670 * We couldn't write out the entire packet, the bio is blocked. Don't write anything else until we become
671 * unblocked!
672 *
673 * Do NOT free the timer. We can still expire old entries. This newly written entry usually ends up as the
674 * _last_ item in the RB tree.
675 */
677{
678 fr_assert(!my->partial);
679 fr_assert(rcode > 0);
680 fr_assert((size_t) rcode < item->reply_size);
681
682 if (fr_bio_dedup_buffer_save(my, item->reply, item->reply_size, rcode) < 0) return fr_bio_error(OOM);
683
684 switch (item->state) {
686 (void) fr_bio_dedup_list_remove(&my->active, item);
687 break;
688
689 /*
690 * We cannot expire this entry, so remove it from the expiration tree. That step lets us
691 * expire other entries.
692 */
694 (void) fr_rb_remove_by_inline_node(&my->rb, &item->node);
696 break;
697
698 /*
699 * The next bio wrote only part of a pending reply. fr_bio_dedup_flush_pending() has already
700 * removed the entry from the pending list.
701 */
703 break;
704
705 /*
706 * None of these states should be possible.
707 */
711 fr_assert(0);
712 return fr_bio_error(GENERIC);
713 }
714
715 my->partial = item;
717
718 /*
719 * Reset the write routine, so that if the application tries any more writes, the partial entry
720 * gets written first.
721 */
722 my->bio.write = fr_bio_dedup_write_partial;
723 return rcode;
724}
725
726/** There's a partial block of data written. Write all of that data first, before writing another packet.
727 */
728static ssize_t fr_bio_dedup_write_data(fr_bio_t *bio, void *packet_ctx, const void *buffer, size_t size)
729{
730 ssize_t rcode;
731 fr_bio_dedup_t *my = talloc_get_type_abort(bio, fr_bio_dedup_t);
732
733 fr_assert(!my->partial);
734
735 /*
736 * Flush out any partly written data.
737 */
738 rcode = fr_bio_dedup_buffer_write(my);
739 if (rcode <= 0) return rcode;
740
741 /*
742 * We have written all of the partial data, so we restore fr_bio_dedup_write() as the write
743 * function. If the next bio writes only part of a pending reply, fr_bio_dedup_blocked() installs
744 * fr_bio_dedup_write_partial().
745 */
746 my->bio.write = fr_bio_dedup_write;
747
748 /*
749 * Flush any packets which were pending during the blocking period.
750 */
751 rcode = fr_bio_dedup_flush_pending(my);
752 if (rcode < 0) return rcode;
753
754 /*
755 * Try to write the packet which we were given.
756 */
757 return fr_bio_dedup_write(bio, packet_ctx, buffer, size);
758}
759
760
761/** The write is blocked, but we don't have "item".
762 *
763 * We couldn't write out the entire packet, the bio is blocked. Don't write anything else until we become
764 * unblocked!
765 *
766 * Do NOT free the timer. We can still expire old entries. This newly written entry usually ends up as the
767 * _last_ item in the RB tree.
768 */
770{
771 fr_assert(!my->partial);
772 fr_assert(rcode > 0);
773 fr_assert((size_t) rcode < size);
774
775 if (fr_bio_dedup_buffer_save(my, buffer, size, rcode) < 0) return fr_bio_error(OOM);
776
777 /*
778 * Reset the write routine, so that if the application tries any more writes, the data
779 * gets written first.
780 */
781 my->bio.write = fr_bio_dedup_write_data;
782 return rcode;
783}
784
785/*
786 * There is no fr_bio_dedup_rewrite(), packets are never re-written by this bio.
787 */
788
789/** Resume writes.
790 *
791 * Write the rest of any partly written reply or data, and then write the replies on the pending list.
792 *
793 * @return
794 * - <0 on error
795 * - 0 for "still blocked"
796 * - 1 for "can resume"
797 */
799{
800 ssize_t rcode;
801 fr_bio_dedup_t *my = talloc_get_type_abort(bio, fr_bio_dedup_t);
802
803 if (my->bio.write == fr_bio_dedup_write) {
804 rcode = fr_bio_dedup_flush_pending(my);
805
806 } else {
807 /*
808 * fr_bio_dedup_write_partial() or fr_bio_dedup_write_data() writes the rest of the partial
809 * data, and then writes the replies on the pending list. The NULL buffer tells
810 * fr_bio_dedup_write() to flush the next bio.
811 */
812 rcode = my->bio.write(bio, NULL, NULL, SIZE_MAX);
813 }
814
815 if ((rcode < 0) && (rcode != fr_bio_error(IO_WOULD_BLOCK))) return rcode;
816
817 /*
818 * We are still blocked if there is partial data, or a pending reply. A flush of the next bio may
819 * also return IO_WOULD_BLOCK, but the write_resume callback of the next bio resumes the next bio.
820 */
821 if (my->bio.write != fr_bio_dedup_write) return 0;
822
823 return (fr_bio_dedup_list_num_elements(&my->pending) == 0);
824}
825
826/** Expire an entry when its timer fires.
827 *
828 * @todo - expire items from the pending list, too
829 */
830static void fr_bio_dedup_timer(UNUSED fr_timer_list_t *tl, fr_time_t now, void *uctx)
831{
832 fr_bio_dedup_t *my = talloc_get_type_abort(uctx, fr_bio_dedup_t);
834 fr_time_t expires;
835
836 fr_assert(my->first != NULL);
837 fr_assert(fr_rb_first(&my->rb) == my->first);
838
839 my->first = NULL;
840
841 /*
842 * Expire all entries which are within 10ms of "now". That way we don't reset the event many
843 * times in short succession.
844 *
845 * @todo - also expire entries on the pending list?
846 */
847 expires = fr_time_add(now, fr_time_delta_from_msec(10));
848
849 while ((item = fr_rb_first(&my->rb)) != NULL) {
850 if (fr_time_gt(item->expires, expires)) break;
851
853 }
854
855 (void) fr_bio_dedup_timer_reset(my);
856}
857
858/** Write raw data to the bio.
859 *
860 * This function is largely a duplicate of fr_bio_dedup_respond(). Except due to the BIO API, it can be
861 * passed a NULL buffer (for flushing the BIOs), and it can't be passed a #fr_bio_dedup_entry_t, and instead
862 * has to be passed a "void *packet_ctx".
863 *
864 * The caller is free to ignore this function,
865 */
866static ssize_t fr_bio_dedup_write(fr_bio_t *bio, void *packet_ctx, void const *buffer, size_t size)
867{
868 ssize_t rcode;
870 fr_bio_dedup_t *my = talloc_get_type_abort(bio, fr_bio_dedup_t);
871 fr_bio_t *next;
872
873 fr_assert(!my->partial);
874
875 /*
876 * There must be a next bio.
877 */
878 next = fr_bio_next(&my->bio);
879 fr_assert(next != NULL);
880
881 /*
882 * The caller is trying to flush partial data. But we don't have any partial data, so just call
883 * the next bio to flush it.
884 */
885 if (!buffer) {
886 return next->write(next, packet_ctx, NULL, size);
887 }
888
889 /*
890 * Write out the packet. If there's an error, OR we wrote nothing, return.
891 *
892 * Note that we don't mark the socket as blocked if the next bio didn't write anything. We want
893 * the caller to know that the write didn't succeed, and the caller takes care of managing the
894 * current packet. So there's no need for us to do that.
895 */
896 rcode = next->write(next, packet_ctx, buffer, size);
897 if (rcode <= 0) return rcode;
898
899 /*
900 * We need the item pointer to mark this entry as blocked. If that doesn't exist, then we try
901 * really hard to write out the un-tracked data.
902 */
903 item = NULL;
904 if (my->get_item) item = my->get_item(bio, packet_ctx);
905 if ((size_t) rcode == size) {
906 if (item) {
908 (void) fr_bio_dedup_timer_reset(my);
909 }
910 return rcode;
911 }
912
913 if (!item) return fr_bio_dedup_blocked_data(my, buffer, size, rcode);
914
915 fr_assert(item->reply_ctx == packet_ctx);
916 fr_assert(item->reply == buffer);
917 fr_assert(item->reply_size == size);
918
919 return fr_bio_dedup_blocked(my, item, rcode);
920}
921
922static ssize_t fr_bio_dedup_read(fr_bio_t *bio, void *packet_ctx, void *buffer, size_t size)
923{
924 ssize_t rcode;
926 fr_bio_dedup_t *my = talloc_get_type_abort(bio, fr_bio_dedup_t);
927 fr_bio_t *next;
928
929 /*
930 * There must be a next bio.
931 */
932 next = fr_bio_next(&my->bio);
933 fr_assert(next != NULL);
934
935 /*
936 * Read the packet. If error or nothing, return immediately.
937 */
938 rcode = next->read(next, packet_ctx, buffer, size);
939 if (rcode <= 0) return rcode;
940
941 /*
942 * Get a free item
943 */
944 item = fr_bio_dedup_list_pop_head(&my->free);
945 if (!item) return fr_bio_error(OOM);
946
947 fr_assert(item->my == my);
949 .my = my,
950 .packet_ctx = packet_ctx,
951 .packet = buffer,
952 .packet_size = (size_t) rcode,
954 };
955
956 /*
957 * See if we want to receive this packet. If this isn't
958 * something we need to receive, then we just discard it.
959 *
960 * The "receive" function is responsible for looking in a local dedup tree to see if there's a
961 * cached reply. It's also responsible for calling the fr_bio_retry_respond() function to send
962 * a duplicate reply, and then return "don't receive" this packet.
963 *
964 * The application can alos call fr_bio_dedup_entry_extend() in order to extend the lifetime of a
965 * packet which has a cached response.
966 *
967 * If there's an active packet, then the receive() function should do whatever it needs to do in
968 * order to update the application for a duplicate packet. And then return "don't receive" for
969 * this packet.
970 *
971 * If we're NOT going to process this packet, then the item we just popped needs to get inserted
972 * back into the free list.
973 *
974 * The caller should cancel any conflicting packets by calling fr_bio_dedup_entry_cancel(). Note
975 * that for sanity, we don't re-use the previous #fr_bio_dedup_entry_t.
976 */
977 if (!my->receive(bio, item, packet_ctx)) {
979 fr_bio_dedup_list_insert_head(&my->free, item);
980 return 0;
981 }
982
983 fr_bio_dedup_list_insert_tail(&my->active, item);
984
985 return rcode;
986}
987
988static fr_cmp_ret_t _entry_cmp(void const *one, void const *two)
989{
990 fr_bio_dedup_entry_t const *a = one;
991 fr_bio_dedup_entry_t const *b = two;
992 int ret;
993
994 fr_assert(a->packet);
995 fr_assert(b->packet);
996
997 ret = fr_time_cmp(a->expires, b->expires);
998 if (ret != 0) return ret;
999
1000 /*
1001 * Technically two timestamps can be equal, even at nanosecond resolution. We pick something
1002 * else to make the entries unique. All we care is that (a) the entries are ordered, and (b)
1003 * that they are unique.
1004 */
1005 return CMP(a, b);
1006}
1007
1008/** Cancel one item.
1009 *
1010 * @param bio the binary IO handler
1011 * @param item the dedup context from #fr_bio_dedup_respond_t
1012 */
1014{
1015 fr_bio_dedup_t *my = talloc_get_type_abort(bio, fr_bio_dedup_t);
1016
1018
1020}
1021
1022/** Extend the expiry time for an entry
1023 *
1024 * @param bio the binary IO handler
1025 * @param item the dedup context from #fr_bio_dedup_respond_t
1026 * @param expires the new expiry time
1027 * @return
1028 * - <0 error
1029 * - 0 success
1030 */
1032{
1033 fr_bio_dedup_t *my = talloc_get_type_abort(bio, fr_bio_dedup_t);
1034
1035 switch (item->state) {
1037 return 0;
1038
1040 break;
1041
1042 /*
1043 * Partially written or pending replies aren't in the expirty tree. We can just change their
1044 * expiry time and be done.
1045 */
1048 item->expires = expires;
1049 return 0;
1050
1053 fr_assert(0);
1054 return fr_bio_error(GENERIC);
1055 }
1056
1057 /*
1058 * Change places in the tree.
1059 */
1060 item->expires = expires;
1061 (void) fr_rb_remove_by_inline_node(&my->rb, &item->node);
1062 (void) fr_rb_insert(&my->rb, item);
1063
1064 /*
1065 * If we're not changing the first item, we don't need to change the timers.
1066 *
1067 * Otherwise we clear the "first" flag, so that the reset timer function will change the timer
1068 * value.
1069 */
1070 if (my->first != item) return 0;
1071
1072 my->first = NULL;
1073
1074 return fr_bio_dedup_timer_reset(my);
1075}
1076
1077
1078/** Remove the dedup cache
1079 *
1080 */
1082{
1085 fr_bio_dedup_t *my = talloc_get_type_abort(bio, fr_bio_dedup_t);
1086
1087 talloc_const_free(my->ev);
1088
1089 /*
1090 * Cancel all outgoing packets. Don't bother updating the tree or the free list, as all of the
1091 * entries will be deleted when the memory is freed.
1092 */
1093 while ((item = fr_rb_iter_init_inorder(&my->rb, &iter)) != NULL) {
1094 fr_rb_iter_delete_inorder(&my->rb, &iter);
1096 }
1097
1098 while ((item = fr_bio_dedup_list_pop_head(&my->active)) != NULL) {
1100 }
1101
1102 while ((item = fr_bio_dedup_list_pop_head(&my->pending)) != NULL) {
1104 }
1105
1106#ifndef NDEBUG
1107 my->ev = NULL;
1108 my->first = NULL;
1109#endif
1110
1111 return 0;
1112}
1113
1114/** Allocate a #fr_bio_dedup_t
1115 *
1116 */
1117fr_bio_t *fr_bio_dedup_alloc(TALLOC_CTX *ctx, size_t max_saved,
1118 fr_bio_dedup_receive_t receive,
1119 fr_bio_dedup_release_t release,
1120 fr_bio_dedup_get_item_t get_item,
1121 fr_bio_dedup_config_t const *cfg,
1122 fr_bio_t *next)
1123{
1124 size_t i;
1125 fr_bio_dedup_t *my;
1126 fr_bio_dedup_entry_t *items;
1127
1128 fr_assert(cfg->el);
1129
1130 /*
1131 * Limit to reasonable values.
1132 */
1133 if (!max_saved) return NULL;
1134 if (max_saved > 65536) return NULL;
1135
1136 my = talloc_zero(ctx, fr_bio_dedup_t);
1137 if (!my) return NULL;
1138
1139 /*
1140 * Allocate everything up front, to get better locality of reference, less memory fragmentation,
1141 * and better reuse of data structures.
1142 */
1143 items = talloc_array(my, fr_bio_dedup_entry_t, max_saved);
1144 if (!items) {
1145 talloc_free(my);
1146 return NULL;
1147 }
1148
1149 /*
1150 * Insert the entries into the free list in order.
1151 */
1152 fr_bio_dedup_list_init(&my->free);
1153
1154 for (i = 0; i < max_saved; i++) {
1155 items[i].my = my;
1156 items[i].state = FR_BIO_DEDUP_STATE_FREE;
1157 fr_bio_dedup_list_insert_tail(&my->free, &items[i]);
1158 }
1159
1160 fr_bio_dedup_list_init(&my->active);
1161 fr_bio_dedup_list_init(&my->pending);
1162
1163 (void) fr_rb_inline_init(&my->rb, fr_bio_dedup_entry_t, node, _entry_cmp, NULL);
1164
1165 my->receive = receive;
1166 my->release = release;
1167 my->get_item = get_item;
1168
1169 my->el = cfg->el;
1170 my->config = *cfg;
1171
1172 my->bio.write = fr_bio_dedup_write;
1173 my->bio.read = fr_bio_dedup_read;
1174
1175 fr_bio_chain(&my->bio, next);
1176
1177 my->priv_cb.shutdown = fr_bio_dedup_shutdown;
1178 my->priv_cb.write_resume = fr_bio_dedup_write_resume;
1179
1180 talloc_set_destructor((fr_bio_t *) my, fr_bio_destructor); /* always use a common destructor */
1181
1182 return (fr_bio_t *) my;
1183}
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
fr_bio_read_t _CONST read
read from the underlying bio
Definition base.h:116
static fr_bio_t * fr_bio_next(fr_bio_t *bio)
Definition base.h:131
#define fr_bio_error(_x)
Definition base.h:200
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
int fr_bio_buf_alloc(TALLOC_CTX *ctx, fr_bio_buf_t *bio_buf, size_t size)
Definition buf.c:117
ssize_t fr_bio_buf_write(fr_bio_buf_t *bio_buf, const void *buffer, size_t size)
Definition buf.c:84
uint8_t * start
start of the buffer
Definition buf.h:30
static size_t fr_bio_buf_used(fr_bio_buf_t const *bio_buf)
Definition buf.h:73
static void fr_bio_buf_reset(fr_bio_buf_t *bio_buf)
Definition buf.h:61
uint8_t * read
where in the buffer reads are taken from
Definition buf.h:33
static size_t fr_bio_buf_size(fr_bio_buf_t const *bio_buf)
Definition buf.h:151
#define FALL_THROUGH
clang 10 doesn't recognised the FALL-THROUGH comment anymore
Definition build.h:422
#define CMP(_a, _b)
Same as CMP_PREFER_SMALLER use when you don't really care about ordering, you just want an ordering.
Definition build.h:130
#define UNUSED
Definition build.h:415
fr_cmp_ret_t
Result of an ordering comparison.
Definition build.h:113
ssize_t fr_bio_dedup_respond(fr_bio_t *bio, fr_bio_dedup_entry_t *item)
Resend a reply when we receive a duplicate request.
Definition dedup.c:212
static int fr_bio_dedup_shutdown(fr_bio_t *bio)
Remove the dedup cache.
Definition dedup.c:1081
fr_bio_dedup_release_t release
called to release a packet
Definition dedup.c:149
static void fr_bio_dedup_timer_reset_item(fr_bio_dedup_t *my, fr_bio_dedup_entry_t *item)
Definition dedup.c:166
static int fr_bio_dedup_write_resume(fr_bio_t *bio)
Resume writes.
Definition dedup.c:798
static int fr_bio_dedup_buffer_save(fr_bio_dedup_t *my, uint8_t const *buffer, size_t size, ssize_t rcode)
Save partially written data to our local buffer.
Definition dedup.c:528
int fr_bio_dedup_entry_extend(fr_bio_t *bio, fr_bio_dedup_entry_t *item, fr_time_t expires)
Extend the expiry time for an entry.
Definition dedup.c:1031
fr_bio_dedup_config_t config
Definition dedup.c:127
fr_bio_dedup_get_item_t get_item
turn a packet_ctx into a fr_bio_dedup_entry_t
Definition dedup.c:151
static ssize_t fr_bio_dedup_write_partial(fr_bio_t *bio, void *packet_ctx, const void *buffer, size_t size)
There's a partial packet written.
Definition dedup.c:589
void fr_bio_dedup_entry_cancel(fr_bio_t *bio, fr_bio_dedup_entry_t *item)
Cancel one item.
Definition dedup.c:1013
static ssize_t fr_bio_dedup_write_data(fr_bio_t *bio, void *packet_ctx, const void *buffer, size_t size)
There's a partial block of data written.
Definition dedup.c:728
fr_bio_dedup_entry_t * partial
Definition dedup.c:146
fr_event_list_t * el
Definition dedup.c:123
fr_bio_t * fr_bio_dedup_alloc(TALLOC_CTX *ctx, size_t max_saved, fr_bio_dedup_receive_t receive, fr_bio_dedup_release_t release, fr_bio_dedup_get_item_t get_item, fr_bio_dedup_config_t const *cfg, fr_bio_t *next)
Allocate a fr_bio_dedup_t.
Definition dedup.c:1117
static ssize_t fr_bio_dedup_flush_pending(fr_bio_dedup_t *my)
Flush any packets in the pending queue.
Definition dedup.c:432
static ssize_t fr_bio_dedup_blocked(fr_bio_dedup_t *my, fr_bio_dedup_entry_t *item, ssize_t rcode)
The write is blocked.
Definition dedup.c:676
static void fr_bio_dedup_timer(UNUSED fr_timer_list_t *el, fr_time_t now, void *uctx)
Expire an entry when its timer fires.
Definition dedup.c:830
fr_rb_tree_t rb
expire list
Definition dedup.c:125
static void fr_bio_dedup_replied(fr_bio_dedup_t *my, fr_bio_dedup_entry_t *item)
Move an item from active to replied.
Definition dedup.c:178
fr_bio_dedup_receive_t receive
called when we receive a potentially new packet
Definition dedup.c:148
static ssize_t fr_bio_dedup_blocked_data(fr_bio_dedup_t *my, uint8_t const *buffer, size_t size, ssize_t rcode)
The write is blocked, but we don't have "item".
Definition dedup.c:769
fr_bio_dedup_state_t
Definition dedup.c:85
@ FR_BIO_DEDUP_STATE_PARTIAL
Partially written.
Definition dedup.c:90
@ FR_BIO_DEDUP_STATE_FREE
Definition dedup.c:86
@ FR_BIO_DEDUP_STATE_ACTIVE
Received, but not replied.
Definition dedup.c:87
@ FR_BIO_DEDUP_STATE_CANCELLED
Partially written, and then cancelled.
Definition dedup.c:91
@ FR_BIO_DEDUP_STATE_PENDING
Have a reply, but we're trying to write it out.
Definition dedup.c:88
@ FR_BIO_DEDUP_STATE_REPLIED
Replied, and waiting for it to expire.
Definition dedup.c:89
static ssize_t fr_bio_dedup_write(fr_bio_t *bio, void *packet_ctx, void const *buffer, size_t size)
Write raw data to the bio.
Definition dedup.c:866
fr_bio_dedup_entry_t * first
Definition dedup.c:138
static ssize_t fr_bio_dedup_buffer_write(fr_bio_dedup_t *my)
Write data from our local buffer to the next bio.
Definition dedup.c:549
fr_bio_buf_t buffer
Definition dedup.c:153
struct fr_bio_dedup_list_s fr_bio_dedup_list_t
Definition dedup.c:71
static int fr_bio_dedup_timer_reset(fr_bio_dedup_t *my)
Reset the timer after changing the rb tree.
Definition dedup.c:332
static ssize_t fr_bio_dedup_read(fr_bio_t *bio, void *packet_ctx, void *buffer, size_t size)
Definition dedup.c:922
fr_timer_t * ev
Definition dedup.c:129
static void fr_bio_dedup_release(fr_bio_dedup_t *my, fr_bio_dedup_entry_t *item, fr_bio_dedup_release_reason_t reason)
Release an entry back to the free list.
Definition dedup.c:375
static fr_cmp_ret_t _entry_cmp(void const *one, void const *two)
Definition dedup.c:988
void * uctx
user-writable context
Definition dedup.c:95
void(* fr_bio_dedup_release_t)(fr_bio_t *bio, fr_bio_dedup_entry_t *dedup_ctx, fr_bio_dedup_release_reason_t reason)
Callback on release the packet (timeout, or cancelled by the application)
Definition dedup.h:92
fr_event_list_t * el
event list
Definition dedup.h:38
fr_bio_dedup_t * my
so we can get to it from the event timer callback
Definition dedup.c:112
fr_time_t expires
when this entry expires
Definition dedup.c:114
fr_time_delta_t lifetime
default lifetime of dedup entry
Definition dedup.h:40
size_t packet_size
size of the cached packet data
Definition dedup.c:98
fr_bio_dedup_state_t state
which tree or list this item is in
Definition dedup.c:115
struct fr_bio_dedup_entry_s fr_bio_dedup_entry_t
Definition dedup.h:43
void * packet_ctx
packet_ctx for dedup purposes
Definition dedup.c:96
uint8_t * reply
reply cached by the application
Definition dedup.c:100
size_t reply_size
size of the cached reply
Definition dedup.c:101
void * reply_ctx
reply ctx
Definition dedup.c:99
uint8_t * packet
cached packet data for finding duplicates
Definition dedup.c:97
fr_rb_node_t dedup
user managed dedup node
Definition dedup.c:103
bool(* fr_bio_dedup_receive_t)(fr_bio_t *bio, fr_bio_dedup_entry_t *dedup_ctx, void *packet_ctx)
Callback on read to see if we should receive the packet.
Definition dedup.h:79
fr_bio_dedup_entry_t *(* fr_bio_dedup_get_item_t)(fr_bio_t *bio, void *packet_ctx)
Definition dedup.h:94
fr_bio_dedup_release_reason_t
Definition dedup.h:59
@ FR_BIO_DEDUP_WRITE_ERROR
Definition dedup.h:62
@ FR_BIO_DEDUP_CANCELLED
Definition dedup.h:61
@ FR_BIO_DEDUP_EXPIRED
Definition dedup.h:60
Definition dedup.c:94
#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)
talloc_free(hp)
int fr_bio_destructor(fr_bio_t *bio)
Free this bio.
Definition base.c:35
#define fr_time()
Definition event.c:58
Stores all information relating to an event list.
Definition event.c:375
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
unsigned long int size_t
static size_t used
#define fr_assert(_expr)
Definition rad_assert.h:37
void * fr_rb_iter_init_inorder(fr_rb_tree_t *tree, fr_rb_iter_inorder_t *iter)
Initialise an in-order iterator.
Definition rb.c:850
void fr_rb_iter_delete_inorder(fr_rb_tree_t *tree, fr_rb_iter_inorder_t *iter)
Remove the current node from the tree.
Definition rb.c:925
void * fr_rb_remove_by_inline_node(fr_rb_tree_t *tree, fr_rb_node_t *node)
Remove an entry from the tree, using the node structure, without freeing the data.
Definition rb.c:748
void * fr_rb_first(fr_rb_tree_t *tree)
Definition rb.c:812
int fr_rb_insert(fr_rb_tree_t *tree, void const *data)
Insert data into a tree.
Definition rb.c:637
#define fr_rb_inline_init(_tree, _type, _field, _data_cmp, _data_free)
Initialises a red black tree.
Definition rb.h:178
Iterator structure for in-order traversal of an rbtree.
Definition rb.h:319
The main red black tree structure.
Definition rb.h:71
static int talloc_const_free(void const *ptr)
Free const'd memory.
Definition talloc.h:289
static fr_time_delta_t fr_time_delta_from_msec(int64_t msec)
Definition time.h:575
static fr_time_t fr_time_add_time_delta(fr_time_t a, fr_time_delta_t b)
Definition time.h:173
#define fr_time_lteq(_a, _b)
Definition time.h:240
static fr_cmp_ret_t fr_time_cmp(fr_time_t a, fr_time_t b)
Compare two fr_time_t values.
Definition time.h:916
#define fr_time_add(_a, _b)
Add a time/time delta together.
Definition time.h:196
#define fr_time_gt(_a, _b)
Definition time.h:237
"server local" time.
Definition time.h:69
An event timer list.
Definition timer.c:49
A timer event.
Definition timer.c:83
#define fr_timer_at(...)
Definition timer.h:81
static fr_event_list_t * el