Coverage Report

Created: 2026-05-11 06:44

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/lib/server/connection.c
Line
Count
Source
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
0
#define LOG_PREFIX conn->pub.name
26
27
typedef struct connection_s connection_t;
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
46
fr_table_num_ordered_t const connection_states[] = {
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
};
56
size_t connection_states_len = NUM_ELEMENTS(connection_states);
57
58
/** Map connection states to trigger names
59
 *
60
 */
61
static fr_table_num_indexed_t const connection_trigger_names[] = {
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
};
71
static size_t connection_trigger_names_len = NUM_ELEMENTS(connection_trigger_names);
72
73
static atomic_uint_fast64_t connection_counter = ATOMIC_VAR_INIT(1);
74
75
/** An entry in a watch function list
76
 *
77
 */
78
typedef struct connection_watch_entry_s {
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.
85
} connection_watch_entry_t;
86
87
struct connection_s {
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
0
#define CONN_TRIGGER(_state) do { \
131
0
  if (conn->triggers) trigger(unlang_interpret_get_thread_default(), \
132
0
    conn->trigger_cs, NULL, fr_table_str_by_value(connection_trigger_names, _state, "<INVALID>"), true, conn->trigger_args); \
133
0
} while (0)
134
135
0
#define STATE_TRANSITION(_new) \
136
0
do { \
137
0
  DEBUG2("Connection changed state %s -> %s", \
138
0
         fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"), \
139
0
         fr_table_str_by_value(connection_states, _new, "<INVALID>")); \
140
0
  conn->pub.prev = conn->pub.state; \
141
0
  conn->pub.state = _new; \
142
0
  CONN_TRIGGER(_new); \
143
0
} while (0)
144
145
0
#define BAD_STATE_TRANSITION(_new) \
146
0
do { \
147
0
  if (!fr_cond_assert_msg(0, "Connection %" PRIu64 " invalid transition %s -> %s", \
148
0
        conn->pub.id, \
149
0
        fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"), \
150
0
        fr_table_str_by_value(connection_states, _new, "<INVALID>"))) return; \
151
0
} while (0)
152
153
0
#define DEFER_SIGNALS(_conn)  ((_conn)->in_handler || (_conn)->signals_pause)
154
155
/** Deferred signals
156
 *
157
 */
158
typedef 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).
166
} connection_dsignal_t;
167
168
static fr_table_num_ordered_t const connection_dsignals[] = {
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
};
177
static size_t connection_dsignals_len = NUM_ELEMENTS(connection_dsignals);
178
179
/** Holds a signal from a handler until it's safe to process it
180
 *
181
 */
182
typedef struct {
183
  fr_dlist_t    entry;    //!< Entry in the signals list.
184
  connection_dsignal_t  signal;   //!< Signal that was deferred.
185
} connection_dsignal_entry_t;
186
187
/*
188
 *  State transition functions
189
 */
190
static void connection_state_enter_closed(connection_t *conn);
191
static void connection_state_enter_failed(connection_t *conn);
192
static void connection_state_enter_timeout(connection_t *conn);
193
static void connection_state_enter_connected(connection_t *conn);
194
static void connection_state_enter_shutdown(connection_t *conn);
195
static void connection_state_enter_connecting(connection_t *conn);
196
static void connection_state_enter_halted(connection_t *conn);
197
static void connection_state_enter_init(connection_t *conn);
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
 */
211
static inline void connection_deferred_signal_add(connection_t *conn, connection_dsignal_t signal)
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
 */
238
static void _deferred_signal_connection_on_halted(UNUSED connection_t *conn,
239
              UNUSED connection_state_t prev,
240
              UNUSED connection_state_t state, void *uctx)
241
0
{
242
0
  bool *freed = uctx;
243
0
  *freed = true;
244
0
}
245
246
/** Process any deferred signals
247
 *
248
 */
249
static void connection_deferred_signal_process(connection_t *conn)
250
0
{
251
0
  connection_dsignal_entry_t  *dsignal;
252
0
  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
0
  if (conn->processing_signals) return;
259
260
  /*
261
   *  Get notified if the connection gets freed
262
   *  out from under us...
263
   */
264
0
  connection_watch_enable_set_uctx(conn->on_halted, &freed);
265
0
  conn->processing_signals = true;
266
267
0
  while ((dsignal = fr_dlist_head(&conn->deferred_signals))) {
268
0
    connection_dsignal_t signal;
269
0
    fr_dlist_remove(&conn->deferred_signals, dsignal);
270
0
    signal = dsignal->signal;
271
0
    talloc_free(dsignal);
272
273
0
    DEBUG4("Processing deferred signal - %s",
274
0
           fr_table_str_by_value(connection_dsignals, signal, "<INVALID>"));
275
276
0
    switch (signal) {
277
0
    case CONNECTION_DSIGNAL_INIT:
278
0
      connection_signal_init(conn);
279
0
      break;
280
281
0
    case CONNECTION_DSIGNAL_CONNECTED:
282
0
      connection_signal_connected(conn);
283
0
      break;
284
285
0
    case CONNECTION_DSIGNAL_RECONNECT_FAILED:   /* Reconnect - Failed */
286
0
      connection_signal_reconnect(conn, CONNECTION_FAILED);
287
0
      break;
288
289
0
    case CONNECTION_DSIGNAL_RECONNECT_EXPIRED:    /* Reconnect - Expired */
290
0
      connection_signal_reconnect(conn, CONNECTION_EXPIRED);
291
0
      break;
292
293
0
    case CONNECTION_DSIGNAL_SHUTDOWN:
294
0
      connection_signal_shutdown(conn);
295
0
      break;
296
297
0
    case CONNECTION_DSIGNAL_HALT:
298
0
      connection_signal_halt(conn);
299
0
      break;
300
301
0
    case CONNECTION_DSIGNAL_FREE:       /* Freed */
302
0
      talloc_free(conn);
303
0
      return;
304
0
    }
305
306
    /*
307
     *  One of the signal handlers freed the connection,
308
     *  reset the processing signals and return.
309
     */
310
0
    if (freed) break;
311
0
  }
312
313
0
  conn->processing_signals = false;
314
0
  connection_watch_disable(conn->on_halted);
315
0
}
316
317
/** Pause processing of deferred signals
318
 *
319
 * @param[in] conn to pause signal processing for.
320
 */
321
void connection_signals_pause(connection_t *conn)
322
0
{
323
0
  conn->signals_pause++;
324
0
}
325
326
/** Resume processing of deferred signals
327
 *
328
 * @param[in] conn to resume signal processing for.
329
 */
330
void connection_signals_resume(connection_t *conn)
331
0
{
332
0
  if (conn->signals_pause > 0) conn->signals_pause--;
333
0
  if (conn->signals_pause > 0) return;
334
335
  /*
336
   *  If we're not in a handler process the
337
   *  deferred signals now.
338
   */
339
0
  if (!conn->in_handler) {
340
0
    connection_deferred_signal_process(conn);
341
0
    return;
342
0
  }
343
0
}
344
345
/** Called when we enter a handler
346
 *
347
 */
348
0
#define HANDLER_BEGIN(_conn, _func) \
349
0
void *_prev_handler = (_conn)->in_handler; \
350
0
do { \
351
0
  (_conn)->in_handler = (void *)(_func); \
352
0
} while (0)
353
354
/** Called when we exit a handler
355
 *
356
 */
357
0
#define HANDLER_END(_conn) \
358
0
do { \
359
0
  (_conn)->in_handler = _prev_handler; \
360
0
  if (!(_conn)->signals_pause && (!(_conn)->in_handler)) connection_deferred_signal_process(_conn); \
361
0
} while(0)
362
363
364
/** Call a list of watch functions associated with a state
365
 *
366
 */
367
CC_NO_UBSAN(function) /* UBSAN: false positive - Public/private version of connection_t trips -fsanitize=function */
368
static inline void connection_watch_call(connection_t *conn, fr_dlist_head_t *list)
369
0
{
370
  /*
371
   *  Nested watcher calls are not allowed
372
   *  and shouldn't be possible because of
373
   *  deferred signal processing.
374
   */
375
0
  fr_assert(conn->next_watcher == NULL);
376
377
0
  while ((conn->next_watcher = fr_dlist_next(list, conn->next_watcher))) {
378
0
    connection_watch_entry_t  *entry = conn->next_watcher;
379
0
    bool        oneshot = entry->oneshot; /* Watcher could be freed, so store now */
380
381
0
    if (!entry->enabled) continue;
382
0
    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
0
    entry->func(conn, conn->pub.prev, conn->pub.state, entry->uctx);
395
396
0
    if (oneshot) talloc_free(entry);
397
0
  }
398
0
  conn->next_watcher = NULL;
399
0
}
400
401
/** Call the pre handler watch functions
402
 *
403
 */
404
0
#define WATCH_PRE(_conn) \
405
0
do { \
406
0
  if (fr_dlist_empty(&(_conn)->watch_pre[(_conn)->pub.state])) break; \
407
0
  { \
408
0
    HANDLER_BEGIN(conn, &(_conn)->watch_pre[(_conn)->pub.state]); \
409
0
    connection_watch_call((_conn), &(_conn)->watch_pre[(_conn)->pub.state]); \
410
0
    HANDLER_END(conn); \
411
0
  } \
412
0
} while(0)
413
414
/** Call the post handler watch functions
415
 *
416
 */
417
0
#define WATCH_POST(_conn) \
418
0
do { \
419
0
  if (fr_dlist_empty(&(_conn)->watch_post[(_conn)->pub.state])) break; \
420
0
  { \
421
0
    HANDLER_BEGIN(conn, &(_conn)->watch_post[(_conn)->pub.state]); \
422
0
    connection_watch_call((_conn), &(_conn)->watch_post[(_conn)->pub.state]); \
423
0
    HANDLER_END(conn); \
424
0
  } \
425
0
} while(0)
426
427
/** Remove a watch function from a pre/post[state] list
428
 *
429
 */
430
static int connection_del_watch(connection_t *conn, fr_dlist_head_t *state_lists,
431
        connection_state_t state, connection_watch_t watch)
432
0
{
433
0
  connection_watch_entry_t  *entry = NULL;
434
0
  fr_dlist_head_t           *list = &state_lists[state];
435
436
0
  while ((entry = fr_dlist_next(list, entry))) {
437
0
    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
0
      if (conn->next_watcher == entry) {
444
0
        conn->next_watcher = fr_dlist_remove(list, entry);
445
0
      } else {
446
0
        fr_dlist_remove(list, entry);
447
0
      }
448
0
      talloc_free(entry);
449
0
      return 0;
450
0
    }
451
0
  }
452
453
0
  return -1;
454
0
}
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
 */
466
int connection_del_watch_pre(connection_t *conn, connection_state_t state, connection_watch_t watch)
467
0
{
468
0
  if (state >= CONNECTION_STATE_MAX) return -2;
469
470
0
  return connection_del_watch(conn, conn->watch_pre, state, watch);
471
0
}
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
 */
483
int connection_del_watch_post(connection_t *conn, connection_state_t state, connection_watch_t watch)
484
0
{
485
0
  if (state >= CONNECTION_STATE_MAX) return -2;
486
487
0
  return connection_del_watch(conn, conn->watch_post, state, watch);
488
0
}
489
490
/** Add a watch entry to the pre/post[state] list
491
 *
492
 */
493
static connection_watch_entry_t *connection_add_watch(connection_t *conn, fr_dlist_head_t *list,
494
               connection_watch_t watch, bool oneshot, void const *uctx)
495
{
496
  connection_watch_entry_t *entry;
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
 */
521
connection_watch_entry_t *connection_add_watch_pre(connection_t *conn, connection_state_t state,
522
               connection_watch_t watch, bool oneshot, void const *uctx)
523
0
{
524
0
  if (state >= CONNECTION_STATE_MAX) return NULL;
525
526
0
  return connection_add_watch(conn, &conn->watch_pre[state], watch, oneshot, uctx);
527
0
}
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
 */
543
connection_watch_entry_t *connection_add_watch_post(connection_t *conn, connection_state_t state,
544
                connection_watch_t watch, bool oneshot, void const *uctx)
545
0
{
546
0
  if (state >= CONNECTION_STATE_MAX) return NULL;
547
548
0
  return connection_add_watch(conn, &conn->watch_post[state], watch, oneshot, uctx);
549
0
}
550
551
/** Enable a watcher
552
 *
553
 * @param[in] entry to enabled.
554
 */
555
void connection_watch_enable(connection_watch_entry_t *entry)
556
0
{
557
0
  (void)talloc_get_type_abort(entry, connection_watch_entry_t);
558
0
  entry->enabled = true;
559
0
}
560
561
/** Disable a watcher
562
 *
563
 * @param[in] entry to disable.
564
 */
565
void connection_watch_disable(connection_watch_entry_t *entry)
566
0
{
567
0
  (void)talloc_get_type_abort(entry, connection_watch_entry_t);
568
0
  entry->enabled = false;
569
0
}
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
 */
576
void connection_watch_enable_set_uctx(connection_watch_entry_t *entry, void const *uctx)
577
0
{
578
0
  (void)talloc_get_type_abort(entry, connection_watch_entry_t);
579
0
  entry->enabled = true;
580
0
  memcpy(&entry->uctx, &uctx, sizeof(entry->uctx));
581
0
}
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
 */
588
void connection_watch_set_uctx(connection_watch_entry_t *entry, void const *uctx)
589
0
{
590
0
  (void)talloc_get_type_abort(entry, connection_watch_entry_t);
591
0
  memcpy(&entry->uctx, &uctx, sizeof(entry->uctx));
592
0
}
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
 */
601
bool connection_watch_is_enabled(connection_watch_entry_t *entry)
602
0
{
603
0
  (void)talloc_get_type_abort(entry, connection_watch_entry_t);
604
0
  return entry->enabled;
605
0
}
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
 */
612
uint64_t connection_get_num_reconnected(connection_t const *conn)
613
0
{
614
0
  if (conn->pub.reconnected == 0) return 0; /* Has never been initialised */
615
616
0
  return conn->pub.reconnected - 1;   /* We don't count the first connection attempt */
617
0
}
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
 */
624
uint64_t connection_get_num_timed_out(connection_t const *conn)
625
0
{
626
0
  return conn->pub.timed_out;
627
0
}
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
 */
635
static void _reconnect_delay_done(UNUSED fr_timer_list_t *tl, UNUSED fr_time_t now, void *uctx)
636
{
637
  connection_t *conn = talloc_get_type_abort(uctx, connection_t);
638
639
  switch (conn->pub.state) {
640
  case CONNECTION_STATE_FAILED:
641
  case CONNECTION_STATE_CLOSED:
642
    connection_state_enter_init(conn);
643
    break;
644
645
  default:
646
    BAD_STATE_TRANSITION(CONNECTION_STATE_INIT);
647
    break;
648
  }
649
}
650
651
/** Close the connection, then wait for another state change
652
 *
653
 */
654
static void connection_state_enter_closed(connection_t *conn)
655
0
{
656
0
  switch (conn->pub.state) {
657
0
  case CONNECTION_STATE_CONNECTING:
658
0
  case CONNECTION_STATE_CONNECTED:
659
0
  case CONNECTION_STATE_SHUTDOWN:
660
0
  case CONNECTION_STATE_TIMEOUT:
661
0
  case CONNECTION_STATE_FAILED:
662
0
    break;
663
664
0
  default:
665
0
    BAD_STATE_TRANSITION(CONNECTION_STATE_CLOSED);
666
0
    return;
667
0
  }
668
669
0
  STATE_TRANSITION(CONNECTION_STATE_CLOSED);
670
671
0
  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
0
  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
0
  if (conn->close && !conn->is_closed) {
687
0
    HANDLER_BEGIN(conn, conn->close);
688
0
    DEBUG4("Calling close(el=%p, h=%p, uctx=%p)", conn->pub.el, conn->pub.h, conn->uctx);
689
0
    conn->close(conn->pub.el, conn->pub.h, conn->uctx);
690
0
    conn->is_closed = true;   /* Ensure close doesn't get called twice if the connection is freed */
691
0
    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
0
    if (conn->pub.state != CONNECTION_STATE_CLOSED) return;
699
0
  } else {
700
0
    conn->is_closed = true;
701
0
  }
702
0
  WATCH_POST(conn);
703
0
}
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
 */
713
static void _connection_timeout(UNUSED fr_timer_list_t *tl, UNUSED fr_time_t now, void *uctx)
714
0
{
715
0
  connection_t *conn = talloc_get_type_abort(uctx, connection_t);
716
717
0
  connection_state_enter_timeout(conn);
718
0
}
719
720
/** Gracefully shutdown the handle
721
 *
722
 */
723
static void connection_state_enter_shutdown(connection_t *conn)
724
0
{
725
0
  connection_state_t ret = CONNECTION_STATE_SHUTDOWN;
726
727
0
  switch (conn->pub.state) {
728
0
  case CONNECTION_STATE_CONNECTED:
729
0
    break;
730
731
0
  default:
732
0
    BAD_STATE_TRANSITION(CONNECTION_STATE_SHUTDOWN);
733
0
    return;
734
0
  }
735
736
0
  STATE_TRANSITION(CONNECTION_STATE_SHUTDOWN);
737
738
0
  WATCH_PRE(conn);
739
0
  if (conn->shutdown) {
740
0
    HANDLER_BEGIN(conn, conn->shutdown);
741
0
    DEBUG4("Calling shutdown(el=%p, h=%p, uctx=%p)", conn->pub.el, conn->pub.h, conn->uctx);
742
0
    ret = conn->shutdown(conn->pub.el, conn->pub.h, conn->uctx);
743
0
    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
0
    if (conn->pub.state != CONNECTION_STATE_SHUTDOWN) return;
751
0
  }
752
0
  switch (ret) {
753
0
  case CONNECTION_STATE_SHUTDOWN:
754
0
    break;
755
756
0
  default:
757
0
    connection_state_enter_failed(conn);
758
0
    return;
759
0
  }
760
0
  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
   */
771
0
  if (fr_time_delta_ispos(conn->connection_timeout)) {
772
0
    if (fr_timer_in(conn, conn->pub.el->tl, &conn->ev,
773
0
        conn->connection_timeout, false, _connection_timeout, conn) < 0) {
774
      /*
775
       *  Can happen when the event loop is exiting
776
       */
777
0
      PERROR("Failed setting connection_timeout timer, closing connection");
778
0
      connection_state_enter_failed(conn);
779
0
    }
780
0
  }
781
0
}
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
 */
794
static void connection_state_enter_failed(connection_t *conn)
795
0
{
796
0
  connection_state_t prev;
797
0
  connection_state_t ret = CONNECTION_STATE_INIT;
798
799
0
  fr_assert(conn->pub.state != CONNECTION_STATE_FAILED);
800
801
  /*
802
   *  Explicit error occurred, delete the connection timer
803
   */
804
0
  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
0
  prev = conn->pub.state;
811
812
  /*
813
   *  Now transition to failed
814
   */
815
0
  STATE_TRANSITION(CONNECTION_STATE_FAILED);
816
817
  /*
818
   *  If there's a failed callback, give it the
819
   *  opportunity to suspend/destroy the
820
   *  connection.
821
   */
822
0
  WATCH_PRE(conn);
823
0
  if (conn->failed) {
824
0
    HANDLER_BEGIN(conn, conn->failed);
825
0
    DEBUG4("Calling failed(h=%p, state=%s, uctx=%p)", conn->pub.h,
826
0
           fr_table_str_by_value(connection_states, prev, "<INVALID>"), conn->uctx);
827
0
    ret = conn->failed(conn->pub.h, prev, conn->uctx);
828
0
    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
0
    if (conn->pub.state != CONNECTION_STATE_FAILED) return;
836
0
  }
837
0
  WATCH_POST(conn);
838
839
  /*
840
   *  Enter the closed state if we failed during
841
   *  connecting, or when we were connected.
842
   */
843
0
  switch (prev) {
844
0
  case CONNECTION_STATE_CONNECTED:
845
0
  case CONNECTION_STATE_CONNECTING:
846
0
  case CONNECTION_STATE_TIMEOUT:    /* Timeout means the connection progress past init */
847
0
  case CONNECTION_STATE_SHUTDOWN:   /* Shutdown means the connection failed whilst shutting down */
848
0
    connection_state_enter_closed(conn);
849
0
    break;
850
851
0
  default:
852
0
    break;
853
0
  }
854
855
0
  if (conn->failed) {
856
0
    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
     */
864
0
    case CONNECTION_STATE_INIT:
865
0
      break;
866
867
    /*
868
     *  The callback signalled it wants the
869
     *  connection to stop.
870
     */
871
0
    case CONNECTION_STATE_HALTED:
872
0
    default:
873
0
      connection_state_enter_halted(conn);
874
0
      return;
875
0
    }
876
0
  }
877
878
  /*
879
   *  What previous state we were in
880
   *  determines if we need to apply the
881
   *  reconnect timeout.
882
   */
883
0
  switch (prev) {
884
0
  case CONNECTION_STATE_INIT:       /* Failed during initialisation */
885
0
  case CONNECTION_STATE_CONNECTED:      /* Failed after connecting */
886
0
  case CONNECTION_STATE_CONNECTING:     /* Failed during connecting */
887
0
  case CONNECTION_STATE_SHUTDOWN:     /* Failed during shutdown */
888
0
    if (fr_time_delta_ispos(conn->reconnection_delay)) {
889
0
      DEBUG2("Delaying reconnection by %pVs", fr_box_time_delta(conn->reconnection_delay));
890
0
      if (fr_timer_in(conn, conn->pub.el->tl, &conn->ev,
891
0
          conn->reconnection_delay, false, _reconnect_delay_done, conn) < 0) {
892
        /*
893
         *  Can happen when the event loop is exiting
894
         */
895
0
        PERROR("Failed inserting reconnection_delay timer event, halting connection");
896
0
        connection_state_enter_halted(conn);
897
0
      }
898
0
      return;
899
0
    }
900
901
    /*
902
     *  If there's no reconnection
903
     *  delay, then don't automatically
904
     *  reconnect, and wait to be
905
     *  signalled.
906
     */
907
0
    connection_state_enter_halted(conn);
908
0
    break;
909
910
0
  case CONNECTION_STATE_TIMEOUT:      /* Failed during connecting due to timeout */
911
0
    connection_state_enter_init(conn);
912
0
    break;
913
914
0
  default:
915
0
    fr_assert(0);
916
0
  }
917
0
}
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
 */
924
static void connection_state_enter_timeout(connection_t *conn)
925
0
{
926
0
  switch (conn->pub.state) {
927
0
  case CONNECTION_STATE_CONNECTING:
928
0
  case CONNECTION_STATE_SHUTDOWN:
929
0
    break;
930
931
0
  default:
932
0
    BAD_STATE_TRANSITION(CONNECTION_STATE_TIMEOUT);
933
0
    break;
934
0
  }
935
936
0
  ERROR("Connection failed - timed out after %pVs", fr_box_time_delta(conn->connection_timeout));
937
938
0
  STATE_TRANSITION(CONNECTION_STATE_TIMEOUT);
939
940
0
  conn->pub.timed_out++;
941
942
0
  connection_state_enter_failed(conn);
943
0
}
944
945
/** Enter the halted state
946
 *
947
 * Here we wait, until signalled by connection_signal_reconnect.
948
 */
949
static void connection_state_enter_halted(connection_t *conn)
950
0
{
951
0
  fr_assert(conn->is_closed);
952
953
0
  switch (conn->pub.state) {
954
0
  case CONNECTION_STATE_INIT:
955
0
  case CONNECTION_STATE_FAILED: /* Init failure */
956
0
  case CONNECTION_STATE_CLOSED:
957
0
    break;
958
959
0
  default:
960
0
    BAD_STATE_TRANSITION(CONNECTION_STATE_HALTED);
961
0
    break;
962
0
  }
963
964
0
  FR_TIMER_DISARM(conn->ev);
965
966
0
  STATE_TRANSITION(CONNECTION_STATE_HALTED);
967
0
  WATCH_PRE(conn);
968
0
  WATCH_POST(conn);
969
0
}
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
 */
984
static void connection_state_enter_connected(connection_t *conn)
985
0
{
986
0
  int ret;
987
988
0
  fr_assert(conn->pub.state == CONNECTION_STATE_CONNECTING || conn->pub.state == CONNECTION_STATE_INIT);
989
990
0
  STATE_TRANSITION(CONNECTION_STATE_CONNECTED);
991
992
0
  FR_TIMER_DISARM(conn->ev);
993
0
  WATCH_PRE(conn);
994
0
  if (conn->open) {
995
0
    HANDLER_BEGIN(conn, conn->open);
996
0
    DEBUG4("Calling open(el=%p, h=%p, uctx=%p)", conn->pub.el, conn->pub.h, conn->uctx);
997
0
    ret = conn->open(conn->pub.el, conn->pub.h, conn->uctx);
998
0
    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
0
    if (conn->pub.state != CONNECTION_STATE_CONNECTED) return;
1006
0
  } else {
1007
0
    ret = CONNECTION_STATE_CONNECTED;
1008
0
  }
1009
1010
0
  switch (ret) {
1011
  /*
1012
   *  Callback agrees everything is connected
1013
   */
1014
0
  case CONNECTION_STATE_CONNECTED:
1015
0
    DEBUG2("Connection established");
1016
0
    WATCH_POST(conn); /* Only call if we successfully connected */
1017
0
    return;
1018
1019
  /*
1020
   *  Open callback failed
1021
   */
1022
0
  case CONNECTION_STATE_FAILED:
1023
0
  default:
1024
0
    PERROR("Connection failed");
1025
0
    connection_state_enter_failed(conn);
1026
0
    return;
1027
0
  }
1028
0
}
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
 */
1037
static void connection_state_enter_connecting(connection_t *conn)
1038
0
{
1039
0
  switch (conn->pub.state) {
1040
0
  case CONNECTION_STATE_INIT:
1041
0
    break;
1042
1043
0
  default:
1044
0
    BAD_STATE_TRANSITION(CONNECTION_STATE_CONNECTING);
1045
0
    return;
1046
0
  }
1047
1048
0
  STATE_TRANSITION(CONNECTION_STATE_CONNECTING);
1049
1050
0
  WATCH_PRE(conn);
1051
0
  WATCH_POST(conn);
1052
1053
  /*
1054
   *  If there's a connection timeout,
1055
   *  set, then add the timer.
1056
   */
1057
0
  if (fr_time_delta_ispos(conn->connection_timeout)) {
1058
0
    if (fr_timer_in(conn, conn->pub.el->tl, &conn->ev,
1059
0
        conn->connection_timeout, false, _connection_timeout, conn) < 0) {
1060
0
      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
       */
1072
0
      connection_state_enter_failed(conn);
1073
0
    }
1074
0
  }
1075
0
}
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
 */
1084
static void connection_state_enter_init(connection_t *conn)
1085
0
{
1086
0
  connection_state_t  ret;
1087
1088
0
  switch (conn->pub.state) {
1089
0
  case CONNECTION_STATE_HALTED:
1090
0
  case CONNECTION_STATE_CLOSED:
1091
0
  case CONNECTION_STATE_FAILED:
1092
0
    break;
1093
1094
0
  default:
1095
0
    BAD_STATE_TRANSITION(CONNECTION_STATE_INIT);
1096
0
    return;
1097
0
  }
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
0
  conn->pub.reconnected++;
1108
1109
0
  STATE_TRANSITION(CONNECTION_STATE_INIT);
1110
1111
  /*
1112
   *  If we have an init callback, call it.
1113
   */
1114
0
  WATCH_PRE(conn);
1115
0
  if (conn->init) {
1116
0
    HANDLER_BEGIN(conn, conn->init);
1117
0
    DEBUG4("Calling init(h_out=%p, conn=%p, uctx=%p)", &conn->pub.h, conn, conn->uctx);
1118
0
    ret = conn->init(&conn->pub.h, conn, conn->uctx);
1119
0
    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
0
    if (conn->pub.state != CONNECTION_STATE_INIT) return;
1127
0
  } else {
1128
0
    ret = CONNECTION_STATE_CONNECTING;
1129
0
  }
1130
1131
0
  switch (ret) {
1132
0
  case CONNECTION_STATE_CONNECTING:
1133
0
    conn->is_closed = false;  /* We now have a handle */
1134
0
    WATCH_POST(conn);   /* Only call if we successfully initialised the handle */
1135
0
    connection_state_enter_connecting(conn);
1136
0
    return;
1137
1138
0
  case CONNECTION_STATE_CONNECTED:
1139
0
    conn->is_closed = false;  /* We now have a handle */
1140
0
    WATCH_POST(conn);   /* Only call if we successfully initialised the handle */
1141
0
    connection_state_enter_connected(conn);
1142
0
    return;
1143
1144
  /*
1145
   *  Initialisation callback failed
1146
   */
1147
0
  case CONNECTION_STATE_FAILED:
1148
0
  default:
1149
0
    PERROR("Connection initialisation failed");
1150
0
    connection_state_enter_failed(conn);
1151
0
    break;
1152
0
  }
1153
0
}
1154
1155
/** Asynchronously signal a halted connection to start
1156
 *
1157
 */
1158
void connection_signal_init(connection_t *conn)
1159
0
{
1160
0
  DEBUG2("Signalled to start from %s state",
1161
0
         fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"));
1162
1163
0
  if (DEFER_SIGNALS(conn)) {
1164
0
    connection_deferred_signal_add(conn, CONNECTION_DSIGNAL_INIT);
1165
0
    return;
1166
0
  }
1167
1168
0
  switch (conn->pub.state) {
1169
0
  case CONNECTION_STATE_HALTED:
1170
0
    connection_state_enter_init(conn);
1171
0
    break;
1172
1173
0
  default:
1174
0
    break;
1175
0
  }
1176
0
}
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
 */
1188
void connection_signal_connected(connection_t *conn)
1189
0
{
1190
0
  fr_assert(!conn->open);  /* Use one or the other not both! */
1191
1192
0
  DEBUG2("Signalled connected from %s state",
1193
0
         fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"));
1194
1195
0
  if (DEFER_SIGNALS(conn)) {
1196
0
    connection_deferred_signal_add(conn, CONNECTION_DSIGNAL_CONNECTED);
1197
0
    return;
1198
0
  }
1199
1200
0
  switch (conn->pub.state) {
1201
0
  case CONNECTION_STATE_CONNECTING:
1202
0
    connection_state_enter_connected(conn);
1203
0
    break;
1204
1205
0
  default:
1206
0
    break;
1207
0
  }
1208
0
}
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
 */
1218
void connection_signal_reconnect(connection_t *conn, connection_reason_t reason)
1219
0
{
1220
0
  DEBUG2("Signalled to reconnect from %s state",
1221
0
         fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"));
1222
1223
0
  if (DEFER_SIGNALS(conn)) {
1224
0
    if ((reason == CONNECTION_EXPIRED) && conn->shutdown) {
1225
0
      connection_deferred_signal_add(conn, CONNECTION_DSIGNAL_RECONNECT_EXPIRED);
1226
0
      return;
1227
0
    }
1228
1229
0
    connection_deferred_signal_add(conn, CONNECTION_DSIGNAL_RECONNECT_FAILED);
1230
0
    return;
1231
0
  }
1232
1233
0
  switch (conn->pub.state) {
1234
0
  case CONNECTION_STATE_CLOSED:     /* Don't circumvent reconnection_delay */
1235
0
  case CONNECTION_STATE_INIT:       /* Already initialising */
1236
0
    break;
1237
1238
0
  case CONNECTION_STATE_HALTED:
1239
0
    connection_signal_init(conn);
1240
0
    break;
1241
1242
0
  case CONNECTION_STATE_SHUTDOWN:
1243
0
    if (reason == CONNECTION_EXPIRED) break; /* Already shutting down */
1244
0
    connection_state_enter_failed(conn);
1245
0
    break;
1246
1247
0
  case CONNECTION_STATE_CONNECTED:
1248
0
    if (reason == CONNECTION_EXPIRED) {
1249
0
      if (conn->shutdown) {
1250
0
        connection_state_enter_shutdown(conn);
1251
0
        break;
1252
0
      }
1253
0
      connection_state_enter_closed(conn);
1254
0
      break;
1255
0
    }
1256
0
    FALL_THROUGH;
1257
1258
0
  case CONNECTION_STATE_CONNECTING:
1259
0
  case CONNECTION_STATE_TIMEOUT:
1260
0
    connection_state_enter_failed(conn);
1261
0
    break;
1262
1263
0
  case CONNECTION_STATE_FAILED: /* already entered failed, don't re-enter */
1264
0
    break;
1265
1266
0
  case CONNECTION_STATE_MAX:
1267
0
    fr_assert(0);
1268
0
    return;
1269
0
  }
1270
0
}
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
 */
1281
void connection_signal_shutdown(connection_t *conn)
1282
0
{
1283
0
  DEBUG2("Signalled to shutdown from %s state",
1284
0
         fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"));
1285
1286
0
  if (DEFER_SIGNALS(conn)) {
1287
0
    connection_deferred_signal_add(conn, CONNECTION_DSIGNAL_SHUTDOWN);
1288
0
    return;
1289
0
  }
1290
1291
0
  switch (conn->pub.state) {
1292
0
  case CONNECTION_STATE_HALTED:
1293
0
  case CONNECTION_STATE_SHUTDOWN:
1294
0
    break;
1295
1296
0
  case CONNECTION_STATE_INIT:
1297
0
    connection_state_enter_halted(conn);
1298
0
    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
   */
1307
0
  case CONNECTION_STATE_CONNECTED:
1308
0
    if (conn->shutdown) {
1309
0
      connection_state_enter_shutdown(conn);
1310
0
      break;
1311
0
    }
1312
0
  FALL_THROUGH;
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
   */
1320
0
  case CONNECTION_STATE_CONNECTING:
1321
0
  case CONNECTION_STATE_TIMEOUT:
1322
0
  case CONNECTION_STATE_FAILED:
1323
0
    connection_state_enter_closed(conn);
1324
0
    fr_assert(conn->is_closed);
1325
1326
0
  FALL_THROUGH;
1327
0
  case CONNECTION_STATE_CLOSED:
1328
0
    connection_state_enter_halted(conn);
1329
0
    break;
1330
1331
0
  case CONNECTION_STATE_MAX:
1332
0
    fr_assert(0);
1333
0
    return;
1334
0
  }
1335
0
}
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
 */
1344
void connection_signal_halt(connection_t *conn)
1345
0
{
1346
0
  DEBUG2("Signalled to halt from %s state",
1347
0
         fr_table_str_by_value(connection_states, conn->pub.state, "<INVALID>"));
1348
1349
0
  if (DEFER_SIGNALS(conn)) {
1350
0
    connection_deferred_signal_add(conn, CONNECTION_DSIGNAL_HALT);
1351
0
    return;
1352
0
  }
1353
1354
0
  switch (conn->pub.state) {
1355
0
  case CONNECTION_STATE_HALTED:
1356
0
    break;
1357
1358
0
  case CONNECTION_STATE_INIT:
1359
0
  case CONNECTION_STATE_CLOSED:
1360
0
    connection_state_enter_halted(conn);
1361
0
    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
   */
1373
0
  case CONNECTION_STATE_CONNECTED:
1374
0
  case CONNECTION_STATE_CONNECTING:
1375
0
  case CONNECTION_STATE_SHUTDOWN:
1376
0
  case CONNECTION_STATE_TIMEOUT:
1377
0
  case CONNECTION_STATE_FAILED:
1378
0
    if (!conn->is_closed) connection_state_enter_closed(conn);
1379
0
    fr_assert(conn->is_closed);
1380
0
    connection_state_enter_halted(conn);
1381
0
    break;
1382
1383
0
  case CONNECTION_STATE_MAX:
1384
0
    fr_assert(0);
1385
0
    return;
1386
0
  }
1387
0
}
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
 */
1396
static void _connection_error(UNUSED fr_event_list_t *el, int fd, UNUSED int flags, int fd_errno, void *uctx)
1397
0
{
1398
0
  connection_t *conn = talloc_get_type_abort(uctx, connection_t);
1399
1400
0
  ERROR("Connection failed for fd (%d): %s", fd, fr_syserror(fd_errno));
1401
0
  connection_state_enter_failed(conn);
1402
0
}
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
 */
1411
static void _connection_writable(fr_event_list_t *el, int fd, UNUSED int flags, void *uctx)
1412
0
{
1413
0
  connection_t    *conn = talloc_get_type_abort(uctx, connection_t);
1414
1415
0
  fr_event_fd_delete(el, fd, FR_EVENT_FILTER_IO);
1416
0
  connection_state_enter_connected(conn);
1417
0
}
1418
1419
/** Remove the FD we were watching for connection open/fail from the event loop
1420
 *
1421
 */
1422
static void _connection_signal_on_fd_cleanup(connection_t *conn,
1423
               UNUSED connection_state_t prev, connection_state_t state, void *uctx)
1424
0
{
1425
0
  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
0
  switch (state) {
1432
0
  case CONNECTION_STATE_CLOSED:
1433
0
    connection_del_watch_pre(conn, CONNECTION_STATE_CONNECTED, _connection_signal_on_fd_cleanup);
1434
0
    break;
1435
1436
0
  case CONNECTION_STATE_CONNECTED:
1437
0
    connection_del_watch_pre(conn, CONNECTION_STATE_CLOSED, _connection_signal_on_fd_cleanup);
1438
0
    break;
1439
1440
0
  default:
1441
0
    fr_assert(0);
1442
0
    break;
1443
0
  }
1444
1445
0
  fr_event_fd_delete(conn->pub.el, fd, FR_EVENT_FILTER_IO);
1446
0
  talloc_free(uctx);
1447
0
}
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
 */
1458
int connection_signal_on_fd(connection_t *conn, int fd)
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,
1468
             _connection_writable,
1469
             _connection_error,
1470
             conn) < 0) {
1471
    PERROR("Failed inserting fd (%d) into event loop %p",
1472
           fd, conn->pub.el);
1473
    connection_state_enter_failed(conn);
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
   */
1490
  connection_add_watch_pre(conn, CONNECTION_STATE_CLOSED,
1491
            _connection_signal_on_fd_cleanup, true, fd_s);
1492
  connection_add_watch_pre(conn, CONNECTION_STATE_CONNECTED,
1493
            _connection_signal_on_fd_cleanup, true, fd_s);
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
 */
1504
static int _connection_free(connection_t *conn)
1505
0
{
1506
  /*
1507
   *  Explicitly cancel any pending events
1508
   */
1509
0
  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
0
  if (DEFER_SIGNALS(conn)) {
1518
0
    connection_deferred_signal_add(conn, CONNECTION_DSIGNAL_FREE);
1519
0
    return -1;
1520
0
  }
1521
1522
0
  switch (conn->pub.state) {
1523
0
  case CONNECTION_STATE_HALTED:
1524
0
    break;
1525
1526
  /*
1527
   *  Need to close the connection first
1528
   */
1529
0
  case CONNECTION_STATE_CONNECTING:
1530
0
  case CONNECTION_STATE_CONNECTED:
1531
0
  case CONNECTION_STATE_SHUTDOWN:
1532
0
  case CONNECTION_STATE_TIMEOUT:
1533
0
    connection_state_enter_closed(conn);
1534
0
    FALL_THROUGH;
1535
1536
0
  default:
1537
0
    connection_state_enter_halted(conn);
1538
0
    break;
1539
0
  }
1540
0
  return 0;
1541
0
}
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
 */
1566
connection_t *connection_alloc(TALLOC_CTX *ctx, fr_event_list_t *el,
1567
             connection_funcs_t const *funcs,
1568
             connection_conf_t const *conf,
1569
             char const *log_prefix,
1570
             void const *uctx)
1571
0
{
1572
0
  size_t i;
1573
0
  connection_t *conn;
1574
0
  uint64_t id;
1575
1576
0
  fr_assert_msg(el, "No event list provided");
1577
1578
0
  MEM(conn = talloc(ctx, connection_t));
1579
0
  talloc_set_destructor(conn, _connection_free);
1580
1581
0
  id = atomic_fetch_add_explicit(&connection_counter, 1, memory_order_relaxed);
1582
1583
0
  *conn = (connection_t){
1584
0
    .pub = {
1585
0
      .id = id,
1586
0
      .state = CONNECTION_STATE_HALTED,
1587
0
      .el = el
1588
0
    },
1589
0
    .reconnection_delay = conf->reconnection_delay,
1590
0
    .connection_timeout = conf->connection_timeout,
1591
0
    .init = funcs->init,
1592
0
    .open = funcs->open,
1593
0
    .close = funcs->close,
1594
0
    .failed = funcs->failed,
1595
0
    .shutdown = funcs->shutdown,
1596
0
    .is_closed = true,    /* Starts closed */
1597
0
    .triggers = conf->triggers,
1598
0
    .trigger_args = conf->trigger_args,
1599
0
    .trigger_cs = conf->trigger_cs,
1600
0
    .pub.name = talloc_asprintf(conn, "%s - [%" PRIu64 "]", log_prefix, id)
1601
0
  };
1602
0
  memcpy(&conn->uctx, &uctx, sizeof(conn->uctx));
1603
1604
0
  for (i = 0; i < NUM_ELEMENTS(conn->watch_pre); i++) {
1605
0
    fr_dlist_talloc_init(&conn->watch_pre[i], connection_watch_entry_t, entry);
1606
0
  }
1607
0
  for (i = 0; i < NUM_ELEMENTS(conn->watch_post); i++) {
1608
0
    fr_dlist_talloc_init(&conn->watch_post[i], connection_watch_entry_t, entry);
1609
0
  }
1610
0
  fr_dlist_talloc_init(&conn->deferred_signals, connection_dsignal_entry_t, entry);
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
   */
1618
0
  conn->on_halted = connection_add_watch_post(conn, CONNECTION_STATE_HALTED,
1619
0
                _deferred_signal_connection_on_halted, false, NULL);
1620
0
  connection_watch_disable(conn->on_halted);  /* Start disabled */
1621
1622
0
  return conn;
1623
0
}