]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Rework connection API to be 100% handle type agnostic
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Sun, 17 Nov 2019 04:15:08 +0000 (23:15 -0500)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Sun, 17 Nov 2019 04:15:25 +0000 (23:15 -0500)
- Add watch points
- Invert relationship between conn and handle.  handle is now a child of conn.  conn is the main struct that's interacted with.
- make conn available in the init callback.

Seems to work, at least with rlm_radius

src/lib/ldap/base.h
src/lib/ldap/connection.c
src/lib/server/connection.c
src/lib/server/connection.h
src/modules/rlm_logtee/rlm_logtee.c
src/modules/rlm_radius/rlm_radius_udp.c

index 5818941e5af65d7d01632d76639f5ad4594b9c87..a47e099b96a8a1973fb45c389c3ceb330801abfc 100644 (file)
@@ -494,8 +494,8 @@ fr_ldap_rcode_t      fr_ldap_sasl_interactive(REQUEST *request,
  */
 fr_ldap_connection_t *fr_ldap_connection_alloc(TALLOC_CTX *ctx);
 
-fr_ldap_connection_t *fr_ldap_connection_state_alloc(TALLOC_CTX *ctx, fr_event_list_t *el,
-                                                    fr_ldap_config_t const *config, char *log_prefix);
+fr_connection_t        *fr_ldap_connection_state_alloc(TALLOC_CTX *ctx, fr_event_list_t *el,
+                                               fr_ldap_config_t const *config, char *log_prefix);
 
 int            fr_ldap_connection_configure(fr_ldap_connection_t *c, fr_ldap_config_t const *config);
 
index 009ca6d687715899b0a7bd3eb6ee76c1093bd0d9..f965c119611e6949ac900852f4f354620bbcebe0 100644 (file)
@@ -142,42 +142,6 @@ static int fr_ldap_rebind(LDAP *handle, LDAP_CONST char *url,
 }
 #endif
 
-/** Close and delete a connection
- *
- * Unbinds the LDAP connection, informing the server and freeing any memory, then releases the memory used by the
- * connection handle.
- *
- * @param[in] c                to destroy.
- * @return always indicates success.
- */
-static int fr_ldap_connection_reset(fr_ldap_connection_t *c)
-{
-       talloc_free_children(c);        /* Force inverted free order */
-
-       fr_ldap_control_clear(c);
-
-       if (!c->handle) return 0;       /* Don't need to do anything else if we don't yet have a handle */
-
-#ifdef HAVE_LDAP_UNBIND_EXT_S
-       LDAPControl     *our_serverctrls[LDAP_MAX_CONTROLS];
-       LDAPControl     *our_clientctrls[LDAP_MAX_CONTROLS];
-
-       fr_ldap_control_merge(our_serverctrls, our_clientctrls,
-                             NUM_ELEMENTS(our_serverctrls),
-                             NUM_ELEMENTS(our_clientctrls),
-                             c, NULL, NULL);
-
-       DEBUG3("Closing libldap handle %p", c->handle);
-       ldap_unbind_ext(c->handle, our_serverctrls, our_clientctrls);   /* Same code as ldap_unbind_ext_s */
-#else
-       DEBUG3("Closing libldap handle %p", c->handle);
-       ldap_unbind(c->handle);                                         /* Same code as ldap_unbind_s */
-#endif
-       c->handle = NULL;
-
-       return 0;
-}
-
 /** Allocate and configure a new connection
  *
  * Configures both our ldap handle, and libldap's handle.
@@ -203,13 +167,6 @@ int fr_ldap_connection_configure(fr_ldap_connection_t *c, fr_ldap_config_t const
 
        rad_assert(config->server);
 
-       /*
-        *      Puts the handle back into a pristine state
-        *      without leaking memory, but leaves the original
-        *      fr_ldap_connection_t intact.
-        */
-       if (c->handle) fr_ldap_connection_reset(c);
-
 #ifdef HAVE_LDAP_INITIALIZE
        ldap_errno = ldap_initialize(&handle, config->server);
        if (ldap_errno != LDAP_SUCCESS) {
@@ -350,18 +307,75 @@ int fr_ldap_connection_configure(fr_ldap_connection_t *c, fr_ldap_config_t const
        return 0;
 }
 
-/** Close libldap's file descriptor
+/** Free the handle, closing the connection to ldap
  *
- * @param[in] fd       to close.
+ * @param[in] h                to close.
  * @param[in] uctx     Connection config and handle.
  */
-static void _ldap_connection_close(UNUSED int fd, void *uctx)
+static void _ldap_connection_close(void *h, UNUSED void *uctx)
+{
+       talloc_free(h);
+}
+
+/** Close and delete a connection
+ *
+ * Unbinds the LDAP connection, informing the server and freeing any memory, then releases the memory used by the
+ * connection handle.
+ *
+ * @param[in] c                to destroy.
+ * @return always indicates success.
+ */
+static int _ldap_connection_free(fr_ldap_connection_t *c)
 {
-       fr_ldap_connection_t    *c = talloc_get_type_abort(uctx, fr_ldap_connection_t);
+       talloc_free_children(c);        /* Force inverted free order */
+
+       fr_ldap_control_clear(c);
+
+       if (!c->handle) return 0;       /* Don't need to do anything else if we don't yet have a handle */
+
+#ifdef HAVE_LDAP_UNBIND_EXT_S
+       LDAPControl     *our_serverctrls[LDAP_MAX_CONTROLS];
+       LDAPControl     *our_clientctrls[LDAP_MAX_CONTROLS];
 
-       INFO("Closing connection");
+       fr_ldap_control_merge(our_serverctrls, our_clientctrls,
+                             NUM_ELEMENTS(our_serverctrls),
+                             NUM_ELEMENTS(our_clientctrls),
+                             c, NULL, NULL);
 
-       fr_ldap_connection_reset(c);
+       DEBUG3("Closing libldap handle %p", c->handle);
+       ldap_unbind_ext(c->handle, our_serverctrls, our_clientctrls);   /* Same code as ldap_unbind_ext_s */
+#else
+       DEBUG3("Closing libldap handle %p", c->handle);
+       ldap_unbind(c->handle);                                         /* Same code as ldap_unbind_s */
+#endif
+       c->handle = NULL;
+
+       return 0;
+}
+
+/** Allocate our ldap connection handle layer
+ *
+ * This is using handles outside of the connection state machine.
+ *
+ * @param[in] ctx to allocate connection handle in.
+ * @return
+ *     - A new unbound/unconfigured connection handle on success.
+ *       Call f#r_ldap_connection_configure next.
+ *     - NULL on OOM.
+ */
+fr_ldap_connection_t *fr_ldap_connection_alloc(TALLOC_CTX *ctx)
+{
+       fr_ldap_connection_t *c;
+
+       /*
+        *      Allocate memory for the handle.
+        */
+       c = talloc_zero(ctx, fr_ldap_connection_t);
+       if (!c) return NULL;
+
+       talloc_set_destructor(c, _ldap_connection_free);
+
+       return c;
 }
 
 /** (Re-)Initialises the libldap side of the connection handle
@@ -401,59 +415,40 @@ static void _ldap_connection_close(UNUSED int fd, void *uctx)
  *        connected.
  *  - Continue running the state machine
  *
- * @param[out] fd_out  Underlying file descriptor from libldap handle.
+ * @param[out] h       Underlying file descriptor from libldap handle.
+ * @arapm[in] conn     Being initialised.
  * @param[in] uctx     Our LDAP connection handle (a #fr_ldap_connection_t).
  * @return
  *     - FR_CONNECTION_STATE_CONNECTING on success.
  *     - FR_CONNECTION_STATE_FAILED on failure.
  */
-static fr_connection_state_t _ldap_connection_init(int *fd_out, void *uctx)
+static fr_connection_state_t _ldap_connection_init(void **h, fr_connection_t *conn, void *uctx)
 {
-       fr_ldap_connection_t    *c = talloc_get_type_abort(uctx, fr_ldap_connection_t);
+       fr_ldap_config_t const  *config = talloc_get_type_abort(uctx, fr_ldap_config_t);
+       fr_ldap_connection_t    *c;
        fr_ldap_state_t         state;
 
-       *fd_out = -1;   /* We set a real value later */
+       c = fr_ldap_connection_alloc(conn);
 
        /*
         *      Configure/allocate the libldap handle
         */
-       if (fr_ldap_connection_configure(c, c->config) < 0) return FR_CONNECTION_STATE_FAILED;
-
-       /* Don't block */
-       if (ldap_set_option(c->handle, LDAP_OPT_CONNECT_ASYNC, LDAP_OPT_ON) != LDAP_OPT_SUCCESS) {
+       if (fr_ldap_connection_configure(c, config) < 0) {
+       error:
+               talloc_free(c);
                return FR_CONNECTION_STATE_FAILED;
        }
+
+       /* Don't block */
+       if (ldap_set_option(c->handle, LDAP_OPT_CONNECT_ASYNC, LDAP_OPT_ON) != LDAP_OPT_SUCCESS) goto error;
        fr_ldap_connection_timeout_set(c, 0);                                   /* Forces LDAP_X_CONNECTING */
 
        state = fr_ldap_state_next(c);
-       if (state == FR_LDAP_STATE_ERROR) return FR_CONNECTION_STATE_FAILED;
-
-       return FR_CONNECTION_STATE_CONNECTING;
-}
-
-/** Allocate our ldap connection handle layer
- *
- * This is using handles outside of the connection state machine.
- *
- * @param[in] ctx to allocate connection handle in.
- * @return
- *     - A new unbound/unconfigured connection handle on success.
- *       Call f#r_ldap_connection_configure next.
- *     - NULL on OOM.
- */
-fr_ldap_connection_t *fr_ldap_connection_alloc(TALLOC_CTX *ctx)
-{
-       fr_ldap_connection_t *c;
-
-       /*
-        *      Allocate memory for the handle.
-        */
-       c = talloc_zero(ctx, fr_ldap_connection_t);
-       if (!c) return NULL;
+       if (state == FR_LDAP_STATE_ERROR) goto error;
 
-       talloc_set_destructor(c, fr_ldap_connection_reset);
+       *h = c; /* Set the handle */
 
-       return c;
+       return FR_CONNECTION_STATE_CONNECTING;
 }
 
 /** Alloc a self re-establishing connection to an LDAP server
@@ -463,22 +458,20 @@ fr_ldap_connection_t *fr_ldap_connection_alloc(TALLOC_CTX *ctx)
  * @param[in] config           to use to bind the connection to an LDAP server.
  * @param[in] log_prefix       to prepend to connection state messages.
  */
-fr_ldap_connection_t *fr_ldap_connection_state_alloc(TALLOC_CTX *ctx, fr_event_list_t *el,
-                                                    fr_ldap_config_t const *config, char *log_prefix)
+fr_connection_t        *fr_ldap_connection_state_alloc(TALLOC_CTX *ctx, fr_event_list_t *el,
+                                               fr_ldap_config_t const *config, char *log_prefix)
 {
-       fr_ldap_connection_t    *c;
+       fr_connection_t *conn;
 
-       MEM(c = fr_ldap_connection_alloc(ctx));
-       c->config = config;
-       c->conn = fr_connection_alloc(c, el,
-                                     config->net_timeout, config->reconnection_delay,
-                                     _ldap_connection_init,
-                                     NULL,
-                                     _ldap_connection_close,
-                                     log_prefix, c);
-       if (!c->conn) return NULL;
+       conn = fr_connection_alloc(ctx, el,
+                                  config->net_timeout, config->reconnection_delay,
+                                  _ldap_connection_init,
+                                  NULL,
+                                  _ldap_connection_close,
+                                  log_prefix, config);
+       if (!conn) return NULL;
 
-       return c;
+       return conn;
 }
 
 int fr_ldap_connection_timeout_set(fr_ldap_connection_t const *c, fr_time_delta_t timeout)
index 537026c64af85d8e9d56336c418d2192926c8e68..0f822cc56efd4d55f7fae8b00c9149c20974822f 100644 (file)
@@ -20,7 +20,7 @@
  * @file src/lib/server/connection.c
  * @brief Simple state machine for managing connection states.
  *
- * @copyright 2017 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
+ * @copyright 2017-2019 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
  */
 #define LOG_PREFIX "[%" PRIu64 "] %s - "
 #define LOG_PREFIX_ARGS conn->id, conn->log_prefix
@@ -52,17 +52,38 @@ size_t fr_connection_states_len = NUM_ELEMENTS(fr_connection_states);
 
 static atomic_uint_fast64_t connection_counter = ATOMIC_VAR_INIT(1);
 
+/** An entry in a watch function list
+ *
+ */
+typedef struct {
+       fr_dlist_t              list;                   //!< List entry.
+       fr_connection_watch_t   func;                   //!< Function to call when a connection enters
+                                                       ///< the state this list belongs to
+       bool                    oneshot;                //!< Remove the function after it's called once.
+       void                    *uctx;                  //!< User data to pass to the function.
+} fr_connection_watch_entry_t;
+
 struct fr_conn {
        uint64_t                id;                     //!< Unique identifier for the connection.
        fr_connection_state_t   state;                  //!< Current connection state.
+       void                    *h;                     //!< Connection handle
+       fr_event_list_t         *el;                    //!< Event list for timers and I/O events.
+       char const              *log_prefix;            //!< Prefix to add to log messages.
+       void                    *uctx;                  //!< User data.
+
+       bool                    in_handler;             //!< Connection is currently in a callback.
+       bool                    deferred_free;          //!< Something freed the connection.
+       bool                    is_closed;              //!< The close callback has previously been called.
+
+       fr_dlist_head_t         watch_pre[FR_CONNECTION_STATE_MAX];     //!< Function called before state callback.
+       fr_dlist_head_t         watch_post[FR_CONNECTION_STATE_MAX];    //!< Function called after state callback.
 
        fr_connection_init_t    init;                   //!< Callback for initialising a connection.
        fr_connection_open_t    open;                   //!< Callback for 'open' notification.
        fr_connection_close_t   close;                  //!< Callback to close a connection.
        fr_connection_failed_t  failed;                 //!< Callback for 'failed' notification.
 
-       int                     fd;                     //!< File descriptor.
-       fr_event_list_t         *el;                    //!< Event list for timers and I/O events.
+
 
        fr_event_timer_t const  *connection_timer;      //!< Timer to prevent connections going on indefinitely.
        fr_event_timer_t const  *reconnection_timer;    //!< Timer to delay retries.
@@ -71,10 +92,6 @@ struct fr_conn {
                                                        //!< #FR_CONNECTION_STATE_CONNECTING state.
        fr_time_delta_t         reconnection_delay;     //!< How long to wait in the
                                                        //!< #FR_CONNECTION_STATE_FAILED state.
-
-       char const              *log_prefix;            //!< Prefix to add to log messages.
-
-       void                    *uctx;                  //!< User data.
 };
 
 #define STATE_TRANSITION(_new) \
@@ -85,8 +102,287 @@ do { \
        conn->state = _new; \
 } while (0)
 
-static void connection_state_init(fr_connection_t *conn, fr_time_t now);
-static void connection_state_failed(fr_connection_t *conn, fr_time_t now);
+/** Called when we enter a handler
+ *
+ */
+#define HANDLER_BEGIN(_conn) (_conn)->in_handler = true
+
+/** Called when we exit a handler
+ *
+ */
+#define HANDLER_END(_conn) \
+do { \
+       (_conn)->in_handler = false; \
+       if ((_conn)->deferred_free) { \
+               talloc_free(_conn); \
+               return; \
+       } \
+} while(0)
+
+/** Call a list of watch functions associated with a state
+ *
+ */
+static inline void connection_watch_call(fr_connection_t *conn, fr_dlist_head_t *list)
+{
+       fr_connection_watch_entry_t *entry = NULL;
+       if (fr_dlist_empty(list)) return;
+
+       while ((entry = fr_dlist_next(list, entry))) {
+               entry->func(conn, conn->state, entry->uctx);
+               if (entry->oneshot) {
+                       fr_connection_watch_entry_t *to_free = entry;
+                       entry = fr_dlist_remove(list, entry);
+                       talloc_free(to_free);
+               }
+       }
+}
+
+/** Call the pre handler watch functions
+ *
+ */
+#define WATCH_PRE(_conn) connection_watch_call((_conn), &(_conn)->watch_pre[(_conn)->state]);
+
+/** Call the post handler watch functions
+ *
+ */
+#define WATCH_POST(_conn) connection_watch_call((_conn), &(_conn)->watch_post[(_conn)->state]);
+
+/*
+ *     State transition functions
+ */
+static void connection_state_failed_enter(fr_connection_t *conn, fr_time_t now);
+static void connection_state_timeout_enter(fr_connection_t *conn, fr_time_t now);
+static void connection_state_connected_enter(fr_connection_t *conn, fr_time_t now);
+static void connection_state_connecting_enter(fr_connection_t *conn, fr_time_t now);
+static void connection_state_halted_enter(fr_connection_t *conn, fr_time_t now);
+static void connection_state_init_enter(fr_connection_t *conn, fr_time_t now);
+
+/** Get the event list associated with the connection
+ *
+ * @param[in] conn     to retrieve the event list from.
+ * @return the event list associated with the connection.
+ */
+fr_event_list_t *fr_connection_get_el(fr_connection_t const *conn)
+{
+       return conn->el;
+}
+
+/** Get the handle associated with a connection
+ *
+ * @param[in] conn     to retrieve fd from.
+ * @return the active connection handle.
+ */
+int fr_connection_get_handle(fr_connection_t const *conn)
+{
+       return conn->h;
+}
+
+/** Set the handle associated with a connection
+ *
+ * Will not free the previous handle.  This must be done manually.
+ *
+ * @param[in] conn     to set fd for.
+ * @param[in] handle   to set.
+ */
+void fr_connection_set_handle(fr_connection_t *conn, void *handle)
+{
+       conn->h = handle;
+}
+
+/** Set an (optional) callback to be called on connection timeout/failure
+ *
+ */
+void fr_connection_set_failed_func(fr_connection_t *conn, fr_connection_failed_t func)
+{
+       conn->failed = func;
+}
+
+/** Remove a watch function from a pre/post[state] list
+ *
+ */
+static int connection_del_watch(fr_dlist_head_t *list, fr_connection_watch_t watch)
+{
+       fr_connection_watch_entry_t     *entry = NULL;
+
+       while ((entry = fr_dlist_next(list, entry))) {
+               if (entry->func == watch) {
+                       fr_dlist_remove(list, entry);
+                       talloc_free(entry);
+                       return 0;
+               }
+       }
+
+       return -1;
+}
+
+/** Remove a watch function from a pre list
+ *
+ * @param[in] conn     The connection to remove the watcher from.
+ * @param[in] state    to remove the watch from.
+ * @param[in] watch    Function to remove.
+ * @return
+ *     - 0 if the function was removed successfully.
+ *     - -1 if the function wasn't present in the watch list.
+ *     - -2 an invalid state was passed.
+ */
+int fr_connection_del_watch_pre(fr_connection_t *conn, fr_connection_state_t state, fr_connection_watch_t watch)
+{
+       if (state >= FR_CONNECTION_STATE_MAX) return -2;
+
+       return connection_del_watch(&conn->watch_pre[state], watch);
+}
+
+/** Remove a watch function from a post list
+ *
+ * @param[in] conn     The connection to remove the watcher from.
+ * @param[in] state    to remove the watch from.
+ * @param[in] watch    Function to remove.
+ * @return
+ *     - 0 if the function was removed successfully.
+ *     - -1 if the function wasn't present in the watch list.
+ *     - -2 an invalid state was passed.
+ */
+int fr_connection_del_watch_post(fr_connection_t *conn, fr_connection_state_t state, fr_connection_watch_t watch)
+{
+       if (state >= FR_CONNECTION_STATE_MAX) return -2;
+
+       return connection_del_watch(&conn->watch_post[state], watch);
+}
+
+/** Add a watch entry to the pre/post[state] list
+ *
+ */
+static void connection_add_watch(fr_connection_t *conn, fr_dlist_head_t *list,
+                                fr_connection_watch_t watch, bool oneshot, void const *uctx)
+{
+       fr_connection_watch_entry_t *entry;
+
+       MEM(entry = talloc_zero(conn, fr_connection_watch_entry_t));
+
+       entry->func = watch;
+       entry->oneshot = oneshot;
+       memcpy(&entry->uctx, &uctx, sizeof(entry->uctx));
+
+       fr_dlist_insert_tail(list, entry);
+}
+
+/** Add a callback to be executed before a state function has been called
+ *
+ * @param[in] conn     to add watcher to.
+ * @param[in] state    to call watcher on entering.
+ * @param[in] watch    function to call.
+ * @param[in] oneshot  If true, remove the function after calling.
+ */
+void fr_connection_add_watch_pre(fr_connection_t *conn, fr_connection_state_t state,
+                                fr_connection_watch_t watch, bool oneshot, void const *uctx)
+{
+       if (state >= FR_CONNECTION_STATE_MAX) return;
+
+       connection_add_watch(conn, &conn->watch_pre[state], watch, oneshot, uctx);
+}
+
+/** Add a callback to be executed after a state function has been called
+ *
+ * @param[in] conn     to add watcher to.
+ * @param[in] state    to call watcher on entering.
+ * @param[in] watch    function to call.
+ * @param[in] oneshot  If true, remove the function after calling.
+ */
+void fr_connection_add_watch_post(fr_connection_t *conn, fr_connection_state_t state,
+                                 fr_connection_watch_t watch, bool oneshot, void const *uctx)
+{
+       if (state >= FR_CONNECTION_STATE_MAX) return;
+
+       connection_add_watch(conn, &conn->watch_pre[state], watch, oneshot, uctx);
+}
+
+/** Close a connection if it's freed
+ *
+ * @param[in] conn to free.
+ * @return
+ *     - 0 connection was freed immediately.
+ *     - 1 connection free was deferred.
+ */
+static int _connection_free(fr_connection_t *conn)
+{
+       /*
+        *      Don't allow the connection to be
+        *      arbitrarily freed by a callback.
+        *
+        *      Set the deferred free flag, and
+        *      free the connection afterwards.
+        */
+       if (conn->in_handler) {
+               conn->deferred_free = true;
+               return 1;
+       }
+
+       switch (conn->state) {
+       case FR_CONNECTION_STATE_HALTED:
+               break;
+
+       default:
+               connection_state_halted_enter(conn, fr_time());
+               break;
+       }
+       return 0;
+}
+
+/** Allocate a new connection
+ *
+ * After the connection has been allocated, it should be started with a call to #fr_connection_signal_init.
+ *
+ * The connection state machine can detect when the connection is open in one of two ways.
+ * - You can install a generic socket open/fail callback, using fr_connection_signal_on_fd.
+ * - You can call either #fr_connection_signal_connected or fr_connection_signal_recommend.
+ *   This allows the connection state machine to work with more difficult library APIs,
+ *   which may not return control to the caller as connections are opened.
+ *
+ * @param[in] ctx              to allocate connection handle in.  If the connection
+ *                             handle is freed, and the #fr_connection_state_t is
+ *                             #FR_CONNECTION_STATE_CONNECTING or #FR_CONNECTION_STATE_CONNECTED the
+ *                             close callback will be called.
+ * @param[in] el               to use for timer events, and to pass to the #fr_connection_open_t callback.
+ * @param[in] connection_timeout       (optional) how long to wait for a connection to open.
+ * @param[in] reconnection_delay       How long to wait on connection failure before retrying.
+ * @param[in] init             (optional) callback to initialise a new file descriptor.
+ * @param[in] open             (optional) callback to receive notifications that the connection is open.
+ * @param[in] close            (optional) Callback to close the connection.
+ * @param[in] log_prefix       To prepend to log messages.
+ * @param[in] uctx             User context to pass to callbacks.
+ * @return
+ *     - A new #fr_connection_t on success.
+ *     - NULL on failure.
+ */
+fr_connection_t *fr_connection_alloc(TALLOC_CTX *ctx, fr_event_list_t *el,
+                                    fr_time_delta_t connection_timeout,
+                                    fr_time_delta_t reconnection_delay,
+                                    fr_connection_init_t init, fr_connection_open_t open, fr_connection_close_t close,
+                                    char const *log_prefix,
+                                    void const *uctx)
+{
+       fr_connection_t *conn;
+
+       rad_assert(el);
+
+       conn = talloc_zero(ctx, fr_connection_t);
+       if (!conn) return NULL;
+       talloc_set_destructor(conn, _connection_free);
+
+       conn->id = atomic_fetch_add_explicit(&connection_counter, 1, memory_order_relaxed);
+       conn->state = FR_CONNECTION_STATE_HALTED;
+       conn->el = el;
+       conn->h = NULL;
+       conn->reconnection_delay = reconnection_delay;
+       conn->connection_timeout = connection_timeout;
+       conn->init = init;
+       conn->open = open;
+       conn->close = close;
+       conn->log_prefix = talloc_typed_strdup(conn, log_prefix);
+       memcpy(&conn->uctx, &uctx, sizeof(conn->uctx));
+
+       return conn;
+}
 
 /** The requisite period of time has passed, try and re-open the connection
  *
@@ -98,7 +394,7 @@ static void _reconnect_delay_done(UNUSED fr_event_list_t *el, fr_time_t now, voi
 {
        fr_connection_t *conn = talloc_get_type_abort(uctx, fr_connection_t);
 
-       connection_state_init(conn, now);
+       connection_state_init_enter(conn, now);
 }
 
 /** Connection failed
@@ -113,18 +409,40 @@ static void _reconnect_delay_done(UNUSED fr_event_list_t *el, fr_time_t now, voi
  * @param[in] conn     that failed.
  * @param[in] now      The current time.
  */
-static void connection_state_failed(fr_connection_t *conn, fr_time_t now)
+static void connection_state_failed_enter(fr_connection_t *conn, fr_time_t now)
 {
        fr_connection_state_t prev;
+
        rad_assert(conn->state != FR_CONNECTION_STATE_FAILED);
 
-       if (conn->fd >= 0) fr_event_fd_delete(conn->el, conn->fd, FR_EVENT_FILTER_IO);  /* Don't leave lingering events */
-       if (conn->close) conn->close(conn->fd, conn->uctx);
-       conn->fd = -1;
+       /*
+        *      Explicit error occurred, delete the connection timer
+        */
+       fr_event_timer_delete(conn->el, &conn->connection_timer);
 
+       /*
+        *      Record what state the connection is currently in
+        *      so we can figure out what to do next.
+        */
        prev = conn->state;
+
+       /*
+        *      Now transition to failed
+        */
        STATE_TRANSITION(FR_CONNECTION_STATE_FAILED);
 
+       /*
+        *      If there's a close callback, call it, so that the
+        *      API client can free any resources associated
+        *      with the connection handle.
+        */
+       HANDLER_BEGIN(conn);
+       WATCH_PRE(conn);
+       if (conn->close && !conn->is_closed) conn->close(conn->h, conn->uctx);
+       conn->is_closed = true;         /* Ensure close doesn't get called twice if the connection is freed */
+       WATCH_POST(conn);
+       HANDLER_END(conn);
+
        /*
         *      If there's a failed callback, give it the
         *      opportunity to suspend/destroy the
@@ -133,27 +451,17 @@ static void connection_state_failed(fr_connection_t *conn, fr_time_t now)
        if (conn->failed) {
                fr_connection_state_t ret;
 
-               /*
-                *      Callback may free the connection, so we
-                *      set this before calling the callback, so
-                *      if the connection isn't freed it's in the
-                *      correct state, without us needing to check.
-                */
-               conn->state = FR_CONNECTION_STATE_HALTED;
-               ret = conn->failed(conn->fd, prev, conn->uctx);
+               HANDLER_BEGIN(conn);
+               ret = conn->failed(conn->h, prev, conn->uctx);
+               HANDLER_END(conn);
                switch (ret) {
                case FR_CONNECTION_STATE_INIT:
-                       conn->state = prev;
                        break;
 
-               case FR_CONNECTION_STATE_HALTED:                /* Do nothing */
-                       DEBUG4("Changed state %s -> %s",
-                              fr_table_str_by_value(fr_connection_states, prev, "<INVALID>"),
-                              fr_table_str_by_value(fr_connection_states, FR_CONNECTION_STATE_HALTED, "<INVALID>"));
-                       return;
-
+               case FR_CONNECTION_STATE_HALTED:
                default:
-                       rad_assert(0);
+                       connection_state_halted_enter(conn, now);
+                       return;
                }
        }
 
@@ -161,7 +469,6 @@ static void connection_state_failed(fr_connection_t *conn, fr_time_t now)
        case FR_CONNECTION_STATE_INIT:                          /* Failed during initialisation */
        case FR_CONNECTION_STATE_CONNECTED:                     /* Failed after connecting */
        case FR_CONNECTION_STATE_CONNECTING:                    /* Failed during connecting */
-               STATE_TRANSITION(FR_CONNECTION_STATE_FAILED);
                if (fr_event_timer_at(conn, conn->el, &conn->reconnection_timer,
                                      now + conn->reconnection_delay, _reconnect_delay_done, conn) < 0) {
                        PERROR("Failed inserting delay timer event");
@@ -170,7 +477,7 @@ static void connection_state_failed(fr_connection_t *conn, fr_time_t now)
                break;
 
        case FR_CONNECTION_STATE_TIMEOUT:                       /* Failed during connecting */
-               connection_state_init(conn, now);
+               connection_state_init_enter(conn, now);
                break;
 
        default:
@@ -178,296 +485,184 @@ static void connection_state_failed(fr_connection_t *conn, fr_time_t now)
        }
 }
 
-/** Connection timeout
+/** Enter the timeout state
  *
- * Fd didn't become writable within the configured period of time.
- *
- * @param[in] el       the time event ocurred on.
- * @param[in] now      the current time.
- * @param[in] uctx     The #fr_connection_t the fd is associated with.
+ * The connection took took long to open.  Timeout the attempt and transition
+ * to the failed state.
  */
-static void _connection_timeout(UNUSED fr_event_list_t *el, fr_time_t now, void *uctx)
+static void connection_state_timeout_enter(fr_connection_t *conn, fr_time_t now)
 {
-       fr_connection_t *conn = talloc_get_type_abort(uctx, fr_connection_t);
-
        ERROR("Connection failed - timed out after %pVs", fr_box_time_delta(conn->connection_timeout));
+
        STATE_TRANSITION(FR_CONNECTION_STATE_TIMEOUT);
-       connection_state_failed(conn, now);
+
+       connection_state_failed_enter(conn, now);
 }
 
-/** Receive an error notification when we're connecting a socket
+/** Enter the halted state
  *
- * @param[in] el       event list the I/O event occurred on.
- * @param[in] sock     the I/O even occurred for.
- * @param[in] flags    from_kevent.
- * @param[in] fd_errno from kevent.
- * @param[in] uctx     The #fr_connection_t this fd is associated with.
+ * Here we wait, until signalled by fr_connection_signal_reconnect.
  */
-static void _connection_error(UNUSED fr_event_list_t *el, UNUSED int sock, UNUSED int flags, int fd_errno, void *uctx)
+static void connection_state_halted_enter(fr_connection_t *conn, UNUSED fr_time_t now)
 {
-       fr_connection_t *conn = talloc_get_type_abort(uctx, fr_connection_t);
+       STATE_TRANSITION(FR_CONNECTION_STATE_HALTED);
 
-       /*
-        *      Explicit error occurred, delete the connection timer
-        */
-       fr_event_timer_delete(conn->el, &conn->connection_timer);
-
-       ERROR("Connection failed: %s", fr_syserror(fd_errno));
-       connection_state_failed(conn, fr_time());
+       WATCH_PRE(conn);
+       if (conn->close && !conn->is_closed) conn->close(conn->h, conn->uctx);
+       WATCH_POST(conn);
 }
 
-/** Receive a write notification after connecting a socket
+/** Enter the connected state
  *
- * @param[in] el       event list the I/O event occurred on.
- * @param[in] sock     the I/O even occurred for.
- * @param[in] flags    from kevent.
- * @param[in] uctx     The #fr_connection_t this fd is associated with.
+ * The connection is now fully connected.  At this point we call the open callback
+ * so that the API client can install its normal set of I/O callbacks to deal with
+ * sending/receiving actual data.
+ *
+ * After this, the connection will only transition states if an API client
+ * explicitly calls fr_connection_signal_reconnect.
+ *
+ * The connection API cannot monitor the connection for failure conditions.
+ *
+ * @param[in] conn     Entering the connecting state.
+ * @param[in] now      The current time.
  */
-static void _connection_writable(UNUSED fr_event_list_t *el, UNUSED int sock, UNUSED int flags, void *uctx)
+static void connection_state_connected_enter(fr_connection_t *conn, UNUSED fr_time_t now)
 {
-       fr_connection_t         *conn = talloc_get_type_abort(uctx, fr_connection_t);
-       fr_connection_state_t   ret;
+       int     ret;
 
-       rad_assert(conn->open); /* I/O handler should not be called unless we have an open callback */
+       rad_assert(conn->state == FR_CONNECTION_STATE_CONNECTING);
+
+       STATE_TRANSITION(FR_CONNECTION_STATE_CONNECTED);
 
-       /*
-        *      Connection is writable, delete the connection timer
-        */
        fr_event_timer_delete(conn->el, &conn->connection_timer);
-       fr_event_fd_delete(conn->el, conn->fd, FR_EVENT_FILTER_IO);
 
-       ret = conn->open(conn->el, conn->fd, conn->uctx);
-       if (conn->state == FR_CONNECTION_STATE_FAILED) return;  /* async signal that connection failed */
+       HANDLER_BEGIN(conn);
+       WATCH_PRE(conn);
+       if (conn->open) {
+               ret = conn->open(conn->el, conn->h, conn->uctx);
+       } else {
+               ret = FR_CONNECTION_STATE_CONNECTED;
+       }
+       WATCH_POST(conn);
+       HANDLER_END(conn);
 
        switch (ret) {
+       /*
+        *      Callback agrees everything is connected
+        */
        case FR_CONNECTION_STATE_CONNECTED:
                DEBUG2("Connection established");
-               STATE_TRANSITION(ret);
                return;
 
        /*
         *      Open callback failed
         */
        case FR_CONNECTION_STATE_FAILED:
+       default:
                PERROR("Connection failed");
-               connection_state_failed(conn, fr_time());
+               connection_state_failed_enter(conn, fr_time());
                return;
-
-       default:
-               rad_assert(0);
        }
 }
 
-/** Enter the initialising state
+/** Connection timeout
  *
- * @param[in] conn     being initialised.
- * @param[in] now      the current ime.
- */
-static void connection_state_init(fr_connection_t *conn, fr_time_t now)
-{
-       fr_connection_state_t ret;
-
-       rad_assert((conn->state == FR_CONNECTION_STATE_HALTED) || (conn->state == FR_CONNECTION_STATE_FAILED));
-
-       DEBUG2("Connection initialising");
-
-       STATE_TRANSITION(FR_CONNECTION_STATE_INIT);
-
-       /*
-        *      If we have an init callback, call it.
-        */
-       if (conn->init) {
-               ret = conn->init(&conn->fd, conn->uctx);
-               if (conn->state == FR_CONNECTION_STATE_FAILED) return;  /* async signal that connection failed */
-       } else {
-               ret = FR_CONNECTION_STATE_CONNECTING;
-       }
-
-       switch (ret) {
-       case FR_CONNECTION_STATE_CONNECTING:
-               DEBUG2("Connection initialised");
-               STATE_TRANSITION(ret);
-
-               /*
-                *      If an open callback is provided, install an I/O
-                *      handler to determine when the FD is writable
-                *      and therefore open.
-                */
-               if (conn->open) {
-                       rad_assert(conn->fd >= 0);      /* ->init() must provide a valid fd */
-
-                       /*
-                        *      If connection becomes writable we
-                        *      assume it's open.
-                        */
-                       if (fr_event_fd_insert(conn, conn->el, conn->fd,
-                                              NULL,
-                                              _connection_writable,
-                                              _connection_error,
-                                              conn) < 0) {
-                               PERROR("Failed inserting file descriptor (%i) into event loop %p",
-                                      conn->fd, conn->el);
-                               connection_state_failed(conn, now);
-                               return;
-                       }
-               }
-
-               /*
-                *      If there's a connection timeout,
-                *      set, then add the timer.
-                */
-               if (conn->connection_timeout) {
-                       if (fr_event_timer_at(conn, conn->el, &conn->connection_timer,
-                                             now + conn->connection_timeout,
-                                             _connection_timeout, conn) < 0) {
-                               PERROR("Failed inserting connection timeout event");
-                               rad_assert(0);
-                       }
-               }
-               break;
-
-       /*
-        *      Initialisation callback failed
-        */
-       case FR_CONNECTION_STATE_FAILED:
-               PERROR("Connection initialisation failed");
-               connection_state_failed(conn, now);
-               break;
-
-       default:
-               rad_assert(0);
-       }
-}
-
-/** Get the event list associated with the connection
+ * Connection wasn't opened within the configured period of time
  *
- * @param[in] conn to retrieve fd from.
- * @return the event list associated with the connection.
+ * @param[in] el       the time event ocurred on.
+ * @param[in] now      the current time.
+ * @param[in] uctx     The #fr_connection_t the fd is associated with.
  */
-fr_event_list_t *fr_connection_get_el(fr_connection_t const *conn)
+static void _connection_timeout(UNUSED fr_event_list_t *el, fr_time_t now, void *uctx)
 {
-       return conn->el;
-}
+       fr_connection_t *conn = talloc_get_type_abort(uctx, fr_connection_t);
 
-/** Get the file descriptor associated with a connection
- *
- * @param[in] conn to retrieve fd from.
- * @return
- *     - -1 if no valid file descriptor is available.
- *     - >= 0 - The file descriptor.
- */
-int fr_connection_get_fd(fr_connection_t const *conn)
-{
-       return conn->fd;
+       connection_state_timeout_enter(conn, now);
 }
 
-/** Set the file descriptor associated with a connection
+/** Enter the connecting state
  *
- * @note Should only be used if no conn->open callback is provided, and the init
- *      function is either NULL, or is unable to provide a file descriptor.
+ * After this function returns we wait to be signalled with fr_connection_singal_connected
+ * or for the connection timer to expire.
  *
- * @param[in] conn     to set fd for.
- * @param[in] fd       to set.  Must be >= 0.
+ * @param[in] conn     Entering the connecting state.
+ * @param[in] now      The current time.
  */
-void fr_connection_set_fd(fr_connection_t *conn, int fd)
+static void connection_state_connecting_enter(fr_connection_t *conn, fr_time_t now)
 {
-       rad_assert(fd >= 0);
-       rad_assert(!conn->open);
+       rad_assert(conn->state == FR_CONNECTION_STATE_INIT);
 
-       conn->fd = fd;
-}
+       STATE_TRANSITION(FR_CONNECTION_STATE_CONNECTING);
 
-/** Close a connection if it's freed
- *
- * @param[in] conn to free.
- * @return 0
- */
-static int _connection_free(fr_connection_t *conn)
-{
-       switch (conn->state) {
-       case FR_CONNECTION_STATE_HALTED:
-               break;
+       WATCH_PRE(conn);
+       WATCH_POST(conn);
 
-       default:
-               if (conn->fd >= 0) {
-                       DEBUG2("Closing connection (%i)", conn->fd);
-                       fr_event_fd_delete(conn->el, conn->fd, FR_EVENT_FILTER_IO);
-                       conn->close(conn->fd, conn->uctx);
-                       conn->fd = -1;
+       /*
+        *      If there's a connection timeout,
+        *      set, then add the timer.
+        */
+       if (conn->connection_timeout) {
+               if (fr_event_timer_at(conn, conn->el, &conn->connection_timer,
+                                     now + conn->connection_timeout,
+                                     _connection_timeout, conn) < 0) {
+                       PERROR("Failed inserting connection timeout event");
+                       rad_assert(0);
                }
-               break;
        }
-       return 0;
 }
 
-/** Allocate a new connection
- *
- * After the connection has been allocated, it should be started with a call to #fr_connection_signal_init.
- *
- * The connection state machine can detect when the connection is open in one of two ways.
- * If an open callback is provided, then once the init phase is complete, the connection state machine
- * will install an I/O handler to determine when the fd is writable (and therefor open).
- *
- * If an open callback is not provided, then once the init phase is complete, the connection state
- * machine should be signalled by calling #fr_connection_signal_open.  This allows the connection state
- * machine to work with more difficult library APIs, which may not return control to the caller as
- * connections are opened.
+/** Initial state of the connection
  *
- * @note If the init callback does not provide the file descriptor, then the file descriptor must be provided
- * via #fr_connection_set_fd, and #fr_connection_signal_open called.
+ * Calls the init function we were passed to allocate a library specific handle or
+ * file descriptor.
  *
- * @param[in] ctx              to allocate connection handle in.  If the connection
- *                             handle is freed, and the #fr_connection_state_t is
- *                             #FR_CONNECTION_STATE_CONNECTING or #FR_CONNECTION_STATE_CONNECTED the
- *                             close callback will be called.
- * @param[in] el               to use for timer events, and to pass to the #fr_connection_open_t callback.
- * @param[in] connection_timeout       (optional) how long to wait for a connection to open.
- * @param[in] reconnection_delay       How long to wait on connection failure before retrying.
- * @param[in] init             (optional) callback to initialise a new file descriptor.
- * @param[in] open             (optional) callback to receive notifications that the connection is open.
- * @param[in] close            (optional) Callback to close the connection.
- * @param[in] log_prefix       To prepend to log messages.
- * @param[in] uctx             User context to pass to callbacks.
- * @return
- *     - A new #fr_connection_t on success.
- *     - NULL on failure.
+ * @param[in] conn     To initialise.
+ * @param[in] now      The current time.
  */
-fr_connection_t *fr_connection_alloc(TALLOC_CTX *ctx, fr_event_list_t *el,
-                                    fr_time_delta_t connection_timeout,
-                                    fr_time_delta_t reconnection_delay,
-                                    fr_connection_init_t init, fr_connection_open_t open, fr_connection_close_t close,
-                                    char const *log_prefix,
-                                    void *uctx)
+static void connection_state_init_enter(fr_connection_t *conn, fr_time_t now)
 {
-       fr_connection_t *conn;
+       fr_connection_state_t   ret;
+       size_t                  i;
 
-       rad_assert(el);
+       rad_assert((conn->state == FR_CONNECTION_STATE_HALTED) || (conn->state == FR_CONNECTION_STATE_FAILED));
 
-       conn = talloc_zero(ctx, fr_connection_t);
-       if (!conn) return NULL;
-       talloc_set_destructor(conn, _connection_free);
+       STATE_TRANSITION(FR_CONNECTION_STATE_INIT);
 
-       conn->id = atomic_fetch_add_explicit(&connection_counter, 1, memory_order_relaxed);
-       conn->state = FR_CONNECTION_STATE_HALTED;
-       conn->el = el;
-       conn->fd = -1;
-       conn->reconnection_delay = reconnection_delay;
-       conn->connection_timeout = connection_timeout;
-       conn->init = init;
-       conn->open = open;
-       conn->close = close;
-       conn->log_prefix = talloc_typed_strdup(conn, log_prefix);
-       conn->uctx = uctx;
+       for (i = 0; i < NUM_ELEMENTS(conn->watch_pre); i++) {
+               fr_dlist_talloc_init(&conn->watch_pre[i], fr_connection_watch_entry_t, list);
+       }
+       for (i = 0; i < NUM_ELEMENTS(conn->watch_post); i++) {
+               fr_dlist_talloc_init(&conn->watch_post[i], fr_connection_watch_entry_t, list);
+       }
 
-       return conn;
-}
+       /*
+        *      If we have an init callback, call it.
+        */
+       HANDLER_BEGIN(conn);
+       WATCH_PRE(conn);
+       if (conn->init) {
+               ret = conn->init(&conn->h, conn, conn->uctx);
+       } else {
+               ret = FR_CONNECTION_STATE_CONNECTING;
+       }
+       WATCH_POST(conn);
+       HANDLER_END(conn);
 
-/** Set an (optional) callback to be called on connection timeout/failure
- *
- */
-void fr_connection_failed_func(fr_connection_t *conn, fr_connection_failed_t func)
-{
-       conn->failed = func;
+       switch (ret) {
+       case FR_CONNECTION_STATE_CONNECTING:
+               conn->is_closed = false;        /* We now have a handle */
+               connection_state_connecting_enter(conn, now);
+               return;
+
+       /*
+        *      Initialisation callback failed
+        */
+       case FR_CONNECTION_STATE_FAILED:
+       default:
+               PERROR("Connection initialisation failed");
+               connection_state_failed_enter(conn, now);
+               break;
+       }
 }
 
 /** Asynchronously signal a halted connection to start
@@ -477,7 +672,7 @@ void fr_connection_signal_init(fr_connection_t *conn)
 {
        switch (conn->state) {
        case FR_CONNECTION_STATE_HALTED:
-               connection_state_init(conn, fr_time());
+               connection_state_init_enter(conn, fr_time());
                return;
 
        default:
@@ -495,14 +690,14 @@ void fr_connection_signal_init(fr_connection_t *conn)
  * signal that the transition has occurred.
  *
  */
-void fr_connection_signal_open(fr_connection_t *conn)
+void fr_connection_signal_connected(fr_connection_t *conn)
 {
        rad_assert(!conn->open);        /* Use one or the other not both! */
 
        switch (conn->state) {
        case FR_CONNECTION_STATE_CONNECTING:
                DEBUG2("Connection established");
-               STATE_TRANSITION(FR_CONNECTION_STATE_CONNECTED);
+               connection_state_connected_enter(conn, fr_time());
                return;
 
        default:
@@ -532,7 +727,121 @@ void fr_connection_signal_reconnect(fr_connection_t *conn)
        case FR_CONNECTION_STATE_CONNECTED:
        case FR_CONNECTION_STATE_TIMEOUT:
                DEBUG2("Reconnecting...");
-               connection_state_failed(conn, fr_time());
+               connection_state_failed_enter(conn, fr_time());
+               return;
+
+       case FR_CONNECTION_STATE_MAX:
+               rad_assert(0);
                return;
        }
 }
+
+/** Receive an error notification when we're connecting a socket
+ *
+ * @param[in] el       event list the I/O event occurred on.
+ * @param[in] fd       the I/O even occurred for.
+ * @param[in] flags    from_kevent.
+ * @param[in] fd_errno from kevent.
+ * @param[in] uctx     The #fr_connection_t this fd is associated with.
+ */
+static void _connection_error(UNUSED fr_event_list_t *el, int fd, UNUSED int flags, int fd_errno, void *uctx)
+{
+       fr_connection_t *conn = talloc_get_type_abort(uctx, fr_connection_t);
+
+       ERROR("Connection failed for fd (%u): %s", fd, fr_syserror(fd_errno));
+       connection_state_failed_enter(conn, fr_time());
+}
+
+/** Receive a write notification after a socket is connected
+ *
+ * @param[in] el       event list the I/O event occurred on.
+ * @param[in] fd       the I/O even occurred for.
+ * @param[in] flags    from kevent.
+ * @param[in] uctx     The #fr_connection_t this fd is associated with.
+ */
+static void _connection_writable(fr_event_list_t *el, int fd, UNUSED int flags, void *uctx)
+{
+       fr_connection_t         *conn = talloc_get_type_abort(uctx, fr_connection_t);
+
+       fr_event_fd_delete(el, fd, FR_EVENT_FILTER_IO);
+       connection_state_connected_enter(conn, fr_time());
+}
+
+/** Remove the FD we were watching for connection open/fail from the event loop
+ *
+ */
+static void _connection_signal_on_fd_cleanup(fr_connection_t *conn, fr_connection_state_t state, void *uctx)
+{
+       int fd = *((int *)uctx);
+
+       /*
+        *      Two states can trigger a cleanup
+        *      Remove the watch on the one that didn't
+        */
+       switch (state) {
+       case FR_CONNECTION_STATE_FAILED:
+               fr_connection_del_watch_pre(conn, FR_CONNECTION_STATE_CONNECTED, _connection_signal_on_fd_cleanup);
+               break;
+
+       case FR_CONNECTION_STATE_CONNECTED:
+               fr_connection_del_watch_pre(conn, FR_CONNECTION_STATE_FAILED, _connection_signal_on_fd_cleanup);
+               break;
+
+       default:
+               rad_assert(0);
+               break;
+       }
+
+       fr_event_fd_delete(conn->el, fd, FR_EVENT_FILTER_IO);
+       talloc_free(uctx);
+}
+
+/** Setup the connection to change states to connected or failed based on I/O events
+ *
+ * Will automatically cleanup after itself, in preparation for
+ * new I/O handlers to be installed in the open() callback.
+ *
+ * @return
+ *     - 0 on success.
+ *     - -1 on failure.
+ */
+int fr_connection_signal_on_fd(fr_connection_t *conn, int fd)
+{
+       fr_time_t       now = fr_time();
+       int             *fd_s;
+
+       /*
+        *      If connection becomes writable we
+        *      assume it's open.
+        */
+       if (fr_event_fd_insert(conn, conn->el, conn->h,
+                              NULL,
+                              _connection_writable,
+                              _connection_error,
+                              conn) < 0) {
+               PERROR("Failed inserting fd (%u) into event loop %p",
+                      fd, conn->el);
+               connection_state_failed_enter(conn, now);
+               return -1;
+       }
+
+       /*
+        *      Stop the static analysis tools
+        *      complaining about assigning ints
+        *      to pointers.
+        */
+       MEM(fd_s = talloc_zero(conn, int));
+       *fd_s = fd;
+
+       /*
+        *      Add a oneshot watcher to remove
+        *      the I/O handlers if the connection
+        *      fails, or is connected.
+        */
+       fr_connection_add_watch_pre(conn, FR_CONNECTION_STATE_FAILED,
+                                   _connection_signal_on_fd_cleanup, true, fd_s);
+       fr_connection_add_watch_pre(conn, FR_CONNECTION_STATE_CONNECTED,
+                                   _connection_signal_on_fd_cleanup, true, fd_s);
+
+       return 0;
+}
index 65a435be6f3f96fd396a5330470c5b36679e4461..eda0438f39c6849d6b6b045c98c266420cec7996 100644 (file)
@@ -43,9 +43,12 @@ typedef enum {
        FR_CONNECTION_STATE_CONNECTING,         //!< Waiting for connection to establish.
        FR_CONNECTION_STATE_TIMEOUT,            //!< Timeout during #FR_CONNECTION_STATE_CONNECTING.
        FR_CONNECTION_STATE_CONNECTED,          //!< File descriptor is open (ready for writing).
-       FR_CONNECTION_STATE_FAILED              //!< Connection failed and is waiting to reconnect.
+       FR_CONNECTION_STATE_FAILED,             //!< Connection failed and is waiting to reconnect.
+       FR_CONNECTION_STATE_MAX
 } fr_connection_state_t;
 
+typedef void(*fr_connection_watch_t)(fr_connection_t *conn, fr_connection_state_t state, void *uctx);
+
 extern fr_table_num_sorted_t const fr_connection_states[];
 extern size_t fr_connection_states_len;
 
@@ -53,13 +56,17 @@ extern size_t fr_connection_states_len;
  *
  * Should attempt to open a non-blocking connection and return it in fd_out.
  *
- * @param[out] fd_out  Where to write the new file descriptor.
+ * @param[out] h_out   Where to write the new handle.
+ * @param[in] conn     If integrating with a 3rd party library
+ *                     that will trigger connection API state transitions,
+ *                     the connection should be passed as the uctx argument
+ *                     for library I/O callbacks.
  * @param[in] uctx     User context.
  * @return
- *     - #FR_CONNECTION_STATE_CONNECTING       if a file descriptor was successfully created.
- *     - #FR_CONNECTION_STATE_FAILED           if we could not open a file descriptor.
+ *     - #FR_CONNECTION_STATE_CONNECTING       if a handle was successfully created.
+ *     - #FR_CONNECTION_STATE_FAILED           if we could not create a handle.
  */
-typedef fr_connection_state_t (*fr_connection_init_t)(int *fd_out, void *uctx);
+typedef fr_connection_state_t (*fr_connection_init_t)(void **h_out, fr_connection_t *conn, void *uctx);
 
 /** Notification that the connection is now open
  *
@@ -67,19 +74,19 @@ typedef fr_connection_state_t (*fr_connection_init_t)(int *fd_out, void *uctx);
  * to call other code if it becomes readable or writable.
  *
  * @param[in] el       to use for inserting I/O events.
- * @param[in] fd       That was successfully opened.
+ * @param[in] h                Handle that was successfully opened.
  * @param[in] uctx     User context.
  * @return
- *     - #FR_CONNECTION_STATE_CONNECTED        if the file descriptor is useable.
- *     - #FR_CONNECTION_STATE_FAILED           if the file descriptor is unusable.
+ *     - #FR_CONNECTION_STATE_CONNECTED        if the handle is useable.
+ *     - #FR_CONNECTION_STATE_FAILED           if the handle is unusable.
  */
-typedef fr_connection_state_t (*fr_connection_open_t)(fr_event_list_t *el, int fd, void *uctx);
+typedef fr_connection_state_t (*fr_connection_open_t)(fr_event_list_t *el, void *h, void *uctx);
 
 /** Notification that a connection attempt has failed
  *
  * @note If the callback frees the connection, it must return #FR_CONNECTION_STATE_HALTED.
  *
- * @param[in] fd       That was successfully opened.
+ * @param[in] h                Handle that failed.
  * @param[in] state    the connection was in when it failed. Usually one of:
  *                     - #FR_CONNECTION_STATE_CONNECTING       the connection attempt explicitly failed.
  *                     - #FR_CONNECTION_STATE_CONNECTED        something called #fr_connection_signal_reconnect.
@@ -91,7 +98,7 @@ typedef fr_connection_state_t (*fr_connection_open_t)(fr_event_list_t *el, int f
  *                                             attempts Can be restarted with
  *                                             #fr_connection_signal_init().
  */
-typedef fr_connection_state_t (*fr_connection_failed_t)(int fd, fr_connection_state_t state, void *uctx);
+typedef fr_connection_state_t (*fr_connection_failed_t)(void *h, fr_connection_state_t state, void *uctx);
 
 /** Notification that the connection has errored and must be closed
  *
@@ -101,27 +108,40 @@ typedef fr_connection_state_t (*fr_connection_failed_t)(int fd, fr_connection_st
  * If this callback does not close the file descriptor, the server will leak
  * file descriptors.
  *
- * @param[in] fd       to close.
+ * @param[in]        to close.
  * @param[in] uctx     User context.
  */
-typedef void (*fr_connection_close_t)(int fd, void *uctx);
+typedef void (*fr_connection_close_t)(void *h, void *uctx);
+
+fr_event_list_t                *fr_connection_get_el(fr_connection_t const *conn);
+
+int                    fr_connection_get_handle(fr_connection_t const *conn);
+void                   fr_connection_set_handle(fr_connection_t *conn, void *handle);
+
+void                   fr_connection_set_failed_func(fr_connection_t *conn, fr_connection_failed_t func);
+
+void                   fr_connection_add_watch_pre(fr_connection_t *conn, fr_connection_state_t state,
+                                                   fr_connection_watch_t watch, bool oneshot, void const *uctx);
+void                   fr_connection_add_watch_post(fr_connection_t *conn, fr_connection_state_t state,
+                                                    fr_connection_watch_t watch, bool oneshot, void const *uctx);
+
+int                    fr_connection_del_watch_pre(fr_connection_t *conn, fr_connection_state_t state,
+                                                   fr_connection_watch_t watch);
+int                    fr_connection_del_watch_post(fr_connection_t *conn, fr_connection_state_t state,
+                                                    fr_connection_watch_t watch);
 
 fr_connection_t                *fr_connection_alloc(TALLOC_CTX *ctx, fr_event_list_t *el,
                                             fr_time_delta_t connection_timeout,
                                             fr_time_delta_t reconnection_delay,
                                             fr_connection_init_t init, fr_connection_open_t open,
                                             fr_connection_close_t close,
-                                            char const *log_prefix, void *uctx);
+                                            char const *log_prefix, void const *uctx);
 
-void                   fr_connection_failed_func(fr_connection_t *conn, fr_connection_failed_t func);
 void                   fr_connection_signal_init(fr_connection_t *conn);
-void                   fr_connection_signal_open(fr_connection_t *conn);
+void                   fr_connection_signal_connected(fr_connection_t *conn);
 void                   fr_connection_signal_reconnect(fr_connection_t *conn);
 
-fr_event_list_t                *fr_connection_get_el(fr_connection_t const *conn);
-int                    fr_connection_get_fd(fr_connection_t const *conn);
-void                   fr_connection_set_fd(fr_connection_t *conn, int fd);
-
+int                    fr_connection_signal_on_fd(fr_connection_t *conn, int fd);
 #ifdef __cplusplus
 }
 #endif
index 9f979ee0db0c4727ef48a38a1a3b5abbc8593d8e..ed0f0bfc972a20af6b4e58f89aae3eaee3ab26d6 100644 (file)
@@ -316,8 +316,8 @@ static void _logtee_conn_writable(UNUSED fr_event_list_t *el, int sock, UNUSED i
  */
 static void logtee_fd_idle(rlm_logtee_thread_t *t)
 {
-       DEBUG3("Marking socket (%i) as idle", fr_connection_get_fd(t->conn));
-       if (fr_event_fd_insert(t->conn, t->el, fr_connection_get_fd(t->conn),
+       DEBUG3("Marking socket (%i) as idle", fr_connection_get_handle(t->conn));
+       if (fr_event_fd_insert(t->conn, t->el, fr_connection_get_handle(t->conn),
                               _logtee_conn_read,
                               NULL,
                               _logtee_conn_error,
@@ -334,8 +334,8 @@ static void logtee_fd_idle(rlm_logtee_thread_t *t)
  */
 static void logtee_fd_active(rlm_logtee_thread_t *t)
 {
-       DEBUG3("Marking socket (%i) as active - Draining requests", fr_connection_get_fd(t->conn));
-       if (fr_event_fd_insert(t->conn, t->el, fr_connection_get_fd(t->conn),
+       DEBUG3("Marking socket (%i) as active - Draining requests", fr_connection_get_handle(t->conn));
+       if (fr_event_fd_insert(t->conn, t->el, fr_connection_get_handle(t->conn),
                               _logtee_conn_read,
                               _logtee_conn_writable,
                               _logtee_conn_error,
@@ -347,8 +347,10 @@ static void logtee_fd_active(rlm_logtee_thread_t *t)
 /** Shutdown/close a file descriptor
  *
  */
-static void _logtee_conn_close(int fd, UNUSED void *uctx)
+static void _logtee_conn_close(void *h, UNUSED void *uctx)
 {
+       int     fd = *((int *)h);
+
        DEBUG3("Closing socket (%i)", fd);
        if (shutdown(fd, SHUT_RDWR) < 0) DEBUG3("Shutdown on socket (%i) failed: %s", fd, fr_syserror(errno));
        if (close(fd) < 0) DEBUG3("Closing socket (%i) failed: %s", fd, fr_syserror(errno));
@@ -357,7 +359,7 @@ static void _logtee_conn_close(int fd, UNUSED void *uctx)
 /** Process notification that fd is open
  *
  */
-static fr_connection_state_t _logtee_conn_open(UNUSED fr_event_list_t *el, UNUSED int fd, void *uctx)
+static fr_connection_state_t _logtee_conn_open(UNUSED fr_event_list_t *el, UNUSED void *h, void *uctx)
 {
        rlm_logtee_thread_t     *t = talloc_get_type_abort(uctx, rlm_logtee_thread_t);
 
@@ -377,14 +379,16 @@ static fr_connection_state_t _logtee_conn_open(UNUSED fr_event_list_t *el, UNUSE
 
 /** Initialise a new outbound connection
  *
- * @param[out] fd_out  Where to write the new file descriptor.
+ * @param[out] h_out   Where to write the new file descriptor.
+ * @param[in] conn     being initialised.
  * @param[in] uctx     A #rlm_logtee_thread_t.
  */
-static fr_connection_state_t _logtee_conn_init(int *fd_out, void *uctx)
+static fr_connection_state_t _logtee_conn_init(void **h_out, fr_connection_t *conn, void *uctx)
 {
        rlm_logtee_thread_t     *t = talloc_get_type_abort(uctx, rlm_logtee_thread_t);
        rlm_logtee_t const      *inst = t->inst;
        int                     fd = -1;
+       int                     *fd_s;
 
        switch (inst->log_dst) {
        case LOGTEE_DST_UNIX:
@@ -416,7 +420,14 @@ static fr_connection_state_t _logtee_conn_init(int *fd_out, void *uctx)
                return FR_CONNECTION_STATE_FAILED;
        }
 
-       *fd_out = fd;
+       /*
+        *      Avoid pointer/integer assignments
+        */
+       MEM(fd_s = talloc(conn, int));
+       *fd_s = fd;
+       *h_out = fd_s;
+
+       fr_connection_signal_on_fd(conn, fd);
 
        return FR_CONNECTION_STATE_CONNECTING;
 }
index 25294dd43c5245bc10754689ee7d2880345c2ee6..7bdb86ee82528ac9e6d76a084ec22e2c2085a286 100644 (file)
@@ -505,8 +505,9 @@ static void conn_error(UNUSED fr_event_list_t *el, UNUSED int fd, UNUSED int fla
 /** Shutdown/close a file descriptor
  *
  */
-static void _conn_close(int fd, void *uctx)
+static void _conn_close(void *h, void *uctx)
 {
+       int fd = *((int *)h);
        fr_io_connection_t *c = talloc_get_type_abort(uctx, fr_io_connection_t);
 
        if (c->idle_ev) fr_event_timer_delete(c->thread->el, &c->idle_ev);
@@ -534,12 +535,14 @@ static void _conn_close(int fd, void *uctx)
 
 /** Initialise a new outbound connection
  *
- * @param[out] fd_out  Where to write the new file descriptor.
+ * @param[out] h_out   Where to write the new file descriptor.
+ * @param[in] conn     to initialise.
  * @param[in] uctx     A #fr_io_connection_thread_t.
  */
-static fr_connection_state_t _conn_init(int *fd_out, void *uctx)
+static fr_connection_state_t _conn_init(void **h_out, fr_connection_t *conn, void *uctx)
 {
        int                             fd;
+       int                             *fd_s;
        fr_io_connection_t              *c = talloc_get_type_abort(uctx, fr_io_connection_t);
 
        /*
@@ -589,12 +592,16 @@ static fr_connection_state_t _conn_init(int *fd_out, void *uctx)
        conn_transition(c, CONN_OPENING);
        c->fd = fd;
 
+       fr_connection_signal_on_fd(conn, fd);
+
+       fd_s = talloc(c, int);
+       *fd_s = fd;
+       *h_out = fd_s;
+
        // @todo - initialize the tracking memory, etc.
        // i.e. histograms (or hyperloglog) of packets, so we can see
        // which connections / home servers are fast / slow.
 
-       *fd_out = fd;
-
        return FR_CONNECTION_STATE_CONNECTING;
 }
 
@@ -2481,11 +2488,11 @@ static int status_udp_request_free(fr_io_request_t *u)
 
 /** Connection failed
  *
- * @param[in] fd       of connection that failed.
+ * @param[in] h                of connection that failed.
  * @param[in] state    the connection was in when it failed.
  * @param[in] uctx     the connection.
  */
-static fr_connection_state_t _conn_failed(UNUSED int fd, fr_connection_state_t state, void *uctx)
+static fr_connection_state_t _conn_failed(UNUSED void *h, fr_connection_state_t state, void *uctx)
 {
        fr_io_connection_t      *c = talloc_get_type_abort(uctx, fr_io_connection_t);
 
@@ -2537,8 +2544,9 @@ static fr_connection_state_t _conn_failed(UNUSED int fd, fr_connection_state_t s
 /** Process notification that fd is open
  *
  */
-static fr_connection_state_t _conn_open(UNUSED fr_event_list_t *el, int fd, void *uctx)
+static fr_connection_state_t _conn_open(UNUSED fr_event_list_t *el, void *h, void *uctx)
 {
+       int                             fd = *((int *)h);
        fr_io_connection_t              *c = talloc_get_type_abort(uctx, fr_io_connection_t);
        fr_io_connection_thread_t       *t = c->thread;
        rlm_radius_udp_connection_t     *radius = c->ctx;
@@ -2706,8 +2714,8 @@ static fr_connection_state_t _conn_open(UNUSED fr_event_list_t *el, int fd, void
  */
 static void conn_alloc(rlm_radius_udp_t *inst, fr_io_connection_thread_t *t)
 {
-       fr_io_connection_t      *c;
-       rlm_radius_udp_connection_t *radius;
+       fr_io_connection_t              *c;
+       rlm_radius_udp_connection_t     *radius;
 
        c = talloc_zero(t, fr_io_connection_t);
        c->module_name = inst->parent->name;
@@ -2763,7 +2771,7 @@ static void conn_alloc(rlm_radius_udp_t *inst, fr_io_connection_thread_t *t)
                           c->module_name);
                return;
        }
-       fr_connection_failed_func(c->conn, _conn_failed);
+       fr_connection_set_failed_func(c->conn, _conn_failed);
 
        /*
         *      Enforce max_connections via atomic variables.