Files
openssl/ssl/rio/poll_immediate.c
Bob Beck 2fab90bb5e 4.0-POST-CLANG-FORMAT-WEBKIT
Reviewed-by: Saša Nedvědický <sashan@openssl.org>
Reviewed-by: Neil Horman <nhorman@openssl.org>
Reviewed-by: Nikola Pajkovsky <nikolap@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/29242)
2025-12-09 00:28:19 -07:00

481 lines
14 KiB
C

/*
* 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;
}