diff options
Diffstat (limited to 'crypto/openssl/ssl/rio')
| -rw-r--r-- | crypto/openssl/ssl/rio/build.info | 6 | ||||
| -rw-r--r-- | crypto/openssl/ssl/rio/poll_builder.c | 180 | ||||
| -rw-r--r-- | crypto/openssl/ssl/rio/poll_builder.h | 79 | ||||
| -rw-r--r-- | crypto/openssl/ssl/rio/poll_immediate.c | 478 | ||||
| -rw-r--r-- | crypto/openssl/ssl/rio/poll_method.h | 29 | ||||
| -rw-r--r-- | crypto/openssl/ssl/rio/rio_notifier.c | 379 |
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; +} |
