aboutsummaryrefslogtreecommitdiff
path: root/crypto/openssl/ssl/rio
diff options
context:
space:
mode:
Diffstat (limited to 'crypto/openssl/ssl/rio')
-rw-r--r--crypto/openssl/ssl/rio/build.info6
-rw-r--r--crypto/openssl/ssl/rio/poll_builder.c180
-rw-r--r--crypto/openssl/ssl/rio/poll_builder.h79
-rw-r--r--crypto/openssl/ssl/rio/poll_immediate.c478
-rw-r--r--crypto/openssl/ssl/rio/poll_method.h29
-rw-r--r--crypto/openssl/ssl/rio/rio_notifier.c379
6 files changed, 1151 insertions, 0 deletions
diff --git a/crypto/openssl/ssl/rio/build.info b/crypto/openssl/ssl/rio/build.info
new file mode 100644
index 000000000000..8e8ebaba4686
--- /dev/null
+++ b/crypto/openssl/ssl/rio/build.info
@@ -0,0 +1,6 @@
+$LIBSSL=../../libssl
+
+SOURCE[$LIBSSL]=poll_immediate.c
+IF[{- !$disabled{quic} -}]
+ SOURCE[$LIBSSL]=rio_notifier.c poll_builder.c
+ENDIF
diff --git a/crypto/openssl/ssl/rio/poll_builder.c b/crypto/openssl/ssl/rio/poll_builder.c
new file mode 100644
index 000000000000..bd9317a8b8bb
--- /dev/null
+++ b/crypto/openssl/ssl/rio/poll_builder.c
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2024-2025 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License"). You may not use
+ * this file except in compliance with the License. You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include "internal/safe_math.h"
+#include "poll_builder.h"
+
+OSSL_SAFE_MATH_UNSIGNED(size_t, size_t)
+
+int ossl_rio_poll_builder_init(RIO_POLL_BUILDER *rpb)
+{
+#if RIO_POLL_METHOD == RIO_POLL_METHOD_NONE
+ return 0;
+#elif RIO_POLL_METHOD == RIO_POLL_METHOD_SELECT
+ FD_ZERO(&rpb->rfd);
+ FD_ZERO(&rpb->wfd);
+ FD_ZERO(&rpb->efd);
+ rpb->hwm_fd = -1;
+#elif RIO_POLL_METHOD == RIO_POLL_METHOD_POLL
+ rpb->pfd_heap = NULL;
+ rpb->pfd_num = 0;
+ rpb->pfd_alloc = OSSL_NELEM(rpb->pfds);
+#endif
+ return 1;
+}
+
+void ossl_rio_poll_builder_cleanup(RIO_POLL_BUILDER *rpb)
+{
+ if (rpb == NULL)
+ return;
+
+#if RIO_POLL_METHOD == RIO_POLL_METHOD_POLL
+ OPENSSL_free(rpb->pfd_heap);
+#endif
+}
+
+#if RIO_POLL_METHOD == RIO_POLL_METHOD_POLL
+static int rpb_ensure_alloc(RIO_POLL_BUILDER *rpb, size_t alloc)
+{
+ struct pollfd *pfd_heap_new;
+ size_t total_size;
+ int error = 0;
+
+ if (alloc <= rpb->pfd_alloc)
+ return 1;
+
+ total_size = safe_mul_size_t(alloc, sizeof(struct pollfd), &error);
+ if (error)
+ return 0;
+
+ pfd_heap_new = OPENSSL_realloc(rpb->pfd_heap, total_size);
+ if (pfd_heap_new == NULL)
+ return 0;
+
+ if (rpb->pfd_heap == NULL) {
+ /* Copy the contents of the stacked array. */
+ memcpy(pfd_heap_new, rpb->pfds, sizeof(rpb->pfds));
+ }
+ rpb->pfd_heap = pfd_heap_new;
+ rpb->pfd_alloc = alloc;
+ return 1;
+}
+#endif
+
+int ossl_rio_poll_builder_add_fd(RIO_POLL_BUILDER *rpb, int fd,
+ int want_read, int want_write)
+{
+#if RIO_POLL_METHOD == RIO_POLL_METHOD_POLL
+ size_t i;
+ struct pollfd *pfds = (rpb->pfd_heap != NULL ? rpb->pfd_heap : rpb->pfds);
+ struct pollfd *pfd;
+#endif
+
+ if (fd < 0)
+ return 0;
+
+#if RIO_POLL_METHOD == RIO_POLL_METHOD_SELECT
+
+# ifndef OPENSSL_SYS_WINDOWS
+ /*
+ * On Windows there is no relevant limit to the magnitude of a fd value (see
+ * above). On *NIX the fd_set uses a bitmap and we must check the limit.
+ */
+ if (fd >= FD_SETSIZE)
+ return 0;
+# endif
+
+ if (want_read)
+ openssl_fdset(fd, &rpb->rfd);
+
+ if (want_write)
+ openssl_fdset(fd, &rpb->wfd);
+
+ openssl_fdset(fd, &rpb->efd);
+ if (fd > rpb->hwm_fd)
+ rpb->hwm_fd = fd;
+
+ return 1;
+
+#elif RIO_POLL_METHOD == RIO_POLL_METHOD_POLL
+ for (i = 0; i < rpb->pfd_num; ++i) {
+ pfd = &pfds[i];
+
+ if (pfd->fd == -1 || pfd->fd == fd)
+ break;
+ }
+
+ if (i >= rpb->pfd_alloc) {
+ if (!rpb_ensure_alloc(rpb, rpb->pfd_alloc * 2))
+ return 0;
+ pfds = rpb->pfd_heap;
+ }
+
+ assert((rpb->pfd_heap != NULL && rpb->pfd_heap == pfds) ||
+ (rpb->pfd_heap == NULL && rpb->pfds == pfds));
+ assert(i <= rpb->pfd_num && rpb->pfd_num <= rpb->pfd_alloc);
+ pfds[i].fd = fd;
+ pfds[i].events = 0;
+
+ if (want_read)
+ pfds[i].events |= POLLIN;
+ if (want_write)
+ pfds[i].events |= POLLOUT;
+
+ if (i == rpb->pfd_num)
+ ++rpb->pfd_num;
+
+ return 1;
+#endif
+}
+
+int ossl_rio_poll_builder_poll(RIO_POLL_BUILDER *rpb, OSSL_TIME deadline)
+{
+ int rc;
+
+#if RIO_POLL_METHOD == RIO_POLL_METHOD_SELECT
+ do {
+ struct timeval timeout, *p_timeout = &timeout;
+
+ /*
+ * select expects a timeout, not a deadline, so do the conversion.
+ * Update for each call to ensure the correct value is used if we repeat
+ * due to EINTR.
+ */
+ if (ossl_time_is_infinite(deadline))
+ p_timeout = NULL;
+ else
+ /*
+ * ossl_time_subtract saturates to zero so we don't need to check if
+ * now > deadline.
+ */
+ timeout = ossl_time_to_timeval(ossl_time_subtract(deadline,
+ ossl_time_now()));
+
+ rc = select(rpb->hwm_fd + 1, &rpb->rfd, &rpb->wfd, &rpb->efd, p_timeout);
+ } while (rc == -1 && get_last_socket_error_is_eintr());
+#elif RIO_POLL_METHOD == RIO_POLL_METHOD_POLL
+ do {
+ int timeout_ms;
+
+ if (ossl_time_is_infinite(deadline))
+ timeout_ms = -1;
+ else
+ timeout_ms = ossl_time2ms(ossl_time_subtract(deadline,
+ ossl_time_now()));
+
+ rc = poll(rpb->pfd_heap != NULL ? rpb->pfd_heap : rpb->pfds,
+ rpb->pfd_num, timeout_ms);
+ } while (rc == -1 && get_last_socket_error_is_eintr());
+#endif
+
+ return rc < 0 ? 0 : 1;
+}
diff --git a/crypto/openssl/ssl/rio/poll_builder.h b/crypto/openssl/ssl/rio/poll_builder.h
new file mode 100644
index 000000000000..0a03f89df74d
--- /dev/null
+++ b/crypto/openssl/ssl/rio/poll_builder.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2024-2025 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License"). You may not use
+ * this file except in compliance with the License. You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+#ifndef OSSL_POLL_BUILDER_H
+# define OSSL_POLL_BUILDER_H
+
+# include "poll_method.h"
+# include "internal/time.h"
+
+/*
+ * RIO_POLL_BUILDER
+ * ================
+ *
+ * RIO_POLL_BUILDER provides support for immediate-mode polling architectures.
+ * It abstracts OS-specific immediate-mode polling APIs such as select(2) and
+ * poll(2) and allows an arbitrarily large number of FDs to be polled for while
+ * providing minimal overhead (over the OS APIs themselves) for small numbers of
+ * FDs.
+ */
+typedef struct rio_poll_builder_st {
+# if RIO_POLL_METHOD == RIO_POLL_METHOD_NONE
+ int unused_dummy; /* make microsoft compiler happy */
+# elif RIO_POLL_METHOD == RIO_POLL_METHOD_SELECT
+ fd_set rfd, wfd, efd;
+ int hwm_fd;
+# elif RIO_POLL_METHOD == RIO_POLL_METHOD_POLL
+# define RIO_NUM_STACK_PFDS 32
+ struct pollfd *pfd_heap;
+ struct pollfd pfds[RIO_NUM_STACK_PFDS];
+ size_t pfd_num, pfd_alloc;
+# else
+# error Unknown RIO poll method
+# endif
+} RIO_POLL_BUILDER;
+
+/*
+ * Initialises a new poll builder.
+ *
+ * Returns 1 on success and 0 on failure.
+ */
+int ossl_rio_poll_builder_init(RIO_POLL_BUILDER *rpb);
+
+/*
+ * Tears down a poll builder, freeing any heap allocations (if any) which may
+ * have been made internally.
+ */
+void ossl_rio_poll_builder_cleanup(RIO_POLL_BUILDER *rpb);
+
+/*
+ * Adds a file descriptor to a poll builder. If want_read is 1, listens for
+ * readability events (POLLIN). If want_write is 1, listens for writability
+ * events (POLLOUT).
+ *
+ * If this is called with the same fd twice, the result equivalent to calling
+ * it one time with logically OR'd values of want_read and want_write.
+ *
+ * Returns 1 on success and 0 on failure.
+ */
+int ossl_rio_poll_builder_add_fd(RIO_POLL_BUILDER *rpb, int fd,
+ int want_read, int want_write);
+
+/*
+ * Polls the set of file descriptors added to a poll builder. deadline is a
+ * deadline time based on the ossl_time_now() clock or ossl_time_infinite() for
+ * no timeout. Returns 1 on success or 0 on failure.
+ */
+int ossl_rio_poll_builder_poll(RIO_POLL_BUILDER *rpb, OSSL_TIME deadline);
+
+/*
+ * TODO(RIO): No support currently for readout of what was readable/writeable as
+ * it is currently not needed.
+ */
+
+#endif
diff --git a/crypto/openssl/ssl/rio/poll_immediate.c b/crypto/openssl/ssl/rio/poll_immediate.c
new file mode 100644
index 000000000000..234ee81bdbb0
--- /dev/null
+++ b/crypto/openssl/ssl/rio/poll_immediate.c
@@ -0,0 +1,478 @@
+/*
+ * Copyright 2024-2025 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License"). You may not use
+ * this file except in compliance with the License. You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#include "internal/common.h"
+#include "internal/quic_ssl.h"
+#include "internal/quic_reactor_wait_ctx.h"
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include "../ssl_local.h"
+#include "poll_builder.h"
+
+#if defined(_AIX)
+/*
+ * Some versions of AIX define macros for events and revents for use when
+ * accessing pollfd structures (see Github issue #24236). That interferes
+ * with our use of these names here. We simply undef them.
+ */
+# undef revents
+# undef events
+#endif
+
+#define ITEM_N(items, stride, n) \
+ (*(SSL_POLL_ITEM *)((char *)(items) + (n)*(stride)))
+
+#define FAIL_FROM(n) \
+ do { \
+ size_t j; \
+ \
+ for (j = (n); j < num_items; ++j) \
+ ITEM_N(items, stride, j).revents = 0; \
+ \
+ ok = 0; \
+ goto out; \
+ } while (0)
+
+#define FAIL_ITEM(idx) \
+ do { \
+ size_t idx_ = (idx); \
+ \
+ ITEM_N(items, stride, idx_).revents = SSL_POLL_EVENT_F; \
+ ++result_count; \
+ FAIL_FROM(idx_ + 1); \
+ } while (0)
+
+#ifndef OPENSSL_NO_QUIC
+static int poll_translate_ssl_quic(SSL *ssl,
+ QUIC_REACTOR_WAIT_CTX *wctx,
+ RIO_POLL_BUILDER *rpb,
+ uint64_t events,
+ int *abort_blocking)
+{
+ BIO_POLL_DESCRIPTOR rd, wd;
+ int fd1 = -1, fd2 = -1, fd_nfy = -1;
+ int fd1_r = 0, fd1_w = 0, fd2_w = 0;
+
+ if (SSL_net_read_desired(ssl)) {
+ if (!SSL_get_rpoll_descriptor(ssl, &rd)) {
+ ERR_raise_data(ERR_LIB_SSL, SSL_R_POLL_REQUEST_NOT_SUPPORTED,
+ "SSL_poll requires the network BIOs underlying "
+ "a QUIC SSL object provide poll descriptors");
+ return 0;
+ }
+
+ if (rd.type != BIO_POLL_DESCRIPTOR_TYPE_SOCK_FD) {
+ ERR_raise_data(ERR_LIB_SSL, SSL_R_POLL_REQUEST_NOT_SUPPORTED,
+ "SSL_poll requires the poll descriptors of the "
+ "network BIOs underlying a QUIC SSL object be "
+ "of socket type");
+ return 0;
+ }
+
+ fd1 = rd.value.fd;
+ fd1_r = 1;
+ }
+
+ if (SSL_net_write_desired(ssl)) {
+ if (!SSL_get_wpoll_descriptor(ssl, &wd)) {
+ ERR_raise_data(ERR_LIB_SSL, SSL_R_POLL_REQUEST_NOT_SUPPORTED,
+ "SSL_poll requires the network BIOs underlying "
+ "a QUIC SSL object provide poll descriptors");
+ return 0;
+ }
+
+ if (wd.type != BIO_POLL_DESCRIPTOR_TYPE_SOCK_FD) {
+ ERR_raise_data(ERR_LIB_SSL, SSL_R_POLL_REQUEST_NOT_SUPPORTED,
+ "SSL_poll requires the poll descriptors of the "
+ "network BIOs underlying a QUIC SSL object be "
+ "of socket type");
+ return 0;
+ }
+
+ fd2 = wd.value.fd;
+ fd2_w = 1;
+ }
+
+ if (fd2 == fd1) {
+ fd2 = -1;
+ fd1_w = fd2_w;
+ }
+
+ if (fd1 != -1)
+ if (!ossl_rio_poll_builder_add_fd(rpb, fd1, fd1_r, fd1_w))
+ return 0;
+
+ if (fd2 != -1 && fd2_w)
+ if (!ossl_rio_poll_builder_add_fd(rpb, fd2, /*r = */0, fd2_w))
+ return 0;
+
+ /*
+ * Add the notifier FD for the QUIC domain this SSL object is a part of (if
+ * there is one). This ensures we get woken up if another thread calls into
+ * that QUIC domain and some readiness event relevant to the SSL_poll call
+ * on this thread arises without the underlying network socket ever becoming
+ * readable.
+ */
+ fd_nfy = ossl_quic_get_notifier_fd(ssl);
+ if (fd_nfy != -1) {
+ uint64_t revents = 0;
+
+ if (!ossl_rio_poll_builder_add_fd(rpb, fd_nfy, /*r = */1, /*w = */0))
+ return 0;
+
+ /* Tell QUIC domain we need to receive notifications. */
+ ossl_quic_enter_blocking_section(ssl, wctx);
+
+ /*
+ * Only after the above call returns is it guaranteed that any readiness
+ * events will cause the above notifier to become readable. Therefore,
+ * it is possible the object became ready after our initial
+ * poll_readout() call (before we determined that nothing was ready and
+ * we needed to block). We now need to do another readout, in which case
+ * blocking is to be aborted.
+ */
+ if (!ossl_quic_conn_poll_events(ssl, events, /*do_tick = */0, &revents)) {
+ ossl_quic_leave_blocking_section(ssl, wctx);
+ return 0;
+ }
+
+ if (revents != 0) {
+ ossl_quic_leave_blocking_section(ssl, wctx);
+ *abort_blocking = 1;
+ return 1;
+ }
+ }
+
+ return 1;
+}
+
+static void postpoll_translation_cleanup_ssl_quic(SSL *ssl,
+ QUIC_REACTOR_WAIT_CTX *wctx)
+{
+ if (ossl_quic_get_notifier_fd(ssl) != -1)
+ ossl_quic_leave_blocking_section(ssl, wctx);
+}
+
+static void postpoll_translation_cleanup(SSL_POLL_ITEM *items,
+ size_t num_items,
+ size_t stride,
+ QUIC_REACTOR_WAIT_CTX *wctx)
+{
+ SSL_POLL_ITEM *item;
+ SSL *ssl;
+ size_t i;
+
+ for (i = 0; i < num_items; ++i) {
+ item = &ITEM_N(items, stride, i);
+
+ switch (item->desc.type) {
+ case BIO_POLL_DESCRIPTOR_TYPE_SSL:
+ ssl = item->desc.value.ssl;
+ if (ssl == NULL)
+ break;
+
+ switch (ssl->type) {
+# ifndef OPENSSL_NO_QUIC
+ case SSL_TYPE_QUIC_LISTENER:
+ case SSL_TYPE_QUIC_CONNECTION:
+ case SSL_TYPE_QUIC_XSO:
+ postpoll_translation_cleanup_ssl_quic(ssl, wctx);
+ break;
+# endif
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+static int poll_translate(SSL_POLL_ITEM *items,
+ size_t num_items,
+ size_t stride,
+ QUIC_REACTOR_WAIT_CTX *wctx,
+ RIO_POLL_BUILDER *rpb,
+ OSSL_TIME *p_earliest_wakeup_deadline,
+ int *abort_blocking,
+ size_t *p_result_count)
+{
+ int ok = 1;
+ SSL_POLL_ITEM *item;
+ size_t result_count = 0;
+ SSL *ssl;
+ OSSL_TIME earliest_wakeup_deadline = ossl_time_infinite();
+ struct timeval timeout;
+ int is_infinite = 0;
+ size_t i;
+
+ for (i = 0; i < num_items; ++i) {
+ item = &ITEM_N(items, stride, i);
+
+ switch (item->desc.type) {
+ case BIO_POLL_DESCRIPTOR_TYPE_SSL:
+ ssl = item->desc.value.ssl;
+ if (ssl == NULL)
+ /* NULL items are no-ops and have revents reported as 0 */
+ break;
+
+ switch (ssl->type) {
+# ifndef OPENSSL_NO_QUIC
+ case SSL_TYPE_QUIC_LISTENER:
+ case SSL_TYPE_QUIC_CONNECTION:
+ case SSL_TYPE_QUIC_XSO:
+ if (!poll_translate_ssl_quic(ssl, wctx, rpb, item->events,
+ abort_blocking))
+ FAIL_ITEM(i);
+
+ if (*abort_blocking)
+ return 1;
+
+ if (!SSL_get_event_timeout(ssl, &timeout, &is_infinite))
+ FAIL_ITEM(i++); /* need to clean up this item too */
+
+ if (!is_infinite)
+ earliest_wakeup_deadline
+ = ossl_time_min(earliest_wakeup_deadline,
+ ossl_time_add(ossl_time_now(),
+ ossl_time_from_timeval(timeout)));
+
+ break;
+# endif
+
+ default:
+ ERR_raise_data(ERR_LIB_SSL, SSL_R_POLL_REQUEST_NOT_SUPPORTED,
+ "SSL_poll currently only supports QUIC SSL "
+ "objects");
+ FAIL_ITEM(i);
+ }
+ break;
+
+ case BIO_POLL_DESCRIPTOR_TYPE_SOCK_FD:
+ ERR_raise_data(ERR_LIB_SSL, SSL_R_POLL_REQUEST_NOT_SUPPORTED,
+ "SSL_poll currently does not support polling "
+ "sockets");
+ FAIL_ITEM(i);
+
+ default:
+ ERR_raise_data(ERR_LIB_SSL, SSL_R_POLL_REQUEST_NOT_SUPPORTED,
+ "SSL_poll does not support unknown poll descriptor "
+ "type %d", item->desc.type);
+ FAIL_ITEM(i);
+ }
+ }
+
+out:
+ if (!ok)
+ postpoll_translation_cleanup(items, i, stride, wctx);
+
+ *p_earliest_wakeup_deadline = earliest_wakeup_deadline;
+ *p_result_count = result_count;
+ return ok;
+}
+
+static int poll_block(SSL_POLL_ITEM *items,
+ size_t num_items,
+ size_t stride,
+ OSSL_TIME user_deadline,
+ size_t *p_result_count)
+{
+ int ok = 0, abort_blocking = 0;
+ RIO_POLL_BUILDER rpb;
+ QUIC_REACTOR_WAIT_CTX wctx;
+ OSSL_TIME earliest_wakeup_deadline;
+
+ /*
+ * Blocking is somewhat involved and involves the following steps:
+ *
+ * - Translation, in which the various logical items (SSL objects, etc.) to
+ * be polled are translated into items an OS polling API understands.
+ *
+ * - Synchronisation bookkeeping. This ensures that we can be woken up
+ * not just by readiness of any underlying file descriptor distilled from
+ * the provided items but also by other threads, which might do work
+ * on a relevant QUIC object to cause the object to be ready without the
+ * underlying file descriptor ever becoming ready from our perspective.
+ *
+ * - The blocking call to the OS polling API.
+ *
+ * - Currently we do not do reverse translation but simply call
+ * poll_readout() again to read out all readiness state for all
+ * descriptors which the user passed.
+ *
+ * TODO(QUIC POLLING): In the future we will do reverse translation here
+ * also to facilitate a more efficient readout.
+ */
+ ossl_quic_reactor_wait_ctx_init(&wctx);
+ ossl_rio_poll_builder_init(&rpb);
+
+ if (!poll_translate(items, num_items, stride, &wctx, &rpb,
+ &earliest_wakeup_deadline,
+ &abort_blocking,
+ p_result_count))
+ goto out;
+
+ if (abort_blocking)
+ goto out;
+
+ earliest_wakeup_deadline = ossl_time_min(earliest_wakeup_deadline,
+ user_deadline);
+
+ ok = ossl_rio_poll_builder_poll(&rpb, earliest_wakeup_deadline);
+
+ postpoll_translation_cleanup(items, num_items, stride, &wctx);
+
+out:
+ ossl_rio_poll_builder_cleanup(&rpb);
+ ossl_quic_reactor_wait_ctx_cleanup(&wctx);
+ return ok;
+}
+#endif
+
+static int poll_readout(SSL_POLL_ITEM *items,
+ size_t num_items,
+ size_t stride,
+ int do_tick,
+ size_t *p_result_count)
+{
+ int ok = 1;
+ size_t i, result_count = 0;
+ SSL_POLL_ITEM *item;
+ SSL *ssl;
+#ifndef OPENSSL_NO_QUIC
+ uint64_t events;
+#endif
+ uint64_t revents;
+
+ for (i = 0; i < num_items; ++i) {
+ item = &ITEM_N(items, stride, i);
+#ifndef OPENSSL_NO_QUIC
+ events = item->events;
+#endif
+ revents = 0;
+
+ switch (item->desc.type) {
+ case BIO_POLL_DESCRIPTOR_TYPE_SSL:
+ ssl = item->desc.value.ssl;
+ if (ssl == NULL)
+ /* NULL items are no-ops and have revents reported as 0 */
+ break;
+
+ switch (ssl->type) {
+#ifndef OPENSSL_NO_QUIC
+ case SSL_TYPE_QUIC_LISTENER:
+ case SSL_TYPE_QUIC_CONNECTION:
+ case SSL_TYPE_QUIC_XSO:
+ if (!ossl_quic_conn_poll_events(ssl, events, do_tick, &revents))
+ /* above call raises ERR */
+ FAIL_ITEM(i);
+
+ if (revents != 0)
+ ++result_count;
+
+ break;
+#endif
+
+ default:
+ ERR_raise_data(ERR_LIB_SSL, SSL_R_POLL_REQUEST_NOT_SUPPORTED,
+ "SSL_poll currently only supports QUIC SSL "
+ "objects");
+ FAIL_ITEM(i);
+ }
+ break;
+ case BIO_POLL_DESCRIPTOR_TYPE_SOCK_FD:
+ ERR_raise_data(ERR_LIB_SSL, SSL_R_POLL_REQUEST_NOT_SUPPORTED,
+ "SSL_poll currently does not support polling "
+ "sockets");
+ FAIL_ITEM(i);
+ default:
+ ERR_raise_data(ERR_LIB_SSL, SSL_R_POLL_REQUEST_NOT_SUPPORTED,
+ "SSL_poll does not support unknown poll descriptor "
+ "type %d", item->desc.type);
+ FAIL_ITEM(i);
+ }
+
+ item->revents = revents;
+ }
+
+out:
+ if (p_result_count != NULL)
+ *p_result_count = result_count;
+
+ return ok;
+}
+
+int SSL_poll(SSL_POLL_ITEM *items,
+ size_t num_items,
+ size_t stride,
+ const struct timeval *timeout,
+ uint64_t flags,
+ size_t *p_result_count)
+{
+ int ok = 1;
+ size_t result_count = 0;
+ ossl_unused int do_tick = ((flags & SSL_POLL_FLAG_NO_HANDLE_EVENTS) == 0);
+ OSSL_TIME deadline;
+
+ /* Trivial case. */
+ if (num_items == 0) {
+ if (timeout == NULL)
+ goto out;
+ OSSL_sleep(ossl_time2ms(ossl_time_from_timeval(*timeout)));
+ goto out;
+ }
+
+ /* Convert timeout to deadline. */
+ if (timeout == NULL)
+ deadline = ossl_time_infinite();
+ else if (timeout->tv_sec == 0 && timeout->tv_usec == 0)
+ deadline = ossl_time_zero();
+ else
+ deadline = ossl_time_add(ossl_time_now(),
+ ossl_time_from_timeval(*timeout));
+
+ /* Loop until we have something to report. */
+ for (;;) {
+ /* Readout phase - poll current state of each item. */
+ if (!poll_readout(items, num_items, stride, do_tick, &result_count)) {
+ ok = 0;
+ goto out;
+ }
+
+ /*
+ * If we got anything, or we are in immediate mode (zero timeout), or
+ * the deadline has expired, we're done.
+ */
+ if (result_count > 0
+ || ossl_time_is_zero(deadline) /* (avoids now call) */
+ || ossl_time_compare(ossl_time_now(), deadline) >= 0)
+ goto out;
+
+ /*
+ * Block until something is ready. Ignore NO_HANDLE_EVENTS from this
+ * point onwards.
+ */
+ do_tick = 1;
+#ifndef OPENSSL_NO_QUIC
+ if (!poll_block(items, num_items, stride, deadline, &result_count)) {
+ ok = 0;
+ goto out;
+ }
+#endif
+ }
+
+ /* TODO(QUIC POLLING): Support for polling FDs */
+
+out:
+ if (p_result_count != NULL)
+ *p_result_count = result_count;
+
+ return ok;
+}
diff --git a/crypto/openssl/ssl/rio/poll_method.h b/crypto/openssl/ssl/rio/poll_method.h
new file mode 100644
index 000000000000..d5af8663c23d
--- /dev/null
+++ b/crypto/openssl/ssl/rio/poll_method.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2024-2025 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License"). You may not use
+ * this file except in compliance with the License. You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+#ifndef OSSL_POLL_METHOD_H
+# define OSSL_POLL_METHOD_H
+
+# include "internal/common.h"
+# include "internal/sockets.h"
+
+# define RIO_POLL_METHOD_SELECT 1
+# define RIO_POLL_METHOD_POLL 2
+# define RIO_POLL_METHOD_NONE 3
+
+# ifndef RIO_POLL_METHOD
+# if defined(OPENSSL_SYS_UEFI)
+# define RIO_POLL_METHOD RIO_POLL_METHOD_NONE
+# elif !defined(OPENSSL_SYS_WINDOWS) && defined(POLLIN)
+# define RIO_POLL_METHOD RIO_POLL_METHOD_POLL
+# else
+# define RIO_POLL_METHOD RIO_POLL_METHOD_SELECT
+# endif
+# endif
+
+#endif
diff --git a/crypto/openssl/ssl/rio/rio_notifier.c b/crypto/openssl/ssl/rio/rio_notifier.c
new file mode 100644
index 000000000000..6dbb2bdc4766
--- /dev/null
+++ b/crypto/openssl/ssl/rio/rio_notifier.c
@@ -0,0 +1,379 @@
+/*
+ * Copyright 2024-2025 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License"). You may not use
+ * this file except in compliance with the License. You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#include "internal/sockets.h"
+#include <openssl/bio.h>
+#include <openssl/err.h>
+#include "internal/thread_once.h"
+#include "internal/rio_notifier.h"
+
+/*
+ * Sets a socket as close-on-exec, except that this is a no-op if we are certain
+ * we do not need to do this or the OS does not support the concept.
+ */
+static int set_cloexec(int fd)
+{
+#if !defined(SOCK_CLOEXEC) && defined(FD_CLOEXEC)
+ return fcntl(fd, F_SETFD, FD_CLOEXEC) >= 0;
+#else
+ return 1;
+#endif
+}
+
+#if defined(OPENSSL_SYS_WINDOWS)
+
+static CRYPTO_ONCE ensure_wsa_startup_once = CRYPTO_ONCE_STATIC_INIT;
+static int wsa_started;
+
+static void ossl_wsa_cleanup(void)
+{
+ if (wsa_started) {
+ wsa_started = 0;
+ WSACleanup();
+ }
+}
+
+DEFINE_RUN_ONCE_STATIC(do_wsa_startup)
+{
+ WORD versionreq = 0x0202; /* Version 2.2 */
+ WSADATA wsadata;
+
+ if (WSAStartup(versionreq, &wsadata) != 0)
+ return 0;
+ wsa_started = 1;
+ OPENSSL_atexit(ossl_wsa_cleanup);
+ return 1;
+}
+
+static ossl_inline int ensure_wsa_startup(void)
+{
+ return RUN_ONCE(&ensure_wsa_startup_once, do_wsa_startup);
+}
+
+#endif
+
+#if RIO_NOTIFIER_METHOD == RIO_NOTIFIER_METHOD_SOCKET
+
+/* Create a close-on-exec socket. */
+static int create_socket(int domain, int socktype, int protocol)
+{
+ int fd;
+# if defined(OPENSSL_SYS_WINDOWS)
+ static const int on = 1;
+
+ /*
+ * Use WSASocketA to create a socket which is immediately marked as
+ * non-inheritable, avoiding race conditions if another thread is about to
+ * call CreateProcess.
+ * NOTE: windows xp (0x501) doesn't support the non-inheritance flag here
+ * but preventing inheritance isn't mandatory, just a safety precaution
+ * so we can get away with not including it for older platforms
+ */
+
+# ifdef WSA_FLAG_NO_HANDLE_INHERIT
+ fd = (int)WSASocketA(domain, socktype, protocol, NULL, 0,
+ WSA_FLAG_NO_HANDLE_INHERIT);
+
+ /*
+ * Its also possible that someone is building a binary on a newer windows
+ * SDK, but running it on a runtime that doesn't support inheritance
+ * supression. In that case the above will return INVALID_SOCKET, and
+ * our response for those older platforms is to try the call again
+ * without the flag
+ */
+ if (fd == INVALID_SOCKET)
+ fd = (int)WSASocketA(domain, socktype, protocol, NULL, 0, 0);
+# else
+ fd = (int)WSASocketA(domain, socktype, protocol, NULL, 0, 0);
+# endif
+ if (fd == INVALID_SOCKET) {
+ int err = get_last_socket_error();
+
+ ERR_raise_data(ERR_LIB_SYS, err,
+ "calling WSASocketA() = %d", err);
+ return INVALID_SOCKET;
+ }
+
+ /* Prevent interference with the socket from other processes on Windows. */
+ if (setsockopt(fd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (void *)&on, sizeof(on)) < 0) {
+ ERR_raise_data(ERR_LIB_SYS, get_last_socket_error(),
+ "calling setsockopt()");
+ BIO_closesocket(fd);
+ return INVALID_SOCKET;
+ }
+
+# else
+# if defined(SOCK_CLOEXEC)
+ socktype |= SOCK_CLOEXEC;
+# endif
+
+ fd = BIO_socket(domain, socktype, protocol, 0);
+ if (fd == INVALID_SOCKET) {
+ ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
+ "calling BIO_socket()");
+ return INVALID_SOCKET;
+ }
+
+ /*
+ * Make socket close-on-exec unless this was already done above at socket
+ * creation time.
+ */
+ if (!set_cloexec(fd)) {
+ ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
+ "calling set_cloexec()");
+ BIO_closesocket(fd);
+ return INVALID_SOCKET;
+ }
+# endif
+
+ return fd;
+}
+
+/*
+ * The SOCKET notifier method manually creates a connected TCP socket pair by
+ * temporarily creating a TCP listener on a random port and connecting back to
+ * it.
+ *
+ * Win32 does not support socketpair(2), and Win32 pipes are not compatible with
+ * Winsock select(2). This means our only means of making select(2) wakeable is
+ * to artifically create a loopback TCP connection and send bytes to it.
+ */
+int ossl_rio_notifier_init(RIO_NOTIFIER *nfy)
+{
+ int rc, lfd = -1, rfd = -1, wfd = -1;
+ struct sockaddr_in sa = {0}, accept_sa;
+ socklen_t sa_len = sizeof(sa), accept_sa_len = sizeof(accept_sa);
+
+# if defined(OPENSSL_SYS_WINDOWS)
+ if (!ensure_wsa_startup()) {
+ ERR_raise_data(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR,
+ "Cannot start Windows sockets");
+ return 0;
+ }
+# endif
+ /* Create a close-on-exec socket. */
+ lfd = create_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (lfd == INVALID_SOCKET) {
+ ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
+ "calling create_socket()");
+ return 0;
+ }
+
+ /* Bind the socket to a random loopback port. */
+ sa.sin_family = AF_INET;
+ sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ rc = bind(lfd, (const struct sockaddr *)&sa, sizeof(sa));
+ if (rc < 0) {
+ ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
+ "calling bind()");
+ goto err;
+ }
+
+ /* Determine what random port was allocated. */
+ rc = getsockname(lfd, (struct sockaddr *)&sa, &sa_len);
+ if (rc < 0) {
+ ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
+ "calling getsockname()");
+ goto err;
+ }
+
+ /* Start listening. */
+ rc = listen(lfd, 1);
+ if (rc < 0) {
+ ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
+ "calling listen()");
+ goto err;
+ }
+
+ /* Create another socket to connect to the listener. */
+ wfd = create_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (wfd == INVALID_SOCKET) {
+ ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
+ "calling create_socket()");
+ goto err;
+ }
+
+ /*
+ * Disable Nagle's algorithm on the writer so that wakeups happen
+ * immediately.
+ */
+ if (!BIO_set_tcp_ndelay(wfd, 1)) {
+ ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
+ "calling BIO_set_tcp_ndelay()");
+ goto err;
+ }
+
+ /*
+ * Connect the writer to the listener.
+ */
+ rc = connect(wfd, (struct sockaddr *)&sa, sizeof(sa));
+ if (rc < 0) {
+ ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
+ "calling connect()");
+ goto err;
+ }
+
+ /*
+ * The connection accepted from the listener is the read side.
+ */
+ rfd = accept(lfd, (struct sockaddr *)&accept_sa, &accept_sa_len);
+ if (rfd == INVALID_SOCKET) {
+ ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
+ "calling accept()");
+ goto err;
+ }
+
+ rc = getsockname(wfd, (struct sockaddr *)&sa, &sa_len);
+ if (rc < 0) {
+ ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
+ "calling getsockname()");
+ goto err;
+ }
+
+ /* Close the listener, which we don't need anymore. */
+ BIO_closesocket(lfd);
+ lfd = -1;
+
+ /*
+ * Sanity check - ensure someone else didn't connect to our listener during
+ * the brief window of possibility above.
+ */
+ if (accept_sa.sin_family != AF_INET || accept_sa.sin_port != sa.sin_port) {
+ ERR_raise_data(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR,
+ "connected address differs from accepted address");
+ goto err;
+ }
+
+ /* Make both sides of the connection non-blocking. */
+ if (!BIO_socket_nbio(rfd, 1)) {
+ ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
+ "calling BIO_socket_nbio()");
+ goto err;
+ }
+
+ if (!BIO_socket_nbio(wfd, 1)) {
+ ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
+ "calling BIO_socket_nbio()");
+ goto err;
+ }
+
+ nfy->rfd = rfd;
+ nfy->wfd = wfd;
+ return 1;
+
+err:
+ if (lfd != INVALID_SOCKET)
+ BIO_closesocket(lfd);
+ if (wfd != INVALID_SOCKET)
+ BIO_closesocket(wfd);
+ if (rfd != INVALID_SOCKET)
+ BIO_closesocket(rfd);
+ return 0;
+}
+
+#elif RIO_NOTIFIER_METHOD == RIO_NOTIFIER_METHOD_SOCKETPAIR
+
+int ossl_rio_notifier_init(RIO_NOTIFIER *nfy)
+{
+ int fds[2], domain = AF_INET, type = SOCK_STREAM;
+
+# if defined(SOCK_CLOEXEC)
+ type |= SOCK_CLOEXEC;
+# endif
+# if defined(SOCK_NONBLOCK)
+ type |= SOCK_NONBLOCK;
+# endif
+
+# if defined(OPENSSL_SYS_UNIX) && defined(AF_UNIX)
+ domain = AF_UNIX;
+# endif
+
+ if (socketpair(domain, type, 0, fds) < 0) {
+ ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
+ "calling socketpair()");
+ return 0;
+ }
+
+ if (!set_cloexec(fds[0]) || !set_cloexec(fds[1])) {
+ ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
+ "calling set_cloexec()");
+ goto err;
+ }
+
+# if !defined(SOCK_NONBLOCK)
+ if (!BIO_socket_nbio(fds[0], 1) || !BIO_socket_nbio(fds[1], 1)) {
+ ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
+ "calling BIO_socket_nbio()");
+ goto err;
+ }
+# endif
+
+ if (domain == AF_INET && !BIO_set_tcp_ndelay(fds[1], 1)) {
+ ERR_raise_data(ERR_LIB_SYS, get_last_sys_error(),
+ "calling BIO_set_tcp_ndelay()");
+ goto err;
+ }
+
+ nfy->rfd = fds[0];
+ nfy->wfd = fds[1];
+ return 1;
+
+err:
+ BIO_closesocket(fds[1]);
+ BIO_closesocket(fds[0]);
+ return 0;
+}
+
+#endif
+
+void ossl_rio_notifier_cleanup(RIO_NOTIFIER *nfy)
+{
+ if (nfy->rfd < 0)
+ return;
+
+ BIO_closesocket(nfy->wfd);
+ BIO_closesocket(nfy->rfd);
+ nfy->rfd = nfy->wfd = -1;
+}
+
+int ossl_rio_notifier_signal(RIO_NOTIFIER *nfy)
+{
+ static const unsigned char ch = 0;
+ ossl_ssize_t wr;
+
+ do
+ /*
+ * Note: If wr returns 0 the buffer is already full so we don't need to
+ * do anything.
+ */
+ wr = writesocket(nfy->wfd, (void *)&ch, sizeof(ch));
+ while (wr < 0 && get_last_socket_error_is_eintr());
+
+ return 1;
+}
+
+int ossl_rio_notifier_unsignal(RIO_NOTIFIER *nfy)
+{
+ unsigned char buf[16];
+ ossl_ssize_t rd;
+
+ /*
+ * signal() might have been called multiple times. Drain the buffer until
+ * it's empty.
+ */
+ do
+ rd = readsocket(nfy->rfd, (void *)buf, sizeof(buf));
+ while (rd == sizeof(buf)
+ || (rd < 0 && get_last_socket_error_is_eintr()));
+
+ if (rd < 0 && !BIO_fd_non_fatal_error(get_last_socket_error()))
+ return 0;
+
+ return 1;
+}