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: 7524eeb831828eb9c156543a361a6afc69147268 $
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 is_closed; //!< The close callback has previously been called.
94 bool processing_signals; //!< Processing deferred signals, don't let the deferred
95 ///< signal processor be called multiple times.
96
97 fr_dlist_head_t watch_pre[CONNECTION_STATE_MAX]; //!< Function called before state callback.
98 fr_dlist_head_t watch_post[CONNECTION_STATE_MAX]; //!< Function called after state callback.
99 connection_watch_entry_t *next_watcher; //!< Hack to insulate watcher iterator from deletions.
100
101 connection_init_t init; //!< Callback for initialising a connection.
102 connection_open_t open; //!< Callback for 'open' notification.
103 connection_close_t close; //!< Callback to close a connection.
104 connection_shutdown_t shutdown; //!< Signal the connection handle to start shutting down.
105 connection_failed_t failed; //!< Callback for 'failed' notification.
106
107 fr_timer_t *ev; //!< State transition timer.
108
109 fr_time_delta_t connection_timeout; //!< How long to wait in the
110 //!< #CONNECTION_STATE_CONNECTING state.
111 fr_time_delta_t reconnection_delay; //!< How long to wait in the
112 //!< #CONNECTION_STATE_FAILED state.
113
114 fr_dlist_head_t deferred_signals; //!< A list of signals we received whilst we were in
115 ///< a handler.
116
117
118
119 connection_watch_entry_t *on_halted; //!< Used by the deferred signal processor to learn
120 ///< if a function deeper in the call stack freed
121 ///< the connection.
122
123 unsigned int signals_pause; //!< Temporarily stop processing of signals.
124};
125
126#define CONN_TRIGGER(_state) do { \
127 if (conn->pub.triggers) { \
128 trigger_exec(unlang_interpret_get_thread_default(), \
129 NULL, fr_table_str_by_value(connection_trigger_names, _state, "<INVALID>"), true, NULL); \
130 } \
131} while (0)
132
133#define STATE_TRANSITION(_new) \
134do { \
135 DEBUG2("Connection changed state %s -> %s", \
136 fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"), \
137 fr_table_str_by_value(connection_states, _new, "<INVALID>")); \
138 conn->pub.prev = conn->pub.state; \
139 conn->pub.state = _new; \
140 CONN_TRIGGER(_new); \
141} while (0)
142
143#define BAD_STATE_TRANSITION(_new) \
144do { \
145 if (!fr_cond_assert_msg(0, "Connection %" PRIu64 " invalid transition %s -> %s", \
146 conn->pub.id, \
147 fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"), \
148 fr_table_str_by_value(connection_states, _new, "<INVALID>"))) return; \
149} while (0)
150
151#define DEFER_SIGNALS(_conn) ((_conn)->in_handler || (_conn)->signals_pause)
152
153/** Deferred signals
154 *
155 */
156typedef enum {
157 CONNECTION_DSIGNAL_INIT, //!< Restart a halted connection.
158 CONNECTION_DSIGNAL_CONNECTED, //!< Signal that a connection is connected.
159 CONNECTION_DSIGNAL_RECONNECT_FAILED, //!< Reconnect a failed connection.
160 CONNECTION_DSIGNAL_RECONNECT_EXPIRED, //!< Reconnect an expired connection (gracefully).
161 CONNECTION_DSIGNAL_SHUTDOWN, //!< Close a connection (gracefully).
162 CONNECTION_DSIGNAL_HALT, //!< Close a connection (ungracefully).
163 CONNECTION_DSIGNAL_FREE //!< Free a connection (no further dsignals processed).
165
167 { L("INIT"), CONNECTION_DSIGNAL_INIT },
168 { L("CONNECTED"), CONNECTION_DSIGNAL_CONNECTED },
169 { L("RECONNECT-FAILED"), CONNECTION_DSIGNAL_RECONNECT_FAILED },
170 { L("RECONNECT-EXPIRED"), CONNECTION_DSIGNAL_RECONNECT_EXPIRED },
171 { L("SHUTDOWN"), CONNECTION_DSIGNAL_SHUTDOWN },
172 { L("HALT"), CONNECTION_DSIGNAL_HALT },
173 { L("FREE"), CONNECTION_DSIGNAL_FREE }
174};
176
177/** Holds a signal from a handler until it's safe to process it
178 *
179 */
180typedef struct {
181 fr_dlist_t entry; //!< Entry in the signals list.
182 connection_dsignal_t signal; //!< Signal that was deferred.
184
185/*
186 * State transition functions
187 */
196
197/** Add a deferred signal to the signal list
198 *
199 * Processing signals whilst in handlers usually leads to weird
200 * inconsistent states within the connection.
201 *
202 * If a public signal function is called, and detects its being called
203 * from within the handler, it instead adds a deferred signal entry
204 * and immediately returns.
205 *
206 * Once the handler is complete, and all pending C stack state changes
207 * are complete, the deferred signals are drained and processed.
208 */
210{
211 connection_dsignal_entry_t *dsignal, *prev;
212
213 prev = fr_dlist_tail(&conn->deferred_signals);
214 if (prev && (prev->signal == signal)) return; /* Don't insert duplicates */
215
216 MEM(dsignal = talloc_zero(conn, connection_dsignal_entry_t));
217 dsignal->signal = signal;
218 fr_dlist_insert_tail(&conn->deferred_signals, dsignal);
219
220// DEBUG4("Adding deferred signal - %s", fr_table_str_by_value(connection_dsignals, signal, "<INVALID>"));
221}
222
223/** Notification function to tell connection_deferred_signal_process that the connection has been freed
224 *
225 */
228 UNUSED connection_state_t state, void *uctx)
229{
230 bool *freed = uctx;
231 *freed = true;
232}
233
234/** Process any deferred signals
235 *
236 */
238{
240 bool freed = false;
241
242 /*
243 * We're inside and an instance of this function
244 * higher in the call stack. Don't do anything.
245 */
246 if (conn->processing_signals) return;
247
248 /*
249 * Get notified if the connection gets freed
250 * out from under us...
251 */
253 conn->processing_signals = true;
254
255 while ((dsignal = fr_dlist_head(&conn->deferred_signals))) {
257 fr_dlist_remove(&conn->deferred_signals, dsignal);
258 signal = dsignal->signal;
259 talloc_free(dsignal);
260
261 DEBUG4("Processing deferred signal - %s",
262 fr_table_str_by_value(connection_dsignals, signal, "<INVALID>"));
263
264 switch (signal) {
267 break;
268
271 break;
272
273 case CONNECTION_DSIGNAL_RECONNECT_FAILED: /* Reconnect - Failed */
275 break;
276
277 case CONNECTION_DSIGNAL_RECONNECT_EXPIRED: /* Reconnect - Expired */
279 break;
280
283 break;
284
287 break;
288
289 case CONNECTION_DSIGNAL_FREE: /* Freed */
290 talloc_free(conn);
291 return;
292 }
293
294 /*
295 * One of the signal handlers freed the connection
296 * return immediately.
297 */
298 /* coverity[dead_error_line] */
299 if (freed) return;
300 }
301
302 conn->processing_signals = false;
304}
305
306/** Pause processing of deferred signals
307 *
308 * @param[in] conn to pause signal processing for.
309 */
311{
312 conn->signals_pause++;
313}
314
315/** Resume processing of deferred signals
316 *
317 * @param[in] conn to resume signal processing for.
318 */
320{
321 if (conn->signals_pause > 0) conn->signals_pause--;
322 if (conn->signals_pause > 0) return;
323
324 /*
325 * If we're not in a handler process the
326 * deferred signals now.
327 */
328 if (!conn->in_handler) {
330 return;
331 }
332}
333
334/** Called when we enter a handler
335 *
336 */
337#define HANDLER_BEGIN(_conn, _func) \
338void *_prev_handler = (_conn)->in_handler; \
339do { \
340 (_conn)->in_handler = (void *)(_func); \
341} while (0)
342
343/** Called when we exit a handler
344 *
345 */
346#define HANDLER_END(_conn) \
347do { \
348 (_conn)->in_handler = _prev_handler; \
349 if (!(_conn)->signals_pause && (!(_conn)->in_handler)) connection_deferred_signal_process(_conn); \
350} while(0)
351
352
353/** Call a list of watch functions associated with a state
354 *
355 */
356CC_NO_UBSAN(function) /* UBSAN: false positive - Public/private version of connection_t trips -fsanitize=function */
357static inline void connection_watch_call(connection_t *conn, fr_dlist_head_t *list)
358{
359 /*
360 * Nested watcher calls are not allowed
361 * and shouldn't be possible because of
362 * deferred signal processing.
363 */
364 fr_assert(conn->next_watcher == NULL);
365
366 while ((conn->next_watcher = fr_dlist_next(list, conn->next_watcher))) {
367 connection_watch_entry_t *entry = conn->next_watcher;
368 bool oneshot = entry->oneshot; /* Watcher could be freed, so store now */
369
370 if (!entry->enabled) continue;
371 if (oneshot) conn->next_watcher = fr_dlist_remove(list, entry);
372
373/*
374 DEBUG4("Notifying %swatcher - (%p)(conn=%p, prev=%s, state=%s, uctx=%p)",
375 entry->oneshot ? "oneshot " : "",
376 entry->func,
377 conn,
378 fr_table_str_by_value(connection_states, conn->pub.prev, "<INVALID>"),
379 fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"),
380 entry->uctx);
381*/
382
383 entry->func(conn, conn->pub.prev, conn->pub.state, entry->uctx);
384
385 if (oneshot) talloc_free(entry);
386 }
387 conn->next_watcher = NULL;
388}
389
390/** Call the pre handler watch functions
391 *
392 */
393#define WATCH_PRE(_conn) \
394do { \
395 if (fr_dlist_empty(&(_conn)->watch_pre[(_conn)->pub.state])) break; \
396 { \
397 HANDLER_BEGIN(conn, &(_conn)->watch_pre[(_conn)->pub.state]); \
398 connection_watch_call((_conn), &(_conn)->watch_pre[(_conn)->pub.state]); \
399 HANDLER_END(conn); \
400 } \
401} while(0)
402
403/** Call the post handler watch functions
404 *
405 */
406#define WATCH_POST(_conn) \
407do { \
408 if (fr_dlist_empty(&(_conn)->watch_post[(_conn)->pub.state])) break; \
409 { \
410 HANDLER_BEGIN(conn, &(_conn)->watch_post[(_conn)->pub.state]); \
411 connection_watch_call((_conn), &(_conn)->watch_post[(_conn)->pub.state]); \
412 HANDLER_END(conn); \
413 } \
414} while(0)
415
416/** Remove a watch function from a pre/post[state] list
417 *
418 */
419static int connection_del_watch(connection_t *conn, fr_dlist_head_t *state_lists,
421{
422 connection_watch_entry_t *entry = NULL;
423 fr_dlist_head_t *list = &state_lists[state];
424
425 while ((entry = fr_dlist_next(list, entry))) {
426 if (entry->func == watch) {
427/*
428 DEBUG4("Removing %s watcher %p",
429 fr_table_str_by_value(connection_states, state, "<INVALID>"),
430 watch);
431*/
432 if (conn->next_watcher == entry) {
433 conn->next_watcher = fr_dlist_remove(list, entry);
434 } else {
435 fr_dlist_remove(list, entry);
436 }
437 talloc_free(entry);
438 return 0;
439 }
440 }
441
442 return -1;
443}
444
445/** Remove a watch function from a pre list
446 *
447 * @param[in] conn The connection to remove the watcher from.
448 * @param[in] state to remove the watch from.
449 * @param[in] watch Function to remove.
450 * @return
451 * - 0 if the function was removed successfully.
452 * - -1 if the function wasn't present in the watch list.
453 * - -2 an invalid state was passed.
454 */
456{
457 if (state >= CONNECTION_STATE_MAX) return -2;
458
459 return connection_del_watch(conn, conn->watch_pre, state, watch);
460}
461
462/** Remove a watch function from a post list
463 *
464 * @param[in] conn The connection to remove the watcher from.
465 * @param[in] state to remove the watch from.
466 * @param[in] watch Function to remove.
467 * @return
468 * - 0 if the function was removed successfully.
469 * - -1 if the function wasn't present in the watch list.
470 * - -2 an invalid state was passed.
471 */
473{
474 if (state >= CONNECTION_STATE_MAX) return -2;
475
476 return connection_del_watch(conn, conn->watch_post, state, watch);
477}
478
479/** Add a watch entry to the pre/post[state] list
480 *
481 */
483 connection_watch_t watch, bool oneshot, void const *uctx)
484{
486
487 MEM(entry = talloc_zero(conn, connection_watch_entry_t));
488
489 entry->func = watch;
490 entry->oneshot = oneshot;
491 entry->enabled = true;
492 memcpy(&entry->uctx, &uctx, sizeof(entry->uctx));
493
494 fr_dlist_insert_tail(list, entry);
495
496 return entry;
497}
498
499/** Add a callback to be executed before a state function has been called
500 *
501 * @param[in] conn to add watcher to.
502 * @param[in] state to call watcher on entering.
503 * @param[in] watch function to call.
504 * @param[in] oneshot If true, remove the function after calling.
505 * @param[in] uctx to pass to callbacks.
506 * @return
507 * - NULL if state value is invalid.
508 * - A new watch entry handle.
509 */
511 connection_watch_t watch, bool oneshot, void const *uctx)
512{
513 if (state >= CONNECTION_STATE_MAX) return NULL;
514
515 return connection_add_watch(conn, &conn->watch_pre[state], watch, oneshot, uctx);
516}
517
518/** Add a callback to be executed after a state function has been called
519 *
520 * Where a user callback is executed on state change, the post function
521 * is only called if the callback succeeds.
522 *
523 * @param[in] conn to add watcher to.
524 * @param[in] state to call watcher on entering.
525 * @param[in] watch function to call.
526 * @param[in] oneshot If true, remove the function after calling.
527 * @param[in] uctx to pass to callbacks.
528 * @return
529 * - NULL if state value is invalid.
530 * - A new watch entry handle.
531 */
533 connection_watch_t watch, bool oneshot, void const *uctx)
534{
535 if (state >= CONNECTION_STATE_MAX) return NULL;
536
537 return connection_add_watch(conn, &conn->watch_post[state], watch, oneshot, uctx);
538}
539
540/** Enable a watcher
541 *
542 * @param[in] entry to enabled.
543 */
545{
546 (void)talloc_get_type_abort(entry, connection_watch_entry_t);
547 entry->enabled = true;
548}
549
550/** Disable a watcher
551 *
552 * @param[in] entry to disable.
553 */
555{
556 (void)talloc_get_type_abort(entry, connection_watch_entry_t);
557 entry->enabled = false;
558}
559
560/** Enable a watcher and replace the uctx
561 *
562 * @param[in] entry to enabled.
563 * @param[in] uctx Opaque data to pass to the callback.
564 */
566{
567 (void)talloc_get_type_abort(entry, connection_watch_entry_t);
568 entry->enabled = true;
569 memcpy(&entry->uctx, &uctx, sizeof(entry->uctx));
570}
571
572/** Change the uctx of an entry
573 *
574 * @param[in] entry to enabled.
575 * @param[in] uctx Opaque data to pass to the callback.
576 */
578{
579 (void)talloc_get_type_abort(entry, connection_watch_entry_t);
580 memcpy(&entry->uctx, &uctx, sizeof(entry->uctx));
581}
582
583/** Return the state of a watch entry
584 *
585 * @param[in] entry to return state of.
586 * @return
587 * - true if enabled.
588 * - false if disabled.
589 */
591{
592 (void)talloc_get_type_abort(entry, connection_watch_entry_t);
593 return entry->enabled;
594}
595
596/** Return the number of times we've attempted to establish or re-establish this connection
597 *
598 * @param[in] conn to get count from.
599 * @return the number of times the connection has reconnected.
600 */
602{
603 if (conn->pub.reconnected == 0) return 0; /* Has never been initialised */
604
605 return conn->pub.reconnected - 1; /* We don't count the first connection attempt */
606}
607
608/** Return the number of times this connection has timed out whilst connecting
609 *
610 * @param[in] conn to get count from.
611 * @return the number of times the connection has timed out whilst connecting.
612 */
614{
615 return conn->pub.timed_out;
616}
617
618/** The requisite period of time has passed, try and re-open the connection
619 *
620 * @param[in] tl containing the timer event.
621 * @param[in] now The current time.
622 * @param[in] uctx The #connection_t the fd is associated with.
623 */
625{
626 connection_t *conn = talloc_get_type_abort(uctx, connection_t);
627
628 switch (conn->pub.state) {
632 break;
633
634 default:
636 break;
637 }
638}
639
640/** Close the connection, then wait for another state change
641 *
642 */
644{
645 switch (conn->pub.state) {
649 break;
650
651 default:
653 return;
654 }
655
657
658 FR_TIMER_DISARM(conn->ev);
659
660 /*
661 * If there's a close callback, call it, so that the
662 * API client can free any resources associated
663 * with the connection handle.
664 */
665 WATCH_PRE(conn);
666
667 /*
668 * is_closed is for pure paranoia. If everything
669 * is working correctly this state should never
670 * be entered if the connection is closed.
671 */
672 fr_assert(!conn->is_closed);
673 if (conn->close && !conn->is_closed) {
674 HANDLER_BEGIN(conn, conn->close);
675 DEBUG4("Calling close(el=%p, h=%p, uctx=%p)", conn->pub.el, conn->pub.h, conn->uctx);
676 conn->close(conn->pub.el, conn->pub.h, conn->uctx);
677 conn->is_closed = true; /* Ensure close doesn't get called twice if the connection is freed */
678 HANDLER_END(conn);
679 } else {
680 conn->is_closed = true;
681 }
682 WATCH_POST(conn);
683}
684
685/** Connection timeout
686 *
687 * Connection wasn't opened within the configured period of time
688 *
689 * @param[in] tl timer list the event belonged to.
690 * @param[in] now The current time.
691 * @param[in] uctx The #connection_t the fd is associated with.
692 */
694{
695 connection_t *conn = talloc_get_type_abort(uctx, connection_t);
696
698}
699
700/** Gracefully shutdown the handle
701 *
702 */
704{
706
707 switch (conn->pub.state) {
709 break;
710
711 default:
713 return;
714 }
715
717
718 WATCH_PRE(conn);
719 {
720 HANDLER_BEGIN(conn, conn->shutdown);
721 DEBUG4("Calling shutdown(el=%p, h=%p, uctx=%p)", conn->pub.el, conn->pub.h, conn->uctx);
722 ret = conn->shutdown(conn->pub.el, conn->pub.h, conn->uctx);
723 HANDLER_END(conn);
724 }
725 switch (ret) {
727 break;
728
729 default:
731 return;
732 }
733 WATCH_POST(conn);
734
735 /*
736 * If there's a connection timeout,
737 * set, then add the timer.
738 *
739 * The connection may be bad, in which
740 * case we want to automatically fail
741 * if it doesn't shutdown within the
742 * timeout period.
743 */
745 if (fr_timer_in(conn, conn->pub.el->tl, &conn->ev,
746 conn->connection_timeout, false, _connection_timeout, conn) < 0) {
747 /*
748 * Can happen when the event loop is exiting
749 */
750 PERROR("Failed setting connection_timeout timer, closing connection");
752 }
753 }
754}
755
756/** Connection failed
757 *
758 * Transition to the CONNECTION_STATE_FAILED state.
759 *
760 * If the connection was open, or couldn't be opened wait for reconnection_delay before transitioning
761 * back to init.
762 *
763 * If no reconnection_delay was set, transition to halted.
764 *
765 * @param[in] conn that failed.
766 */
768{
771
773
774 /*
775 * Explicit error occurred, delete the connection timer
776 */
777 FR_TIMER_DISARM(conn->ev);
778
779 /*
780 * Record what state the connection is currently in
781 * so we can figure out what to do next.
782 */
783 prev = conn->pub.state;
784
785 /*
786 * Now transition to failed
787 */
789
790 /*
791 * If there's a failed callback, give it the
792 * opportunity to suspend/destroy the
793 * connection.
794 */
795 WATCH_PRE(conn);
796 if (conn->failed) {
797 HANDLER_BEGIN(conn, conn->failed);
798 DEBUG4("Calling failed(h=%p, state=%s, uctx=%p)", conn->pub.h,
799 fr_table_str_by_value(connection_states, prev, "<INVALID>"), conn->uctx);
800 ret = conn->failed(conn->pub.h, prev, conn->uctx);
801 HANDLER_END(conn);
802 }
803 WATCH_POST(conn);
804
805 /*
806 * Enter the closed state if we failed during
807 * connecting, or when we were connected.
808 */
809 switch (prev) {
812 case CONNECTION_STATE_TIMEOUT: /* Timeout means the connection progress past init */
813 case CONNECTION_STATE_SHUTDOWN: /* Shutdown means the connection failed whilst shutting down */
815 break;
816
817 default:
818 break;
819 }
820
821 if (conn->failed) {
822 switch (ret) {
823 /*
824 * The callback signalled it wants the
825 * connection to be reinitialised
826 * after reconnection_delay, or
827 * immediately if the failure was due
828 * to a connection timeout.
829 */
831 break;
832
833 /*
834 * The callback signalled it wants the
835 * connection to stop.
836 */
838 default:
840 return;
841 }
842 }
843
844 /*
845 * What previous state we were in
846 * determines if we need to apply the
847 * reconnect timeout.
848 */
849 switch (prev) {
850 case CONNECTION_STATE_INIT: /* Failed during initialisation */
851 case CONNECTION_STATE_CONNECTED: /* Failed after connecting */
852 case CONNECTION_STATE_CONNECTING: /* Failed during connecting */
853 case CONNECTION_STATE_SHUTDOWN: /* Failed during shutdown */
855 DEBUG2("Delaying reconnection by %pVs", fr_box_time_delta(conn->reconnection_delay));
856 if (fr_timer_in(conn, conn->pub.el->tl, &conn->ev,
857 conn->reconnection_delay, false, _reconnect_delay_done, conn) < 0) {
858 /*
859 * Can happen when the event loop is exiting
860 */
861 PERROR("Failed inserting reconnection_delay timer event, halting connection");
863 }
864 return;
865 }
866
867 /*
868 * If there's no reconnection
869 * delay, then don't automatically
870 * reconnect, and wait to be
871 * signalled.
872 */
874 break;
875
876 case CONNECTION_STATE_TIMEOUT: /* Failed during connecting due to timeout */
878 break;
879
880 default:
881 fr_assert(0);
882 }
883}
884
885/** Enter the timeout state
886 *
887 * The connection took took long to open. Timeout the attempt and transition
888 * to the failed state.
889 */
891{
892 switch (conn->pub.state) {
895 break;
896
897 default:
899 }
900
901 ERROR("Connection failed - timed out after %pVs", fr_box_time_delta(conn->connection_timeout));
902
904
905 conn->pub.timed_out++;
906
908}
909
910/** Enter the halted state
911 *
912 * Here we wait, until signalled by connection_signal_reconnect.
913 */
915{
916 fr_assert(conn->is_closed);
917
918 switch (conn->pub.state) {
919 case CONNECTION_STATE_FAILED: /* Init failure */
921 break;
922
923 default:
925 }
926
927 FR_TIMER_DISARM(conn->ev);
928
930 WATCH_PRE(conn);
931 WATCH_POST(conn);
932}
933
934/** Enter the connected state
935 *
936 * The connection is now fully connected. At this point we call the open callback
937 * so that the API client can install its normal set of I/O callbacks to deal with
938 * sending/receiving actual data.
939 *
940 * After this, the connection will only transition states if an API client
941 * explicitly calls connection_signal_reconnect.
942 *
943 * The connection API cannot monitor the connection for failure conditions.
944 *
945 * @param[in] conn Entering the connecting state.
946 */
948{
949 int ret;
950
952
954
955 FR_TIMER_DISARM(conn->ev);
956 WATCH_PRE(conn);
957 if (conn->open) {
958 HANDLER_BEGIN(conn, conn->open);
959 DEBUG4("Calling open(el=%p, h=%p, uctx=%p)", conn->pub.el, conn->pub.h, conn->uctx);
960 ret = conn->open(conn->pub.el, conn->pub.h, conn->uctx);
961 HANDLER_END(conn);
962 } else {
964 }
965
966 switch (ret) {
967 /*
968 * Callback agrees everything is connected
969 */
971 DEBUG2("Connection established");
972 WATCH_POST(conn); /* Only call if we successfully connected */
973 return;
974
975 /*
976 * Open callback failed
977 */
979 default:
980 PERROR("Connection failed");
982 return;
983 }
984}
985
986/** Enter the connecting state
987 *
988 * After this function returns we wait to be signalled with connection_singal_connected
989 * or for the connection timer to expire.
990 *
991 * @param[in] conn Entering the connecting state.
992 */
994{
995 switch (conn->pub.state) {
997 break;
998
999 default:
1001 return;
1002 }
1003
1005
1006 WATCH_PRE(conn);
1007 WATCH_POST(conn);
1008
1009 /*
1010 * If there's a connection timeout,
1011 * set, then add the timer.
1012 */
1014 if (fr_timer_in(conn, conn->pub.el->tl, &conn->ev,
1015 conn->connection_timeout, false, _connection_timeout, conn) < 0) {
1016 PERROR("Failed setting connection_timeout event, failing connection");
1017
1018 /*
1019 * This can happen when the event loop
1020 * is exiting.
1021 *
1022 * Entering fail will close partially
1023 * open connection and then, if we still
1024 * can't insert a timer, then the connection
1025 * will be halted and sit idle until its
1026 * freed.
1027 */
1029 }
1030 }
1031}
1032
1033/** Initial state of the connection
1034 *
1035 * Calls the init function we were passed to allocate a library specific handle or
1036 * file descriptor.
1037 *
1038 * @param[in] conn To initialise.
1039 */
1041{
1043
1044 switch (conn->pub.state) {
1048 break;
1049
1050 default:
1052 return;
1053 }
1054
1055 /*
1056 * Increment every time we enter
1057 * We have to do this, as we don't know
1058 * whether the connection was halted by
1059 * the failed callback, and is now being
1060 * reconnected, or was automatically
1061 * reconnected.
1062 */
1063 conn->pub.reconnected++;
1064
1066
1067 /*
1068 * If we have an init callback, call it.
1069 */
1070 WATCH_PRE(conn);
1071 if (conn->init) {
1072 HANDLER_BEGIN(conn, conn->init);
1073 DEBUG4("Calling init(h_out=%p, conn=%p, uctx=%p)", &conn->pub.h, conn, conn->uctx);
1074 ret = conn->init(&conn->pub.h, conn, conn->uctx);
1075 HANDLER_END(conn);
1076 } else {
1078 }
1079
1080 switch (ret) {
1082 conn->is_closed = false; /* We now have a handle */
1083 WATCH_POST(conn); /* Only call if we successfully initialised the handle */
1085 return;
1086
1088 conn->is_closed = false; /* We now have a handle */
1089 WATCH_POST(conn); /* Only call if we successfully initialised the handle */
1091 return;
1092
1093 /*
1094 * Initialisation callback failed
1095 */
1097 default:
1098 PERROR("Connection initialisation failed");
1100 break;
1101 }
1102}
1103
1104/** Asynchronously signal a halted connection to start
1105 *
1106 */
1108{
1109 DEBUG2("Signalled to start from %s state",
1110 fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"));
1111
1112 if (DEFER_SIGNALS(conn)) {
1114 return;
1115 }
1116
1117 switch (conn->pub.state) {
1120 break;
1121
1122 default:
1123 break;
1124 }
1125}
1126
1127/** Asynchronously signal that the connection is open
1128 *
1129 * Some libraries like libldap are extremely annoying and only return control
1130 * to the caller after a connection is open.
1131 *
1132 * For these libraries, we can't use an I/O handler to determine when the
1133 * connection is open so we rely on callbacks built into the library to
1134 * signal that the transition has occurred.
1135 *
1136 */
1138{
1139 fr_assert(!conn->open); /* Use one or the other not both! */
1140
1141 DEBUG2("Signalled connected from %s state",
1142 fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"));
1143
1144 if (DEFER_SIGNALS(conn)) {
1146 return;
1147 }
1148
1149 switch (conn->pub.state) {
1152 break;
1153
1154 default:
1155 break;
1156 }
1157}
1158
1159/** Asynchronously signal the connection should be reconnected
1160 *
1161 * Should be called if the caller has knowledge that the connection is bad
1162 * and should be reconnected.
1163 *
1164 * @param[in] conn to reconnect.
1165 * @param[in] reason Why the connection was signalled to reconnect.
1166 */
1168{
1169 DEBUG2("Signalled to reconnect from %s state",
1170 fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"));
1171
1172 if (DEFER_SIGNALS(conn)) {
1173 if ((reason == CONNECTION_EXPIRED) && conn->shutdown) {
1175 return;
1176 }
1177
1179 return;
1180 }
1181
1182 switch (conn->pub.state) {
1183 case CONNECTION_STATE_CLOSED: /* Don't circumvent reconnection_delay */
1184 case CONNECTION_STATE_INIT: /* Already initialising */
1185 break;
1186
1189 break;
1190
1192 if (reason == CONNECTION_EXPIRED) break; /* Already shutting down */
1194 break;
1195
1197 if (reason == CONNECTION_EXPIRED) {
1198 if (conn->shutdown) {
1200 break;
1201 }
1203 break;
1204 }
1206
1211 break;
1212
1214 fr_assert(0);
1215 return;
1216 }
1217}
1218
1219/** Shuts down a connection gracefully
1220 *
1221 * If a shutdown function has been provided, it is called.
1222 * It's then up to the shutdown function to install I/O handlers to signal
1223 * when the connection has finished shutting down and should be closed
1224 * via #connection_signal_halt.
1225 *
1226 * @param[in] conn to shutdown.
1227 */
1229{
1230 DEBUG2("Signalled to shutdown from %s state",
1231 fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"));
1232
1233 if (DEFER_SIGNALS(conn)) {
1235 return;
1236 }
1237
1238 switch (conn->pub.state) {
1241 break;
1242
1245 break;
1246
1247 /*
1248 * If the connection is connected it needs to be
1249 * shutdown first.
1250 *
1251 * The shutdown callback or an FD event it inserts then
1252 * to signal that the connection should be closed.
1253 */
1255 if (conn->shutdown) {
1257 break;
1258 }
1260
1261 /*
1262 * If the connection is any of these states it
1263 * must have completed INIT which means it has
1264 * an active handle which needs to be closed before
1265 * the connection is halted.
1266 */
1271 fr_assert(conn->is_closed);
1272
1276 break;
1277
1279 fr_assert(0);
1280 return;
1281 }
1282}
1283
1284/** Shuts down a connection ungracefully
1285 *
1286 * If a connection is in an open or connection state it will be closed immediately.
1287 * Otherwise the connection will transition directly to the halted state.
1288 *
1289 * @param[in] conn to halt.
1290 */
1292{
1293 DEBUG2("Signalled to halt from %s state",
1294 fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"));
1295
1296 if (DEFER_SIGNALS(conn)) {
1298 return;
1299 }
1300
1301 switch (conn->pub.state) {
1303 break;
1304
1308 break;
1309
1310 /*
1311 * If the connection is any of these states it
1312 * must have completed INIT which means it has
1313 * an active handle which needs to be closed before
1314 * the connection is halted.
1315 *
1316 * The exception is when a connection fails to open
1317 * so goes from INIT -> FAILED, means is_closed
1318 * is true, as the connection has never opened.
1319 */
1325 if (!conn->is_closed) connection_state_enter_closed(conn);
1326 fr_assert(conn->is_closed);
1328 break;
1329
1331 fr_assert(0);
1332 return;
1333 }
1334}
1335/** Receive an error notification when we're connecting a socket
1336 *
1337 * @param[in] el event list the I/O event occurred on.
1338 * @param[in] fd the I/O even occurred for.
1339 * @param[in] flags from_kevent.
1340 * @param[in] fd_errno from kevent.
1341 * @param[in] uctx The #connection_t this fd is associated with.
1342 */
1343static void _connection_error(UNUSED fr_event_list_t *el, int fd, UNUSED int flags, int fd_errno, void *uctx)
1344{
1345 connection_t *conn = talloc_get_type_abort(uctx, connection_t);
1346
1347 ERROR("Connection failed for fd (%d): %s", fd, fr_syserror(fd_errno));
1349}
1350
1351/** Receive a write notification after a socket is connected
1352 *
1353 * @param[in] el event list the I/O event occurred on.
1354 * @param[in] fd the I/O even occurred for.
1355 * @param[in] flags from kevent.
1356 * @param[in] uctx The #connection_t this fd is associated with.
1357 */
1358static void _connection_writable(fr_event_list_t *el, int fd, UNUSED int flags, void *uctx)
1359{
1360 connection_t *conn = talloc_get_type_abort(uctx, connection_t);
1361
1364}
1365
1366/** Remove the FD we were watching for connection open/fail from the event loop
1367 *
1368 */
1370 UNUSED connection_state_t prev, connection_state_t state, void *uctx)
1371{
1372 int fd = *(talloc_get_type_abort(uctx, int));
1373
1374 /*
1375 * Two states can trigger a cleanup
1376 * Remove the watch on the one that didn't
1377 */
1378 switch (state) {
1381 break;
1382
1385 break;
1386
1387 default:
1388 fr_assert(0);
1389 break;
1390 }
1391
1393 talloc_free(uctx);
1394}
1395
1396/** Setup the connection to change states to connected or failed based on I/O events
1397 *
1398 * Will automatically cleanup after itself, in preparation for
1399 * new I/O handlers to be installed in the open() callback.
1400 *
1401 * @return
1402 * - 0 on success.
1403 * - -1 on failure.
1404 */
1406{
1407 int *fd_s;
1408
1409 /*
1410 * If connection becomes writable we
1411 * assume it's open.
1412 */
1413 if (fr_event_fd_insert(conn, NULL, conn->pub.el, fd,
1414 NULL,
1417 conn) < 0) {
1418 PERROR("Failed inserting fd (%d) into event loop %p",
1419 fd, conn->pub.el);
1421 return -1;
1422 }
1423
1424 /*
1425 * Stop the static analysis tools
1426 * complaining about assigning ints
1427 * to pointers.
1428 */
1429 MEM(fd_s = talloc_zero(conn, int));
1430 *fd_s = fd;
1431
1432 /*
1433 * Add a oneshot watcher to remove
1434 * the I/O handlers if the connection
1435 * fails, or is connected.
1436 */
1441 return 0;
1442}
1443
1444/** Close a connection if it's freed
1445 *
1446 * @param[in] conn to free.
1447 * @return
1448 * - 0 connection was freed immediately.
1449 * - 1 connection free was deferred.
1450 */
1452{
1453 /*
1454 * Explicitly cancel any pending events
1455 */
1456 FR_TIMER_DELETE_RETURN(&conn->ev);
1457 /*
1458 * Don't allow the connection to be
1459 * arbitrarily freed by a callback.
1460 *
1461 * Add a deferred signal to free the
1462 * connection later.
1463 */
1464 if (DEFER_SIGNALS(conn)) {
1466 return -1;
1467 }
1468
1469 switch (conn->pub.state) {
1471 break;
1472
1473 /*
1474 * Need to close the connection first
1475 */
1480
1481 default:
1483 break;
1484 }
1485 return 0;
1486}
1487
1488/** Allocate a new connection
1489 *
1490 * After the connection has been allocated, it should be started with a call to #connection_signal_init.
1491 *
1492 * The connection state machine can detect when the connection is open in one of two ways.
1493 * - You can install a generic socket open/fail callback, using connection_signal_on_fd.
1494 * - You can call either #connection_signal_connected or connection_signal_recommend.
1495 * This allows the connection state machine to work with more difficult library APIs,
1496 * which may not return control to the caller as connections are opened.
1497 *
1498 * @param[in] ctx to allocate connection handle in. If the connection
1499 * handle is freed, and the #connection_state_t is
1500 * #CONNECTION_STATE_CONNECTING or #CONNECTION_STATE_CONNECTED the
1501 * close callback will be called.
1502 * @param[in] el to use for timer events, and to pass to the #connection_open_t callback.
1503 * @param[in] funcs callback functions.
1504 * @param[in] conf our configuration.
1505 * @param[in] log_prefix To prepend to log messages.
1506 * @param[in] uctx User context to pass to callbacks.
1507 * @return
1508 * - A new #connection_t on success.
1509 * - NULL on failure.
1510 */
1512 connection_funcs_t const *funcs,
1513 connection_conf_t const *conf,
1514 char const *log_prefix,
1515 void const *uctx)
1516{
1517 size_t i;
1518 connection_t *conn;
1519 uint64_t id;
1520
1521 fr_assert(el);
1522
1523 MEM(conn = talloc(ctx, connection_t));
1524 talloc_set_destructor(conn, _connection_free);
1525
1527
1528 *conn = (connection_t){
1529 .pub = {
1530 .id = id,
1531 .state = CONNECTION_STATE_HALTED,
1532 .el = el
1533 },
1534 .reconnection_delay = conf->reconnection_delay,
1535 .connection_timeout = conf->connection_timeout,
1536 .init = funcs->init,
1537 .open = funcs->open,
1538 .close = funcs->close,
1539 .failed = funcs->failed,
1540 .shutdown = funcs->shutdown,
1541 .is_closed = true, /* Starts closed */
1542 .pub.name = talloc_asprintf(conn, "%s - [%" PRIu64 "]", log_prefix, id)
1543 };
1544 memcpy(&conn->uctx, &uctx, sizeof(conn->uctx));
1545
1546 for (i = 0; i < NUM_ELEMENTS(conn->watch_pre); i++) {
1548 }
1549 for (i = 0; i < NUM_ELEMENTS(conn->watch_post); i++) {
1551 }
1553
1554 /*
1555 * Pre-allocate a on_halt watcher for deferred signal processing
1556 */
1559 connection_watch_disable(conn->on_halted); /* Start disabled */
1560
1561 return conn;
1562}
#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:324
#define CC_NO_UBSAN(_sanitize)
Definition build.h:428
#define UNUSED
Definition build.h:317
#define NUM_ELEMENTS(_t)
Definition build.h:339
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:248
@ FR_EVENT_FILTER_IO
Combined filter for read/write functions/.
Definition event.h:84
#define PERROR(_fmt,...)
Definition log.h:228
#define DEBUG4(_fmt,...)
Definition log.h:267
talloc_free(reap)
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:1203
Stores all information relating to an event list.
Definition event.c:377
#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:357
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:98
void connection_watch_enable(connection_watch_entry_t *entry)
Enable a watcher.
Definition connection.c:544
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:419
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:613
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:151
static fr_table_num_ordered_t const connection_dsignals[]
Definition connection.c:166
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:226
void connection_watch_disable(connection_watch_entry_t *entry)
Disable a watcher.
Definition connection.c:554
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:472
connection_shutdown_t shutdown
Signal the connection handle to start shutting down.
Definition connection.c:104
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:109
static void _connection_timeout(UNUSED fr_timer_list_t *tl, UNUSED fr_time_t now, void *uctx)
Connection timeout.
Definition connection.c:693
bool connection_watch_is_enabled(connection_watch_entry_t *entry)
Return the state of a watch entry.
Definition connection.c:590
static atomic_uint_fast64_t connection_counter
Definition connection.c:73
static size_t connection_dsignals_len
Definition connection.c:175
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:114
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:767
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:624
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:406
void connection_signals_resume(connection_t *conn)
Resume processing of deferred signals.
Definition connection.c:319
#define HANDLER_BEGIN(_conn, _func)
Called when we enter a handler.
Definition connection.c:337
connection_init_t init
Callback for initialising a connection.
Definition connection.c:101
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:482
connection_close_t close
Callback to close a connection.
Definition connection.c:103
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:601
static void connection_state_enter_connecting(connection_t *conn)
Enter the connecting state.
Definition connection.c:993
connection_watch_entry_t * next_watcher
Hack to insulate watcher iterator from deletions.
Definition connection.c:99
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:393
static void connection_deferred_signal_process(connection_t *conn)
Process any deferred signals.
Definition connection.c:237
connection_dsignal_t signal
Signal that was deferred.
Definition connection.c:182
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:102
connection_failed_t failed
Callback for 'failed' notification.
Definition connection.c:105
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:577
fr_time_delta_t reconnection_delay
How long to wait in the CONNECTION_STATE_FAILED state.
Definition connection.c:111
static void connection_state_enter_closed(connection_t *conn)
Close the connection, then wait for another state change.
Definition connection.c:643
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.
connection_dsignal_t
Deferred signals.
Definition connection.c:156
@ CONNECTION_DSIGNAL_RECONNECT_FAILED
Reconnect a failed connection.
Definition connection.c:159
@ CONNECTION_DSIGNAL_HALT
Close a connection (ungracefully).
Definition connection.c:162
@ CONNECTION_DSIGNAL_INIT
Restart a halted connection.
Definition connection.c:157
@ CONNECTION_DSIGNAL_FREE
Free a connection (no further dsignals processed).
Definition connection.c:163
@ CONNECTION_DSIGNAL_SHUTDOWN
Close a connection (gracefully).
Definition connection.c:161
@ CONNECTION_DSIGNAL_CONNECTED
Signal that a connection is connected.
Definition connection.c:158
@ CONNECTION_DSIGNAL_RECONNECT_EXPIRED
Reconnect an expired connection (gracefully).
Definition connection.c:160
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:97
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:346
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:93
#define STATE_TRANSITION(_new)
Definition connection.c:133
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:914
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:209
fr_dlist_t entry
Entry in the signals list.
Definition connection.c:181
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:510
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:532
static void connection_state_enter_connected(connection_t *conn)
Enter the connected state.
Definition connection.c:947
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:455
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:119
#define BAD_STATE_TRANSITION(_new)
Definition connection.c:143
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:565
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:703
void connection_signals_pause(connection_t *conn)
Pause processing of deferred signals.
Definition connection.c:310
bool processing_signals
Processing deferred signals, don't let the deferred signal processor be called multiple times.
Definition connection.c:94
unsigned int signals_pause
Temporarily stop processing of signals.
Definition connection.c:123
fr_timer_t * ev
State transition timer.
Definition connection.c:107
static void connection_state_enter_timeout(connection_t *conn)
Enter the timeout state.
Definition connection.c:890
Holds a signal from a handler until it's safe to process it.
Definition connection.c:180
An entry in a watch function list.
Definition connection.c:78
@ 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
An event timer list.
Definition timer.c:49
A timer event.
Definition timer.c:75
#define FR_TIMER_DELETE_RETURN(_ev_p)
Definition timer.h:109
#define fr_timer_in(...)
Definition timer.h:86
#define FR_TIMER_DISARM(_ev)
Definition timer.h:90
static fr_event_list_t * el
#define fr_box_time_delta(_val)
Definition value.h:354