The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
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: d513449a687a11eb679d13710d226f7bc47432fc $
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 CONF_SECTION *trigger_cs; //!< Where to search locally for triggers.
126 fr_pair_list_t *trigger_args; //!< Arguments to pass to the trigger functions.
127 bool triggers; //!< Do we run triggers.
128};
129
130#define CONN_TRIGGER(_state) do { \
131 if (conn->triggers) trigger(unlang_interpret_get_thread_default(), \
132 conn->trigger_cs, NULL, fr_table_str_by_value(connection_trigger_names, _state, "<INVALID>"), true, conn->trigger_args); \
133} while (0)
134
135#define STATE_TRANSITION(_new) \
136do { \
137 DEBUG2("Connection changed state %s -> %s", \
138 fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"), \
139 fr_table_str_by_value(connection_states, _new, "<INVALID>")); \
140 conn->pub.prev = conn->pub.state; \
141 conn->pub.state = _new; \
142 CONN_TRIGGER(_new); \
143} while (0)
144
145#define BAD_STATE_TRANSITION(_new) \
146do { \
147 if (!fr_cond_assert_msg(0, "Connection %" PRIu64 " invalid transition %s -> %s", \
148 conn->pub.id, \
149 fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"), \
150 fr_table_str_by_value(connection_states, _new, "<INVALID>"))) return; \
151} while (0)
152
153#define DEFER_SIGNALS(_conn) ((_conn)->in_handler || (_conn)->signals_pause)
154
155/** Deferred signals
156 *
157 */
158typedef enum {
159 CONNECTION_DSIGNAL_INIT, //!< Restart a halted connection.
160 CONNECTION_DSIGNAL_CONNECTED, //!< Signal that a connection is connected.
161 CONNECTION_DSIGNAL_RECONNECT_FAILED, //!< Reconnect a failed connection.
162 CONNECTION_DSIGNAL_RECONNECT_EXPIRED, //!< Reconnect an expired connection (gracefully).
163 CONNECTION_DSIGNAL_SHUTDOWN, //!< Close a connection (gracefully).
164 CONNECTION_DSIGNAL_HALT, //!< Close a connection (ungracefully).
165 CONNECTION_DSIGNAL_FREE //!< Free a connection (no further dsignals processed).
167
169 { L("INIT"), CONNECTION_DSIGNAL_INIT },
170 { L("CONNECTED"), CONNECTION_DSIGNAL_CONNECTED },
171 { L("RECONNECT-FAILED"), CONNECTION_DSIGNAL_RECONNECT_FAILED },
172 { L("RECONNECT-EXPIRED"), CONNECTION_DSIGNAL_RECONNECT_EXPIRED },
173 { L("SHUTDOWN"), CONNECTION_DSIGNAL_SHUTDOWN },
174 { L("HALT"), CONNECTION_DSIGNAL_HALT },
175 { L("FREE"), CONNECTION_DSIGNAL_FREE }
176};
178
179/** Holds a signal from a handler until it's safe to process it
180 *
181 */
182typedef struct {
183 fr_dlist_t entry; //!< Entry in the signals list.
184 connection_dsignal_t signal; //!< Signal that was deferred.
186
187/*
188 * State transition functions
189 */
198
199/** Add a deferred signal to the signal list
200 *
201 * Processing signals whilst in handlers usually leads to weird
202 * inconsistent states within the connection.
203 *
204 * If a public signal function is called, and detects its being called
205 * from within the handler, it instead adds a deferred signal entry
206 * and immediately returns.
207 *
208 * Once the handler is complete, and all pending C stack state changes
209 * are complete, the deferred signals are drained and processed.
210 */
212{
213 connection_dsignal_entry_t *dsignal, *prev;
214
215 /*
216 * We only suppresses consecutive duplicates at the
217 * tail. A sequence such as [INIT, HALT, INIT] will push
218 * the second INIT, because the tail is HALT. The signal
219 * handlers are generally idempotent (they check current
220 * state), so redundant signals are harmless. Avoiding
221 * this corner case involves doing more work in the
222 * common case, in order to avoid a small amount of work
223 * in the rare case.
224 */
225 prev = fr_dlist_tail(&conn->deferred_signals);
226 if (prev && (prev->signal == signal)) return; /* Don't insert duplicates */
227
228 MEM(dsignal = talloc_zero(conn, connection_dsignal_entry_t));
229 dsignal->signal = signal;
230 fr_dlist_insert_tail(&conn->deferred_signals, dsignal);
231
232// DEBUG4("Adding deferred signal - %s", fr_table_str_by_value(connection_dsignals, signal, "<INVALID>"));
233}
234
235/** Notification function to tell connection_deferred_signal_process that the connection has been freed
236 *
237 */
240 UNUSED connection_state_t state, void *uctx)
241{
242 bool *freed = uctx;
243 *freed = true;
244}
245
246/** Process any deferred signals
247 *
248 */
250{
252 bool freed = false;
253
254 /*
255 * We're inside and an instance of this function
256 * higher in the call stack. Don't do anything.
257 */
258 if (conn->processing_signals) return;
259
260 /*
261 * Get notified if the connection gets freed
262 * out from under us...
263 */
265 conn->processing_signals = true;
266
267 while ((dsignal = fr_dlist_head(&conn->deferred_signals))) {
269 fr_dlist_remove(&conn->deferred_signals, dsignal);
270 signal = dsignal->signal;
271 talloc_free(dsignal);
272
273 DEBUG4("Processing deferred signal - %s",
274 fr_table_str_by_value(connection_dsignals, signal, "<INVALID>"));
275
276 switch (signal) {
279 break;
280
283 break;
284
285 case CONNECTION_DSIGNAL_RECONNECT_FAILED: /* Reconnect - Failed */
287 break;
288
289 case CONNECTION_DSIGNAL_RECONNECT_EXPIRED: /* Reconnect - Expired */
291 break;
292
295 break;
296
299 break;
300
301 case CONNECTION_DSIGNAL_FREE: /* Freed */
302 talloc_free(conn);
303 return;
304 }
305
306 /*
307 * One of the signal handlers freed the connection,
308 * reset the processing signals and return.
309 */
310 if (freed) break;
311 }
312
313 conn->processing_signals = false;
315}
316
317/** Pause processing of deferred signals
318 *
319 * @param[in] conn to pause signal processing for.
320 */
322{
323 conn->signals_pause++;
324}
325
326/** Resume processing of deferred signals
327 *
328 * @param[in] conn to resume signal processing for.
329 */
331{
332 if (conn->signals_pause > 0) conn->signals_pause--;
333 if (conn->signals_pause > 0) return;
334
335 /*
336 * If we're not in a handler process the
337 * deferred signals now.
338 */
339 if (!conn->in_handler) {
341 return;
342 }
343}
344
345/** Called when we enter a handler
346 *
347 */
348#define HANDLER_BEGIN(_conn, _func) \
349void *_prev_handler = (_conn)->in_handler; \
350do { \
351 (_conn)->in_handler = (void *)(_func); \
352} while (0)
353
354/** Called when we exit a handler
355 *
356 */
357#define HANDLER_END(_conn) \
358do { \
359 (_conn)->in_handler = _prev_handler; \
360 if (!(_conn)->signals_pause && (!(_conn)->in_handler)) connection_deferred_signal_process(_conn); \
361} while(0)
362
363
364/** Call a list of watch functions associated with a state
365 *
366 */
367CC_NO_UBSAN(function) /* UBSAN: false positive - Public/private version of connection_t trips -fsanitize=function */
368static inline void connection_watch_call(connection_t *conn, fr_dlist_head_t *list)
369{
370 /*
371 * Nested watcher calls are not allowed
372 * and shouldn't be possible because of
373 * deferred signal processing.
374 */
375 fr_assert(conn->next_watcher == NULL);
376
377 while ((conn->next_watcher = fr_dlist_next(list, conn->next_watcher))) {
378 connection_watch_entry_t *entry = conn->next_watcher;
379 bool oneshot = entry->oneshot; /* Watcher could be freed, so store now */
380
381 if (!entry->enabled) continue;
382 if (oneshot) conn->next_watcher = fr_dlist_remove(list, entry);
383
384/*
385 DEBUG4("Notifying %swatcher - (%p)(conn=%p, prev=%s, state=%s, uctx=%p)",
386 entry->oneshot ? "oneshot " : "",
387 entry->func,
388 conn,
389 fr_table_str_by_value(connection_states, conn->pub.prev, "<INVALID>"),
390 fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"),
391 entry->uctx);
392*/
393
394 entry->func(conn, conn->pub.prev, conn->pub.state, entry->uctx);
395
396 if (oneshot) talloc_free(entry);
397 }
398 conn->next_watcher = NULL;
399}
400
401/** Call the pre handler watch functions
402 *
403 */
404#define WATCH_PRE(_conn) \
405do { \
406 if (fr_dlist_empty(&(_conn)->watch_pre[(_conn)->pub.state])) break; \
407 { \
408 HANDLER_BEGIN(conn, &(_conn)->watch_pre[(_conn)->pub.state]); \
409 connection_watch_call((_conn), &(_conn)->watch_pre[(_conn)->pub.state]); \
410 HANDLER_END(conn); \
411 } \
412} while(0)
413
414/** Call the post handler watch functions
415 *
416 */
417#define WATCH_POST(_conn) \
418do { \
419 if (fr_dlist_empty(&(_conn)->watch_post[(_conn)->pub.state])) break; \
420 { \
421 HANDLER_BEGIN(conn, &(_conn)->watch_post[(_conn)->pub.state]); \
422 connection_watch_call((_conn), &(_conn)->watch_post[(_conn)->pub.state]); \
423 HANDLER_END(conn); \
424 } \
425} while(0)
426
427/** Remove a watch function from a pre/post[state] list
428 *
429 */
430static int connection_del_watch(connection_t *conn, fr_dlist_head_t *state_lists,
432{
433 connection_watch_entry_t *entry = NULL;
434 fr_dlist_head_t *list = &state_lists[state];
435
436 while ((entry = fr_dlist_next(list, entry))) {
437 if (entry->func == watch) {
438/*
439 DEBUG4("Removing %s watcher %p",
440 fr_table_str_by_value(connection_states, state, "<INVALID>"),
441 watch);
442*/
443 if (conn->next_watcher == entry) {
444 conn->next_watcher = fr_dlist_remove(list, entry);
445 } else {
446 fr_dlist_remove(list, entry);
447 }
448 talloc_free(entry);
449 return 0;
450 }
451 }
452
453 return -1;
454}
455
456/** Remove a watch function from a pre list
457 *
458 * @param[in] conn The connection to remove the watcher from.
459 * @param[in] state to remove the watch from.
460 * @param[in] watch Function to remove.
461 * @return
462 * - 0 if the function was removed successfully.
463 * - -1 if the function wasn't present in the watch list.
464 * - -2 an invalid state was passed.
465 */
467{
468 if (state >= CONNECTION_STATE_MAX) return -2;
469
470 return connection_del_watch(conn, conn->watch_pre, state, watch);
471}
472
473/** Remove a watch function from a post list
474 *
475 * @param[in] conn The connection to remove the watcher from.
476 * @param[in] state to remove the watch from.
477 * @param[in] watch Function to remove.
478 * @return
479 * - 0 if the function was removed successfully.
480 * - -1 if the function wasn't present in the watch list.
481 * - -2 an invalid state was passed.
482 */
484{
485 if (state >= CONNECTION_STATE_MAX) return -2;
486
487 return connection_del_watch(conn, conn->watch_post, state, watch);
488}
489
490/** Add a watch entry to the pre/post[state] list
491 *
492 */
494 connection_watch_t watch, bool oneshot, void const *uctx)
495{
497
498 MEM(entry = talloc_zero(conn, connection_watch_entry_t));
499
500 entry->func = watch;
501 entry->oneshot = oneshot;
502 entry->enabled = true;
503 memcpy(&entry->uctx, &uctx, sizeof(entry->uctx));
504
505 fr_dlist_insert_tail(list, entry);
506
507 return entry;
508}
509
510/** Add a callback to be executed before a state function has been called
511 *
512 * @param[in] conn to add watcher to.
513 * @param[in] state to call watcher on entering.
514 * @param[in] watch function to call.
515 * @param[in] oneshot If true, remove the function after calling.
516 * @param[in] uctx to pass to callbacks.
517 * @return
518 * - NULL if state value is invalid.
519 * - A new watch entry handle.
520 */
522 connection_watch_t watch, bool oneshot, void const *uctx)
523{
524 if (state >= CONNECTION_STATE_MAX) return NULL;
525
526 return connection_add_watch(conn, &conn->watch_pre[state], watch, oneshot, uctx);
527}
528
529/** Add a callback to be executed after a state function has been called
530 *
531 * Where a user callback is executed on state change, the post function
532 * is only called if the callback succeeds.
533 *
534 * @param[in] conn to add watcher to.
535 * @param[in] state to call watcher on entering.
536 * @param[in] watch function to call.
537 * @param[in] oneshot If true, remove the function after calling.
538 * @param[in] uctx to pass to callbacks.
539 * @return
540 * - NULL if state value is invalid.
541 * - A new watch entry handle.
542 */
544 connection_watch_t watch, bool oneshot, void const *uctx)
545{
546 if (state >= CONNECTION_STATE_MAX) return NULL;
547
548 return connection_add_watch(conn, &conn->watch_post[state], watch, oneshot, uctx);
549}
550
551/** Enable a watcher
552 *
553 * @param[in] entry to enabled.
554 */
556{
557 (void)talloc_get_type_abort(entry, connection_watch_entry_t);
558 entry->enabled = true;
559}
560
561/** Disable a watcher
562 *
563 * @param[in] entry to disable.
564 */
566{
567 (void)talloc_get_type_abort(entry, connection_watch_entry_t);
568 entry->enabled = false;
569}
570
571/** Enable a watcher and replace the uctx
572 *
573 * @param[in] entry to enabled.
574 * @param[in] uctx Opaque data to pass to the callback.
575 */
577{
578 (void)talloc_get_type_abort(entry, connection_watch_entry_t);
579 entry->enabled = true;
580 memcpy(&entry->uctx, &uctx, sizeof(entry->uctx));
581}
582
583/** Change the uctx of an entry
584 *
585 * @param[in] entry to enabled.
586 * @param[in] uctx Opaque data to pass to the callback.
587 */
589{
590 (void)talloc_get_type_abort(entry, connection_watch_entry_t);
591 memcpy(&entry->uctx, &uctx, sizeof(entry->uctx));
592}
593
594/** Return the state of a watch entry
595 *
596 * @param[in] entry to return state of.
597 * @return
598 * - true if enabled.
599 * - false if disabled.
600 */
602{
603 (void)talloc_get_type_abort(entry, connection_watch_entry_t);
604 return entry->enabled;
605}
606
607/** Return the number of times we've attempted to establish or re-establish this connection
608 *
609 * @param[in] conn to get count from.
610 * @return the number of times the connection has reconnected.
611 */
613{
614 if (conn->pub.reconnected == 0) return 0; /* Has never been initialised */
615
616 return conn->pub.reconnected - 1; /* We don't count the first connection attempt */
617}
618
619/** Return the number of times this connection has timed out whilst connecting
620 *
621 * @param[in] conn to get count from.
622 * @return the number of times the connection has timed out whilst connecting.
623 */
625{
626 return conn->pub.timed_out;
627}
628
629/** The requisite period of time has passed, try and re-open the connection
630 *
631 * @param[in] tl containing the timer event.
632 * @param[in] now The current time.
633 * @param[in] uctx The #connection_t the fd is associated with.
634 */
636{
637 connection_t *conn = talloc_get_type_abort(uctx, connection_t);
638
639 switch (conn->pub.state) {
643 break;
644
645 default:
647 break;
648 }
649}
650
651/** Close the connection, then wait for another state change
652 *
653 */
655{
656 switch (conn->pub.state) {
662 break;
663
664 default:
666 return;
667 }
668
670
671 FR_TIMER_DISARM(conn->ev);
672
673 /*
674 * If there's a close callback, call it, so that the
675 * API client can free any resources associated
676 * with the connection handle.
677 */
678 WATCH_PRE(conn);
679
680 /*
681 * We can reach "is_closed" if a connection is halted,
682 * then signaled to INIT, which fails, and then sits in
683 * the FAILED state. Eventually the connection is
684 * shutdown, and enter_shutdown calls this function.
685 */
686 if (conn->close && !conn->is_closed) {
687 HANDLER_BEGIN(conn, conn->close);
688 DEBUG4("Calling close(el=%p, h=%p, uctx=%p)", conn->pub.el, conn->pub.h, conn->uctx);
689 conn->close(conn->pub.el, conn->pub.h, conn->uctx);
690 conn->is_closed = true; /* Ensure close doesn't get called twice if the connection is freed */
691 HANDLER_END(conn);
692
693 /*
694 * A deferred signal may have moved the connection to a
695 * different state. If so, that signal handler already
696 * took care of the transition.
697 */
698 if (conn->pub.state != CONNECTION_STATE_CLOSED) return;
699 } else {
700 conn->is_closed = true;
701 }
702 WATCH_POST(conn);
703}
704
705/** Connection timeout
706 *
707 * Connection wasn't opened within the configured period of time
708 *
709 * @param[in] tl timer list the event belonged to.
710 * @param[in] now The current time.
711 * @param[in] uctx The #connection_t the fd is associated with.
712 */
714{
715 connection_t *conn = talloc_get_type_abort(uctx, connection_t);
716
718}
719
720/** Gracefully shutdown the handle
721 *
722 */
724{
726
727 switch (conn->pub.state) {
729 break;
730
731 default:
733 return;
734 }
735
737
738 WATCH_PRE(conn);
739 if (conn->shutdown) {
740 HANDLER_BEGIN(conn, conn->shutdown);
741 DEBUG4("Calling shutdown(el=%p, h=%p, uctx=%p)", conn->pub.el, conn->pub.h, conn->uctx);
742 ret = conn->shutdown(conn->pub.el, conn->pub.h, conn->uctx);
743 HANDLER_END(conn);
744
745 /*
746 * A deferred signal may have moved the connection to a
747 * different state. If so, that signal handler already
748 * took care of the transition.
749 */
750 if (conn->pub.state != CONNECTION_STATE_SHUTDOWN) return;
751 }
752 switch (ret) {
754 break;
755
756 default:
758 return;
759 }
760 WATCH_POST(conn);
761
762 /*
763 * If there's a connection timeout,
764 * set, then add the timer.
765 *
766 * The connection may be bad, in which
767 * case we want to automatically fail
768 * if it doesn't shutdown within the
769 * timeout period.
770 */
772 if (fr_timer_in(conn, conn->pub.el->tl, &conn->ev,
773 conn->connection_timeout, false, _connection_timeout, conn) < 0) {
774 /*
775 * Can happen when the event loop is exiting
776 */
777 PERROR("Failed setting connection_timeout timer, closing connection");
779 }
780 }
781}
782
783/** Connection failed
784 *
785 * Transition to the CONNECTION_STATE_FAILED state.
786 *
787 * If the connection was open, or couldn't be opened wait for reconnection_delay before transitioning
788 * back to init.
789 *
790 * If no reconnection_delay was set, transition to halted.
791 *
792 * @param[in] conn that failed.
793 */
795{
798
800
801 /*
802 * Explicit error occurred, delete the connection timer
803 */
804 FR_TIMER_DISARM(conn->ev);
805
806 /*
807 * Record what state the connection is currently in
808 * so we can figure out what to do next.
809 */
810 prev = conn->pub.state;
811
812 /*
813 * Now transition to failed
814 */
816
817 /*
818 * If there's a failed callback, give it the
819 * opportunity to suspend/destroy the
820 * connection.
821 */
822 WATCH_PRE(conn);
823 if (conn->failed) {
824 HANDLER_BEGIN(conn, conn->failed);
825 DEBUG4("Calling failed(h=%p, state=%s, uctx=%p)", conn->pub.h,
826 fr_table_str_by_value(connection_states, prev, "<INVALID>"), conn->uctx);
827 ret = conn->failed(conn->pub.h, prev, conn->uctx);
828 HANDLER_END(conn);
829
830 /*
831 * A deferred signal may have moved the connection to a
832 * different state. If so, that signal handler already
833 * took care of the transition.
834 */
835 if (conn->pub.state != CONNECTION_STATE_FAILED) return;
836 }
837 WATCH_POST(conn);
838
839 /*
840 * Enter the closed state if we failed during
841 * connecting, or when we were connected.
842 */
843 switch (prev) {
846 case CONNECTION_STATE_TIMEOUT: /* Timeout means the connection progress past init */
847 case CONNECTION_STATE_SHUTDOWN: /* Shutdown means the connection failed whilst shutting down */
849 break;
850
851 default:
852 break;
853 }
854
855 if (conn->failed) {
856 switch (ret) {
857 /*
858 * The callback signalled it wants the
859 * connection to be reinitialised
860 * after reconnection_delay, or
861 * immediately if the failure was due
862 * to a connection timeout.
863 */
865 break;
866
867 /*
868 * The callback signalled it wants the
869 * connection to stop.
870 */
872 default:
874 return;
875 }
876 }
877
878 /*
879 * What previous state we were in
880 * determines if we need to apply the
881 * reconnect timeout.
882 */
883 switch (prev) {
884 case CONNECTION_STATE_INIT: /* Failed during initialisation */
885 case CONNECTION_STATE_CONNECTED: /* Failed after connecting */
886 case CONNECTION_STATE_CONNECTING: /* Failed during connecting */
887 case CONNECTION_STATE_SHUTDOWN: /* Failed during shutdown */
889 DEBUG2("Delaying reconnection by %pVs", fr_box_time_delta(conn->reconnection_delay));
890 if (fr_timer_in(conn, conn->pub.el->tl, &conn->ev,
891 conn->reconnection_delay, false, _reconnect_delay_done, conn) < 0) {
892 /*
893 * Can happen when the event loop is exiting
894 */
895 PERROR("Failed inserting reconnection_delay timer event, halting connection");
897 }
898 return;
899 }
900
901 /*
902 * If there's no reconnection
903 * delay, then don't automatically
904 * reconnect, and wait to be
905 * signalled.
906 */
908 break;
909
910 case CONNECTION_STATE_TIMEOUT: /* Failed during connecting due to timeout */
912 break;
913
914 default:
915 fr_assert(0);
916 }
917}
918
919/** Enter the timeout state
920 *
921 * The connection took took long to open. Timeout the attempt and transition
922 * to the failed state.
923 */
925{
926 switch (conn->pub.state) {
929 break;
930
931 default:
933 break;
934 }
935
936 ERROR("Connection failed - timed out after %pVs", fr_box_time_delta(conn->connection_timeout));
937
939
940 conn->pub.timed_out++;
941
943}
944
945/** Enter the halted state
946 *
947 * Here we wait, until signalled by connection_signal_reconnect.
948 */
950{
951 fr_assert(conn->is_closed);
952
953 switch (conn->pub.state) {
955 case CONNECTION_STATE_FAILED: /* Init failure */
957 break;
958
959 default:
961 break;
962 }
963
964 FR_TIMER_DISARM(conn->ev);
965
967 WATCH_PRE(conn);
968 WATCH_POST(conn);
969}
970
971/** Enter the connected state
972 *
973 * The connection is now fully connected. At this point we call the open callback
974 * so that the API client can install its normal set of I/O callbacks to deal with
975 * sending/receiving actual data.
976 *
977 * After this, the connection will only transition states if an API client
978 * explicitly calls connection_signal_reconnect.
979 *
980 * The connection API cannot monitor the connection for failure conditions.
981 *
982 * @param[in] conn Entering the connecting state.
983 */
985{
986 int ret;
987
989
991
992 FR_TIMER_DISARM(conn->ev);
993 WATCH_PRE(conn);
994 if (conn->open) {
995 HANDLER_BEGIN(conn, conn->open);
996 DEBUG4("Calling open(el=%p, h=%p, uctx=%p)", conn->pub.el, conn->pub.h, conn->uctx);
997 ret = conn->open(conn->pub.el, conn->pub.h, conn->uctx);
998 HANDLER_END(conn);
999
1000 /*
1001 * A deferred signal may have moved the connection to a
1002 * different state. If so, that signal handler already
1003 * took care of the transition.
1004 */
1005 if (conn->pub.state != CONNECTION_STATE_CONNECTED) return;
1006 } else {
1008 }
1009
1010 switch (ret) {
1011 /*
1012 * Callback agrees everything is connected
1013 */
1015 DEBUG2("Connection established");
1016 WATCH_POST(conn); /* Only call if we successfully connected */
1017 return;
1018
1019 /*
1020 * Open callback failed
1021 */
1023 default:
1024 PERROR("Connection failed");
1026 return;
1027 }
1028}
1029
1030/** Enter the connecting state
1031 *
1032 * After this function returns we wait to be signalled with connection_singal_connected
1033 * or for the connection timer to expire.
1034 *
1035 * @param[in] conn Entering the connecting state.
1036 */
1038{
1039 switch (conn->pub.state) {
1041 break;
1042
1043 default:
1045 return;
1046 }
1047
1049
1050 WATCH_PRE(conn);
1051 WATCH_POST(conn);
1052
1053 /*
1054 * If there's a connection timeout,
1055 * set, then add the timer.
1056 */
1058 if (fr_timer_in(conn, conn->pub.el->tl, &conn->ev,
1059 conn->connection_timeout, false, _connection_timeout, conn) < 0) {
1060 PERROR("Failed setting connection_timeout event, failing connection");
1061
1062 /*
1063 * This can happen when the event loop
1064 * is exiting.
1065 *
1066 * Entering fail will close partially
1067 * open connection and then, if we still
1068 * can't insert a timer, then the connection
1069 * will be halted and sit idle until its
1070 * freed.
1071 */
1073 }
1074 }
1075}
1076
1077/** Initial state of the connection
1078 *
1079 * Calls the init function we were passed to allocate a library specific handle or
1080 * file descriptor.
1081 *
1082 * @param[in] conn To initialise.
1083 */
1085{
1087
1088 switch (conn->pub.state) {
1092 break;
1093
1094 default:
1096 return;
1097 }
1098
1099 /*
1100 * Increment every time we enter
1101 * We have to do this, as we don't know
1102 * whether the connection was halted by
1103 * the failed callback, and is now being
1104 * reconnected, or was automatically
1105 * reconnected.
1106 */
1107 conn->pub.reconnected++;
1108
1110
1111 /*
1112 * If we have an init callback, call it.
1113 */
1114 WATCH_PRE(conn);
1115 if (conn->init) {
1116 HANDLER_BEGIN(conn, conn->init);
1117 DEBUG4("Calling init(h_out=%p, conn=%p, uctx=%p)", &conn->pub.h, conn, conn->uctx);
1118 ret = conn->init(&conn->pub.h, conn, conn->uctx);
1119 HANDLER_END(conn);
1120
1121 /*
1122 * A deferred signal may have moved the connection to a
1123 * different state. If so, that signal handler already
1124 * took care of the transition.
1125 */
1126 if (conn->pub.state != CONNECTION_STATE_INIT) return;
1127 } else {
1129 }
1130
1131 switch (ret) {
1133 conn->is_closed = false; /* We now have a handle */
1134 WATCH_POST(conn); /* Only call if we successfully initialised the handle */
1136 return;
1137
1139 conn->is_closed = false; /* We now have a handle */
1140 WATCH_POST(conn); /* Only call if we successfully initialised the handle */
1142 return;
1143
1144 /*
1145 * Initialisation callback failed
1146 */
1148 default:
1149 PERROR("Connection initialisation failed");
1151 break;
1152 }
1153}
1154
1155/** Asynchronously signal a halted connection to start
1156 *
1157 */
1159{
1160 DEBUG2("Signalled to start from %s state",
1161 fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"));
1162
1163 if (DEFER_SIGNALS(conn)) {
1165 return;
1166 }
1167
1168 switch (conn->pub.state) {
1171 break;
1172
1173 default:
1174 break;
1175 }
1176}
1177
1178/** Asynchronously signal that the connection is open
1179 *
1180 * Some libraries like libldap are extremely annoying and only return control
1181 * to the caller after a connection is open.
1182 *
1183 * For these libraries, we can't use an I/O handler to determine when the
1184 * connection is open so we rely on callbacks built into the library to
1185 * signal that the transition has occurred.
1186 *
1187 */
1189{
1190 fr_assert(!conn->open); /* Use one or the other not both! */
1191
1192 DEBUG2("Signalled connected from %s state",
1193 fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"));
1194
1195 if (DEFER_SIGNALS(conn)) {
1197 return;
1198 }
1199
1200 switch (conn->pub.state) {
1203 break;
1204
1205 default:
1206 break;
1207 }
1208}
1209
1210/** Asynchronously signal the connection should be reconnected
1211 *
1212 * Should be called if the caller has knowledge that the connection is bad
1213 * and should be reconnected.
1214 *
1215 * @param[in] conn to reconnect.
1216 * @param[in] reason Why the connection was signalled to reconnect.
1217 */
1219{
1220 DEBUG2("Signalled to reconnect from %s state",
1221 fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"));
1222
1223 if (DEFER_SIGNALS(conn)) {
1224 if ((reason == CONNECTION_EXPIRED) && conn->shutdown) {
1226 return;
1227 }
1228
1230 return;
1231 }
1232
1233 switch (conn->pub.state) {
1234 case CONNECTION_STATE_CLOSED: /* Don't circumvent reconnection_delay */
1235 case CONNECTION_STATE_INIT: /* Already initialising */
1236 break;
1237
1240 break;
1241
1243 if (reason == CONNECTION_EXPIRED) break; /* Already shutting down */
1245 break;
1246
1248 if (reason == CONNECTION_EXPIRED) {
1249 if (conn->shutdown) {
1251 break;
1252 }
1254 break;
1255 }
1257
1261 break;
1262
1263 case CONNECTION_STATE_FAILED: /* already entered failed, don't re-enter */
1264 break;
1265
1267 fr_assert(0);
1268 return;
1269 }
1270}
1271
1272/** Shuts down a connection gracefully
1273 *
1274 * If a shutdown function has been provided, it is called.
1275 * It's then up to the shutdown function to install I/O handlers to signal
1276 * when the connection has finished shutting down and should be closed
1277 * via #connection_signal_halt.
1278 *
1279 * @param[in] conn to shutdown.
1280 */
1282{
1283 DEBUG2("Signalled to shutdown from %s state",
1284 fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"));
1285
1286 if (DEFER_SIGNALS(conn)) {
1288 return;
1289 }
1290
1291 switch (conn->pub.state) {
1294 break;
1295
1298 break;
1299
1300 /*
1301 * If the connection is connected it needs to be
1302 * shutdown first.
1303 *
1304 * The shutdown callback or an FD event it inserts then
1305 * to signal that the connection should be closed.
1306 */
1308 if (conn->shutdown) {
1310 break;
1311 }
1313
1314 /*
1315 * If the connection is any of these states it
1316 * must have completed INIT which means it has
1317 * an active handle which needs to be closed before
1318 * the connection is halted.
1319 */
1324 fr_assert(conn->is_closed);
1325
1329 break;
1330
1332 fr_assert(0);
1333 return;
1334 }
1335}
1336
1337/** Shuts down a connection ungracefully
1338 *
1339 * If a connection is in an open or connection state it will be closed immediately.
1340 * Otherwise the connection will transition directly to the halted state.
1341 *
1342 * @param[in] conn to halt.
1343 */
1345{
1346 DEBUG2("Signalled to halt from %s state",
1347 fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"));
1348
1349 if (DEFER_SIGNALS(conn)) {
1351 return;
1352 }
1353
1354 switch (conn->pub.state) {
1356 break;
1357
1361 break;
1362
1363 /*
1364 * If the connection is any of these states it
1365 * must have completed INIT which means it has
1366 * an active handle which needs to be closed before
1367 * the connection is halted.
1368 *
1369 * The exception is when a connection fails to open
1370 * so goes from INIT -> FAILED, means is_closed
1371 * is true, as the connection has never opened.
1372 */
1378 if (!conn->is_closed) connection_state_enter_closed(conn);
1379 fr_assert(conn->is_closed);
1381 break;
1382
1384 fr_assert(0);
1385 return;
1386 }
1387}
1388/** Receive an error notification when we're connecting a socket
1389 *
1390 * @param[in] el event list the I/O event occurred on.
1391 * @param[in] fd the I/O event occurred for.
1392 * @param[in] flags from kevent.
1393 * @param[in] fd_errno from kevent.
1394 * @param[in] uctx The #connection_t this fd is associated with.
1395 */
1396static void _connection_error(UNUSED fr_event_list_t *el, int fd, UNUSED int flags, int fd_errno, void *uctx)
1397{
1398 connection_t *conn = talloc_get_type_abort(uctx, connection_t);
1399
1400 ERROR("Connection failed for fd (%d): %s", fd, fr_syserror(fd_errno));
1402}
1403
1404/** Receive a write notification after a socket is connected
1405 *
1406 * @param[in] el event list the I/O event occurred on.
1407 * @param[in] fd the I/O event occurred for.
1408 * @param[in] flags from kevent.
1409 * @param[in] uctx The #connection_t this fd is associated with.
1410 */
1411static void _connection_writable(fr_event_list_t *el, int fd, UNUSED int flags, void *uctx)
1412{
1413 connection_t *conn = talloc_get_type_abort(uctx, connection_t);
1414
1417}
1418
1419/** Remove the FD we were watching for connection open/fail from the event loop
1420 *
1421 */
1423 UNUSED connection_state_t prev, connection_state_t state, void *uctx)
1424{
1425 int fd = *(talloc_get_type_abort(uctx, int));
1426
1427 /*
1428 * Two states can trigger a cleanup
1429 * Remove the watch on the one that didn't
1430 */
1431 switch (state) {
1434 break;
1435
1438 break;
1439
1440 default:
1441 fr_assert(0);
1442 break;
1443 }
1444
1446 talloc_free(uctx);
1447}
1448
1449/** Setup the connection to change states to connected or failed based on I/O events
1450 *
1451 * Will automatically cleanup after itself, in preparation for
1452 * new I/O handlers to be installed in the open() callback.
1453 *
1454 * @return
1455 * - 0 on success.
1456 * - -1 on failure.
1457 */
1459{
1460 int *fd_s;
1461
1462 /*
1463 * If connection becomes writable we
1464 * assume it's open.
1465 */
1466 if (fr_event_fd_insert(conn, NULL, conn->pub.el, fd,
1467 NULL,
1470 conn) < 0) {
1471 PERROR("Failed inserting fd (%d) into event loop %p",
1472 fd, conn->pub.el);
1474 return -1;
1475 }
1476
1477 /*
1478 * Stop the static analysis tools
1479 * complaining about assigning ints
1480 * to pointers.
1481 */
1482 MEM(fd_s = talloc_zero(conn, int));
1483 *fd_s = fd;
1484
1485 /*
1486 * Add a oneshot watcher to remove
1487 * the I/O handlers if the connection
1488 * fails, or is connected.
1489 */
1494 return 0;
1495}
1496
1497/** Close a connection if it's freed
1498 *
1499 * @param[in] conn to free.
1500 * @return
1501 * - 0 connection was freed immediately.
1502 * - 1 connection free was deferred.
1503 */
1505{
1506 /*
1507 * Explicitly cancel any pending events
1508 */
1509 FR_TIMER_DELETE_RETURN(&conn->ev);
1510 /*
1511 * Don't allow the connection to be
1512 * arbitrarily freed by a callback.
1513 *
1514 * Add a deferred signal to free the
1515 * connection later.
1516 */
1517 if (DEFER_SIGNALS(conn)) {
1519 return -1;
1520 }
1521
1522 switch (conn->pub.state) {
1524 break;
1525
1526 /*
1527 * Need to close the connection first
1528 */
1535
1536 default:
1538 break;
1539 }
1540 return 0;
1541}
1542
1543/** Allocate a new connection
1544 *
1545 * After the connection has been allocated, it should be started with a call to #connection_signal_init.
1546 *
1547 * The connection state machine can detect when the connection is open in one of two ways.
1548 * - You can install a generic socket open/fail callback, using connection_signal_on_fd.
1549 * - You can call either #connection_signal_connected or connection_signal_recommend.
1550 * This allows the connection state machine to work with more difficult library APIs,
1551 * which may not return control to the caller as connections are opened.
1552 *
1553 * @param[in] ctx to allocate connection handle in. If the connection
1554 * handle is freed, and the #connection_state_t is
1555 * #CONNECTION_STATE_CONNECTING or #CONNECTION_STATE_CONNECTED the
1556 * close callback will be called.
1557 * @param[in] el to use for timer events, and to pass to the #connection_open_t callback.
1558 * @param[in] funcs callback functions.
1559 * @param[in] conf our configuration.
1560 * @param[in] log_prefix To prepend to log messages.
1561 * @param[in] uctx User context to pass to callbacks.
1562 * @return
1563 * - A new #connection_t on success.
1564 * - NULL on failure.
1565 */
1567 connection_funcs_t const *funcs,
1568 connection_conf_t const *conf,
1569 char const *log_prefix,
1570 void const *uctx)
1571{
1572 size_t i;
1573 connection_t *conn;
1574 uint64_t id;
1575
1576 fr_assert_msg(el, "No event list provided");
1577
1578 MEM(conn = talloc(ctx, connection_t));
1579 talloc_set_destructor(conn, _connection_free);
1580
1582
1583 *conn = (connection_t){
1584 .pub = {
1585 .id = id,
1586 .state = CONNECTION_STATE_HALTED,
1587 .el = el
1588 },
1589 .reconnection_delay = conf->reconnection_delay,
1590 .connection_timeout = conf->connection_timeout,
1591 .init = funcs->init,
1592 .open = funcs->open,
1593 .close = funcs->close,
1594 .failed = funcs->failed,
1595 .shutdown = funcs->shutdown,
1596 .is_closed = true, /* Starts closed */
1597 .triggers = conf->triggers,
1598 .trigger_args = conf->trigger_args,
1599 .trigger_cs = conf->trigger_cs,
1600 .pub.name = talloc_asprintf(conn, "%s - [%" PRIu64 "]", log_prefix, id)
1601 };
1602 memcpy(&conn->uctx, &uctx, sizeof(conn->uctx));
1603
1604 for (i = 0; i < NUM_ELEMENTS(conn->watch_pre); i++) {
1606 }
1607 for (i = 0; i < NUM_ELEMENTS(conn->watch_post); i++) {
1609 }
1611
1612 /*
1613 * Pre-allocate a on_halt watcher for deferred signal processing
1614 *
1615 * Note that we do NOT set "oneshot". This lets the watcher remain valid after the oneshot
1616 * watcher fires. Otherwise the connection is freed out from under the watcher.
1617 */
1620 connection_watch_disable(conn->on_halted); /* Start disabled */
1621
1622 return conn;
1623}
#define L(_str)
Helper for initialising arrays of string literals.
Definition build.h:228
#define FALL_THROUGH
clang 10 doesn't recognised the FALL-THROUGH comment anymore
Definition build.h:343
#define CC_NO_UBSAN(_sanitize)
Definition build.h:449
#define UNUSED
Definition build.h:336
#define NUM_ELEMENTS(_t)
Definition build.h:358
A section grouping multiple CONF_PAIR.
Definition cf_priv.h:101
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:213
connection_state_t(* connection_failed_t)(void *h, connection_state_t state, void *uctx)
Notification that a connection attempt has failed.
Definition connection.h:176
uint64_t _CONST timed_out
How many times has this connection timed out when connecting.
Definition connection.h:80
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:190
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:125
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
uint64_t _CONST reconnected
How many times we've attempted to establish or re-establish this connection.
Definition connection.h:78
void *_CONST h
Connection handle.
Definition connection.h:75
connection_reason_t
Definition connection.h:84
@ CONNECTION_EXPIRED
Connection is being reconnected because it's at the end of its life.
Definition connection.h:86
@ CONNECTION_FAILED
Connection is being reconnected because it failed.
Definition connection.h:85
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:139
connection_state_t _CONST state
Current connection state.
Definition connection.h:72
connection_init_t init
Definition connection.h:196
connection_shutdown_t shutdown
Definition connection.h:198
connection_failed_t failed
Definition connection.h:199
connection_open_t open
Definition connection.h:197
uint64_t _CONST id
Unique identifier for the connection.
Definition connection.h:74
connection_close_t close
Definition connection.h:200
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:158
Holds a complete set of functions for a connection.
Definition connection.h:195
Public fields for the connection.
Definition connection.h:69
#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:212
#define MEM(x)
Definition debug.h:46
#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:228
#define DEBUG4(_fmt,...)
Definition log.h:267
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: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:368
CONF_SECTION * trigger_cs
Where to search locally for triggers.
Definition connection.c:125
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:555
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:430
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:624
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:153
static fr_table_num_ordered_t const connection_dsignals[]
Definition connection.c:168
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:238
void connection_watch_disable(connection_watch_entry_t *entry)
Disable a watcher.
Definition connection.c:565
fr_pair_list_t * trigger_args
Arguments to pass to the trigger functions.
Definition connection.c:126
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:483
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:713
bool connection_watch_is_enabled(connection_watch_entry_t *entry)
Return the state of a watch entry.
Definition connection.c:601
static atomic_uint_fast64_t connection_counter
Definition connection.c:73
static size_t connection_dsignals_len
Definition connection.c:177
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:794
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:635
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:417
void connection_signals_resume(connection_t *conn)
Resume processing of deferred signals.
Definition connection.c:330
#define HANDLER_BEGIN(_conn, _func)
Called when we enter a handler.
Definition connection.c:348
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:493
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:612
bool triggers
Do we run triggers.
Definition connection.c:127
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: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:404
static void connection_deferred_signal_process(connection_t *conn)
Process any deferred signals.
Definition connection.c:249
connection_dsignal_t signal
Signal that was deferred.
Definition connection.c:184
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:588
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:654
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:158
@ CONNECTION_DSIGNAL_RECONNECT_FAILED
Reconnect a failed connection.
Definition connection.c:161
@ CONNECTION_DSIGNAL_HALT
Close a connection (ungracefully).
Definition connection.c:164
@ CONNECTION_DSIGNAL_INIT
Restart a halted connection.
Definition connection.c:159
@ CONNECTION_DSIGNAL_FREE
Free a connection (no further dsignals processed).
Definition connection.c:165
@ CONNECTION_DSIGNAL_SHUTDOWN
Close a connection (gracefully).
Definition connection.c:163
@ CONNECTION_DSIGNAL_CONNECTED
Signal that a connection is connected.
Definition connection.c:160
@ CONNECTION_DSIGNAL_RECONNECT_EXPIRED
Reconnect an expired connection (gracefully).
Definition connection.c:162
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:357
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:135
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:949
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:211
fr_dlist_t entry
Entry in the signals list.
Definition connection.c:183
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:521
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:543
static void connection_state_enter_connected(connection_t *conn)
Enter the connected state.
Definition connection.c:984
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:466
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:145
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:576
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:723
void connection_signals_pause(connection_t *conn)
Pause processing of deferred signals.
Definition connection.c:321
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:924
Holds a signal from a handler until it's safe to process it.
Definition connection.c:182
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 talloc_asprintf
Definition talloc.h:144
#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:110
#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:366