]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
update fr_bio_fd_connect()
authorAlan T. DeKok <aland@freeradius.org>
Thu, 14 Nov 2024 18:46:07 +0000 (13:46 -0500)
committerAlan T. DeKok <aland@freeradius.org>
Fri, 15 Nov 2024 13:53:36 +0000 (08:53 -0500)
so that it takes and uses callbacks for connections and timeouts.

src/lib/bio/fd.c
src/lib/bio/fd.h
src/lib/bio/fd_priv.h

index 838f3396e594b4bf28dfd2da7f1cbdd5efb4ec73..c35f9750c4cc15933027e7ee7cbbf817018598c4 100644 (file)
@@ -102,6 +102,16 @@ static int fr_bio_fd_destructor(fr_bio_fd_t *my)
 
        if (!my->info.eof && my->cb.eof) my->cb.eof(&my->bio);
 
+       if (my->connect_ev) {
+               talloc_const_free(my->connect_ev);
+               my->connect_ev = NULL;
+       }
+
+       if (my->connect_el) {
+               (void) fr_event_fd_delete(my->connect_el, my->info.socket.fd, FR_EVENT_FILTER_IO);
+               my->connect_el = NULL;
+       }
+
        if (my->cb.shutdown) my->cb.shutdown(&my->bio);
 
        return fr_bio_fd_close(&my->bio);
@@ -1106,52 +1116,204 @@ retry:
        return 0;
 }
 
+/** FD error when trying to connect, give up on the BIO.
+ *
+ */
+static void fr_bio_fd_el_error(UNUSED fr_event_list_t *el, UNUSED int fd, UNUSED int flags, int fd_errno, void *uctx)
+{
+       fr_bio_fd_t *my = talloc_get_type_abort(uctx, fr_bio_fd_t);
+
+       if (my->connect_error) {
+               my->connect_error(&my->bio, fd_errno);
+       }
+
+       fr_bio_shutdown(&my->bio);
+}
+
+/** Connect callback for when the socket is writable.
+ *
+ *  We try to connect the socket, and if so, call the application which should update the BIO status.
+ */
+static void fr_bio_fd_el_connect(NDEBUG_UNUSED fr_event_list_t *el, NDEBUG_UNUSED int fd, NDEBUG_UNUSED int flags, void *uctx)
+{
+       fr_bio_fd_t *my = talloc_get_type_abort(uctx, fr_bio_fd_t);
+
+       fr_assert(my->info.type == FR_BIO_FD_CONNECTED);
+       fr_assert(my->info.state == FR_BIO_FD_STATE_CONNECTING);
+       fr_assert(my->connect_el == el); /* and not NULL */
+       fr_assert(my->connect_success != NULL);
+       fr_assert(my->info.socket.fd == fd);
+
+#ifndef NDEBUG
+       /*
+        *      This check shouldn't be necessary, as we have a kqeueue error callback.  That should be called
+        *      when there's a connect error.
+        */
+       {
+               int error;
+               socklen_t socklen = sizeof(error);
+
+               /*
+                *      The socket is writeable.  Let's see if there's an error.
+                *
+                *      Unix Network Programming says:
+                *
+                *      ""If so_error is nonzero when the process calls write, -1 is returned with errno set to the
+                *      value of SO_ERROR (p. 495 of TCPv2) and SO_ERROR is reset to 0.  We have to check for the
+                *      error, and if there's no error, set the state to "open". ""
+                *
+                *      The same applies to connect().  If a non-blocking connect returns INPROGRESS, it may later
+                *      become writable.  It will be writable even if the connection fails.  Rather than writing some
+                *      random application data, we call SO_ERROR, and get the underlying error.
+                */
+               if (getsockopt(my->info.socket.fd, SOL_SOCKET, SO_ERROR, (void *)&error, &socklen) < 0) {
+                       fr_bio_fd_el_error(el, fd, flags, errno, uctx);
+                       return;
+               }
+
+               fr_assert(error == 0);
+
+               /*
+                *      There was an error, we call the error handler.
+                */
+               if (error) {
+                       fr_bio_fd_el_error(el, fd, flags, error, uctx);
+                       return;
+               }
+       }
+#endif
+
+       /*
+        *      Try to connect it.  Any magic handling is done in the callbacks.
+        */
+       if (fr_bio_fd_try_connect(my) < 0) return;
+
+       fr_assert(my->connect_success);
+
+       if (my->connect_ev) {
+               talloc_const_free(my->connect_ev);
+               my->connect_ev = NULL;
+       }
+       my->connect_el = NULL;
+
+       /*
+        *      This function MUST change the read/write/error callbacks for the FD.
+        */
+       my->connect_success(&my->bio);
+}
+
+/**  We have a timeout on the conenction
+ *
+ */
+static void fr_bio_fd_el_timeout(UNUSED fr_event_list_t *el, UNUSED fr_time_t now, void *uctx)
+{
+       fr_bio_fd_t *my = talloc_get_type_abort(uctx, fr_bio_fd_t);
+
+       fr_assert(my->connect_timeout);
+
+       my->connect_timeout(&my->bio);
+
+       fr_bio_shutdown(&my->bio);
+}
+
+
 /** Finalize a connect()
  *
  *  connect() said "come back when the socket is writeable".  It's now writeable, so we check if there was a
  *  connection error.
+ *
+ *  @param bio         the binary IO handler
+ *  @param el          the event list
+ *  @param connected_cb        callback to run when the BIO is connected
+ *  @param error_cb    callback to run when the FD has an error
+ *  @param timeout     when to time out the connect() attempt
+ *  @param timeout_cb  to call when the timeout runs.
+ *  @return
+ *     - <0 on error
+ *     - 0 for "try again later".  If callbacks are set, the callbacks will try again.  Otherwise the application has to try again.
+ *     - 1 for "we are now connected".
  */
-int fr_bio_fd_connect(fr_bio_t *bio)
+int fr_bio_fd_connect_full(fr_bio_t *bio, fr_event_list_t *el, fr_bio_callback_t connected_cb,
+                          fr_bio_fd_connect_error_t error_cb,
+                          fr_time_delta_t *timeout, fr_bio_callback_t timeout_cb)
 {
-       int error;
-       socklen_t socklen = sizeof(error);
        fr_bio_fd_t *my = talloc_get_type_abort(bio, fr_bio_fd_t);
 
-       if (my->info.state == FR_BIO_FD_STATE_OPEN) return 0;
+       /*
+        *      We shouldn't be connected an unconnected socket.
+        */
+       if (my->info.type == FR_BIO_FD_UNCONNECTED) {
+       error:
+               fr_bio_shutdown(&my->bio);
+               return fr_bio_error(GENERIC);
+       }
 
        /*
-        *      The caller may just call us without caring about the underlying bio.
+        *      The initial open may have succeeded in connecting the socket.  In which case we just run the
+        *      callbacks and return.
+        */
+       if (my->info.state == FR_BIO_FD_STATE_OPEN) {
+       connected:
+               if (connected_cb) connected_cb(bio);
+
+               return 1;
+       }
+
+       /*
+        *      The caller may just call us without caring about what the underlying BIO is.  In which case we
+        *      need to be safe.
         */
        if ((my->info.socket.af == AF_FILE_BIO) || (my->info.type == FR_BIO_FD_ACCEPT)) {
                fr_bio_fd_set_open(my);
-               return 0;
+               goto connected;
        }
 
-       if (my->info.state != FR_BIO_FD_STATE_CONNECTING) return fr_bio_error(GENERIC);
+       /*
+        *      It must be in the connecting state, i.e. not INVALID or CLOSED.
+        */
+       if (my->info.state != FR_BIO_FD_STATE_CONNECTING) goto error;
 
        /*
-        *      The socket is writeable.  Let's see if there's an error.
-        *
-        *      Unix Network Programming says:
-        *
-        *      ""If so_error is nonzero when the process calls write, -1 is returned with errno set to the
-        *      value of SO_ERROR (p. 495 of TCPv2) and SO_ERROR is reset to 0.  We have to check for the
-        *      error, and if there's no error, set the state to "open". ""
-        *
-        *      The same applies to connect().  If a non-blocking connect returns INPROGRESS, it may later
-        *      become writable.  It will be writable even if the connection fails.  Rather than writing some
-        *      random application data, we call SO_ERROR, and get the underlying error.
+        *      No callback
         */
-       if (getsockopt(my->info.socket.fd, SOL_SOCKET, SO_ERROR, (void *)&error, &socklen) < 0) {
-       fail:
-               fr_bio_shutdown(bio);
-               return fr_bio_error(IO);
+       if (!connected_cb) {
+               ssize_t rcode;
+
+               rcode = fr_bio_fd_try_connect(my);
+               if (rcode < 0) return rcode; /* it already called shutdown */
+
+               return 1;
        }
 
        /*
-        *      The socket is connected, so initialize the normal IO handlers.
+        *      It's not connected, the caller has to try again.
         */
-       if (fr_bio_fd_init_common(my) < 0) goto fail;
+       if (!el) return 0;
+
+       /*
+        *      Set the callbacks to run when something happens.
+        */
+       my->connect_success = connected_cb;
+       my->connect_error = error_cb;
+       my->connect_timeout = timeout_cb;
+
+       /*
+        *      Set the timeout callback if asked.
+        */
+       if (timeout_cb) {
+               if (fr_event_timer_in(my, el, &my->connect_ev, *timeout, fr_bio_fd_el_timeout, my) < 0) {
+                       goto error;
+               }
+       }
+
+       /*
+        *      Set the FD callbacks, and tell the caller that we're not connected.
+        */
+       if (fr_event_fd_insert(my, NULL, el, my->info.socket.fd, NULL,
+                              fr_bio_fd_el_connect, fr_bio_fd_el_error, my) < 0) {
+               goto error;
+       }
+       my->connect_el = el;
 
        return 0;
 }
index b44fb1cb44535aac23b938ac33b9c0c06ef902f5..21fedadf3858accd3e67e76f921f591f4068d376 100644 (file)
@@ -28,6 +28,7 @@ RCSIDH(lib_bio_fd_h, "$Id$")
 
 #include <freeradius-devel/bio/base.h>
 #include <freeradius-devel/util/socket.h>
+#include <freeradius-devel/util/event.h>
 
 #include <fcntl.h>
 
@@ -119,11 +120,17 @@ typedef struct {
        fr_bio_fd_config_t const *cfg;  //!< so we know what was asked, vs what was granted.
 } fr_bio_fd_info_t;
 
+typedef void (*fr_bio_fd_connect_error_t)(fr_bio_t *bio, int fd_errno);
+
 fr_bio_t       *fr_bio_fd_alloc(TALLOC_CTX *ctx, fr_bio_fd_config_t const *cfg, size_t offset) CC_HINT(nonnull(1));
 
 int            fr_bio_fd_close(fr_bio_t *bio) CC_HINT(nonnull);
 
-int            fr_bio_fd_connect(fr_bio_t *bio) CC_HINT(nonnull);
+int            fr_bio_fd_connect_full(fr_bio_t *bio, fr_event_list_t *el,
+                                      fr_bio_callback_t connected_cb, fr_bio_fd_connect_error_t error_cb,
+                                      fr_time_delta_t *timeout, fr_bio_callback_t timeout_cb) CC_HINT(nonnull(1));
+
+#define fr_bio_fd_connect(_x) fr_bio_fd_connect_full(_x, NULL, NULL, NULL, NULL, NULL)
 
 fr_bio_fd_info_t const *fr_bio_fd_info(fr_bio_t *bio) CC_HINT(nonnull);
 
index 704fdf55a81fd3907f68ad9aeabbc1f3f2c58c28..b839ff7b6a4bc94fd894da5a6012b47adac74c4f 100644 (file)
@@ -38,6 +38,12 @@ typedef struct fr_bio_fd_s {
 
        fr_bio_fd_info_t  info;
 
+       fr_bio_callback_t  connect_success;     //!< for fr_bio_fd_connect()
+       fr_bio_fd_connect_error_t  connect_error;       //!< for fr_bio_fd_connect()
+       fr_bio_callback_t  connect_timeout;     //!< for fr_bio_fd_connect()
+       fr_event_list_t    *connect_el;         //!< for fr_bio_fd_connect()
+       fr_event_timer_t const *connect_ev;             //!< for fr_bio_fd_connect()
+
        int             max_tries;              //!< how many times we retry on EINTR
        size_t          offset;                 //!< where #fr_bio_fd_packet_ctx_t is stored