The FreeRADIUS server $Id: a4e7f8c94e8038b7627bcf52356b7ed15955c1d7 $
Loading...
Searching...
No Matches
connection.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: a5da78404137bda866384654d7043eec64c0306c $
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/syserror.h>
36
37#ifdef HAVE_STDATOMIC_H
38# include <stdatomic.h>
39# ifndef ATOMIC_VAR_INIT
40# define ATOMIC_VAR_INIT(_x) (_x)
41# endif
42#else
43# include <freeradius-devel/util/stdatomic.h>
44#endif
45
47 { L("HALTED"), CONNECTION_STATE_HALTED },
48 { L("INIT"), CONNECTION_STATE_INIT },
49 { L("CONNECTING"), CONNECTION_STATE_CONNECTING },
50 { L("TIMEOUT"), CONNECTION_STATE_TIMEOUT },
51 { L("CONNECTED"), CONNECTION_STATE_CONNECTED },
52 { L("SHUTDOWN"), CONNECTION_STATE_SHUTDOWN },
53 { L("FAILED"), CONNECTION_STATE_FAILED },
54 { L("CLOSED"), CONNECTION_STATE_CLOSED },
55};
57
58/** Map connection states to trigger names
59 *
60 */
62 [CONNECTION_STATE_HALTED] = { L("connection.halted"), CONNECTION_STATE_HALTED },
63 [CONNECTION_STATE_INIT] = { L("connection.init"), CONNECTION_STATE_INIT },
64 [CONNECTION_STATE_CONNECTING] = { L("connection.connecting"), CONNECTION_STATE_CONNECTING },
65 [CONNECTION_STATE_TIMEOUT] = { L("connection.timeout"), CONNECTION_STATE_TIMEOUT },
66 [CONNECTION_STATE_CONNECTED] = { L("connection.connected"), CONNECTION_STATE_CONNECTED },
67 [CONNECTION_STATE_SHUTDOWN] = { L("connection.shutdown"), CONNECTION_STATE_SHUTDOWN },
68 [CONNECTION_STATE_FAILED] = { L("connection.failed"), CONNECTION_STATE_FAILED },
69 [CONNECTION_STATE_CLOSED] = { L("connection.closed"), CONNECTION_STATE_CLOSED }
70};
72
73static atomic_uint_fast64_t connection_counter = ATOMIC_VAR_INIT(1);
74
75/** An entry in a watch function list
76 *
77 */
79 fr_dlist_t entry; //!< List entry.
80 connection_watch_t func; //!< Function to call when a connection enters
81 ///< the state this list belongs to
82 bool oneshot; //!< Remove the function after it's called once.
83 bool enabled; //!< Whether the watch entry is enabled.
84 void *uctx; //!< User data to pass to the function.
86
88 struct connection_pub_s pub; //!< Public fields
89
90 void *uctx; //!< User data.
91
92 void *in_handler; //!< Connection is currently in a callback.
93 bool processing_signals; //!< Processing deferred signals, don't let the deferred
94 ///< signal processor be called multiple times.
95
96 fr_dlist_head_t watch_pre[CONNECTION_STATE_MAX]; //!< Function called before state callback.
97 fr_dlist_head_t watch_post[CONNECTION_STATE_MAX]; //!< Function called after state callback.
98 connection_watch_entry_t *next_watcher; //!< Hack to insulate watcher iterator from deletions.
99
100 connection_init_t init; //!< Callback for initialising a connection.
101 connection_open_t open; //!< Callback for 'open' notification.
102 connection_close_t close; //!< Callback to close a connection.
103 connection_shutdown_t shutdown; //!< Signal the connection handle to start shutting down.
104 connection_failed_t failed; //!< Callback for 'failed' notification.
105
106 fr_timer_t *ev; //!< State transition timer.
107
108 fr_time_delta_t connection_timeout; //!< How long to wait in the
109 //!< #CONNECTION_STATE_CONNECTING state.
110 fr_time_delta_t reconnection_delay; //!< How long to wait in the
111 //!< #CONNECTION_STATE_FAILED state.
112
113 fr_dlist_head_t deferred_signals; //!< A list of signals we received whilst we were in
114 ///< a handler.
115
116
117 unsigned int signals_pause; //!< Temporarily stop processing of signals.
118
119 CONF_SECTION *trigger_cs; //!< Where to search locally for triggers.
120 fr_pair_list_t *trigger_args; //!< Arguments to pass to the trigger functions.
121 bool triggers; //!< Do we run triggers.
122};
123
124#define CONN_TRIGGER(_state) do { \
125 if (conn->triggers) trigger(unlang_interpret_get_thread_default(), \
126 conn->trigger_cs, NULL, fr_table_str_by_value(connection_trigger_names, _state, "<INVALID>"), true, conn->trigger_args, conn); \
127} while (0)
128
129#define STATE_TRANSITION(_new) \
130do { \
131 DEBUG2("Connection changed state %s -> %s", \
132 fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"), \
133 fr_table_str_by_value(connection_states, _new, "<INVALID>")); \
134 conn->pub.prev = conn->pub.state; \
135 conn->pub.state = _new; \
136 CONN_TRIGGER(_new); \
137} while (0)
138
139#define BAD_STATE_TRANSITION(_new) \
140do { \
141 if (!fr_cond_assert_msg(0, "Connection %" PRIu64 " invalid transition %s -> %s", \
142 conn->pub.id, \
143 fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"), \
144 fr_table_str_by_value(connection_states, _new, "<INVALID>"))) return; \
145} while (0)
146
147#define DEFER_SIGNALS(_conn) ((_conn)->in_handler || (_conn)->signals_pause)
148
149/** Deferred signals
150 *
151 */
152typedef enum : uint8_t {
153 CONNECTION_DSIGNAL_INIT, //!< Restart a halted connection.
154 CONNECTION_DSIGNAL_CONNECTED, //!< Signal that a connection is connected.
155 CONNECTION_DSIGNAL_RECONNECT_FAILED, //!< Reconnect a failed connection.
156 CONNECTION_DSIGNAL_RECONNECT_EXPIRED, //!< Reconnect an expired connection (gracefully).
157 CONNECTION_DSIGNAL_SHUTDOWN, //!< Close a connection (gracefully).
158 CONNECTION_DSIGNAL_HALT, //!< Close a connection (ungracefully).
159 CONNECTION_DSIGNAL_FREE //!< Free a connection (no further dsignals processed).
161
163 { L("INIT"), CONNECTION_DSIGNAL_INIT },
164 { L("CONNECTED"), CONNECTION_DSIGNAL_CONNECTED },
165 { L("RECONNECT-FAILED"), CONNECTION_DSIGNAL_RECONNECT_FAILED },
166 { L("RECONNECT-EXPIRED"), CONNECTION_DSIGNAL_RECONNECT_EXPIRED },
167 { L("SHUTDOWN"), CONNECTION_DSIGNAL_SHUTDOWN },
168 { L("HALT"), CONNECTION_DSIGNAL_HALT },
169 { L("FREE"), CONNECTION_DSIGNAL_FREE }
170};
172
173/** Holds a signal from a handler until it's safe to process it
174 *
175 */
176typedef struct {
177 fr_dlist_t entry; //!< Entry in the signals list.
178 connection_dsignal_t signal; //!< Signal that was deferred.
180
181/*
182 * State transition functions
183 */
192
193/** Add a deferred signal to the signal list
194 *
195 * Processing signals whilst in handlers usually leads to weird
196 * inconsistent states within the connection.
197 *
198 * If a public signal function is called, and detects its being called
199 * from within the handler, it instead adds a deferred signal entry
200 * and immediately returns.
201 *
202 * Once the handler is complete, and all pending C stack state changes
203 * are complete, the deferred signals are drained and processed.
204 */
206{
207 connection_dsignal_entry_t *dsignal, *prev;
208
209 /*
210 * We only suppresses consecutive duplicates at the
211 * tail. A sequence such as [INIT, HALT, INIT] will push
212 * the second INIT, because the tail is HALT. The signal
213 * handlers are generally idempotent (they check current
214 * state), so redundant signals are harmless. Avoiding
215 * this corner case involves doing more work in the
216 * common case, in order to avoid a small amount of work
217 * in the rare case.
218 */
219 prev = fr_dlist_tail(&conn->deferred_signals);
220 if (prev && (prev->signal == signal)) return; /* Don't insert duplicates */
221
222 MEM(dsignal = talloc_zero(conn, connection_dsignal_entry_t));
223 dsignal->signal = signal;
224 fr_dlist_insert_tail(&conn->deferred_signals, dsignal);
225
226// DEBUG4("Adding deferred signal - %s", fr_table_str_by_value(connection_dsignals, signal, "<INVALID>"));
227}
228
229/** Process any deferred signals
230 *
231 */
233{
235
236 /*
237 * We're inside and an instance of this function
238 * higher in the call stack. Don't do anything.
239 *
240 * Every callback runs inside a handler, so a
241 * talloc_free() of the connection from a callback
242 * queues CONNECTION_DSIGNAL_FREE here instead of
243 * freeing it.
244 */
245 if (conn->processing_signals) return;
246 conn->processing_signals = true;
247
248 while ((dsignal = fr_dlist_head(&conn->deferred_signals))) {
250 fr_dlist_remove(&conn->deferred_signals, dsignal);
251 signal = dsignal->signal;
252 talloc_free(dsignal);
253
254 DEBUG4("Processing deferred signal - %s",
255 fr_table_str_by_value(connection_dsignals, signal, "<INVALID>"));
256
257 switch (signal) {
260 break;
261
264 break;
265
266 case CONNECTION_DSIGNAL_RECONNECT_FAILED: /* Reconnect - Failed */
268 break;
269
270 case CONNECTION_DSIGNAL_RECONNECT_EXPIRED: /* Reconnect - Expired */
272 break;
273
276 break;
277
280 break;
281
282 case CONNECTION_DSIGNAL_FREE: /* Freed */
283 talloc_free(conn);
284 return;
285 }
286 }
287
288 conn->processing_signals = false;
289}
290
291/** Pause processing of deferred signals
292 *
293 * @param[in] conn to pause signal processing for.
294 */
296{
297 conn->signals_pause++;
298}
299
300/** Resume processing of deferred signals
301 *
302 * @param[in] conn to resume signal processing for.
303 */
305{
306 if (conn->signals_pause > 0) conn->signals_pause--;
307 if (conn->signals_pause > 0) return;
308
309 /*
310 * If we're not in a handler process the
311 * deferred signals now.
312 */
313 if (!conn->in_handler) {
315 return;
316 }
317}
318
319/** Called when we enter a handler
320 *
321 */
322#define HANDLER_BEGIN(_conn, _func) \
323void *_prev_handler = (_conn)->in_handler; \
324do { \
325 (_conn)->in_handler = (void *)(_func); \
326} while (0)
327
328/** Called when we exit a handler
329 *
330 */
331#define HANDLER_END(_conn) \
332do { \
333 (_conn)->in_handler = _prev_handler; \
334 if (!(_conn)->signals_pause && (!(_conn)->in_handler)) connection_deferred_signal_process(_conn); \
335} while(0)
336
337
338/** Call a list of watch functions associated with a state
339 *
340 */
341CC_NO_UBSAN(function) /* UBSAN: false positive - Public/private version of connection_t trips -fsanitize=function */
342static inline void connection_watch_call(connection_t *conn, fr_dlist_head_t *list)
343{
344 /*
345 * Nested watcher calls are not allowed
346 * and shouldn't be possible because of
347 * deferred signal processing.
348 */
349 fr_assert(conn->next_watcher == NULL);
350
351 while ((conn->next_watcher = fr_dlist_next(list, conn->next_watcher))) {
352 connection_watch_entry_t *entry = conn->next_watcher;
353 bool oneshot = entry->oneshot; /* Watcher could be freed, so store now */
354
355 if (!entry->enabled) continue;
356 if (oneshot) conn->next_watcher = fr_dlist_remove(list, entry);
357
358/*
359 DEBUG4("Notifying %swatcher - (%p)(conn=%p, prev=%s, state=%s, uctx=%p)",
360 entry->oneshot ? "oneshot " : "",
361 entry->func,
362 conn,
363 fr_table_str_by_value(connection_states, conn->pub.prev, "<INVALID>"),
364 fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"),
365 entry->uctx);
366*/
367
368 entry->func(conn, conn->pub.prev, conn->pub.state, entry->uctx);
369
370 if (oneshot) talloc_free(entry);
371 }
372 conn->next_watcher = NULL;
373}
374
375/** Call the pre handler watch functions
376 *
377 */
378#define WATCH_PRE(_conn) \
379do { \
380 if (fr_dlist_empty(&(_conn)->watch_pre[(_conn)->pub.state])) break; \
381 { \
382 HANDLER_BEGIN(conn, &(_conn)->watch_pre[(_conn)->pub.state]); \
383 connection_watch_call((_conn), &(_conn)->watch_pre[(_conn)->pub.state]); \
384 HANDLER_END(conn); \
385 } \
386} while(0)
387
388/** Call the post handler watch functions
389 *
390 */
391#define WATCH_POST(_conn) \
392do { \
393 if (fr_dlist_empty(&(_conn)->watch_post[(_conn)->pub.state])) break; \
394 { \
395 HANDLER_BEGIN(conn, &(_conn)->watch_post[(_conn)->pub.state]); \
396 connection_watch_call((_conn), &(_conn)->watch_post[(_conn)->pub.state]); \
397 HANDLER_END(conn); \
398 } \
399} while(0)
400
401/** Remove a watch function from a pre/post[state] list
402 *
403 */
404static int connection_del_watch(connection_t *conn, fr_dlist_head_t *state_lists,
406{
407 connection_watch_entry_t *entry = NULL;
408 fr_dlist_head_t *list = &state_lists[state];
409
410 while ((entry = fr_dlist_next(list, entry))) {
411 if (entry->func == watch) {
412/*
413 DEBUG4("Removing %s watcher %p",
414 fr_table_str_by_value(connection_states, state, "<INVALID>"),
415 watch);
416*/
417 if (conn->next_watcher == entry) {
418 conn->next_watcher = fr_dlist_remove(list, entry);
419 } else {
420 fr_dlist_remove(list, entry);
421 }
422 talloc_free(entry);
423 return 0;
424 }
425 }
426
427 return -1;
428}
429
430/** Remove a watch function from a pre list
431 *
432 * @param[in] conn The connection to remove the watcher from.
433 * @param[in] state to remove the watch from.
434 * @param[in] watch Function to remove.
435 * @return
436 * - 0 if the function was removed successfully.
437 * - -1 if the function wasn't present in the watch list.
438 * - -2 an invalid state was passed.
439 */
441{
442 if (state >= CONNECTION_STATE_MAX) return -2;
443
444 return connection_del_watch(conn, conn->watch_pre, state, watch);
445}
446
447/** Remove a watch function from a post list
448 *
449 * @param[in] conn The connection to remove the watcher from.
450 * @param[in] state to remove the watch from.
451 * @param[in] watch Function to remove.
452 * @return
453 * - 0 if the function was removed successfully.
454 * - -1 if the function wasn't present in the watch list.
455 * - -2 an invalid state was passed.
456 */
458{
459 if (state >= CONNECTION_STATE_MAX) return -2;
460
461 return connection_del_watch(conn, conn->watch_post, state, watch);
462}
463
464/** Add a watch entry to the pre/post[state] list
465 *
466 */
468 connection_watch_t watch, bool oneshot, void const *uctx)
469{
471
472 MEM(entry = talloc_zero(conn, connection_watch_entry_t));
473
474 entry->func = watch;
475 entry->oneshot = oneshot;
476 entry->enabled = true;
477 memcpy(&entry->uctx, &uctx, sizeof(entry->uctx));
478
479 fr_dlist_insert_tail(list, entry);
480
481 return entry;
482}
483
484/** Add a callback to be executed before a state function has been called
485 *
486 * @param[in] conn to add watcher to.
487 * @param[in] state to call watcher on entering.
488 * @param[in] watch function to call.
489 * @param[in] oneshot If true, remove the function after calling.
490 * @param[in] uctx to pass to callbacks.
491 * @return
492 * - NULL if state value is invalid.
493 * - A new watch entry handle.
494 */
496 connection_watch_t watch, bool oneshot, void const *uctx)
497{
498 if (state >= CONNECTION_STATE_MAX) return NULL;
499
500 return connection_add_watch(conn, &conn->watch_pre[state], watch, oneshot, uctx);
501}
502
503/** Add a callback to be executed after a state function has been called
504 *
505 * Where a user callback is executed on state change, the post function
506 * is only called if the callback succeeds.
507 *
508 * @param[in] conn to add watcher to.
509 * @param[in] state to call watcher on entering.
510 * @param[in] watch function to call.
511 * @param[in] oneshot If true, remove the function after calling.
512 * @param[in] uctx to pass to callbacks.
513 * @return
514 * - NULL if state value is invalid.
515 * - A new watch entry handle.
516 */
518 connection_watch_t watch, bool oneshot, void const *uctx)
519{
520 if (state >= CONNECTION_STATE_MAX) return NULL;
521
522 return connection_add_watch(conn, &conn->watch_post[state], watch, oneshot, uctx);
523}
524
525/** Enable a watcher
526 *
527 * @param[in] entry to enabled.
528 */
530{
531 (void)talloc_get_type_abort(entry, connection_watch_entry_t);
532 entry->enabled = true;
533}
534
535/** Disable a watcher
536 *
537 * @param[in] entry to disable.
538 */
540{
541 (void)talloc_get_type_abort(entry, connection_watch_entry_t);
542 entry->enabled = false;
543}
544
545/** Enable a watcher and replace the uctx
546 *
547 * @param[in] entry to enabled.
548 * @param[in] uctx Opaque data to pass to the callback.
549 */
551{
552 (void)talloc_get_type_abort(entry, connection_watch_entry_t);
553 entry->enabled = true;
554 memcpy(&entry->uctx, &uctx, sizeof(entry->uctx));
555}
556
557/** Change the uctx of an entry
558 *
559 * @param[in] entry to enabled.
560 * @param[in] uctx Opaque data to pass to the callback.
561 */
563{
564 (void)talloc_get_type_abort(entry, connection_watch_entry_t);
565 memcpy(&entry->uctx, &uctx, sizeof(entry->uctx));
566}
567
568/** Return the state of a watch entry
569 *
570 * @param[in] entry to return state of.
571 * @return
572 * - true if enabled.
573 * - false if disabled.
574 */
576{
577 (void)talloc_get_type_abort(entry, connection_watch_entry_t);
578 return entry->enabled;
579}
580
581/** Return the number of times we've attempted to establish or re-establish this connection
582 *
583 * @param[in] conn to get count from.
584 * @return the number of times the connection has reconnected.
585 */
587{
588 if (conn->pub.reconnected == 0) return 0; /* Has never been initialised */
589
590 return conn->pub.reconnected - 1; /* We don't count the first connection attempt */
591}
592
593/** Return the number of times this connection has timed out whilst connecting
594 *
595 * @param[in] conn to get count from.
596 * @return the number of times the connection has timed out whilst connecting.
597 */
599{
600 return conn->pub.timed_out;
601}
602
603/** The requisite period of time has passed, try and re-open the connection
604 *
605 * @param[in] tl containing the timer event.
606 * @param[in] now The current time.
607 * @param[in] uctx The #connection_t the fd is associated with.
608 */
610{
611 connection_t *conn = talloc_get_type_abort(uctx, connection_t);
612
613 switch (conn->pub.state) {
617 break;
618
619 default:
621 break;
622 }
623}
624
625/** Close the connection, then wait for another state change
626 *
627 */
629{
630 switch (conn->pub.state) {
636 break;
637
638 default:
640 return;
641 }
642
644
645 FR_TIMER_DISARM(conn->ev);
646
647 /*
648 * If there's a close callback, call it, so that the
649 * API client can free any resources associated
650 * with the connection handle.
651 */
652 WATCH_PRE(conn);
653
654 /*
655 * We can reach "is_closed" if a connection is halted,
656 * then signaled to INIT, which fails, and then sits in
657 * the FAILED state. Eventually the connection is
658 * shutdown, and enter_shutdown calls this function.
659 */
660 if (conn->close && !conn->pub.is_closed) {
661 HANDLER_BEGIN(conn, conn->close);
662 DEBUG4("Calling close(el=%p, h=%p, uctx=%p)", conn->pub.el, conn->pub.h, conn->uctx);
663 conn->close(conn->pub.el, conn->pub.h, conn->uctx);
664 conn->pub.is_closed = true; /* Ensure close doesn't get called twice if the connection is freed */
665 HANDLER_END(conn);
666
667 /*
668 * A deferred signal may have moved the connection to a
669 * different state. If so, that signal handler already
670 * took care of the transition.
671 */
672 if (conn->pub.state != CONNECTION_STATE_CLOSED) return;
673 } else {
674 conn->pub.is_closed = true;
675 }
676 WATCH_POST(conn);
677}
678
679/** Connection timeout
680 *
681 * Connection wasn't opened within the configured period of time
682 *
683 * @param[in] tl timer list the event belonged to.
684 * @param[in] now The current time.
685 * @param[in] uctx The #connection_t the fd is associated with.
686 */
688{
689 connection_t *conn = talloc_get_type_abort(uctx, connection_t);
690
692}
693
694/** Gracefully shutdown the handle
695 *
696 */
698{
700
701 switch (conn->pub.state) {
703 break;
704
705 default:
707 return;
708 }
709
711
712 WATCH_PRE(conn);
713 if (conn->shutdown) {
714 HANDLER_BEGIN(conn, conn->shutdown);
715 DEBUG4("Calling shutdown(el=%p, h=%p, uctx=%p)", conn->pub.el, conn->pub.h, conn->uctx);
716 ret = conn->shutdown(conn->pub.el, conn->pub.h, conn->uctx);
717 HANDLER_END(conn);
718
719 /*
720 * A deferred signal may have moved the connection to a
721 * different state. If so, that signal handler already
722 * took care of the transition.
723 */
724 if (conn->pub.state != CONNECTION_STATE_SHUTDOWN) return;
725 }
726 switch (ret) {
728 break;
729
730 default:
732 return;
733 }
734 WATCH_POST(conn);
735
736 /*
737 * If a deferred signal changed the connection state, we're done.
738 */
739 if (conn->pub.state != CONNECTION_STATE_SHUTDOWN) return;
740
741 /*
742 * If there's a connection timeout,
743 * set, then add the timer.
744 *
745 * The connection may be bad, in which
746 * case we want to automatically fail
747 * if it doesn't shutdown within the
748 * timeout period.
749 */
751 if (fr_timer_in(conn, conn->pub.el->tl, &conn->ev,
752 conn->connection_timeout, false, _connection_timeout, conn) < 0) {
753 /*
754 * Can happen when the event loop is exiting
755 */
756 PERROR("Failed setting connection_timeout timer, closing connection");
758 }
759 }
760}
761
762/** Connection failed
763 *
764 * Transition to the CONNECTION_STATE_FAILED state.
765 *
766 * If the connection was open, or couldn't be opened wait for reconnection_delay before transitioning
767 * back to init.
768 *
769 * If no reconnection_delay was set, transition to halted.
770 *
771 * @param[in] conn that failed.
772 */
774{
777
779
780 /*
781 * Explicit error occurred, delete the connection timer
782 */
783 FR_TIMER_DISARM(conn->ev);
784
785 /*
786 * Record what state the connection is currently in
787 * so we can figure out what to do next.
788 */
789 prev = conn->pub.state;
790
791 /*
792 * Now transition to failed
793 */
795
796 /*
797 * If there's a failed callback, give it the
798 * opportunity to suspend/destroy the
799 * connection.
800 */
801 WATCH_PRE(conn);
802 if (conn->failed) {
803 HANDLER_BEGIN(conn, conn->failed);
804 DEBUG4("Calling failed(h=%p, state=%s, uctx=%p)", conn->pub.h,
805 fr_table_str_by_value(connection_states, prev, "<INVALID>"), conn->uctx);
806 ret = conn->failed(conn->pub.h, prev, conn->uctx);
807 HANDLER_END(conn);
808
809 /*
810 * A deferred signal may have moved the connection to a
811 * different state. If so, that signal handler already
812 * took care of the transition.
813 */
814 if (conn->pub.state != CONNECTION_STATE_FAILED) return;
815 }
816 WATCH_POST(conn);
817
818 /*
819 * Enter the closed state if we failed during
820 * connecting, or when we were connected.
821 */
822 switch (prev) {
825 case CONNECTION_STATE_TIMEOUT: /* Timeout means the connection progress past init */
826 case CONNECTION_STATE_SHUTDOWN: /* Shutdown means the connection failed whilst shutting down */
828 break;
829
830 default:
831 break;
832 }
833
834 if (conn->failed) {
835 switch (ret) {
836 /*
837 * The callback signalled it wants the
838 * connection to be reinitialised
839 * after reconnection_delay, or
840 * immediately if the failure was due
841 * to a connection timeout.
842 */
844 break;
845
846 /*
847 * The callback signalled it wants the
848 * connection to stop.
849 */
851 default:
853 return;
854 }
855 }
856
857 /*
858 * What previous state we were in
859 * determines if we need to apply the
860 * reconnect timeout.
861 */
862 switch (prev) {
863 case CONNECTION_STATE_INIT: /* Failed during initialisation */
864 case CONNECTION_STATE_CONNECTED: /* Failed after connecting */
865 case CONNECTION_STATE_CONNECTING: /* Failed during connecting */
866 case CONNECTION_STATE_SHUTDOWN: /* Failed during shutdown */
868 DEBUG2("Delaying reconnection by %pVs", fr_box_time_delta(conn->reconnection_delay));
869 if (fr_timer_in(conn, conn->pub.el->tl, &conn->ev,
870 conn->reconnection_delay, false, _reconnect_delay_done, conn) < 0) {
871 /*
872 * Can happen when the event loop is exiting
873 */
874 PERROR("Failed inserting reconnection_delay timer event, halting connection");
876 }
877 return;
878 }
879
880 /*
881 * If there's no reconnection
882 * delay, then don't automatically
883 * reconnect, and wait to be
884 * signalled.
885 */
887 break;
888
889 case CONNECTION_STATE_TIMEOUT: /* Failed during connecting due to timeout */
891 break;
892
893 default:
894 fr_assert(0);
895 }
896}
897
898/** Enter the timeout state
899 *
900 * The connection took took long to open. Timeout the attempt and transition
901 * to the failed state.
902 */
904{
905 switch (conn->pub.state) {
908 break;
909
910 default:
912 break;
913 }
914
915 ERROR("Connection failed - timed out after %pVs", fr_box_time_delta(conn->connection_timeout));
916
918
919 conn->pub.timed_out++;
920
922}
923
924/** Enter the halted state
925 *
926 * Here we wait, until signalled by connection_signal_reconnect.
927 */
929{
930 fr_assert(conn->pub.is_closed);
931
932 switch (conn->pub.state) {
934 case CONNECTION_STATE_FAILED: /* Init failure */
936 break;
937
938 default:
940 break;
941 }
942
943 FR_TIMER_DISARM(conn->ev);
944
946 WATCH_PRE(conn);
947 WATCH_POST(conn);
948}
949
950/** Enter the connected state
951 *
952 * The connection is now fully connected. At this point we call the open callback
953 * so that the API client can install its normal set of I/O callbacks to deal with
954 * sending/receiving actual data.
955 *
956 * After this, the connection will only transition states if an API client
957 * explicitly calls connection_signal_reconnect.
958 *
959 * The connection API cannot monitor the connection for failure conditions.
960 *
961 * @param[in] conn Entering the connecting state.
962 */
964{
965 int ret;
966
968
970
971 FR_TIMER_DISARM(conn->ev);
972 WATCH_PRE(conn);
973 if (conn->open) {
974 HANDLER_BEGIN(conn, conn->open);
975 DEBUG4("Calling open(el=%p, h=%p, uctx=%p)", conn->pub.el, conn->pub.h, conn->uctx);
976 ret = conn->open(conn->pub.el, conn->pub.h, conn->uctx);
977 HANDLER_END(conn);
978
979 /*
980 * A deferred signal may have moved the connection to a
981 * different state. If so, that signal handler already
982 * took care of the transition.
983 */
984 if (conn->pub.state != CONNECTION_STATE_CONNECTED) return;
985 } else {
987 }
988
989 switch (ret) {
990 /*
991 * Callback agrees everything is connected
992 */
994 DEBUG2("Connection established");
995 WATCH_POST(conn); /* Only call if we successfully connected */
996 return;
997
998 /*
999 * Open callback failed
1000 */
1002 default:
1003 PERROR("Connection failed");
1005 return;
1006 }
1007}
1008
1009/** Enter the connecting state
1010 *
1011 * After this function returns we wait to be signalled with connection_singal_connected
1012 * or for the connection timer to expire.
1013 *
1014 * @param[in] conn Entering the connecting state.
1015 */
1017{
1018 switch (conn->pub.state) {
1020 break;
1021
1022 default:
1024 return;
1025 }
1026
1028
1029 WATCH_PRE(conn);
1030 WATCH_POST(conn);
1031
1032 /*
1033 * If there's a connection timeout,
1034 * set, then add the timer.
1035 */
1037 if (fr_timer_in(conn, conn->pub.el->tl, &conn->ev,
1038 conn->connection_timeout, false, _connection_timeout, conn) < 0) {
1039 PERROR("Failed setting connection_timeout event, failing connection");
1040
1041 /*
1042 * This can happen when the event loop
1043 * is exiting.
1044 *
1045 * Entering fail will close partially
1046 * open connection and then, if we still
1047 * can't insert a timer, then the connection
1048 * will be halted and sit idle until its
1049 * freed.
1050 */
1052 }
1053 }
1054}
1055
1056/** Initial state of the connection
1057 *
1058 * Calls the init function we were passed to allocate a library specific handle or
1059 * file descriptor.
1060 *
1061 * @param[in] conn To initialise.
1062 */
1064{
1066
1067 switch (conn->pub.state) {
1071 break;
1072
1073 default:
1075 return;
1076 }
1077
1078 /*
1079 * Increment every time we enter
1080 * We have to do this, as we don't know
1081 * whether the connection was halted by
1082 * the failed callback, and is now being
1083 * reconnected, or was automatically
1084 * reconnected.
1085 */
1086 conn->pub.reconnected++;
1087
1089
1090 /*
1091 * If we have an init callback, call it.
1092 */
1093 WATCH_PRE(conn);
1094 if (conn->init) {
1095 HANDLER_BEGIN(conn, conn->init);
1096 DEBUG4("Calling init(h_out=%p, conn=%p, uctx=%p)", &conn->pub.h, conn, conn->uctx);
1097 ret = conn->init(&conn->pub.h, conn, conn->uctx);
1098 HANDLER_END(conn);
1099
1100 /*
1101 * A deferred signal may have moved the connection to a
1102 * different state. If so, that signal handler already
1103 * took care of the transition.
1104 */
1105 if (conn->pub.state != CONNECTION_STATE_INIT) return;
1106 } else {
1108 }
1109
1110 switch (ret) {
1112 conn->pub.is_closed = false; /* We now have a handle */
1113 WATCH_POST(conn); /* Only call if we successfully initialised the handle */
1115 return;
1116
1118 conn->pub.is_closed = false; /* We now have a handle */
1119 WATCH_POST(conn); /* Only call if we successfully initialised the handle */
1121 return;
1122
1123 /*
1124 * Initialisation callback failed
1125 */
1127 default:
1128 PERROR("Connection initialisation failed");
1130 break;
1131 }
1132}
1133
1134/** Asynchronously signal a halted connection to start
1135 *
1136 */
1138{
1139 DEBUG2("Signalled to start from %s state",
1140 fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"));
1141
1142 if (DEFER_SIGNALS(conn)) {
1144 return;
1145 }
1146
1147 switch (conn->pub.state) {
1150 break;
1151
1152 default:
1153 break;
1154 }
1155}
1156
1157/** Asynchronously signal that the connection is open
1158 *
1159 * Some libraries like libldap are extremely annoying and only return control
1160 * to the caller after a connection is open.
1161 *
1162 * For these libraries, we can't use an I/O handler to determine when the
1163 * connection is open so we rely on callbacks built into the library to
1164 * signal that the transition has occurred.
1165 *
1166 */
1168{
1169 fr_assert(!conn->open); /* Use one or the other not both! */
1170
1171 DEBUG2("Signalled connected from %s state",
1172 fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"));
1173
1174 if (DEFER_SIGNALS(conn)) {
1176 return;
1177 }
1178
1179 switch (conn->pub.state) {
1182 break;
1183
1184 default:
1185 break;
1186 }
1187}
1188
1189/** Asynchronously signal the connection should be reconnected
1190 *
1191 * Should be called if the caller has knowledge that the connection is bad
1192 * and should be reconnected.
1193 *
1194 * @param[in] conn to reconnect.
1195 * @param[in] reason Why the connection was signalled to reconnect.
1196 */
1198{
1199 DEBUG2("Signalled to reconnect from %s state",
1200 fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"));
1201
1202 if (DEFER_SIGNALS(conn)) {
1203 if ((reason == CONNECTION_EXPIRED) && conn->shutdown) {
1205 return;
1206 }
1207
1209 return;
1210 }
1211
1212 switch (conn->pub.state) {
1213 case CONNECTION_STATE_CLOSED: /* Don't circumvent reconnection_delay */
1214 case CONNECTION_STATE_INIT: /* Already initialising */
1215 break;
1216
1219 break;
1220
1222 if (reason == CONNECTION_EXPIRED) break; /* Already shutting down */
1224 break;
1225
1227 if (reason == CONNECTION_EXPIRED) {
1228 if (conn->shutdown) {
1230 break;
1231 }
1234 break;
1235 }
1237
1241 break;
1242
1243 case CONNECTION_STATE_FAILED: /* already entered failed, don't re-enter */
1244 break;
1245
1247 fr_assert(0);
1248 return;
1249 }
1250}
1251
1252/** Shuts down a connection gracefully
1253 *
1254 * If a shutdown function has been provided, it is called.
1255 * It's then up to the shutdown function to install I/O handlers to signal
1256 * when the connection has finished shutting down and should be closed
1257 * via #connection_signal_halt.
1258 *
1259 * @param[in] conn to shutdown.
1260 */
1262{
1263 DEBUG2("Signalled to shutdown from %s state",
1264 fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"));
1265
1266 if (DEFER_SIGNALS(conn)) {
1268 return;
1269 }
1270
1271 switch (conn->pub.state) {
1274 break;
1275
1278 break;
1279
1280 /*
1281 * If the connection is connected it needs to be
1282 * shutdown first.
1283 *
1284 * The shutdown callback or an FD event it inserts then
1285 * to signal that the connection should be closed.
1286 */
1288 if (conn->shutdown) {
1290 break;
1291 }
1293
1294 /*
1295 * If the connection is any of these states it
1296 * must have completed INIT which means it has
1297 * an active handle which needs to be closed before
1298 * the connection is halted.
1299 */
1304 fr_assert(conn->pub.is_closed);
1305
1309 break;
1310
1312 fr_assert(0);
1313 return;
1314 }
1315}
1316
1317/** Shuts down a connection ungracefully
1318 *
1319 * If a connection is in an open or connection state it will be closed immediately.
1320 * Otherwise the connection will transition directly to the halted state.
1321 *
1322 * @param[in] conn to halt.
1323 */
1325{
1326 DEBUG2("Signalled to halt from %s state",
1327 fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"));
1328
1329 if (DEFER_SIGNALS(conn)) {
1331 return;
1332 }
1333
1334 switch (conn->pub.state) {
1336 break;
1337
1341 break;
1342
1343 /*
1344 * If the connection is any of these states it
1345 * must have completed INIT which means it has
1346 * an active handle which needs to be closed before
1347 * the connection is halted.
1348 *
1349 * The exception is when a connection fails to open
1350 * so goes from INIT -> FAILED, means is_closed
1351 * is true, as the connection has never opened.
1352 */
1359 fr_assert(conn->pub.is_closed);
1361 break;
1362
1364 fr_assert(0);
1365 return;
1366 }
1367}
1368/** Receive an error notification when we're connecting a socket
1369 *
1370 * @param[in] el event list the I/O event occurred on.
1371 * @param[in] fd the I/O event occurred for.
1372 * @param[in] flags from kevent.
1373 * @param[in] fd_errno from kevent.
1374 * @param[in] uctx The #connection_t this fd is associated with.
1375 */
1376static void _connection_error(UNUSED fr_event_list_t *el, int fd, UNUSED int flags, int fd_errno, void *uctx)
1377{
1378 connection_t *conn = talloc_get_type_abort(uctx, connection_t);
1379
1380 ERROR("Connection failed for fd (%d): %s", fd, fr_syserror(fd_errno));
1382}
1383
1384/** Receive a write notification after a socket is connected
1385 *
1386 * @param[in] el event list the I/O event occurred on.
1387 * @param[in] fd the I/O event occurred for.
1388 * @param[in] flags from kevent.
1389 * @param[in] uctx The #connection_t this fd is associated with.
1390 */
1391static void _connection_writable(fr_event_list_t *el, int fd, UNUSED int flags, void *uctx)
1392{
1393 connection_t *conn = talloc_get_type_abort(uctx, connection_t);
1394
1397}
1398
1399/** Remove the FD we were watching for connection open/fail from the event loop
1400 *
1401 */
1403 UNUSED connection_state_t prev, connection_state_t state, void *uctx)
1404{
1405 int fd = *(talloc_get_type_abort(uctx, int));
1406
1407 /*
1408 * Two states can trigger a cleanup
1409 * Remove the watch on the one that didn't
1410 */
1411 switch (state) {
1414 break;
1415
1418 break;
1419
1420 default:
1421 fr_assert(0);
1422 break;
1423 }
1424
1426 talloc_free(uctx);
1427}
1428
1429/** Setup the connection to change states to connected or failed based on I/O events
1430 *
1431 * Will automatically cleanup after itself, in preparation for
1432 * new I/O handlers to be installed in the open() callback.
1433 *
1434 * @return
1435 * - 0 on success.
1436 * - -1 on failure.
1437 */
1439{
1440 int *fd_s;
1441
1442 /*
1443 * If connection becomes writable we
1444 * assume it's open.
1445 */
1446 if (fr_event_fd_insert(conn, NULL, conn->pub.el, fd,
1447 NULL,
1450 conn) < 0) {
1451 PERROR("Failed inserting fd (%d) into event loop %p",
1452 fd, conn->pub.el);
1454 return -1;
1455 }
1456
1457 /*
1458 * Stop the static analysis tools
1459 * complaining about assigning ints
1460 * to pointers.
1461 */
1462 MEM(fd_s = talloc_zero(conn, int));
1463 *fd_s = fd;
1464
1465 /*
1466 * Add a oneshot watcher to remove
1467 * the I/O handlers if the connection
1468 * fails, or is connected.
1469 */
1474 return 0;
1475}
1476
1477/** Close a connection if it's freed
1478 *
1479 * @param[in] conn to free.
1480 * @return
1481 * - 0 connection was freed immediately.
1482 * - 1 connection free was deferred.
1483 */
1485{
1486 /*
1487 * Explicitly cancel any pending events
1488 */
1489 FR_TIMER_DELETE_RETURN(&conn->ev);
1490 /*
1491 * Don't allow the connection to be
1492 * arbitrarily freed by a callback.
1493 *
1494 * Add a deferred signal to free the
1495 * connection later.
1496 */
1497 if (DEFER_SIGNALS(conn)) {
1499 return -1;
1500 }
1501
1502 switch (conn->pub.state) {
1504 break;
1505
1506 /*
1507 * Need to close the connection first
1508 */
1515
1516 default:
1518 break;
1519 }
1520 return 0;
1521}
1522
1523/** Allocate a new connection
1524 *
1525 * After the connection has been allocated, it should be started with a call to #connection_signal_init.
1526 *
1527 * The connection state machine can detect when the connection is open in one of two ways.
1528 * - You can install a generic socket open/fail callback, using connection_signal_on_fd.
1529 * - You can call either #connection_signal_connected or connection_signal_recommend.
1530 * This allows the connection state machine to work with more difficult library APIs,
1531 * which may not return control to the caller as connections are opened.
1532 *
1533 * @param[in] ctx to allocate connection handle in. If the connection
1534 * handle is freed, and the #connection_state_t is
1535 * #CONNECTION_STATE_CONNECTING or #CONNECTION_STATE_CONNECTED the
1536 * close callback will be called.
1537 * @param[in] el to use for timer events, and to pass to the #connection_open_t callback.
1538 * @param[in] funcs callback functions.
1539 * @param[in] conf our configuration.
1540 * @param[in] log_prefix To prepend to log messages.
1541 * @param[in] uctx User context to pass to callbacks.
1542 * @return
1543 * - A new #connection_t on success.
1544 * - NULL on failure.
1545 */
1547 connection_funcs_t const *funcs,
1548 connection_conf_t const *conf,
1549 char const *log_prefix,
1550 void const *uctx)
1551{
1552 size_t i;
1553 connection_t *conn;
1554 uint64_t id;
1555
1556 fr_assert_msg(el, "No event list provided");
1557
1558 MEM(conn = talloc(ctx, connection_t));
1559 talloc_set_destructor(conn, _connection_free);
1560
1562
1563 *conn = (connection_t){
1564 .pub = {
1565 .id = id,
1566 .state = CONNECTION_STATE_HALTED,
1567 .el = el,
1568 .is_closed = true /* Starts closed */
1569 },
1570 .reconnection_delay = conf->reconnection_delay,
1571 .connection_timeout = conf->connection_timeout,
1572 .init = funcs->init,
1573 .open = funcs->open,
1574 .close = funcs->close,
1575 .failed = funcs->failed,
1576 .shutdown = funcs->shutdown,
1577 .triggers = conf->triggers,
1578 .trigger_args = conf->trigger_args,
1579 .trigger_cs = conf->trigger_cs,
1580 .pub.name = talloc_asprintf(conn, "%s - [%" PRIu64 "]", log_prefix, id)
1581 };
1582 memcpy(&conn->uctx, &uctx, sizeof(conn->uctx));
1583
1584 for (i = 0; i < NUM_ELEMENTS(conn->watch_pre); i++) {
1586 }
1587 for (i = 0; i < NUM_ELEMENTS(conn->watch_post); i++) {
1589 }
1591
1592 return conn;
1593}
1594
1596 return conn->uctx;
1597}
#define L(_str)
Helper for initialising arrays of string literals.
Definition build.h:245
#define FALL_THROUGH
clang 10 doesn't recognised the FALL-THROUGH comment anymore
Definition build.h:422
#define CC_NO_UBSAN(_sanitize)
Definition build.h:534
#define UNUSED
Definition build.h:415
#define NUM_ELEMENTS(_t)
Definition build.h:437
A section grouping multiple CONF_PAIR.
Definition cf_priv.h:106
#define fr_assert_msg(_x, _msg,...)
Calls panic_action ifndef NDEBUG, else logs error and causes the server to exit immediately with code...
Definition debug.h:248
#define MEM(x)
Definition debug.h:38
#define ERROR(fmt,...)
Definition dhcpclient.c:40
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:468
static void * fr_dlist_remove(fr_dlist_head_t *list_head, void *ptr)
Remove an item from the list.
Definition dlist.h:620
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:513
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:360
#define fr_dlist_talloc_init(_head, _type, _field)
Initialise the head structure of a doubly linked list.
Definition dlist.h:257
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:537
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:247
@ FR_EVENT_FILTER_IO
Combined filter for read/write functions/.
Definition event.h:83
talloc_free(hp)
#define PERROR(_fmt,...)
Definition log.h:233
#define DEBUG4(_fmt,...)
Definition log.h:272
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:1204
Stores all information relating to an event list.
Definition event.c:375
unsigned char uint8_t
#define fr_assert(_expr)
Definition rad_assert.h:37
#define DEBUG2(fmt,...)
static rs_t * conf
Definition radsniff.c:52
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:342
CONF_SECTION * trigger_cs
Where to search locally for triggers.
Definition connection.c:119
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:97
void connection_watch_enable(connection_watch_entry_t *entry)
Enable a watcher.
Definition connection.c:529
static size_t connection_trigger_names_len
Definition connection.c:71
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:404
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:598
fr_dlist_t entry
List entry.
Definition connection.c:79
bool enabled
Whether the watch entry is enabled.
Definition connection.c:83
void * in_handler
Connection is currently in a callback.
Definition connection.c:92
#define DEFER_SIGNALS(_conn)
Definition connection.c:147
static fr_table_num_ordered_t const connection_dsignals[]
Definition connection.c:162
void connection_watch_disable(connection_watch_entry_t *entry)
Disable a watcher.
Definition connection.c:539
fr_pair_list_t * trigger_args
Arguments to pass to the trigger functions.
Definition connection.c:120
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:457
void * connection_uctx_get(connection_t *conn)
connection_shutdown_t shutdown
Signal the connection handle to start shutting down.
Definition connection.c:103
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:108
static void _connection_timeout(UNUSED fr_timer_list_t *tl, UNUSED fr_time_t now, void *uctx)
Connection timeout.
Definition connection.c:687
bool connection_watch_is_enabled(connection_watch_entry_t *entry)
Return the state of a watch entry.
Definition connection.c:575
static atomic_uint_fast64_t connection_counter
Definition connection.c:73
static size_t connection_dsignals_len
Definition connection.c:171
void * uctx
User data.
Definition connection.c:90
fr_dlist_head_t deferred_signals
A list of signals we received whilst we were in a handler.
Definition connection.c:113
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:773
static void _reconnect_delay_done(UNUSED fr_timer_list_t *tl, UNUSED fr_time_t now, void *uctx)
The requisite period of time has passed, try and re-open the connection.
Definition connection.c:609
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:391
void connection_signals_resume(connection_t *conn)
Resume processing of deferred signals.
Definition connection.c:304
#define HANDLER_BEGIN(_conn, _func)
Called when we enter a handler.
Definition connection.c:322
connection_init_t init
Callback for initialising a connection.
Definition connection.c:100
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:467
connection_close_t close
Callback to close a connection.
Definition connection.c:102
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:586
bool triggers
Do we run triggers.
Definition connection.c:121
static void connection_state_enter_connecting(connection_t *conn)
Enter the connecting state.
connection_watch_entry_t * next_watcher
Hack to insulate watcher iterator from deletions.
Definition connection.c:98
connection_watch_t func
Function to call when a connection enters the state this list belongs to.
Definition connection.c:80
#define WATCH_PRE(_conn)
Call the pre handler watch functions.
Definition connection.c:378
static void connection_deferred_signal_process(connection_t *conn)
Process any deferred signals.
Definition connection.c:232
connection_dsignal_t signal
Signal that was deferred.
Definition connection.c:178
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:101
connection_failed_t failed
Callback for 'failed' notification.
Definition connection.c:104
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:562
fr_time_delta_t reconnection_delay
How long to wait in the CONNECTION_STATE_FAILED state.
Definition connection.c:110
static void connection_state_enter_closed(connection_t *conn)
Close the connection, then wait for another state change.
Definition connection.c:628
void * uctx
User data to pass to the function.
Definition connection.c:84
void connection_signal_reconnect(connection_t *conn, connection_reason_t reason)
Asynchronously signal the connection should be reconnected.
bool oneshot
Remove the function after it's called once.
Definition connection.c:82
fr_dlist_head_t watch_pre[CONNECTION_STATE_MAX]
Function called before state callback.
Definition connection.c:96
fr_table_num_ordered_t const connection_states[]
Definition connection.c:46
#define HANDLER_END(_conn)
Called when we exit a handler.
Definition connection.c:331
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.
#define STATE_TRANSITION(_new)
Definition connection.c:129
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:61
static void connection_state_enter_halted(connection_t *conn)
Enter the halted state.
Definition connection.c:928
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:205
fr_dlist_t entry
Entry in the signals list.
Definition connection.c:177
connection_dsignal_t
Deferred signals.
Definition connection.c:152
@ CONNECTION_DSIGNAL_RECONNECT_FAILED
Reconnect a failed connection.
Definition connection.c:155
@ CONNECTION_DSIGNAL_HALT
Close a connection (ungracefully).
Definition connection.c:158
@ CONNECTION_DSIGNAL_INIT
Restart a halted connection.
Definition connection.c:153
@ CONNECTION_DSIGNAL_FREE
Free a connection (no further dsignals processed).
Definition connection.c:159
@ CONNECTION_DSIGNAL_SHUTDOWN
Close a connection (gracefully).
Definition connection.c:157
@ CONNECTION_DSIGNAL_CONNECTED
Signal that a connection is connected.
Definition connection.c:154
@ CONNECTION_DSIGNAL_RECONNECT_EXPIRED
Reconnect an expired connection (gracefully).
Definition connection.c:156
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:495
size_t connection_states_len
Definition connection.c:56
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:517
static void connection_state_enter_connected(connection_t *conn)
Enter the connected state.
Definition connection.c:963
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:440
#define BAD_STATE_TRANSITION(_new)
Definition connection.c:139
struct connection_pub_s pub
Public fields.
Definition connection.c:88
void connection_watch_enable_set_uctx(connection_watch_entry_t *entry, void const *uctx)
Enable a watcher and replace the uctx.
Definition connection.c:550
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:697
void connection_signals_pause(connection_t *conn)
Pause processing of deferred signals.
Definition connection.c:295
bool processing_signals
Processing deferred signals, don't let the deferred signal processor be called multiple times.
Definition connection.c:93
unsigned int signals_pause
Temporarily stop processing of signals.
Definition connection.c:117
fr_timer_t * ev
State transition timer.
Definition connection.c:106
static void connection_state_enter_timeout(connection_t *conn)
Enter the timeout state.
Definition connection.c:903
Holds a signal from a handler until it's safe to process it.
Definition connection.c:176
An entry in a watch function list.
Definition connection.c:78
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:217
connection_state_t(* connection_failed_t)(void *h, connection_state_t state, void *uctx)
Notification that a connection attempt has failed.
Definition connection.h:180
uint64_t _CONST timed_out
How many times has this connection timed out when connecting.
Definition connection.h:84
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:194
fr_event_list_t *_CONST el
Event list for timers and I/O events.
Definition connection.h:76
connection_state_t(* connection_init_t)(void **h_out, connection_t *conn, void *uctx)
Callback for the initialise state.
Definition connection.h:129
bool _CONST is_closed
The close callback has previously been called, so the handle is gone.
Definition connection.h:78
uint64_t _CONST reconnected
How many times we've attempted to establish or re-establish this connection.
Definition connection.h:82
void *_CONST h
Connection handle.
Definition connection.h:75
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:143
connection_reason_t
Definition connection.h:88
@ CONNECTION_EXPIRED
Connection is being reconnected because it's at the end of its life.
Definition connection.h:90
@ CONNECTION_FAILED
Connection is being reconnected because it failed.
Definition connection.h:89
connection_state_t _CONST state
Current connection state.
Definition connection.h:72
connection_init_t init
Definition connection.h:200
connection_state_t
Definition connection.h:47
@ CONNECTION_STATE_FAILED
Connection has failed.
Definition connection.h:56
@ CONNECTION_STATE_HALTED
The connection is in a halted stat.
Definition connection.h:48
@ CONNECTION_STATE_CLOSED
Connection has been closed.
Definition connection.h:57
@ CONNECTION_STATE_CONNECTED
File descriptor is open (ready for writing).
Definition connection.h:54
@ CONNECTION_STATE_TIMEOUT
Timeout during CONNECTION_STATE_CONNECTING.
Definition connection.h:53
@ CONNECTION_STATE_INIT
Init state, sets up connection.
Definition connection.h:51
@ CONNECTION_STATE_MAX
Definition connection.h:58
@ CONNECTION_STATE_CONNECTING
Waiting for connection to establish.
Definition connection.h:52
@ CONNECTION_STATE_SHUTDOWN
Connection is shutting down.
Definition connection.h:55
connection_shutdown_t shutdown
Definition connection.h:202
connection_failed_t failed
Definition connection.h:203
connection_open_t open
Definition connection.h:201
uint64_t _CONST id
Unique identifier for the connection.
Definition connection.h:74
connection_close_t close
Definition connection.h:204
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:162
Holds a complete set of functions for a connection.
Definition connection.h:199
Public fields for the connection.
Definition connection.h:69
@ 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:804
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 talloc_asprintf
Definition talloc.h:151
#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
An event timer list.
Definition timer.c:49
A timer event.
Definition timer.c:83
#define FR_TIMER_DELETE_RETURN(_ev_p)
Definition timer.h:112
#define fr_timer_in(...)
Definition timer.h:87
#define FR_TIMER_DISARM(_ev)
Definition timer.h:91
static fr_event_list_t * el
#define fr_box_time_delta(_val)
Definition value.h:397