The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
retry.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: ecb49797dda4c8796c67e863159892b833e615ca $
19  * @file lib/bio/retry.c
20  * @brief Binary IO abstractions for retrying 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/null.h>
27 #include <freeradius-devel/bio/buf.h>
28 #include <freeradius-devel/util/rb.h>
29 #include <freeradius-devel/util/dlist.h>
30 
31 #define _BIO_RETRY_PRIVATE
32 #include <freeradius-devel/bio/retry.h>
33 
34 typedef struct fr_bio_retry_list_s fr_bio_retry_list_t;
35 typedef struct fr_bio_retry_s fr_bio_retry_t;
36 
37 /*
38  * Define type-safe wrappers for head and entry definitions.
39  */
40 FR_DLIST_TYPES(fr_bio_retry_list)
41 
43  void *uctx;
44  void *packet_ctx;
45  fr_bio_retry_rewrite_t rewrite; //!< per-packet rewrite callback
46 
47  union {
48  fr_rb_node_t node; //!< for the timers
49  FR_DLIST_ENTRY(fr_bio_retry_list) entry; //!< for the free list
50  };
51 
52  fr_bio_retry_t *my; //!< so we can get to it from the event timer callback
53  fr_retry_t retry; //!< retry timers and counters
54 
55  uint8_t const *buffer;
56  size_t size;
57 
58  bool cancelled; //!< was this item cancelled?
59 };
60 
61 FR_DLIST_FUNCS(fr_bio_retry_list, fr_bio_retry_entry_t, entry)
62 
65 
68 
70 
72 
74 
75  /*
76  * The "first" entry is cached here so that we can detect when it changes. The insert / delete
77  * code can just do its work without worrying about timers. And then when the tree manipulation
78  * is done, call the fr_bio_retry_reset_timer() function to reset (or not) the timer.
79  */
80  fr_bio_retry_entry_t *first; //!< for timers
81 
82  /*
83  * Cache a partial write when IO is blocked.
84  *
85  * When the IO is blocked, the timer "ev" event AND the "first" entry MUST be set to NULL.
86  * There's no point in running retry timers when we can't send packets due to IO blockage. And
87  * since there's no timer, there's no need to track which entry is first.
88  */
89  fr_bio_retry_entry_t *partial; //!< for partial writes
90 
95 
97 
98  FR_DLIST_HEAD(fr_bio_retry_list) free;
99 };
100 
101 static void fr_bio_retry_timer(UNUSED fr_event_list_t *el, fr_time_t now, void *uctx);
102 static ssize_t fr_bio_retry_write(fr_bio_t *bio, void *packet_ctx, void const *buffer, size_t size);
104 
105 /** Reset the timer after changing the rb tree.
106  *
107  */
109 {
110  fr_bio_retry_entry_t *first;
111 
112  /*
113  * Nothing to do, don't set any timers.
114  */
115  first = fr_rb_first(&my->rb);
116  if (!first) {
117  cancel_timer:
118  talloc_const_free(my->ev);
119  my->first = NULL;
120  return 0;
121  }
122 
123  /*
124  * We're partially writing a response. Don't bother with the timer, and delete any existing
125  * timer. It will be reset when the partial entry is placed back into the queue.
126  */
127  if (first == my->partial) goto cancel_timer;
128 
129  /*
130  * The timer is already set correctly, we're done.
131  */
132  if (first == my->first) return 0;
133 
134  /*
135  * Update the timer. This should never fail.
136  */
137  if (fr_event_timer_at(my, my->el, &my->ev, first->retry.next, fr_bio_retry_timer, my) < 0) return -1;
138 
139  my->first = first;
140  return 0;
141 }
142 
143 /** Release an entry back to the free list.
144  *
145  */
147 {
148  /*
149  * Tell the caller that we've released it before doing anything else. That way we can safely
150  * modify anything we want.
151  */
152  my->release((fr_bio_t *) my, item, reason);
153 
154  /*
155  * We've partially written this item. Don't bother changing it's position in any of the lists,
156  * as it's in progress.
157  */
158  if (my->partial == item) {
159  item->cancelled = true;
160  return;
161  }
162 
163  (void) fr_rb_remove_by_inline_node(&my->rb, &item->node);
164 
165  /*
166  * We're deleting the timer entry. Go reset the timer.
167  */
168  if (my->first == item) {
169  my->first = NULL;
170  (void) fr_bio_retry_reset_timer(my);
171  }
172 
173 #ifndef NDEBUG
174  item->buffer = NULL;
175 #endif
176  item->uctx = NULL;
177  item->packet_ctx = NULL;
178 
179  fr_assert(my->first != item);
180  fr_bio_retry_list_insert_head(&my->free, item);
181 }
182 
183 /** Write one item.
184  *
185  * @return
186  * - <0 on error
187  * - 0 for "can't write any more"
188  * - 1 for "wrote a packet"
189  */
191 {
192  ssize_t rcode;
193  fr_retry_state_t state;
194 
195  fr_assert(!my->partial);
196 
197  /*
198  * Are we there yet?
199  *
200  * Release it, indicating whether or not we successfully got a reply.
201  */
202  state = fr_retry_next(&item->retry, now);
203  if (state != FR_RETRY_CONTINUE) {
204  fr_bio_retry_release(my, item, (fr_bio_retry_release_reason_t) (item->retry.replies > 0));
205  return 1;
206  }
207 
208  /*
209  * Write out the packet. On failure release this item.
210  *
211  * If there's an error, we hope that the next "real" write will find the error, and do any
212  * necessary cleanups. Note that we can't call bio shutdown here, as the bio is controlled by the
213  * application, and not by us.
214  */
215  if (item->rewrite) {
216  rcode = item->rewrite(&my->bio, item, item->buffer, item->size);
217  } else {
218  rcode = my->rewrite(&my->bio, item, item->buffer, item->size);
219  }
220  if (rcode < 0) {
221  if (rcode == fr_bio_error(IO_WOULD_BLOCK)) return rcode;
222 
224  return rcode;
225  }
226 
227  /*
228  * We didn't write the whole packet, we're blocked.
229  */
230  if ((size_t) rcode < item->size) {
231  if (fr_bio_retry_blocked(my, item, rcode) < 0) return fr_bio_error(GENERIC); /* oom */
232 
233  return 0;
234  }
235 
236  /*
237  * We wrote the whole packet. Remove it from the tree, which is done _without_ doing calls to
238  * cmp(), so we it's OK for us to rewrite item->retry.next.
239  */
240  (void) fr_rb_remove_by_inline_node(&my->rb, &item->node);
241 
242  /*
243  * We have more things to do, insert the entry back into the tree, and update the timer.
244  */
245  (void) fr_rb_insert(&my->rb, item);
246 
247  return 1;
248 }
249 
250 /*
251  * Check for the "next next" retry. If that's still in the past,
252  * then skip it. But _don't_ update retry.count, as we don't
253  * send packets. Instead, just enforce MRD, etc.
254  */
256 {
258 
259  /*
260  * We can't be in this function if there's a partial packet. We must be in
261  * fr_bio_retry_write_partial().
262  */
263  fr_assert(!my->partial);
264 
265  while ((item = fr_rb_first(&my->rb)) != NULL) {
266  int rcode;
267 
268  /*
269  * This item needs to be sent in the future, we're done.
270  */
271  if (fr_time_cmp(now, item->retry.next) > 0) break;
272 
273  /*
274  * Write one item, and don't update timers.
275  */
276  rcode = fr_bio_retry_write_item(my, item, now);
277  if (rcode <= 0) return rcode;
278  }
279 
280  /*
281  * Now that we've written multiple items, reset the timer.
282  *
283  * We do this at the end of the loop so that we don't update it for each item in the loop.
284  *
285  * @todo - set generic write error?
286  */
287  (void) fr_bio_retry_reset_timer(my);
288 
289  return 0;
290 }
291 
292 
293 /** There's a partial packet written. Write all of that one first, before writing another packet.
294  *
295  * The packet can either be cancelled, or IO blocked. In either case, we must write the full packet before
296  * going on to the next one, OR retrying another packet.
297  */
298 static ssize_t fr_bio_retry_write_partial(fr_bio_t *bio, void *packet_ctx, const void *buffer, size_t size)
299 {
300  size_t used;
301  ssize_t rcode;
302  fr_bio_retry_t *my = talloc_get_type_abort(bio, fr_bio_retry_t);
303  fr_bio_t *next;
305 
306  fr_assert(!my->first);
307  fr_assert(!my->ev);
308  fr_assert(my->partial != NULL);
309  fr_assert(my->buffer.start);
310 
311  used = fr_bio_buf_used(&my->buffer);
312  fr_assert(used > 0);
313 
314  /*
315  * There must be a next bio.
316  */
317  next = fr_bio_next(&my->bio);
318  fr_assert(next != NULL);
319 
320  rcode = next->write(next, NULL, my->buffer.read, used);
321  if (rcode <= 0) return rcode;
322 
323  my->buffer.read += rcode;
324 
325  /*
326  * Still data in the buffer. We can't send more packets until we finished writing this one.
327  */
328  if (fr_bio_buf_used(&my->buffer) > 0) return 0;
329 
330  /*
331  * We're done. Reset the buffer and clean up our cached partial packet.
332  */
333  fr_bio_buf_reset(&my->buffer);
334  my->partial = NULL;
335 
336  /*
337  * The item was cancelled. It's still in the tree, so we remove it, and reset its fields.
338  * We then insert it into the free list.
339  *
340  * If it's not cancelled, then we leave it in the tree, and run its timers s normal.
341  */
342  if (item->cancelled) {
343  (void) fr_rb_remove_by_inline_node(&my->rb, &item->node);
344 
345 #ifndef NDEBUG
346  item->buffer = NULL;
347 #endif
348  item->uctx = NULL;
349  item->packet_ctx = NULL;
350 
351  fr_bio_retry_list_insert_head(&my->free, item);
352  }
353 
354  /*
355  * Walk through the list to see if we need to retry writes and jump ahead with packets.
356  *
357  * Note that the retried packets are sent _before_ the new one. If the caller doesn't want this
358  * behavior, he can cancel the old ones.
359  *
360  * @todo - have a way to prioritize packets? i.e. to insert a packet at the _head_ of the list,
361  * and write it _now_, as with Status-Server.
362  */
363  item = fr_rb_first(&my->rb);
364  if (item) {
365  fr_time_t now = fr_time();
366 
367  /*
368  * We're supposed to send the next retry now. i.e. the socket has been blocked for a
369  * long time.
370  */
371  if (fr_time_cmp(now, item->retry.next) <= 0) {
372  rcode = fr_bio_retry_write_delayed(my, now);
373  if (rcode < 0) return rcode;
374  }
375 
376  /*
377  * We now have an active socket but no timers, so we set up the timers.
378  */
379  (void) fr_bio_retry_reset_timer(my);
380  }
381 
382  /*
383  * Try to write the packet which we were given.
384  */
385  my->bio.write = fr_bio_retry_write;
386  return fr_bio_retry_write(bio, packet_ctx, buffer, size);
387 }
388 
389 /** The write is blocked.
390  *
391  * We couldn't write out the entire packet, the bio is blocked. Don't write anything else until we become
392  * unblocked!
393  *
394  * And free the timer. There's no point in trying to write things if the socket is blocked.
395  */
397 {
398  fr_assert(!my->partial);
399  fr_assert(rcode > 0);
400  fr_assert((size_t) rcode < item->size);
401 
402  /*
403  * (re)-alloc the buffer for partial writes.
404  */
405  if (!my->buffer.start ||
406  (item->size > fr_bio_buf_size(&my->buffer))) {
407  if (fr_bio_buf_alloc(my, &my->buffer, item->size)) return fr_bio_error(GENERIC);
408  }
409 
410  fr_assert(fr_bio_buf_used(&my->buffer) == 0);
411  fr_assert(my->buffer.read == my->buffer.start);
412 
413  fr_bio_buf_write(&my->buffer, item->buffer + rcode, item->size - rcode);
414 
415  my->partial = item;
416 
417  /*
418  * There's no timer, as the write is blocked, so we can't retry.
419  */
420  talloc_const_free(my->ev);
421  my->first = NULL;
422 
423  my->bio.write = fr_bio_retry_write_partial;
424 
425  /*
426  * We leave the entry in the timer tree so that the expiry timer will get hit.
427  *
428  * And then return the size of the partial data we wrote.
429  */
430  return rcode;
431 }
432 
433 
434 /** Resend a packet.
435  *
436  * This function should be called by the rewrite() callback, after (possibly) re-encoding the packet.
437  *
438  * @param bio the binary IO handler
439  * @param item the retry context from #fr_bio_retry_sent_t
440  * @param buffer raw data for the packet. May be NULL, in which case the previous packet is retried
441  * @param size size of the raw data
442  * @return
443  * - <0 on error
444  * - 0 for "wrote no data"
445  * - >0 for "wrote data".
446  */
448 {
449  ssize_t rcode;
450  fr_bio_retry_t *my = talloc_get_type_abort(bio, fr_bio_retry_t);
451  fr_bio_t *next;
452 
453  /*
454  * The caller may (accidentally or intentionally) call this function when there's a partial
455  * packet. The intention for rewrite() is that it is only called from timers, and those only run
456  * when the socket isn't blocked. But the caller might not pay attention to those issues.
457  */
458  if (my->partial) return 0;
459 
460  /*
461  * There must be a next bio.
462  */
463  next = fr_bio_next(&my->bio);
464  fr_assert(next != NULL);
465 
466  /*
467  * The caller should pass NULL for "use the previous packet".
468  */
469  if (buffer) {
470  item->buffer = buffer;
471  item->size = size;
472  }
473 
474  /*
475  * Write out the packet, if everything is OK, return.
476  *
477  * Note that we don't update any timers if the write succeeded. That is handled by the caller.
478  */
479  rcode = next->write(next, item->packet_ctx, item->buffer, item->size);
480  if ((size_t) rcode == size) return rcode;
481 
482  /*
483  * Can't write anything, be sad.
484  */
485  if (rcode == 0) return 0;
486 
487  /*
488  * There's an error writing the packet. Release it, and move the item to the free list.
489  *
490  * Note that we don't bother resetting the timer. There's no point in changing the timer when
491  * the bio is likely dead.
492  */
493  if (rcode < 0) {
494  if (rcode == fr_bio_error(IO_WOULD_BLOCK)) return rcode;
495 
497  return rcode;
498  }
499 
500  /*
501  * We had previously written the packet, so save the re-sent one, too.
502  */
503  return fr_bio_retry_blocked(my, item, rcode);
504 }
505 
506 /** A previous timer write had a fatal error, so we forbid further writes.
507  *
508  */
509 static ssize_t fr_bio_retry_write_fatal(fr_bio_t *bio, UNUSED void *packet_ctx, UNUSED void const *buffer, UNUSED size_t size)
510 {
511  fr_bio_retry_t *my = talloc_get_type_abort(bio, fr_bio_retry_t);
512 
513  return my->error;
514 }
515 
516 /** Run a timer event. Usually to write out another packet.
517  *
518  */
519 static void fr_bio_retry_timer(UNUSED fr_event_list_t *el, fr_time_t now, void *uctx)
520 {
521  ssize_t rcode;
522  fr_bio_retry_t *my = talloc_get_type_abort(uctx, fr_bio_retry_t);
524 
525  /*
526  * For the timer to be running, there must be a "first" entry which causes the timer to fire.
527  *
528  * There must also be no partially written entry. If the IO is blocked, then all timers are
529  * suspended.
530  */
531  fr_assert(my->first != NULL);
532  fr_assert(my->partial == NULL);
533 
534  item = my->first;
535 
536  /*
537  * Retry one item.
538  */
539  rcode = fr_bio_retry_write_item(my, item, now);
540  if (rcode < 0) {
541  if (rcode == fr_bio_error(IO_WOULD_BLOCK)) return;
542 
543  my->error = rcode;
544  my->bio.write = fr_bio_retry_write_fatal;
545  return;
546  }
547 
548  /*
549  * Partial write - no timers get set.
550  */
551  if (rcode == 0) return;
552 
553  /*
554  * We successfull wrote this item. Reset the timer to the next one, which is likely to be a
555  * different one from the item we just updated.
556  */
557  (void) fr_bio_retry_reset_timer(my);
558 }
559 
560 /** Write a request, and see if we have a reply.
561  *
562  */
563 static ssize_t fr_bio_retry_write(fr_bio_t *bio, void *packet_ctx, void const *buffer, size_t size)
564 {
565  ssize_t rcode;
567  fr_bio_retry_t *my = talloc_get_type_abort(bio, fr_bio_retry_t);
568  fr_bio_t *next;
569 
570  fr_assert(!my->partial);
571 
572  /*
573  * There must be a next bio.
574  */
575  next = fr_bio_next(&my->bio);
576  fr_assert(next != NULL);
577 
578  /*
579  * The caller is trying to flush partial data. But we don't have any partial data, so just call
580  * the next bio to flush it.
581  */
582  if (!buffer) {
583  return next->write(next, packet_ctx, NULL, size);
584  }
585 
586  /*
587  * Catch the corner case where the max number of saved packets is exceeded.
588  */
589  if (fr_bio_retry_list_num_elements(&my->free) == 0) {
590  item = fr_rb_last(&my->rb);
591 
592  fr_assert(item != NULL);
593 
594  if (!item->retry.replies) return fr_bio_error(BUFFER_FULL);
595 
596  if (fr_bio_retry_entry_cancel(bio, item) < 0) return fr_bio_error(BUFFER_FULL);
597 
598  /*
599  * We now have a free item, so we can use it.
600  */
601  fr_assert(fr_bio_retry_list_num_elements(&my->free) > 0);
602  }
603 
604  /*
605  * Write out the packet. If there's an error, OR we wrote nothing, return.
606  *
607  * Note that we don't mark the socket as blocked if the next bio didn't write anything. We want
608  * the caller to know that the write didn't succeed, and the caller takes care of managing the
609  * current packet. So there's no need for us to do that.
610  */
611  rcode = next->write(next, packet_ctx, buffer, size);
612  if (rcode <= 0) return rcode;
613 
614  /*
615  * Initialize the retry timers after writing the packet.
616  */
617  item = fr_bio_retry_list_pop_head(&my->free);
618  fr_assert(item != NULL);
619 
620  fr_assert(item->my == my);
621  item->retry.config = NULL;
622  item->retry.start = fr_time();
623  item->packet_ctx = packet_ctx;
624  item->buffer = buffer;
625  item->size = size;
626 
627  /*
628  * Tell the application that we've saved the packet. The "item" pointer allows the application
629  * to cancel this packet if necessary.
630  */
631  my->sent(bio, packet_ctx, buffer, size, item);
632 
633  if (!item->retry.config) {
634  fr_retry_init(&item->retry, item->retry.start, &my->retry_config);
635  }
636 
637  /*
638  * This should never fail.
639  */
640  if (!fr_rb_insert(&my->rb, item)) {
641  fr_assert(my->first != item);
642 
644  fr_bio_retry_list_insert_head(&my->free, item);
645  return size;
646  }
647 
648  /*
649  * We only wrote part of the packet, remember to write the rest of it.
650  */
651  if ((size_t) rcode < size) {
652  return fr_bio_retry_blocked(my, item, rcode);
653  }
654 
655  /*
656  * We've just inserted this packet into the timer tree, so it can't be used as the current timer.
657  * Once we've inserted it, we update the timer.
658  */
659  fr_assert(my->first != item);
660 
661  /*
662  * If we can't set the timer, then release this item.
663  */
664  if (fr_bio_retry_reset_timer(my) < 0) {
665  fr_strerror_const("Failed adding timer for packet");
666 
668  return fr_bio_error(GENERIC);
669  }
670 
671  return size;
672 }
673 
674 static ssize_t fr_bio_retry_read(fr_bio_t *bio, void *packet_ctx, void *buffer, size_t size)
675 {
676  ssize_t rcode;
678  fr_bio_retry_t *my = talloc_get_type_abort(bio, fr_bio_retry_t);
679  fr_bio_t *next;
680 
681  /*
682  * There must be a next bio.
683  */
684  next = fr_bio_next(&my->bio);
685  fr_assert(next != NULL);
686 
687  /*
688  * Read the packet. If error or nothing, return immediately.
689  */
690  rcode = next->read(next, packet_ctx, buffer, size);
691  if (rcode <= 0) return rcode;
692 
693  /*
694  * Not a valid response to a request, OR a duplicate response to a request: don't return it to
695  * the caller.
696  *
697  * But if it is a duplicate response, update the counters and do cleanups as necessary.
698  */
699  item = NULL;
700  if (!my->response(bio, &item, packet_ctx, buffer, size)) {
701  if (!item) return 0;
702 
703  item->retry.replies++;
704  if (item->retry.replies < item->retry.count) return 0;
705 
706  /*
707  * We have a reply, so we can't possibly be partially writing the request
708  */
709  fr_assert(item != my->partial);
710 
711  /*
712  * We've received all of the responses, we can clean up the packet.
713  */
715  return 0;
716  }
717 
718  fr_assert(item != NULL);
719  fr_assert(item->retry.replies == 0);
720  fr_assert(item != my->partial);
721 
722  /*
723  * We have a new reply. If we've received all of the replies (i.e. one), OR we don't have a
724  * maximum lifetime for this request, then release it immediately.
725  */
726  item->retry.replies++;
727  if ((item->retry.replies >= item->retry.count) || !fr_time_delta_ispos(my->retry_config.mrd)) {
729  return rcode;
730  }
731 
732  /*
733  * There are more replies pending. Wait passively for more replies, and clean up the item
734  * when the timer has expired.
735  */
736  item->retry.next = fr_time_add_time_delta(item->retry.start, my->retry_config.mrd);
737 
738  (void) fr_rb_remove_by_inline_node(&my->rb, &item->node);
739  (void) fr_rb_insert(&my->rb, item);
740  (void) fr_bio_retry_reset_timer(my);
741 
742  return rcode;
743 }
744 
745 static int8_t _entry_cmp(void const *one, void const *two)
746 {
747  fr_bio_retry_entry_t const *a = one;
748  fr_bio_retry_entry_t const *b = two;
749 
750  fr_assert(a->buffer);
751  fr_assert(b->buffer);
752 
753  return fr_time_cmp(a->retry.next, b->retry.next);
754 }
755 
756 /** Cancel one item.
757  *
758  * If "item" is NULL, the last entry in the timer tree is cancelled.
759  *
760  * @param bio the binary IO handler
761  * @param item the retry context from #fr_bio_retry_sent_t
762  * @return
763  * - <0 error
764  * - 0 - didn't cancel
765  * - 1 - did cancel
766  */
768 {
769  fr_bio_retry_t *my = talloc_get_type_abort(bio, fr_bio_retry_t);
770 
771  /*
772  * No item passed, try to cancel the oldest one.
773  */
774  if (!item) {
775  item = fr_rb_last(&my->rb);
776  if (!item) return 0;
777 
778  /*
779  * This item hasn't had a response, we can't cancel it.
780  */
781  if (!item->retry.replies) return 0;
782  }
783 
784  /*
785  * If the caller has cached a previously finished item, then that's a fatal error.
786  */
787  fr_assert(item->buffer != NULL);
788 
790 
791  return 1;
792 }
793 
794 /** Set a per-packet retry config
795  *
796  * This function should be called from the #fr_bio_retry_sent_t callback to set a unique retry timer for this
797  * packet. If no retry configuration is set, then the main one from the alloc() function is used.
798  */
800 {
801  fr_assert(item->buffer != NULL);
802 
803  if (item->retry.config) return -1;
804 
805  fr_assert(fr_time_delta_unwrap(cfg->irt) != 0);
806 
807  fr_retry_init(&item->retry, item->retry.start, cfg);
808 
809  return 0;
810 }
811 
812 /** Allow the callbacks / application to know when things are being retried.
813  *
814  * This is not initialized util _after_ fr_bio_retry_entry_start() has been called.
815  */
817 {
818  fr_assert(item->buffer != NULL);
819 
820  if (!item->retry.config) return NULL;
821 
822  return &item->retry;
823 }
824 
825 
826 /** Cancel all outstanding packets.
827  *
828  */
830 {
833 
834  talloc_const_free(my->ev);
835 
836  /*
837  * Cancel all outgoing packets. Don't bother updating the tree or the free list, as all of the
838  * entries will be deleted when the memory is freed.
839  */
840  while ((item = fr_rb_iter_init_inorder(&iter, &my->rb)) != NULL) {
842  }
843 
844  my->first = NULL;
845 
846  return 0;
847 }
848 
849 /** Allocate a #fr_bio_retry_t
850  *
851  */
852 fr_bio_t *fr_bio_retry_alloc(TALLOC_CTX *ctx, size_t max_saved,
853  fr_bio_retry_sent_t sent,
854  fr_bio_retry_response_t response,
855  fr_bio_retry_rewrite_t rewrite,
856  fr_bio_retry_release_t release,
857  fr_bio_retry_config_t const *cfg,
858  fr_bio_t *next)
859 {
860  size_t i;
861  fr_bio_retry_t *my;
862  fr_bio_retry_entry_t *items;
863 
864  fr_assert(cfg->el);
865 
866  /*
867  * Limit to reasonable values.
868  */
869  if (!max_saved) return NULL;
870  if (max_saved > 65536) return NULL;
871 
872  my = talloc_zero(ctx, fr_bio_retry_t);
873  if (!my) return NULL;
874 
875  /*
876  * Allocate everything up front, to get better locality of reference, less memory fragmentation,
877  * and better reuse of data structures.
878  */
879  items = talloc_array(my, fr_bio_retry_entry_t, max_saved);
880  if (!items) return NULL;
881 
882  /*
883  * Insert the entries into the free list in order.
884  */
885  fr_bio_retry_list_init(&my->free);
886  for (i = 0; i < max_saved; i++) {
887  items[i].my = my;
888  fr_bio_retry_list_insert_tail(&my->free, &items[i]);
889  }
890 
891  (void) fr_rb_inline_init(&my->rb, fr_bio_retry_entry_t, node, _entry_cmp, NULL);
892 
893  my->sent = sent;
894  if (!rewrite) {
896  } else {
897  my->rewrite = rewrite;
898  }
899  my->response = response;
900  my->release = release;
901 
902  my->el = cfg->el;
903  my->retry_config = cfg->retry_config;
904 
905  my->bio.write = fr_bio_retry_write;
906  my->bio.read = fr_bio_retry_read;
907 
908  fr_bio_chain(&my->bio, next);
909 
910  talloc_set_destructor(my, fr_bio_retry_destructor);
911 
912  return (fr_bio_t *) my;
913 }
static int const char char buffer[256]
Definition: acutest.h:574
fr_bio_write_t _CONST write
write to the underlying bio
Definition: base.h:107
fr_bio_read_t _CONST read
read from the underlying bio
Definition: base.h:106
static fr_bio_t * fr_bio_next(fr_bio_t *bio)
Definition: base.h:121
#define fr_bio_error(_x)
Definition: base.h:184
Definition: base.h:103
static ssize_t fr_bio_retry_read(fr_bio_t *bio, void *packet_ctx, void *buffer, size_t size)
Definition: retry.c:674
const fr_retry_t * fr_bio_retry_entry_info(UNUSED fr_bio_t *bio, fr_bio_retry_entry_t *item)
Allow the callbacks / application to know when things are being retried.
Definition: retry.c:816
fr_bio_retry_release_t release
Definition: retry.c:94
fr_bio_t * fr_bio_retry_alloc(TALLOC_CTX *ctx, size_t max_saved, fr_bio_retry_sent_t sent, fr_bio_retry_response_t response, fr_bio_retry_rewrite_t rewrite, fr_bio_retry_release_t release, fr_bio_retry_config_t const *cfg, fr_bio_t *next)
Allocate a fr_bio_retry_t.
Definition: retry.c:852
int fr_bio_retry_entry_start(UNUSED fr_bio_t *bio, fr_bio_retry_entry_t *item, fr_retry_config_t const *cfg)
Set a per-packet retry config.
Definition: retry.c:799
fr_bio_buf_t buffer
Definition: retry.c:96
static void fr_bio_retry_release(fr_bio_retry_t *my, fr_bio_retry_entry_t *item, fr_bio_retry_release_reason_t reason)
Release an entry back to the free list.
Definition: retry.c:146
static int fr_bio_retry_destructor(fr_bio_retry_t *my)
Cancel all outstanding packets.
Definition: retry.c:829
int fr_bio_retry_entry_cancel(fr_bio_t *bio, fr_bio_retry_entry_t *item)
Cancel one item.
Definition: retry.c:767
struct fr_bio_retry_list_s fr_bio_retry_list_t
Definition: retry.c:34
fr_event_list_t * el
Definition: retry.c:66
ssize_t fr_bio_retry_rewrite(fr_bio_t *bio, fr_bio_retry_entry_t *item, const void *buffer, size_t size)
Resend a packet.
Definition: retry.c:447
static int fr_bio_retry_write_item(fr_bio_retry_t *my, fr_bio_retry_entry_t *item, fr_time_t now)
Write one item.
Definition: retry.c:190
static void fr_bio_retry_timer(UNUSED fr_event_list_t *el, fr_time_t now, void *uctx)
Run a timer event.
Definition: retry.c:519
fr_retry_config_t retry_config
Definition: retry.c:69
static int fr_bio_retry_write_delayed(fr_bio_retry_t *my, fr_time_t now)
Definition: retry.c:255
fr_bio_retry_entry_t * partial
for partial writes
Definition: retry.c:89
fr_bio_retry_sent_t sent
Definition: retry.c:91
static int8_t _entry_cmp(void const *one, void const *two)
Definition: retry.c:745
fr_bio_retry_response_t response
Definition: retry.c:93
static ssize_t fr_bio_retry_write(fr_bio_t *bio, void *packet_ctx, void const *buffer, size_t size)
Write a request, and see if we have a reply.
Definition: retry.c:563
static ssize_t fr_bio_retry_blocked(fr_bio_retry_t *my, fr_bio_retry_entry_t *item, ssize_t rcode)
The write is blocked.
Definition: retry.c:396
ssize_t error
Definition: retry.c:71
static ssize_t fr_bio_retry_write_fatal(fr_bio_t *bio, UNUSED void *packet_ctx, UNUSED void const *buffer, UNUSED size_t size)
A previous timer write had a fatal error, so we forbid further writes.
Definition: retry.c:509
static ssize_t fr_bio_retry_write_partial(fr_bio_t *bio, void *packet_ctx, const void *buffer, size_t size)
There's a partial packet written.
Definition: retry.c:298
fr_rb_tree_t rb
Definition: retry.c:67
fr_bio_retry_entry_t * first
for timers
Definition: retry.c:80
fr_bio_retry_rewrite_t rewrite
Definition: retry.c:92
fr_event_timer_t const * ev
Definition: retry.c:73
static int fr_bio_retry_reset_timer(fr_bio_retry_t *my)
Reset the timer after changing the rb tree.
Definition: retry.c:108
fr_retry_config_t retry_config
base retry config
Definition: retry.h:38
void(* fr_bio_retry_release_t)(fr_bio_t *bio, fr_bio_retry_entry_t *retry_ctx, fr_bio_retry_release_reason_t reason)
Callback on release the packet (timeout or have all replies)
Definition: retry.h:111
fr_retry_t retry
retry timers and counters
Definition: retry.c:53
fr_bio_retry_release_reason_t
Definition: retry.h:54
@ FR_BIO_RETRY_WRITE_ERROR
Definition: retry.h:58
@ FR_BIO_RETRY_FATAL_ERROR
Definition: retry.h:59
@ FR_BIO_RETRY_CANCELLED
Definition: retry.h:57
@ FR_BIO_RETRY_DONE
Definition: retry.h:55
uint8_t const * buffer
Definition: retry.c:55
void(* fr_bio_retry_sent_t)(fr_bio_t *bio, void *packet_ctx, const void *buffer, size_t size, fr_bio_retry_entry_t *retry_ctx)
Callback for when a packet is sent.
Definition: retry.h:73
ssize_t(* fr_bio_retry_rewrite_t)(fr_bio_t *bio, fr_bio_retry_entry_t *retry_ctx, const void *buffer, size_t size)
Definition: retry.h:43
bool(* fr_bio_retry_response_t)(fr_bio_t *bio, fr_bio_retry_entry_t **item_p, void *packet_ctx, const void *buffer, size_t size)
Callback on read to see if a packet is a response.
Definition: retry.h:98
fr_event_list_t * el
event list
Definition: retry.h:36
void * packet_ctx
packet_ctx from the write() call
Definition: retry.c:44
bool cancelled
was this item cancelled?
Definition: retry.c:58
fr_bio_retry_t * my
so we can get to it from the event timer callback
Definition: retry.c:52
fr_bio_retry_rewrite_t rewrite
per-packet rewrite callback
Definition: retry.c:45
void * uctx
user-writable context
Definition: retry.c:43
Definition: retry.c:42
static void fr_bio_chain(fr_bio_t *first, fr_bio_t *second)
Chain one bio after another.
Definition: bio_priv.h:57
int fr_bio_buf_alloc(TALLOC_CTX *ctx, fr_bio_buf_t *bio_buf, size_t size)
Definition: buf.c:114
ssize_t fr_bio_buf_write(fr_bio_buf_t *bio_buf, const void *buffer, size_t size)
Definition: buf.c:81
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 UNUSED
Definition: build.h:313
#define FR_DLIST_TYPES(_name)
Define type specific wrapper structs for dlists.
Definition: dlist.h:1129
#define FR_DLIST_ENTRY(_name)
Expands to the type name used for the entry wrapper structure.
Definition: dlist.h:1115
#define FR_DLIST_FUNCS(_name, _element_type, _element_entry)
Define type specific wrapper functions for dlists.
Definition: dlist.h:1152
#define fr_event_timer_at(...)
Definition: event.h:250
free(array)
Stores all information relating to an event list.
Definition: event.c:411
A timer event.
Definition: event.c:102
static void * item(fr_lst_t const *lst, fr_lst_index_t idx)
Definition: lst.c:122
typedef FR_DLIST_HEAD(map_list) map_list_t
Given these are used in so many places, it's more friendly to have a proper type.
long int ssize_t
Definition: merged_model.c:24
unsigned char uint8_t
Definition: merged_model.c:30
static size_t used
static fr_bio_t * bio
Definition: radclient-ng.c:86
void * fr_rb_first(fr_rb_tree_t *tree)
Definition: rb.c:780
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:718
bool fr_rb_insert(fr_rb_tree_t *tree, void const *data)
Insert data into a tree.
Definition: rb.c:624
void * fr_rb_last(fr_rb_tree_t *tree)
Definition: rb.c:795
void * fr_rb_iter_init_inorder(fr_rb_iter_inorder_t *iter, fr_rb_tree_t *tree)
Initialise an in-order iterator.
Definition: rb.c:818
#define fr_rb_inline_init(_tree, _type, _field, _data_cmp, _data_free)
Initialises a red black tree.
Definition: rb.h:180
Iterator structure for in-order traversal of an rbtree.
Definition: rb.h:321
The main red black tree structure.
Definition: rb.h:73
fr_assert(0)
#define fr_time()
Allow us to arbitrarily manipulate time.
Definition: state_test.c:8
static int talloc_const_free(void const *ptr)
Free const'd memory.
Definition: talloc.h:212
static fr_time_t fr_time_add_time_delta(fr_time_t a, fr_time_delta_t b)
Definition: time.h:173
static int64_t fr_time_delta_unwrap(fr_time_delta_t time)
Definition: time.h:154
#define fr_time_delta_ispos(_a)
Definition: time.h:288
static int8_t fr_time_cmp(fr_time_t a, fr_time_t b)
Compare two fr_time_t values.
Definition: time.h:914
"server local" time.
Definition: time.h:69
static fr_event_list_t * el
fr_retry_state_t fr_retry_next(fr_retry_t *r, fr_time_t now)
Initialize a retransmission counter.
Definition: retry.c:84
void fr_retry_init(fr_retry_t *r, fr_time_t now, fr_retry_config_t const *config)
Initialize a retransmission counter.
Definition: retry.c:36
fr_time_delta_t irt
Initial transmission time.
Definition: retry.h:33
fr_retry_state_t
Definition: retry.h:55
@ FR_RETRY_CONTINUE
Definition: retry.h:56
fr_time_delta_t mrd
Maximum retransmission duration.
Definition: retry.h:35
fr_time_t next
when the next timer should be set
Definition: retry.h:44
#define fr_strerror_const(_msg)
Definition: strerror.h:223