From caa3446eef277318fa465c3f6c2dc2b575995d74 Mon Sep 17 00:00:00 2001 From: Hugo Landau Date: Mon, 13 May 2024 20:20:23 +0100 Subject: [PATCH] RIO: Add poll builder to support immediate-mode polling API MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Reviewed-by: Matt Caswell Reviewed-by: Saša Nedvědický Reviewed-by: Tomas Mraz (Merged from https://github.com/openssl/openssl/pull/25416) --- ssl/rio/build.info | 1 + ssl/rio/poll_builder.c | 170 +++++++++++++++++++++++++++++++++++++++++ ssl/rio/poll_builder.h | 77 +++++++++++++++++++ ssl/rio/poll_method.h | 26 +++++++ 4 files changed, 274 insertions(+) create mode 100644 ssl/rio/poll_builder.c create mode 100644 ssl/rio/poll_builder.h create mode 100644 ssl/rio/poll_method.h diff --git a/ssl/rio/build.info b/ssl/rio/build.info index 15f93fb07d5..58b0f8bc935 100644 --- a/ssl/rio/build.info +++ b/ssl/rio/build.info @@ -4,3 +4,4 @@ SOURCE[$LIBSSL]=poll_immediate.c IF[{- !$disabled{quic} -}] SOURCE[$LIBSSL]=rio_notifier.c ENDIF +SOURCE[$LIBSSL]=poll_builder.c diff --git a/ssl/rio/poll_builder.c b/ssl/rio/poll_builder.c new file mode 100644 index 00000000000..bd0979ad375 --- /dev/null +++ b/ssl/rio/poll_builder.c @@ -0,0 +1,170 @@ +#include "poll_builder.h" +#include "internal/safe_math.h" +#include + +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_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; + + 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 (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 + size_t num_loop; + + for (num_loop = 0;; ++num_loop) { + size_t i; + struct pollfd *pfds = (rpb->pfd_heap != NULL ? rpb->pfd_heap : rpb->pfds); + struct pollfd *pfd; + + for (i = 0; i < rpb->pfd_num; ++i) { + pfd = &pfds[i]; + + if (pfd->fd == -1 || pfd->fd == fd) + break; + } + + if (i >= rpb->pfd_alloc) { + /* Was not able to find room - enlarge and try again. */ + if (num_loop > 0) + /* Should not happen twice. */ + return 0; + + if (!rpb_ensure_alloc(rpb, rpb->pfd_alloc * 2)) + return 0; + + continue; + } + + 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/ssl/rio/poll_builder.h b/ssl/rio/poll_builder.h new file mode 100644 index 00000000000..456e4048430 --- /dev/null +++ b/ssl/rio/poll_builder.h @@ -0,0 +1,77 @@ +/* + * Copyright 2023 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_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/ssl/rio/poll_method.h b/ssl/rio/poll_method.h new file mode 100644 index 00000000000..917bbb70cab --- /dev/null +++ b/ssl/rio/poll_method.h @@ -0,0 +1,26 @@ +/* + * Copyright 2024 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 + +# ifndef RIO_POLL_METHOD +# if !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 -- 2.47.2