The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
connection.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: e495f01f34de7a7fa254914ae056a66e186bb027 $
19 *
20 * @file src/lib/server/connection.c
21 * @brief Simple state machine for managing connection states.
22 *
23 * @copyright 2017-2019 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
24 */
25#define LOG_PREFIX conn->pub.name
26
28#define _CONNECTION_PRIVATE 1
29#include <freeradius-devel/server/connection.h>
30
31#include <freeradius-devel/server/log.h>
32#include <freeradius-devel/server/trigger.h>
33
34#include <freeradius-devel/util/debug.h>
35#include <freeradius-devel/util/event.h>
36#include <freeradius-devel/util/talloc.h>
37#include <freeradius-devel/util/syserror.h>
38#include <freeradius-devel/util/log.h>
39
40#ifdef HAVE_STDATOMIC_H
41# include <stdatomic.h>
42# ifndef ATOMIC_VAR_INIT
43# define ATOMIC_VAR_INIT(_x) (_x)
44# endif
45#else
46# include <freeradius-devel/util/stdatomic.h>
47#endif
48
50 { L("HALTED"), CONNECTION_STATE_HALTED },
51 { L("INIT"), CONNECTION_STATE_INIT },
52 { L("CONNECTING"), CONNECTION_STATE_CONNECTING },
53 { L("TIMEOUT"), CONNECTION_STATE_TIMEOUT },
54 { L("CONNECTED"), CONNECTION_STATE_CONNECTED },
55 { L("SHUTDOWN"), CONNECTION_STATE_SHUTDOWN },
56 { L("FAILED"), CONNECTION_STATE_FAILED },
57 { L("CLOSED"), CONNECTION_STATE_CLOSED },
58};
60
61/** Map connection states to trigger names
62 *
63 */
65 [CONNECTION_STATE_HALTED] = { L("connection.halted"), CONNECTION_STATE_HALTED },
66 [CONNECTION_STATE_INIT] = { L("connection.init"), CONNECTION_STATE_INIT },
67 [CONNECTION_STATE_CONNECTING] = { L("connection.connecting"), CONNECTION_STATE_CONNECTING },
68 [CONNECTION_STATE_TIMEOUT] = { L("connection.timeout"), CONNECTION_STATE_TIMEOUT },
69 [CONNECTION_STATE_CONNECTED] = { L("connection.connected"), CONNECTION_STATE_CONNECTED },
70 [CONNECTION_STATE_SHUTDOWN] = { L("connection.shutdown"), CONNECTION_STATE_SHUTDOWN },
71 [CONNECTION_STATE_FAILED] = { L("connection.failed"), CONNECTION_STATE_FAILED },
72 [CONNECTION_STATE_CLOSED] = { L("connection.closed"), CONNECTION_STATE_CLOSED }
73};
75
76static atomic_uint_fast64_t connection_counter = ATOMIC_VAR_INIT(1);
77
78/** An entry in a watch function list
79 *
80 */
82 fr_dlist_t entry; //!< List entry.
83 connection_watch_t func; //!< Function to call when a connection enters
84 ///< the state this list belongs to
85 bool oneshot; //!< Remove the function after it's called once.
86 bool enabled; //!< Whether the watch entry is enabled.
87 void *uctx; //!< User data to pass to the function.
89
91 struct connection_pub_s pub; //!< Public fields
92
93 void *uctx; //!< User data.
94
95 void *in_handler; //!< Connection is currently in a callback.
96 bool is_closed; //!< The close callback has previously been called.
97 bool processing_signals; //!< Processing deferred signals, don't let the deferred
98 ///< signal processor be called multiple times.
99
100 fr_dlist_head_t watch_pre[CONNECTION_STATE_MAX]; //!< Function called before state callback.
101 fr_dlist_head_t watch_post[CONNECTION_STATE_MAX]; //!< Function called after state callback.
102 connection_watch_entry_t *next_watcher; //!< Hack to insulate watcher iterator from deletions.
103
104 connection_init_t init; //!< Callback for initialising a connection.
105 connection_open_t open; //!< Callback for 'open' notification.
106 connection_close_t close; //!< Callback to close a connection.
107 connection_shutdown_t shutdown; //!< Signal the connection handle to start shutting down.
108 connection_failed_t failed; //!< Callback for 'failed' notification.
109
110 fr_event_timer_t const *ev; //!< State transition timer.
111
112 fr_time_delta_t connection_timeout; //!< How long to wait in the
113 //!< #CONNECTION_STATE_CONNECTING state.
114 fr_time_delta_t reconnection_delay; //!< How long to wait in the
115 //!< #CONNECTION_STATE_FAILED state.
116
117 fr_dlist_head_t deferred_signals; //!< A list of signals we received whilst we were in
118 ///< a handler.
119
120
121
122 connection_watch_entry_t *on_halted; //!< Used by the deferred signal processor to learn
123 ///< if a function deeper in the call stack freed
124 ///< the connection.
125
126 unsigned int signals_pause; //!< Temporarily stop processing of signals.
127};
128
129#define CONN_TRIGGER(_state) do { \
130 if (conn->pub.triggers) { \
131 trigger_exec(unlang_interpret_get_thread_default(), \
132 NULL, fr_table_str_by_value(connection_trigger_names, _state, "<INVALID>"), true, NULL); \
133 } \
134} while (0)
135
136#define STATE_TRANSITION(_new) \
137do { \
138 DEBUG2("Connection changed state %s -> %s", \
139 fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"), \
140 fr_table_str_by_value(connection_states, _new, "<INVALID>")); \
141 conn->pub.prev = conn->pub.state; \
142 conn->pub.state = _new; \
143 CONN_TRIGGER(_new); \
144} while (0)
145
146#define BAD_STATE_TRANSITION(_new) \
147do { \
148 if (!fr_cond_assert_msg(0, "Connection %" PRIu64 " invalid transition %s -> %s", \
149 conn->pub.id, \
150 fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"), \
151 fr_table_str_by_value(connection_states, _new, "<INVALID>"))) return; \
152} while (0)
153
154#define DEFER_SIGNALS(_conn) ((_conn)->in_handler || (_conn)->signals_pause)
155
156/** Deferred signals
157 *
158 */
159typedef enum {
160 CONNECTION_DSIGNAL_INIT, //!< Restart a halted connection.
161 CONNECTION_DSIGNAL_CONNECTED, //!< Signal that a connection is connected.
162 CONNECTION_DSIGNAL_RECONNECT_FAILED, //!< Reconnect a failed connection.
163 CONNECTION_DSIGNAL_RECONNECT_EXPIRED, //!< Reconnect an expired connection (gracefully).
164 CONNECTION_DSIGNAL_SHUTDOWN, //!< Close a connection (gracefully).
165 CONNECTION_DSIGNAL_HALT, //!< Close a connection (ungracefully).
166 CONNECTION_DSIGNAL_FREE //!< Free a connection (no further dsignals processed).
168
170 { L("INIT"), CONNECTION_DSIGNAL_INIT },
171 { L("CONNECTED"), CONNECTION_DSIGNAL_CONNECTED },
172 { L("RECONNECT-FAILED"), CONNECTION_DSIGNAL_RECONNECT_FAILED },
173 { L("RECONNECT-EXPIRED"), CONNECTION_DSIGNAL_RECONNECT_EXPIRED },
174 { L("SHUTDOWN"), CONNECTION_DSIGNAL_SHUTDOWN },
175 { L("HALT"), CONNECTION_DSIGNAL_HALT },
176 { L("FREE"), CONNECTION_DSIGNAL_FREE }
177};
179
180/** Holds a signal from a handler until it's safe to process it
181 *
182 */
183typedef struct {
184 fr_dlist_t entry; //!< Entry in the signals list.
185 connection_dsignal_t signal; //!< Signal that was deferred.
187
188/*
189 * State transition functions
190 */
199
200/** Add a deferred signal to the signal list
201 *
202 * Processing signals whilst in handlers usually leads to weird
203 * inconsistent states within the connection.
204 *
205 * If a public signal function is called, and detects its being called
206 * from within the handler, it instead adds a deferred signal entry
207 * and immediately returns.
208 *
209 * Once the handler is complete, and all pending C stack state changes
210 * are complete, the deferred signals are drained and processed.
211 */
213{
214 connection_dsignal_entry_t *dsignal, *prev;
215
216 prev = fr_dlist_tail(&conn->deferred_signals);
217 if (prev && (prev->signal == signal)) return; /* Don't insert duplicates */
218
219 MEM(dsignal = talloc_zero(conn, connection_dsignal_entry_t));
220 dsignal->signal = signal;
221 fr_dlist_insert_tail(&conn->deferred_signals, dsignal);
222
223// DEBUG4("Adding deferred signal - %s", fr_table_str_by_value(connection_dsignals, signal, "<INVALID>"));
224}
225
226/** Notification function to tell connection_deferred_signal_process that the connection has been freed
227 *
228 */
231 UNUSED connection_state_t state, void *uctx)
232{
233 bool *freed = uctx;
234 *freed = true;
235}
236
237/** Process any deferred signals
238 *
239 */
241{
243 bool freed = false;
244
245 /*
246 * We're inside and an instance of this function
247 * higher in the call stack. Don't do anything.
248 */
249 if (conn->processing_signals) return;
250
251 /*
252 * Get notified if the connection gets freed
253 * out from under us...
254 */
256 conn->processing_signals = true;
257
258 while ((dsignal = fr_dlist_head(&conn->deferred_signals))) {
260 fr_dlist_remove(&conn->deferred_signals, dsignal);
261 signal = dsignal->signal;
262 talloc_free(dsignal);
263
264 DEBUG4("Processing deferred signal - %s",
265 fr_table_str_by_value(connection_dsignals, signal, "<INVALID>"));
266
267 switch (signal) {
270 break;
271
274 break;
275
276 case CONNECTION_DSIGNAL_RECONNECT_FAILED: /* Reconnect - Failed */
278 break;
279
280 case CONNECTION_DSIGNAL_RECONNECT_EXPIRED: /* Reconnect - Expired */
282 break;
283
286 break;
287
290 break;
291
292 case CONNECTION_DSIGNAL_FREE: /* Freed */
293 talloc_free(conn);
294 return;
295 }
296
297 /*
298 * One of the signal handlers freed the connection
299 * return immediately.
300 */
301 /* coverity[dead_error_line] */
302 if (freed) return;
303 }
304
305 conn->processing_signals = false;
307}
308
309/** Pause processing of deferred signals
310 *
311 * @param[in] conn to pause signal processing for.
312 */
314{
315 conn->signals_pause++;
316}
317
318/** Resume processing of deferred signals
319 *
320 * @param[in] conn to resume signal processing for.
321 */
323{
324 if (conn->signals_pause > 0) conn->signals_pause--;
325 if (conn->signals_pause > 0) return;
326
327 /*
328 * If we're not in a handler process the
329 * deferred signals now.
330 */
331 if (!conn->in_handler) {
333 return;
334 }
335}
336
337/** Called when we enter a handler
338 *
339 */
340#define HANDLER_BEGIN(_conn, _func) \
341void *_prev_handler = (_conn)->in_handler; \
342do { \
343 (_conn)->in_handler = (void *)(_func); \
344} while (0)
345
346/** Called when we exit a handler
347 *
348 */
349#define HANDLER_END(_conn) \
350do { \
351 (_conn)->in_handler = _prev_handler; \
352 if (!(_conn)->signals_pause && (!(_conn)->in_handler)) connection_deferred_signal_process(_conn); \
353} while(0)
354
355
356/** Call a list of watch functions associated with a state
357 *
358 */
359CC_NO_UBSAN(function) /* UBSAN: false positive - Public/private version of connection_t trips -fsanitize=function */
360static inline void connection_watch_call(connection_t *conn, fr_dlist_head_t *list)
361{
362 /*
363 * Nested watcher calls are not allowed
364 * and shouldn't be possible because of
365 * deferred signal processing.
366 */
367 fr_assert(conn->next_watcher == NULL);
368
369 while ((conn->next_watcher = fr_dlist_next(list, conn->next_watcher))) {
370 connection_watch_entry_t *entry = conn->next_watcher;
371 bool oneshot = entry->oneshot; /* Watcher could be freed, so store now */
372
373 if (!entry->enabled) continue;
374 if (oneshot) conn->next_watcher = fr_dlist_remove(list, entry);
375
376/*
377 DEBUG4("Notifying %swatcher - (%p)(conn=%p, prev=%s, state=%s, uctx=%p)",
378 entry->oneshot ? "oneshot " : "",
379 entry->func,
380 conn,
381 fr_table_str_by_value(connection_states, conn->pub.prev, "<INVALID>"),
382 fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"),
383 entry->uctx);
384*/
385
386 entry->func(conn, conn->pub.prev, conn->pub.state, entry->uctx);
387
388 if (oneshot) talloc_free(entry);
389 }
390 conn->next_watcher = NULL;
391}
392
393/** Call the pre handler watch functions
394 *
395 */
396#define WATCH_PRE(_conn) \
397do { \
398 if (fr_dlist_empty(&(_conn)->watch_pre[(_conn)->pub.state])) break; \
399 { \
400 HANDLER_BEGIN(conn, &(_conn)->watch_pre[(_conn)->pub.state]); \
401 connection_watch_call((_conn), &(_conn)->watch_pre[(_conn)->pub.state]); \
402 HANDLER_END(conn); \
403 } \
404} while(0)
405
406/** Call the post handler watch functions
407 *
408 */
409#define WATCH_POST(_conn) \
410do { \
411 if (fr_dlist_empty(&(_conn)->watch_post[(_conn)->pub.state])) break; \
412 { \
413 HANDLER_BEGIN(conn, &(_conn)->watch_post[(_conn)->pub.state]); \
414 connection_watch_call((_conn), &(_conn)->watch_post[(_conn)->pub.state]); \
415 HANDLER_END(conn); \
416 } \
417} while(0)
418
419/** Remove a watch function from a pre/post[state] list
420 *
421 */
422static int connection_del_watch(connection_t *conn, fr_dlist_head_t *state_lists,
424{
425 connection_watch_entry_t *entry = NULL;
426 fr_dlist_head_t *list = &state_lists[state];
427
428 while ((entry = fr_dlist_next(list, entry))) {
429 if (entry->func == watch) {
430/*
431 DEBUG4("Removing %s watcher %p",
432 fr_table_str_by_value(connection_states, state, "<INVALID>"),
433 watch);
434*/
435 if (conn->next_watcher == entry) {
436 conn->next_watcher = fr_dlist_remove(list, entry);
437 } else {
438 fr_dlist_remove(list, entry);
439 }
440 talloc_free(entry);
441 return 0;
442 }
443 }
444
445 return -1;
446}
447
448/** Remove a watch function from a pre list
449 *
450 * @param[in] conn The connection to remove the watcher from.
451 * @param[in] state to remove the watch from.
452 * @param[in] watch Function to remove.
453 * @return
454 * - 0 if the function was removed successfully.
455 * - -1 if the function wasn't present in the watch list.
456 * - -2 an invalid state was passed.
457 */
459{
460 if (state >= CONNECTION_STATE_MAX) return -2;
461
462 return connection_del_watch(conn, conn->watch_pre, state, watch);
463}
464
465/** Remove a watch function from a post list
466 *
467 * @param[in] conn The connection to remove the watcher from.
468 * @param[in] state to remove the watch from.
469 * @param[in] watch Function to remove.
470 * @return
471 * - 0 if the function was removed successfully.
472 * - -1 if the function wasn't present in the watch list.
473 * - -2 an invalid state was passed.
474 */
476{
477 if (state >= CONNECTION_STATE_MAX) return -2;
478
479 return connection_del_watch(conn, conn->watch_post, state, watch);
480}
481
482/** Add a watch entry to the pre/post[state] list
483 *
484 */
486 connection_watch_t watch, bool oneshot, void const *uctx)
487{
489
490 MEM(entry = talloc_zero(conn, connection_watch_entry_t));
491
492 entry->func = watch;
493 entry->oneshot = oneshot;
494 entry->enabled = true;
495 memcpy(&entry->uctx, &uctx, sizeof(entry->uctx));
496
497 fr_dlist_insert_tail(list, entry);
498
499 return entry;
500}
501
502/** Add a callback to be executed before a state function has been called
503 *
504 * @param[in] conn to add watcher to.
505 * @param[in] state to call watcher on entering.
506 * @param[in] watch function to call.
507 * @param[in] oneshot If true, remove the function after calling.
508 * @param[in] uctx to pass to callbacks.
509 * @return
510 * - NULL if state value is invalid.
511 * - A new watch entry handle.
512 */
514 connection_watch_t watch, bool oneshot, void const *uctx)
515{
516 if (state >= CONNECTION_STATE_MAX) return NULL;
517
518 return connection_add_watch(conn, &conn->watch_pre[state], watch, oneshot, uctx);
519}
520
521/** Add a callback to be executed after a state function has been called
522 *
523 * Where a user callback is executed on state change, the post function
524 * is only called if the callback succeeds.
525 *
526 * @param[in] conn to add watcher to.
527 * @param[in] state to call watcher on entering.
528 * @param[in] watch function to call.
529 * @param[in] oneshot If true, remove the function after calling.
530 * @param[in] uctx to pass to callbacks.
531 * @return
532 * - NULL if state value is invalid.
533 * - A new watch entry handle.
534 */
536 connection_watch_t watch, bool oneshot, void const *uctx)
537{
538 if (state >= CONNECTION_STATE_MAX) return NULL;
539
540 return connection_add_watch(conn, &conn->watch_post[state], watch, oneshot, uctx);
541}
542
543/** Enable a watcher
544 *
545 * @param[in] entry to enabled.
546 */
548{
549 (void)talloc_get_type_abort(entry, connection_watch_entry_t);
550 entry->enabled = true;
551}
552
553/** Disable a watcher
554 *
555 * @param[in] entry to disable.
556 */
558{
559 (void)talloc_get_type_abort(entry, connection_watch_entry_t);
560 entry->enabled = false;
561}
562
563/** Enable a watcher and replace the uctx
564 *
565 * @param[in] entry to enabled.
566 * @param[in] uctx Opaque data to pass to the callback.
567 */
569{
570 (void)talloc_get_type_abort(entry, connection_watch_entry_t);
571 entry->enabled = true;
572 memcpy(&entry->uctx, &uctx, sizeof(entry->uctx));
573}
574
575/** Change the uctx of an entry
576 *
577 * @param[in] entry to enabled.
578 * @param[in] uctx Opaque data to pass to the callback.
579 */
581{
582 (void)talloc_get_type_abort(entry, connection_watch_entry_t);
583 memcpy(&entry->uctx, &uctx, sizeof(entry->uctx));
584}
585
586/** Return the state of a watch entry
587 *
588 * @param[in] entry to return state of.
589 * @return
590 * - true if enabled.
591 * - false if disabled.
592 */
594{
595 (void)talloc_get_type_abort(entry, connection_watch_entry_t);
596 return entry->enabled;
597}
598
599/** Return the number of times we've attempted to establish or re-establish this connection
600 *
601 * @param[in] conn to get count from.
602 * @return the number of times the connection has reconnected.
603 */
605{
606 if (conn->pub.reconnected == 0) return 0; /* Has never been initialised */
607
608 return conn->pub.reconnected - 1; /* We don't count the first connection attempt */
609}
610
611/** Return the number of times this connection has timed out whilst connecting
612 *
613 * @param[in] conn to get count from.
614 * @return the number of times the connection has timed out whilst connecting.
615 */
617{
618 return conn->pub.timed_out;
619}
620
621/** The requisite period of time has passed, try and re-open the connection
622 *
623 * @param[in] el the time event occurred on.
624 * @param[in] now The current time.
625 * @param[in] uctx The #connection_t the fd is associated with.
626 */
628{
629 connection_t *conn = talloc_get_type_abort(uctx, connection_t);
630
631 switch (conn->pub.state) {
635 break;
636
637 default:
639 break;
640 }
641}
642
643/** Close the connection, then wait for another state change
644 *
645 */
647{
648 switch (conn->pub.state) {
652 break;
653
654 default:
656 return;
657 }
658
660
662
663 /*
664 * If there's a close callback, call it, so that the
665 * API client can free any resources associated
666 * with the connection handle.
667 */
668 WATCH_PRE(conn);
669
670 /*
671 * is_closed is for pure paranoia. If everything
672 * is working correctly this state should never
673 * be entered if the connection is closed.
674 */
675 fr_assert(!conn->is_closed);
676 if (conn->close && !conn->is_closed) {
677 HANDLER_BEGIN(conn, conn->close);
678 DEBUG4("Calling close(el=%p, h=%p, uctx=%p)", conn->pub.el, conn->pub.h, conn->uctx);
679 conn->close(conn->pub.el, conn->pub.h, conn->uctx);
680 conn->is_closed = true; /* Ensure close doesn't get called twice if the connection is freed */
681 HANDLER_END(conn);
682 } else {
683 conn->is_closed = true;
684 }
685 WATCH_POST(conn);
686}
687
688/** Connection timeout
689 *
690 * Connection wasn't opened within the configured period of time
691 *
692 * @param[in] el the time event occurred on.
693 * @param[in] now The current time.
694 * @param[in] uctx The #connection_t the fd is associated with.
695 */
697{
698 connection_t *conn = talloc_get_type_abort(uctx, connection_t);
699
701}
702
703/** Gracefully shutdown the handle
704 *
705 */
707{
709
710 switch (conn->pub.state) {
712 break;
713
714 default:
716 return;
717 }
718
720
721 WATCH_PRE(conn);
722 {
723 HANDLER_BEGIN(conn, conn->shutdown);
724 DEBUG4("Calling shutdown(el=%p, h=%p, uctx=%p)", conn->pub.el, conn->pub.h, conn->uctx);
725 ret = conn->shutdown(conn->pub.el, conn->pub.h, conn->uctx);
726 HANDLER_END(conn);
727 }
728 switch (ret) {
730 break;
731
732 default:
734 return;
735 }
736 WATCH_POST(conn);
737
738 /*
739 * If there's a connection timeout,
740 * set, then add the timer.
741 *
742 * The connection may be bad, in which
743 * case we want to automatically fail
744 * if it doesn't shutdown within the
745 * timeout period.
746 */
748 if (fr_event_timer_in(conn, conn->pub.el, &conn->ev,
749 conn->connection_timeout, _connection_timeout, conn) < 0) {
750 /*
751 * Can happen when the event loop is exiting
752 */
753 PERROR("Failed setting connection_timeout timer, closing connection");
755 }
756 }
757}
758
759/** Connection failed
760 *
761 * Transition to the CONNECTION_STATE_FAILED state.
762 *
763 * If the connection was open, or couldn't be opened wait for reconnection_delay before transitioning
764 * back to init.
765 *
766 * If no reconnection_delay was set, transition to halted.
767 *
768 * @param[in] conn that failed.
769 */
771{
774
776
777 /*
778 * Explicit error occurred, delete the connection timer
779 */
781
782 /*
783 * Record what state the connection is currently in
784 * so we can figure out what to do next.
785 */
786 prev = conn->pub.state;
787
788 /*
789 * Now transition to failed
790 */
792
793 /*
794 * If there's a failed callback, give it the
795 * opportunity to suspend/destroy the
796 * connection.
797 */
798 WATCH_PRE(conn);
799 if (conn->failed) {
800 HANDLER_BEGIN(conn, conn->failed);
801 DEBUG4("Calling failed(h=%p, state=%s, uctx=%p)", conn->pub.h,
802 fr_table_str_by_value(connection_states, prev, "<INVALID>"), conn->uctx);
803 ret = conn->failed(conn->pub.h, prev, conn->uctx);
804 HANDLER_END(conn);
805 }
806 WATCH_POST(conn);
807
808 /*
809 * Enter the closed state if we failed during
810 * connecting, or when we were connected.
811 */
812 switch (prev) {
815 case CONNECTION_STATE_TIMEOUT: /* Timeout means the connection progress past init */
816 case CONNECTION_STATE_SHUTDOWN: /* Shutdown means the connection failed whilst shutting down */
818 break;
819
820 default:
821 break;
822 }
823
824 if (conn->failed) {
825 switch (ret) {
826 /*
827 * The callback signalled it wants the
828 * connection to be reinitialised
829 * after reconnection_delay, or
830 * immediately if the failure was due
831 * to a connection timeout.
832 */
834 break;
835
836 /*
837 * The callback signalled it wants the
838 * connection to stop.
839 */
841 default:
843 return;
844 }
845 }
846
847 /*
848 * What previous state we were in
849 * determines if we need to apply the
850 * reconnect timeout.
851 */
852 switch (prev) {
853 case CONNECTION_STATE_INIT: /* Failed during initialisation */
854 case CONNECTION_STATE_CONNECTED: /* Failed after connecting */
855 case CONNECTION_STATE_CONNECTING: /* Failed during connecting */
856 case CONNECTION_STATE_SHUTDOWN: /* Failed during shutdown */
858 DEBUG2("Delaying reconnection by %pVs", fr_box_time_delta(conn->reconnection_delay));
859 if (fr_event_timer_in(conn, conn->pub.el, &conn->ev,
860 conn->reconnection_delay, _reconnect_delay_done, conn) < 0) {
861 /*
862 * Can happen when the event loop is exiting
863 */
864 PERROR("Failed inserting reconnection_delay timer event, halting connection");
866 }
867 return;
868 }
869
870 /*
871 * If there's no reconnection
872 * delay, then don't automatically
873 * reconnect, and wait to be
874 * signalled.
875 */
877 break;
878
879 case CONNECTION_STATE_TIMEOUT: /* Failed during connecting due to timeout */
881 break;
882
883 default:
884 fr_assert(0);
885 }
886}
887
888/** Enter the timeout state
889 *
890 * The connection took took long to open. Timeout the attempt and transition
891 * to the failed state.
892 */
894{
895 switch (conn->pub.state) {
898 break;
899
900 default:
902 }
903
904 ERROR("Connection failed - timed out after %pVs", fr_box_time_delta(conn->connection_timeout));
905
907
908 conn->pub.timed_out++;
909
911}
912
913/** Enter the halted state
914 *
915 * Here we wait, until signalled by connection_signal_reconnect.
916 */
918{
919 fr_assert(conn->is_closed);
920
921 switch (conn->pub.state) {
922 case CONNECTION_STATE_FAILED: /* Init failure */
924 break;
925
926 default:
928 }
929
931
933 WATCH_PRE(conn);
934 WATCH_POST(conn);
935}
936
937/** Enter the connected state
938 *
939 * The connection is now fully connected. At this point we call the open callback
940 * so that the API client can install its normal set of I/O callbacks to deal with
941 * sending/receiving actual data.
942 *
943 * After this, the connection will only transition states if an API client
944 * explicitly calls connection_signal_reconnect.
945 *
946 * The connection API cannot monitor the connection for failure conditions.
947 *
948 * @param[in] conn Entering the connecting state.
949 */
951{
952 int ret;
953
955
957
959 WATCH_PRE(conn);
960 if (conn->open) {
961 HANDLER_BEGIN(conn, conn->open);
962 DEBUG4("Calling open(el=%p, h=%p, uctx=%p)", conn->pub.el, conn->pub.h, conn->uctx);
963 ret = conn->open(conn->pub.el, conn->pub.h, conn->uctx);
964 HANDLER_END(conn);
965 } else {
967 }
968
969 switch (ret) {
970 /*
971 * Callback agrees everything is connected
972 */
974 DEBUG2("Connection established");
975 WATCH_POST(conn); /* Only call if we successfully connected */
976 return;
977
978 /*
979 * Open callback failed
980 */
982 default:
983 PERROR("Connection failed");
985 return;
986 }
987}
988
989/** Enter the connecting state
990 *
991 * After this function returns we wait to be signalled with connection_singal_connected
992 * or for the connection timer to expire.
993 *
994 * @param[in] conn Entering the connecting state.
995 */
997{
998 switch (conn->pub.state) {
1000 break;
1001
1002 default:
1004 return;
1005 }
1006
1008
1009 WATCH_PRE(conn);
1010 WATCH_POST(conn);
1011
1012 /*
1013 * If there's a connection timeout,
1014 * set, then add the timer.
1015 */
1017 if (fr_event_timer_in(conn, conn->pub.el, &conn->ev,
1018 conn->connection_timeout, _connection_timeout, conn) < 0) {
1019 PERROR("Failed setting connection_timeout event, failing connection");
1020
1021 /*
1022 * This can happen when the event loop
1023 * is exiting.
1024 *
1025 * Entering fail will close partially
1026 * open connection and then, if we still
1027 * can't insert a timer, then the connection
1028 * will be halted and sit idle until its
1029 * freed.
1030 */
1032 }
1033 }
1034}
1035
1036/** Initial state of the connection
1037 *
1038 * Calls the init function we were passed to allocate a library specific handle or
1039 * file descriptor.
1040 *
1041 * @param[in] conn To initialise.
1042 */
1044{
1046
1047 switch (conn->pub.state) {
1051 break;
1052
1053 default:
1055 return;
1056 }
1057
1058 /*
1059 * Increment every time we enter
1060 * We have to do this, as we don't know
1061 * whether the connection was halted by
1062 * the failed callback, and is now being
1063 * reconnected, or was automatically
1064 * reconnected.
1065 */
1066 conn->pub.reconnected++;
1067
1069
1070 /*
1071 * If we have an init callback, call it.
1072 */
1073 WATCH_PRE(conn);
1074 if (conn->init) {
1075 HANDLER_BEGIN(conn, conn->init);
1076 DEBUG4("Calling init(h_out=%p, conn=%p, uctx=%p)", &conn->pub.h, conn, conn->uctx);
1077 ret = conn->init(&conn->pub.h, conn, conn->uctx);
1078 HANDLER_END(conn);
1079 } else {
1081 }
1082
1083 switch (ret) {
1085 conn->is_closed = false; /* We now have a handle */
1086 WATCH_POST(conn); /* Only call if we successfully initialised the handle */
1088 return;
1089
1091 conn->is_closed = false; /* We now have a handle */
1092 WATCH_POST(conn); /* Only call if we successfully initialised the handle */
1094 return;
1095
1096 /*
1097 * Initialisation callback failed
1098 */
1100 default:
1101 PERROR("Connection initialisation failed");
1103 break;
1104 }
1105}
1106
1107/** Asynchronously signal a halted connection to start
1108 *
1109 */
1111{
1112 DEBUG2("Signalled to start from %s state",
1113 fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"));
1114
1115 if (DEFER_SIGNALS(conn)) {
1117 return;
1118 }
1119
1120 switch (conn->pub.state) {
1123 break;
1124
1125 default:
1126 break;
1127 }
1128}
1129
1130/** Asynchronously signal that the connection is open
1131 *
1132 * Some libraries like libldap are extremely annoying and only return control
1133 * to the caller after a connection is open.
1134 *
1135 * For these libraries, we can't use an I/O handler to determine when the
1136 * connection is open so we rely on callbacks built into the library to
1137 * signal that the transition has occurred.
1138 *
1139 */
1141{
1142 fr_assert(!conn->open); /* Use one or the other not both! */
1143
1144 DEBUG2("Signalled connected from %s state",
1145 fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"));
1146
1147 if (DEFER_SIGNALS(conn)) {
1149 return;
1150 }
1151
1152 switch (conn->pub.state) {
1155 break;
1156
1157 default:
1158 break;
1159 }
1160}
1161
1162/** Asynchronously signal the connection should be reconnected
1163 *
1164 * Should be called if the caller has knowledge that the connection is bad
1165 * and should be reconnected.
1166 *
1167 * @param[in] conn to reconnect.
1168 * @param[in] reason Why the connection was signalled to reconnect.
1169 */
1171{
1172 DEBUG2("Signalled to reconnect from %s state",
1173 fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"));
1174
1175 if (DEFER_SIGNALS(conn)) {
1176 if ((reason == CONNECTION_EXPIRED) && conn->shutdown) {
1178 return;
1179 }
1180
1182 return;
1183 }
1184
1185 switch (conn->pub.state) {
1186 case CONNECTION_STATE_CLOSED: /* Don't circumvent reconnection_delay */
1187 case CONNECTION_STATE_INIT: /* Already initialising */
1188 break;
1189
1192 break;
1193
1195 if (reason == CONNECTION_EXPIRED) break; /* Already shutting down */
1197 break;
1198
1200 if (reason == CONNECTION_EXPIRED) {
1201 if (conn->shutdown) {
1203 break;
1204 }
1206 break;
1207 }
1209
1214 break;
1215
1217 fr_assert(0);
1218 return;
1219 }
1220}
1221
1222/** Shuts down a connection gracefully
1223 *
1224 * If a shutdown function has been provided, it is called.
1225 * It's then up to the shutdown function to install I/O handlers to signal
1226 * when the connection has finished shutting down and should be closed
1227 * via #connection_signal_halt.
1228 *
1229 * @param[in] conn to shutdown.
1230 */
1232{
1233 DEBUG2("Signalled to shutdown from %s state",
1234 fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"));
1235
1236 if (DEFER_SIGNALS(conn)) {
1238 return;
1239 }
1240
1241 switch (conn->pub.state) {
1244 break;
1245
1248 break;
1249
1250 /*
1251 * If the connection is connected it needs to be
1252 * shutdown first.
1253 *
1254 * The shutdown callback or an FD event it inserts then
1255 * to signal that the connection should be closed.
1256 */
1258 if (conn->shutdown) {
1260 break;
1261 }
1263
1264 /*
1265 * If the connection is any of these states it
1266 * must have completed INIT which means it has
1267 * an active handle which needs to be closed before
1268 * the connection is halted.
1269 */
1274 fr_assert(conn->is_closed);
1275
1279 break;
1280
1282 fr_assert(0);
1283 return;
1284 }
1285}
1286
1287/** Shuts down a connection ungracefully
1288 *
1289 * If a connection is in an open or connection state it will be closed immediately.
1290 * Otherwise the connection will transition directly to the halted state.
1291 *
1292 * @param[in] conn to halt.
1293 */
1295{
1296 DEBUG2("Signalled to halt from %s state",
1297 fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"));
1298
1299 if (DEFER_SIGNALS(conn)) {
1301 return;
1302 }
1303
1304 switch (conn->pub.state) {
1306 break;
1307
1311 break;
1312
1313 /*
1314 * If the connection is any of these states it
1315 * must have completed INIT which means it has
1316 * an active handle which needs to be closed before
1317 * the connection is halted.
1318 *
1319 * The exception is when a connection fails to open
1320 * so goes from INIT -> FAILED, means is_closed
1321 * is true, as the connection has never opened.
1322 */
1328 if (!conn->is_closed) connection_state_enter_closed(conn);
1329 fr_assert(conn->is_closed);
1331 break;
1332
1334 fr_assert(0);
1335 return;
1336 }
1337}
1338/** Receive an error notification when we're connecting a socket
1339 *
1340 * @param[in] el event list the I/O event occurred on.
1341 * @param[in] fd the I/O even occurred for.
1342 * @param[in] flags from_kevent.
1343 * @param[in] fd_errno from kevent.
1344 * @param[in] uctx The #connection_t this fd is associated with.
1345 */
1346static void _connection_error(UNUSED fr_event_list_t *el, int fd, UNUSED int flags, int fd_errno, void *uctx)
1347{
1348 connection_t *conn = talloc_get_type_abort(uctx, connection_t);
1349
1350 ERROR("Connection failed for fd (%d): %s", fd, fr_syserror(fd_errno));
1352}
1353
1354/** Receive a write notification after a socket is connected
1355 *
1356 * @param[in] el event list the I/O event occurred on.
1357 * @param[in] fd the I/O even occurred for.
1358 * @param[in] flags from kevent.
1359 * @param[in] uctx The #connection_t this fd is associated with.
1360 */
1361static void _connection_writable(fr_event_list_t *el, int fd, UNUSED int flags, void *uctx)
1362{
1363 connection_t *conn = talloc_get_type_abort(uctx, connection_t);
1364
1367}
1368
1369/** Remove the FD we were watching for connection open/fail from the event loop
1370 *
1371 */
1373 UNUSED connection_state_t prev, connection_state_t state, void *uctx)
1374{
1375 int fd = *(talloc_get_type_abort(uctx, int));
1376
1377 /*
1378 * Two states can trigger a cleanup
1379 * Remove the watch on the one that didn't
1380 */
1381 switch (state) {
1384 break;
1385
1388 break;
1389
1390 default:
1391 fr_assert(0);
1392 break;
1393 }
1394
1396 talloc_free(uctx);
1397}
1398
1399/** Setup the connection to change states to connected or failed based on I/O events
1400 *
1401 * Will automatically cleanup after itself, in preparation for
1402 * new I/O handlers to be installed in the open() callback.
1403 *
1404 * @return
1405 * - 0 on success.
1406 * - -1 on failure.
1407 */
1409{
1410 int *fd_s;
1411
1412 /*
1413 * If connection becomes writable we
1414 * assume it's open.
1415 */
1416 if (fr_event_fd_insert(conn, NULL, conn->pub.el, fd,
1417 NULL,
1420 conn) < 0) {
1421 PERROR("Failed inserting fd (%d) into event loop %p",
1422 fd, conn->pub.el);
1424 return -1;
1425 }
1426
1427 /*
1428 * Stop the static analysis tools
1429 * complaining about assigning ints
1430 * to pointers.
1431 */
1432 MEM(fd_s = talloc_zero(conn, int));
1433 *fd_s = fd;
1434
1435 /*
1436 * Add a oneshot watcher to remove
1437 * the I/O handlers if the connection
1438 * fails, or is connected.
1439 */
1444 return 0;
1445}
1446
1447/** Close a connection if it's freed
1448 *
1449 * @param[in] conn to free.
1450 * @return
1451 * - 0 connection was freed immediately.
1452 * - 1 connection free was deferred.
1453 */
1455{
1456 /*
1457 * Explicitly cancel any pending events
1458 */
1459 fr_event_timer_delete(&conn->ev);
1460
1461 /*
1462 * Don't allow the connection to be
1463 * arbitrarily freed by a callback.
1464 *
1465 * Add a deferred signal to free the
1466 * connection later.
1467 */
1468 if (DEFER_SIGNALS(conn)) {
1470 return -1;
1471 }
1472
1473 switch (conn->pub.state) {
1475 break;
1476
1477 /*
1478 * Need to close the connection first
1479 */
1484
1485 default:
1487 break;
1488 }
1489 return 0;
1490}
1491
1492/** Allocate a new connection
1493 *
1494 * After the connection has been allocated, it should be started with a call to #connection_signal_init.
1495 *
1496 * The connection state machine can detect when the connection is open in one of two ways.
1497 * - You can install a generic socket open/fail callback, using connection_signal_on_fd.
1498 * - You can call either #connection_signal_connected or connection_signal_recommend.
1499 * This allows the connection state machine to work with more difficult library APIs,
1500 * which may not return control to the caller as connections are opened.
1501 *
1502 * @param[in] ctx to allocate connection handle in. If the connection
1503 * handle is freed, and the #connection_state_t is
1504 * #CONNECTION_STATE_CONNECTING or #CONNECTION_STATE_CONNECTED the
1505 * close callback will be called.
1506 * @param[in] el to use for timer events, and to pass to the #connection_open_t callback.
1507 * @param[in] funcs callback functions.
1508 * @param[in] conf our configuration.
1509 * @param[in] log_prefix To prepend to log messages.
1510 * @param[in] uctx User context to pass to callbacks.
1511 * @return
1512 * - A new #connection_t on success.
1513 * - NULL on failure.
1514 */
1516 connection_funcs_t const *funcs,
1517 connection_conf_t const *conf,
1518 char const *log_prefix,
1519 void const *uctx)
1520{
1521 size_t i;
1522 connection_t *conn;
1523 uint64_t id;
1524
1525 fr_assert(el);
1526
1527 MEM(conn = talloc(ctx, connection_t));
1528 talloc_set_destructor(conn, _connection_free);
1529
1531
1532 *conn = (connection_t){
1533 .pub = {
1534 .id = id,
1535 .state = CONNECTION_STATE_HALTED,
1536 .el = el
1537 },
1538 .reconnection_delay = conf->reconnection_delay,
1539 .connection_timeout = conf->connection_timeout,
1540 .init = funcs->init,
1541 .open = funcs->open,
1542 .close = funcs->close,
1543 .failed = funcs->failed,
1544 .shutdown = funcs->shutdown,
1545 .is_closed = true, /* Starts closed */
1546 .pub.name = talloc_asprintf(conn, "%s - [%" PRIu64 "]", log_prefix, id)
1547 };
1548 memcpy(&conn->uctx, &uctx, sizeof(conn->uctx));
1549
1550 for (i = 0; i < NUM_ELEMENTS(conn->watch_pre); i++) {
1552 }
1553 for (i = 0; i < NUM_ELEMENTS(conn->watch_post); i++) {
1555 }
1557
1558 /*
1559 * Pre-allocate a on_halt watcher for deferred signal processing
1560 */
1563 connection_watch_disable(conn->on_halted); /* Start disabled */
1564
1565 return conn;
1566}
#define L(_str)
Helper for initialising arrays of string literals.
Definition build.h:209
#define FALL_THROUGH
clang 10 doesn't recognised the FALL-THROUGH comment anymore
Definition build.h:322
#define CC_NO_UBSAN(_sanitize)
Definition build.h:426
#define UNUSED
Definition build.h:315
#define NUM_ELEMENTS(_t)
Definition build.h:337
void(* connection_watch_t)(connection_t *conn, connection_state_t prev, connection_state_t state, void *uctx)
Receive a notification when a connection enters a particular state.
Definition connection.h:204
connection_state_t(* connection_failed_t)(void *h, connection_state_t state, void *uctx)
Notification that a connection attempt has failed.
Definition connection.h:167
uint64_t _CONST timed_out
How many times has this connection timed out when connecting.
Definition connection.h:78
void(* connection_close_t)(fr_event_list_t *el, void *h, void *uctx)
Notification that the connection has errored and must be closed.
Definition connection.h:181
fr_event_list_t *_CONST el
Event list for timers and I/O events.
Definition connection.h:74
connection_state_t(* connection_init_t)(void **h_out, connection_t *conn, void *uctx)
Callback for the initialise state.
Definition connection.h:116
connection_state_t
Definition connection.h:45
@ CONNECTION_STATE_FAILED
Connection has failed.
Definition connection.h:54
@ CONNECTION_STATE_HALTED
The connection is in a halted stat.
Definition connection.h:46
@ CONNECTION_STATE_CLOSED
Connection has been closed.
Definition connection.h:55
@ CONNECTION_STATE_CONNECTED
File descriptor is open (ready for writing).
Definition connection.h:52
@ CONNECTION_STATE_TIMEOUT
Timeout during CONNECTION_STATE_CONNECTING.
Definition connection.h:51
@ CONNECTION_STATE_INIT
Init state, sets up connection.
Definition connection.h:49
@ CONNECTION_STATE_MAX
Definition connection.h:56
@ CONNECTION_STATE_CONNECTING
Waiting for connection to establish.
Definition connection.h:50
@ CONNECTION_STATE_SHUTDOWN
Connection is shutting down.
Definition connection.h:53
uint64_t _CONST reconnected
How many times we've attempted to establish or re-establish this connection.
Definition connection.h:76
void *_CONST h
Connection handle.
Definition connection.h:73
connection_reason_t
Definition connection.h:83
@ CONNECTION_EXPIRED
Connection is being reconnected because it's at the end of its life.
Definition connection.h:85
@ CONNECTION_FAILED
Connection is being reconnected because it failed.
Definition connection.h:84
connection_state_t(* connection_open_t)(fr_event_list_t *el, void *h, void *uctx)
Notification that the connection is now open.
Definition connection.h:130
connection_state_t _CONST state
Current connection state.
Definition connection.h:70
connection_init_t init
Definition connection.h:187
connection_shutdown_t shutdown
Definition connection.h:189
connection_failed_t failed
Definition connection.h:190
connection_open_t open
Definition connection.h:188
uint64_t _CONST id
Unique identifier for the connection.
Definition connection.h:72
connection_close_t close
Definition connection.h:191
connection_state_t(* connection_shutdown_t)(fr_event_list_t *el, void *h, void *uctx)
Start the process of gracefully shutting down the connection.
Definition connection.h:149
Holds a complete set of functions for a connection.
Definition connection.h:186
Public fields for the connection.
Definition connection.h:67
#define MEM(x)
Definition debug.h:36
#define ERROR(fmt,...)
Definition dhcpclient.c:41
static void * fr_dlist_head(fr_dlist_head_t const *list_head)
Return the HEAD item of a list or NULL if the list is empty.
Definition dlist.h:486
static void * fr_dlist_remove(fr_dlist_head_t *list_head, void *ptr)
Remove an item from the list.
Definition dlist.h:638
static void * fr_dlist_tail(fr_dlist_head_t const *list_head)
Return the TAIL item of a list or NULL if the list is empty.
Definition dlist.h:531
static int fr_dlist_insert_tail(fr_dlist_head_t *list_head, void *ptr)
Insert an item into the tail of a list.
Definition dlist.h:378
#define fr_dlist_talloc_init(_head, _type, _field)
Initialise the head structure of a doubly linked list.
Definition dlist.h:275
static void * fr_dlist_next(fr_dlist_head_t const *list_head, void const *ptr)
Get the next item in a list.
Definition dlist.h:555
Head of a doubly linked list.
Definition dlist.h:51
Entry in a doubly linked list.
Definition dlist.h:41
#define fr_event_fd_insert(...)
Definition event.h:232
@ FR_EVENT_FILTER_IO
Combined filter for read/write functions/.
Definition event.h:62
#define fr_event_timer_in(...)
Definition event.h:255
#define PERROR(_fmt,...)
Definition log.h:228
#define DEBUG4(_fmt,...)
Definition log.h:267
talloc_free(reap)
int fr_event_timer_delete(fr_event_timer_t const **ev_p)
Delete a timer event from the event list.
Definition event.c:1611
int fr_event_fd_delete(fr_event_list_t *el, int fd, fr_event_filter_t filter)
Remove a file descriptor from the event loop.
Definition event.c:1260
Stores all information relating to an event list.
Definition event.c:411
A timer event.
Definition event.c:102
#define fr_assert(_expr)
Definition rad_assert.h:38
#define DEBUG2(fmt,...)
Definition radclient.h:43
static rs_t * conf
Definition radsniff.c:53
static void connection_watch_call(connection_t *conn, fr_dlist_head_t *list)
Call a list of watch functions associated with a state.
Definition connection.c:360
struct connection_watch_entry_s connection_watch_entry_t
An entry in a watch function list.
void connection_signal_shutdown(connection_t *conn)
Shuts down a connection gracefully.
fr_dlist_head_t watch_post[CONNECTION_STATE_MAX]
Function called after state callback.
Definition connection.c:101
void connection_watch_enable(connection_watch_entry_t *entry)
Enable a watcher.
Definition connection.c:547
static size_t connection_trigger_names_len
Definition connection.c:74
static int connection_del_watch(connection_t *conn, fr_dlist_head_t *state_lists, connection_state_t state, connection_watch_t watch)
Remove a watch function from a pre/post[state] list.
Definition connection.c:422
uint64_t connection_get_num_timed_out(connection_t const *conn)
Return the number of times this connection has timed out whilst connecting.
Definition connection.c:616
fr_dlist_t entry
List entry.
Definition connection.c:82
bool enabled
Whether the watch entry is enabled.
Definition connection.c:86
void * in_handler
Connection is currently in a callback.
Definition connection.c:95
static void _connection_timeout(UNUSED fr_event_list_t *el, UNUSED fr_time_t now, void *uctx)
Connection timeout.
Definition connection.c:696
#define DEFER_SIGNALS(_conn)
Definition connection.c:154
static fr_table_num_ordered_t const connection_dsignals[]
Definition connection.c:169
static void _deferred_signal_connection_on_halted(UNUSED connection_t *conn, UNUSED connection_state_t prev, UNUSED connection_state_t state, void *uctx)
Notification function to tell connection_deferred_signal_process that the connection has been freed.
Definition connection.c:229
void connection_watch_disable(connection_watch_entry_t *entry)
Disable a watcher.
Definition connection.c:557
int connection_del_watch_post(connection_t *conn, connection_state_t state, connection_watch_t watch)
Remove a watch function from a post list.
Definition connection.c:475
connection_shutdown_t shutdown
Signal the connection handle to start shutting down.
Definition connection.c:107
static void _connection_signal_on_fd_cleanup(connection_t *conn, UNUSED connection_state_t prev, connection_state_t state, void *uctx)
Remove the FD we were watching for connection open/fail from the event loop.
fr_time_delta_t connection_timeout
How long to wait in the CONNECTION_STATE_CONNECTING state.
Definition connection.c:112
bool connection_watch_is_enabled(connection_watch_entry_t *entry)
Return the state of a watch entry.
Definition connection.c:593
static atomic_uint_fast64_t connection_counter
Definition connection.c:76
static size_t connection_dsignals_len
Definition connection.c:178
void * uctx
User data.
Definition connection.c:93
fr_dlist_head_t deferred_signals
A list of signals we received whilst we were in a handler.
Definition connection.c:117
static int _connection_free(connection_t *conn)
Close a connection if it's freed.
static void connection_state_enter_failed(connection_t *conn)
Connection failed.
Definition connection.c:770
void connection_signal_halt(connection_t *conn)
Shuts down a connection ungracefully.
#define WATCH_POST(_conn)
Call the post handler watch functions.
Definition connection.c:409
void connection_signals_resume(connection_t *conn)
Resume processing of deferred signals.
Definition connection.c:322
#define HANDLER_BEGIN(_conn, _func)
Called when we enter a handler.
Definition connection.c:340
connection_init_t init
Callback for initialising a connection.
Definition connection.c:104
static connection_watch_entry_t * connection_add_watch(connection_t *conn, fr_dlist_head_t *list, connection_watch_t watch, bool oneshot, void const *uctx)
Add a watch entry to the pre/post[state] list.
Definition connection.c:485
fr_event_timer_t const * ev
State transition timer.
Definition connection.c:110
connection_close_t close
Callback to close a connection.
Definition connection.c:106
static void connection_state_enter_init(connection_t *conn)
Initial state of the connection.
uint64_t connection_get_num_reconnected(connection_t const *conn)
Return the number of times we've attempted to establish or re-establish this connection.
Definition connection.c:604
static void connection_state_enter_connecting(connection_t *conn)
Enter the connecting state.
Definition connection.c:996
connection_watch_entry_t * next_watcher
Hack to insulate watcher iterator from deletions.
Definition connection.c:102
connection_watch_t func
Function to call when a connection enters the state this list belongs to.
Definition connection.c:83
#define WATCH_PRE(_conn)
Call the pre handler watch functions.
Definition connection.c:396
static void connection_deferred_signal_process(connection_t *conn)
Process any deferred signals.
Definition connection.c:240
connection_dsignal_t signal
Signal that was deferred.
Definition connection.c:185
static void _connection_error(UNUSED fr_event_list_t *el, int fd, UNUSED int flags, int fd_errno, void *uctx)
Receive an error notification when we're connecting a socket.
connection_open_t open
Callback for 'open' notification.
Definition connection.c:105
connection_failed_t failed
Callback for 'failed' notification.
Definition connection.c:108
struct connection_s connection_t
Definition connection.c:27
void connection_watch_set_uctx(connection_watch_entry_t *entry, void const *uctx)
Change the uctx of an entry.
Definition connection.c:580
fr_time_delta_t reconnection_delay
How long to wait in the CONNECTION_STATE_FAILED state.
Definition connection.c:114
static void connection_state_enter_closed(connection_t *conn)
Close the connection, then wait for another state change.
Definition connection.c:646
void * uctx
User data to pass to the function.
Definition connection.c:87
void connection_signal_reconnect(connection_t *conn, connection_reason_t reason)
Asynchronously signal the connection should be reconnected.
connection_dsignal_t
Deferred signals.
Definition connection.c:159
@ CONNECTION_DSIGNAL_RECONNECT_FAILED
Reconnect a failed connection.
Definition connection.c:162
@ CONNECTION_DSIGNAL_HALT
Close a connection (ungracefully).
Definition connection.c:165
@ CONNECTION_DSIGNAL_INIT
Restart a halted connection.
Definition connection.c:160
@ CONNECTION_DSIGNAL_FREE
Free a connection (no further dsignals processed).
Definition connection.c:166
@ CONNECTION_DSIGNAL_SHUTDOWN
Close a connection (gracefully).
Definition connection.c:164
@ CONNECTION_DSIGNAL_CONNECTED
Signal that a connection is connected.
Definition connection.c:161
@ CONNECTION_DSIGNAL_RECONNECT_EXPIRED
Reconnect an expired connection (gracefully).
Definition connection.c:163
bool oneshot
Remove the function after it's called once.
Definition connection.c:85
fr_dlist_head_t watch_pre[CONNECTION_STATE_MAX]
Function called before state callback.
Definition connection.c:100
fr_table_num_ordered_t const connection_states[]
Definition connection.c:49
#define HANDLER_END(_conn)
Called when we exit a handler.
Definition connection.c:349
int connection_signal_on_fd(connection_t *conn, int fd)
Setup the connection to change states to connected or failed based on I/O events.
bool is_closed
The close callback has previously been called.
Definition connection.c:96
#define STATE_TRANSITION(_new)
Definition connection.c:136
void connection_signal_init(connection_t *conn)
Asynchronously signal a halted connection to start.
static fr_table_num_indexed_t const connection_trigger_names[]
Map connection states to trigger names.
Definition connection.c:64
static void connection_state_enter_halted(connection_t *conn)
Enter the halted state.
Definition connection.c:917
connection_t * connection_alloc(TALLOC_CTX *ctx, fr_event_list_t *el, connection_funcs_t const *funcs, connection_conf_t const *conf, char const *log_prefix, void const *uctx)
Allocate a new connection.
static void connection_deferred_signal_add(connection_t *conn, connection_dsignal_t signal)
Add a deferred signal to the signal list.
Definition connection.c:212
fr_dlist_t entry
Entry in the signals list.
Definition connection.c:184
static void _connection_writable(fr_event_list_t *el, int fd, UNUSED int flags, void *uctx)
Receive a write notification after a socket is connected.
connection_watch_entry_t * connection_add_watch_pre(connection_t *conn, connection_state_t state, connection_watch_t watch, bool oneshot, void const *uctx)
Add a callback to be executed before a state function has been called.
Definition connection.c:513
size_t connection_states_len
Definition connection.c:59
connection_watch_entry_t * connection_add_watch_post(connection_t *conn, connection_state_t state, connection_watch_t watch, bool oneshot, void const *uctx)
Add a callback to be executed after a state function has been called.
Definition connection.c:535
static void connection_state_enter_connected(connection_t *conn)
Enter the connected state.
Definition connection.c:950
int connection_del_watch_pre(connection_t *conn, connection_state_t state, connection_watch_t watch)
Remove a watch function from a pre list.
Definition connection.c:458
connection_watch_entry_t * on_halted
Used by the deferred signal processor to learn if a function deeper in the call stack freed the conne...
Definition connection.c:122
static void _reconnect_delay_done(UNUSED fr_event_list_t *el, UNUSED fr_time_t now, void *uctx)
The requisite period of time has passed, try and re-open the connection.
Definition connection.c:627
#define BAD_STATE_TRANSITION(_new)
Definition connection.c:146
struct connection_pub_s pub
Public fields.
Definition connection.c:91
void connection_watch_enable_set_uctx(connection_watch_entry_t *entry, void const *uctx)
Enable a watcher and replace the uctx.
Definition connection.c:568
void connection_signal_connected(connection_t *conn)
Asynchronously signal that the connection is open.
static void connection_state_enter_shutdown(connection_t *conn)
Gracefully shutdown the handle.
Definition connection.c:706
void connection_signals_pause(connection_t *conn)
Pause processing of deferred signals.
Definition connection.c:313
bool processing_signals
Processing deferred signals, don't let the deferred signal processor be called multiple times.
Definition connection.c:97
unsigned int signals_pause
Temporarily stop processing of signals.
Definition connection.c:126
static void connection_state_enter_timeout(connection_t *conn)
Enter the timeout state.
Definition connection.c:893
Holds a signal from a handler until it's safe to process it.
Definition connection.c:183
An entry in a watch function list.
Definition connection.c:81
@ memory_order_relaxed
Definition stdatomic.h:127
#define atomic_fetch_add_explicit(object, operand, order)
Definition stdatomic.h:302
#define ATOMIC_VAR_INIT(value)
Definition stdatomic.h:88
char const * fr_syserror(int num)
Guaranteed to be thread-safe version of strerror.
Definition syserror.c:243
#define fr_table_str_by_value(_table, _number, _def)
Convert an integer to a string.
Definition table.h:772
An element in a table indexed by numeric value.
Definition table.h:92
An element in an arbitrarily ordered array of name to num mappings.
Definition table.h:57
#define fr_time_delta_ispos(_a)
Definition time.h:290
A time delta, a difference in time measured in nanoseconds.
Definition time.h:80
"server local" time.
Definition time.h:69
static fr_event_list_t * el
#define fr_box_time_delta(_val)
Definition value.h:343