diff options
author | Hugo Landau <hlandau@openssl.org> | 2024-04-04 11:30:15 +0100 |
---|---|---|
committer | Hugo Landau <hlandau@openssl.org> | 2024-04-19 09:33:53 +0100 |
commit | 08cc2f41a4b573220dd82c66c1b2a0df28ecb1db (patch) | |
tree | 446ff2f43e74ecd67a0e5492808025e729770001 | |
parent | ae859d7c723c82410799581cff03b0078d6db6d7 (diff) |
QUIC APL: Unify blocking mode handling for all object types
Reviewed-by: Neil Horman <nhorman@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/24037)
-rw-r--r-- | include/internal/quic_engine.h | 8 | ||||
-rw-r--r-- | include/internal/quic_port.h | 7 | ||||
-rw-r--r-- | ssl/quic/quic_engine.c | 18 | ||||
-rw-r--r-- | ssl/quic/quic_impl.c | 128 | ||||
-rw-r--r-- | ssl/quic/quic_local.h | 24 | ||||
-rw-r--r-- | ssl/quic/quic_obj.c | 36 | ||||
-rw-r--r-- | ssl/quic/quic_obj_local.h | 38 | ||||
-rw-r--r-- | ssl/quic/quic_port.c | 14 | ||||
-rw-r--r-- | ssl/quic/quic_port_local.h | 6 |
9 files changed, 149 insertions, 130 deletions
diff --git a/include/internal/quic_engine.h b/include/internal/quic_engine.h index e19ee5fb73..454c4bfaaa 100644 --- a/include/internal/quic_engine.h +++ b/include/internal/quic_engine.h @@ -82,6 +82,14 @@ QUIC_REACTOR *ossl_quic_engine_get0_reactor(QUIC_ENGINE *qeng); OSSL_LIB_CTX *ossl_quic_engine_get0_libctx(QUIC_ENGINE *qeng); const char *ossl_quic_engine_get0_propq(QUIC_ENGINE *qeng); +/* + * Look through all the engine's ports and determine if any of them have had a + * BIO changed. If so, update the blocking support detection data in the + * QUIC_REACTOR. If force is 1, always do the update even if nothing seems + * to have changed. + */ +void ossl_quic_engine_update_poll_descriptors(QUIC_ENGINE *qeng, int force); + # endif #endif diff --git a/include/internal/quic_port.h b/include/internal/quic_port.h index 9c74390ab2..a150eff90d 100644 --- a/include/internal/quic_port.h +++ b/include/internal/quic_port.h @@ -99,10 +99,11 @@ int ossl_quic_port_set_net_rbio(QUIC_PORT *port, BIO *net_rbio); int ossl_quic_port_set_net_wbio(QUIC_PORT *port, BIO *net_wbio); /* - * Re-poll the network BIOs already set to determine if their support - * for polling has changed. + * Re-poll the network BIOs already set to determine if their support for + * polling has changed. If force is 0, only check again if the BIOs have been + * changed. */ -int ossl_quic_port_update_poll_descriptors(QUIC_PORT *port); +int ossl_quic_port_update_poll_descriptors(QUIC_PORT *port, int force); /* Gets the engine which this port is a child of. */ QUIC_ENGINE *ossl_quic_port_get0_engine(QUIC_PORT *port); diff --git a/ssl/quic/quic_engine.c b/ssl/quic/quic_engine.c index 932ac2a7f0..679ef051a0 100644 --- a/ssl/quic/quic_engine.c +++ b/ssl/quic/quic_engine.c @@ -97,6 +97,24 @@ const char *ossl_quic_engine_get0_propq(QUIC_ENGINE *qeng) return qeng->propq; } +void ossl_quic_engine_update_poll_descriptors(QUIC_ENGINE *qeng, int force) +{ + QUIC_PORT *port; + + /* + * TODO(QUIC MULTIPORT): The implementation of + * ossl_quic_port_update_poll_descriptors assumes an engine only ever has a + * single port for now due to reactor limitations. This limitation will be + * removed in future. + * + * TODO(QUIC MULTIPORT): Consider only iterating the port list when dirty at + * the engine level in future when we can have multiple ports. This is not + * important currently as the port list has a single entry. + */ + LIST_FOREACH(port, port, &qeng->port_list) + ossl_quic_port_update_poll_descriptors(port, force); +} + /* * QUIC Engine: Child Object Lifecycle Management * ============================================== diff --git a/ssl/quic/quic_impl.c b/ssl/quic/quic_impl.c index c9151d9522..a45adb0949 100644 --- a/ssl/quic/quic_impl.c +++ b/ssl/quic/quic_impl.c @@ -41,8 +41,6 @@ static void qc_set_default_xso_keep_ref(QUIC_CONNECTION *qc, QUIC_XSO *xso, static SSL *quic_conn_stream_new(QCTX *ctx, uint64_t flags, int need_lock); static int quic_validate_for_write(QUIC_XSO *xso, int *err); static int quic_mutation_allowed(QUIC_CONNECTION *qc, int req_active); -static int qc_blocking_mode(const QUIC_CONNECTION *qc); -static int xso_blocking_mode(const QUIC_XSO *xso); static void qctx_maybe_autotick(QCTX *ctx); static int qctx_should_autotick(QCTX *ctx); @@ -480,6 +478,16 @@ static int quic_mutation_allowed(QUIC_CONNECTION *qc, int req_active) return 1; } +static int qctx_is_top_level(QCTX *ctx) +{ + return ctx->obj->parent_obj == NULL; +} + +static int qctx_blocking(QCTX *ctx) +{ + return ossl_quic_obj_blocking(ctx->obj); +} + /* * Block until a predicate is met. * @@ -585,11 +593,8 @@ SSL *ossl_quic_new(SSL_CTX *ctx) qc->default_stream_mode = SSL_DEFAULT_STREAM_MODE_AUTO_BIDI; qc->default_ssl_mode = qc->obj.ssl.ctx->mode; qc->default_ssl_options = qc->obj.ssl.ctx->options & OSSL_QUIC_PERMITTED_OPTIONS; - qc->desires_blocking = 1; - qc->blocking = 0; qc->incoming_stream_policy = SSL_INCOMING_STREAM_POLICY_AUTO; qc->last_error = SSL_ERROR_NONE; - qc->last_net_bio_epoch = UINT64_MAX; qc_update_reject_policy(qc); @@ -1051,24 +1056,6 @@ static int csm_analyse_init_peer_addr(BIO *net_wbio, BIO_ADDR *peer) return 1; } -static int qc_can_support_blocking_cached(QUIC_CONNECTION *qc) -{ - QUIC_REACTOR *rtor = ossl_quic_channel_get_reactor(qc->ch); - - return ossl_quic_reactor_can_poll_r(rtor) - && ossl_quic_reactor_can_poll_w(rtor); -} - -static void qc_update_can_support_blocking(QUIC_CONNECTION *qc) -{ - ossl_quic_port_update_poll_descriptors(qc->port); /* best effort */ -} - -static void qc_update_blocking_mode(QUIC_CONNECTION *qc) -{ - qc->blocking = qc->desires_blocking && qc_can_support_blocking_cached(qc); -} - static int quic_set0_net_rbio(QUIC_OBJ *obj, BIO *net_rbio) { @@ -1165,22 +1152,20 @@ int ossl_quic_conn_get_blocking_mode(const SSL *s) { QCTX ctx; - if (!expect_quic_cs(s, &ctx)) + if (!expect_quic_csl(s, &ctx)) return 0; - if (ctx.is_stream) - return xso_blocking_mode(ctx.xso); - - return qc_blocking_mode(ctx.qc); + return qctx_blocking(&ctx); } QUIC_TAKES_LOCK int ossl_quic_conn_set_blocking_mode(SSL *s, int blocking) { int ret = 0; + unsigned int mode; QCTX ctx; - if (!expect_quic_cs(s, &ctx)) + if (!expect_quic_csl(s, &ctx)) return 0; qctx_lock(&ctx); @@ -1188,38 +1173,27 @@ int ossl_quic_conn_set_blocking_mode(SSL *s, int blocking) /* Sanity check - can we support the request given the current network BIO? */ if (blocking) { /* - * If called directly on a QCSO, update our information on network BIO - * capabilities. + * If called directly on a top-level object (QCSO or QLSO), update our + * information on network BIO capabilities. */ - if (!ctx.is_stream) - qc_update_can_support_blocking(ctx.qc); + if (qctx_is_top_level(&ctx)) + ossl_quic_engine_update_poll_descriptors(ctx.obj->engine, /*force=*/1); /* Cannot enable blocking mode if we do not have pollable FDs. */ - if (!qc_can_support_blocking_cached(ctx.qc)) { + if (!ossl_quic_obj_can_support_blocking(ctx.obj)) { ret = QUIC_RAISE_NON_NORMAL_ERROR(&ctx, ERR_R_UNSUPPORTED, NULL); goto out; } } - if (!ctx.is_stream) - /* - * If called directly on a QCSO, update default and connection-level - * blocking modes. - */ - ctx.qc->desires_blocking = (blocking != 0); + mode = (blocking != 0) + ? QUIC_BLOCKING_MODE_BLOCKING + : QUIC_BLOCKING_MODE_NONBLOCKING; - if (ctx.xso != NULL) { - /* - * If called on a QSSO or a QCSO with a default XSO, update the blocking - * mode. - */ - ctx.xso->desires_blocking = (blocking != 0); - ctx.xso->desires_blocking_set = 1; - } + ossl_quic_obj_set_blocking_mode(ctx.obj, mode); ret = 1; out: - qc_update_blocking_mode(ctx.qc); qctx_unlock(&ctx); return ret; } @@ -1254,34 +1228,6 @@ int ossl_quic_conn_set_initial_peer_addr(SSL *s, * (BIO/)SSL_get_poll_fd => ossl_quic_get_poll_fd * */ -static void qc_try_update_blocking(QUIC_CONNECTION *qc) -{ - uint64_t cur_epoch; - - cur_epoch = ossl_quic_port_get_net_bio_epoch(qc->port); - if (qc->last_net_bio_epoch == cur_epoch) - return; - - qc_update_can_support_blocking(qc); - qc_update_blocking_mode(qc); - qc->last_net_bio_epoch = cur_epoch; -} - -/* Returns 1 if the connection is being used in blocking mode. */ -static int qc_blocking_mode(const QUIC_CONNECTION *qc) -{ - qc_try_update_blocking((QUIC_CONNECTION *)qc); - return qc->blocking; -} - -static int xso_blocking_mode(const QUIC_XSO *xso) -{ - if (xso->desires_blocking_set) - return xso->desires_blocking && qc_can_support_blocking_cached(xso->conn); - else - /* Only ever set if we can support blocking. */ - return xso->conn->blocking; -} /* SSL_handle_events; performs QUIC I/O and timeout processing. */ QUIC_TAKES_LOCK @@ -1501,7 +1447,7 @@ int ossl_quic_conn_shutdown(SSL *s, uint64_t flags, qc_shutdown_flush_init(ctx.qc); if (!qc_shutdown_flush_finished(ctx.qc)) { - if (!no_block && qc_blocking_mode(ctx.qc)) { + if (!no_block && qctx_blocking(&ctx)) { ret = block_until_pred(&ctx, quic_shutdown_flush_wait, ctx.qc, 0); if (ret < 1) { ret = 0; @@ -1520,7 +1466,7 @@ int ossl_quic_conn_shutdown(SSL *s, uint64_t flags, /* Phase 2: Connection Closure */ if (wait_peer && !ossl_quic_channel_is_term_any(ctx.qc->ch)) { - if (!no_block && qc_blocking_mode(ctx.qc)) { + if (!no_block && qctx_blocking(&ctx)) { ret = block_until_pred(&ctx, quic_shutdown_peer_wait, ctx.qc, 0); if (ret < 1) { ret = 0; @@ -1560,7 +1506,7 @@ int ossl_quic_conn_shutdown(SSL *s, uint64_t flags, } /* Phase 3: Terminating Wait Time */ - if (!no_block && qc_blocking_mode(ctx.qc) + if (!no_block && qctx_blocking(&ctx) && (flags & SSL_SHUTDOWN_FLAG_RAPID) == 0) { ret = block_until_pred(&ctx, quic_shutdown_wait, ctx.qc, 0); if (ret < 1) { @@ -1889,7 +1835,7 @@ static int quic_do_handshake(QCTX *ctx) /* The handshake is now done. */ return 1; - if (!qc_blocking_mode(qc)) { + if (!qctx_blocking(ctx)) { /* Try to advance the reactor. */ qctx_maybe_autotick(ctx); @@ -1900,7 +1846,7 @@ static int quic_do_handshake(QCTX *ctx) if (ossl_quic_channel_is_term_any(qc->ch)) { QUIC_RAISE_NON_NORMAL_ERROR(ctx, SSL_R_PROTOCOL_IS_SHUTDOWN, NULL); return 0; - } else if (qc->desires_blocking) { + } else if (ossl_quic_obj_desires_blocking(&qc->obj)) { /* * As a special case when doing a handshake when blocking mode is * desired yet not available, see if the network BIOs have become @@ -1908,16 +1854,14 @@ static int quic_do_handshake(QCTX *ctx) * which do late creation of socket FDs and therefore cannot expose * a poll descriptor until after a network BIO is set on the QCSO. */ - assert(!qc->blocking); - qc_update_can_support_blocking(qc); - qc_update_blocking_mode(qc); + ossl_quic_engine_update_poll_descriptors(qc->obj.engine, /*force=*/1); } } /* * We are either in blocking mode or just entered it due to the code above. */ - if (qc_blocking_mode(qc)) { + if (qctx_blocking(ctx)) { /* In blocking mode, wait for the handshake to complete. */ struct quic_handshake_wait_args args; @@ -2107,7 +2051,7 @@ static int qc_wait_for_default_xso_for_read(QCTX *ctx, int peek) if (peek) return 0; - if (!qc_blocking_mode(qc)) + if (!qctx_blocking(ctx)) /* Non-blocking mode, so just bail immediately. */ return QUIC_RAISE_NORMAL_ERROR(ctx, SSL_ERROR_WANT_READ); @@ -2231,7 +2175,7 @@ static SSL *quic_conn_stream_new(QCTX *ctx, uint64_t flags, int need_lock) * Stream count flow control currently doesn't permit this stream to be * opened. */ - if (no_blocking || !qc_blocking_mode(qc)) { + if (no_blocking || !qctx_blocking(ctx)) { QUIC_RAISE_NON_NORMAL_ERROR(ctx, SSL_R_STREAM_COUNT_LIMITED, NULL); goto err; } @@ -2792,7 +2736,7 @@ int ossl_quic_write_flags(SSL *s, const void *buf, size_t len, goto out; } - if (xso_blocking_mode(ctx.xso)) + if (qctx_blocking(&ctx)) ret = quic_write_blocking(&ctx, buf, len, flags, written); else if (partial_write) ret = quic_write_nonblocking_epw(&ctx, buf, len, flags, written); @@ -3002,7 +2946,7 @@ static int quic_read(SSL *s, void *buf, size_t len, size_t *bytes_read, int peek */ qctx_maybe_autotick(&ctx); ret = 1; - } else if (xso_blocking_mode(ctx.xso)) { + } else if (qctx_blocking(&ctx)) { /* * We were not able to read anything immediately, so our stream * buffer is empty. This means we need to block until we get @@ -3802,7 +3746,7 @@ SSL *ossl_quic_accept_stream(SSL *s, uint64_t flags) qs = ossl_quic_stream_map_peek_accept_queue(qsm); if (qs == NULL) { - if (qc_blocking_mode(ctx.qc) + if (qctx_blocking(&ctx) && (flags & SSL_ACCEPT_STREAM_NO_BLOCK) == 0) { struct wait_for_incoming_stream_args args; @@ -4333,11 +4277,9 @@ static QUIC_CONNECTION *create_qc_from_incoming_conn(QUIC_LISTENER *ql, QUIC_CHA qc->mutex = ql->mutex; #endif qc->tls = ossl_quic_channel_get0_tls(ch); - qc->last_net_bio_epoch = UINT64_MAX; qc->started = 1; qc->as_server = 1; qc->as_server_state = 1; - qc->desires_blocking = 1; qc->default_stream_mode = SSL_DEFAULT_STREAM_MODE_AUTO_BIDI; qc->default_ssl_options = ql->obj.ssl.ctx->options & OSSL_QUIC_PERMITTED_OPTIONS; qc->incoming_stream_policy = SSL_INCOMING_STREAM_POLICY_AUTO; diff --git a/ssl/quic/quic_local.h b/ssl/quic/quic_local.h index 85c73fe6c7..f75d128362 100644 --- a/ssl/quic/quic_local.h +++ b/ssl/quic/quic_local.h @@ -42,19 +42,6 @@ struct quic_xso_st { /* The stream object. Always non-NULL for as long as the XSO exists. */ QUIC_STREAM *stream; - /* - * Has this stream been logically configured into blocking mode? Only - * meaningful if desires_blocking_set is 1. Ignored if blocking is not - * currently possible given QUIC_CONNECTION configuration. - */ - unsigned int desires_blocking : 1; - - /* - * Has SSL_set_blocking_mode been called on this stream? If not set, we - * inherit from the QUIC_CONNECTION blocking state. - */ - unsigned int desires_blocking_set : 1; - /* The application has retired a FIN (i.e. SSL_ERROR_ZERO_RETURN). */ unsigned int retired_fin : 1; @@ -205,12 +192,6 @@ struct quic_conn_st { /* Are we using thread assisted mode? Never changes after init. */ unsigned int is_thread_assisted : 1; - /* Do connection-level operations (e.g. handshakes) run in blocking mode? */ - unsigned int blocking : 1; - - /* Does the application want blocking mode? */ - unsigned int desires_blocking : 1; - /* Have we created a default XSO yet? */ unsigned int default_xso_created : 1; @@ -245,11 +226,6 @@ struct quic_conn_st { uint64_t incoming_stream_aec; /* - * Last network BIO epoch at which blocking mode compatibility was checked. - */ - uint64_t last_net_bio_epoch; - - /* * Last 'normal' error during an app-level I/O operation, used by * SSL_get_error(); used to track data-path errors like SSL_ERROR_WANT_READ * and SSL_ERROR_WANT_WRITE. diff --git a/ssl/quic/quic_obj.c b/ssl/quic/quic_obj.c index ffe6b1bcda..827b0c38b0 100644 --- a/ssl/quic/quic_obj.c +++ b/ssl/quic/quic_obj.c @@ -39,6 +39,7 @@ int ossl_quic_obj_init(QUIC_OBJ *obj, obj->is_port_leader = is_port_leader; obj->engine = engine; obj->port = port; + obj->req_blocking_mode = QUIC_BLOCKING_MODE_INHERIT; if (!obj_update_cache(obj)) goto err; @@ -87,3 +88,38 @@ SSL_CONNECTION *ossl_quic_obj_get0_handshake_layer(QUIC_OBJ *obj) return SSL_CONNECTION_FROM_SSL_ONLY(((QUIC_CONNECTION *)obj)->tls); } + +/* (Returns a cached result.) */ +int ossl_quic_obj_can_support_blocking(const QUIC_OBJ *obj) +{ + QUIC_REACTOR *rtor = ossl_quic_obj_get0_reactor(obj); + + return ossl_quic_reactor_can_poll_r(rtor) + || ossl_quic_reactor_can_poll_w(rtor); +} + +int ossl_quic_obj_desires_blocking(const QUIC_OBJ *obj) +{ + unsigned int req_blocking_mode; + + for (; (req_blocking_mode = obj->req_blocking_mode) + == QUIC_BLOCKING_MODE_INHERIT && obj->parent_obj != NULL; + obj = obj->parent_obj); + + return req_blocking_mode != QUIC_BLOCKING_MODE_NONBLOCKING; +} + +int ossl_quic_obj_blocking(const QUIC_OBJ *obj) +{ + if (!ossl_quic_obj_desires_blocking(obj)) + return 0; + + ossl_quic_engine_update_poll_descriptors(ossl_quic_obj_get0_engine(obj), + /*force=*/0); + return ossl_quic_obj_can_support_blocking(obj); +} + +void ossl_quic_obj_set_blocking_mode(QUIC_OBJ *obj, unsigned int mode) +{ + obj->req_blocking_mode = mode; +} diff --git a/ssl/quic/quic_obj_local.h b/ssl/quic/quic_obj_local.h index fa5b33f265..efd11bac25 100644 --- a/ssl/quic/quic_obj_local.h +++ b/ssl/quic/quic_obj_local.h @@ -103,6 +103,18 @@ struct quic_obj_st { unsigned int init_done : 1; unsigned int is_event_leader : 1; unsigned int is_port_leader : 1; + + /* + * Blocking mode configuration is handled generically through QUIC_OBJ as it + * by default inherits from the parent SSL object. + */ + unsigned int req_blocking_mode : 2; /* QUIC_BLOCKING_MODE */ +}; + +enum { + QUIC_BLOCKING_MODE_INHERIT, + QUIC_BLOCKING_MODE_NONBLOCKING, + QUIC_BLOCKING_MODE_BLOCKING }; /* @@ -220,6 +232,32 @@ ossl_quic_obj_get0_port_local(const QUIC_OBJ *obj) } /* + * Return 1 if we are currently capable of supporting blocking mode (regardless + * of whether it is actually turned on). + */ +int ossl_quic_obj_can_support_blocking(const QUIC_OBJ *obj); + +/* + * Returns 1 if we *desire* to do blocking I/O, regardless of whether it will + * actually be used (e.g. because it cannot currently be supported). + */ +int ossl_quic_obj_desires_blocking(const QUIC_OBJ *obj); + +/* + * Return 1 if an API call directly to the given object should use blocking mode + * and 0 otherwise. + */ +int ossl_quic_obj_blocking(const QUIC_OBJ *obj); + +/* + * Set the (requested) blocking mode, which might or might not be honoured + * depending on whether the BIO configuration can support it. Argument is a + * QUIC_BLOCKING_MODE value. If the top-level object in a QSO hierarchy is set + * to QUIC_BLOCKING_MODE_INHERIT, defaults to blocking mode. + */ +void ossl_quic_obj_set_blocking_mode(QUIC_OBJ *obj, unsigned int mode); + +/* * Convenience Inlines * =================== * diff --git a/ssl/quic/quic_port.c b/ssl/quic/quic_port.c index 64323c1c6e..936b97b5fe 100644 --- a/ssl/quic/quic_port.c +++ b/ssl/quic/quic_port.c @@ -95,6 +95,7 @@ static int port_init(QUIC_PORT *port) ossl_list_port_insert_tail(&port->engine->port_list, port); port->on_engine_list = 1; + port->bio_changed = 1; return 1; err: @@ -238,16 +239,20 @@ static int port_update_poll_desc(QUIC_PORT *port, BIO *net_bio, int for_write) return 1; } -int ossl_quic_port_update_poll_descriptors(QUIC_PORT *port) +int ossl_quic_port_update_poll_descriptors(QUIC_PORT *port, int force) { int ok = 1; + if (!force && !port->bio_changed) + return 0; + if (!port_update_poll_desc(port, port->net_rbio, /*for_write=*/0)) ok = 0; if (!port_update_poll_desc(port, port->net_wbio, /*for_write=*/1)) ok = 0; + port->bio_changed = 0; return ok; } @@ -292,7 +297,7 @@ static void port_update_addressing_mode(QUIC_PORT *port) port->addressed_mode_r = ((rcaps & BIO_DGRAM_CAP_PROVIDES_SRC_ADDR) != 0); port->addressed_mode_w = ((wcaps & BIO_DGRAM_CAP_HANDLES_DST_ADDR) != 0); - ++port->net_bio_epoch; + port->bio_changed = 1; } int ossl_quic_port_is_addressed_r(const QUIC_PORT *port) @@ -348,11 +353,6 @@ int ossl_quic_port_set_net_wbio(QUIC_PORT *port, BIO *net_wbio) return 1; } -uint64_t ossl_quic_port_get_net_bio_epoch(const QUIC_PORT *port) -{ - return port->net_bio_epoch; -} - /* * QUIC Port: Channel Lifecycle * ============================ diff --git a/ssl/quic/quic_port_local.h b/ssl/quic/quic_port_local.h index 79336ebef5..0b954d6d1c 100644 --- a/ssl/quic/quic_port_local.h +++ b/ssl/quic/quic_port_local.h @@ -81,9 +81,6 @@ struct quic_port_st { /* Port-level permanent errors (causing failure state) are stored here. */ ERR_STATE *err_state; - /* Network BIO epoch. Increments whenever network BIO config changes. */ - uint64_t net_bio_epoch; - /* DCID length used for incoming short header packets. */ unsigned char rx_short_dcid_len; /* For clients, CID length used for outgoing Initial packets. */ @@ -107,6 +104,9 @@ struct quic_port_st { /* Are we using addressed mode (BIO_sendmmsg with non-NULL peer)? */ unsigned int addressed_mode_w : 1; unsigned int addressed_mode_r : 1; + + /* Has the BIO been changed since we last updated reactor pollability? */ + unsigned int bio_changed : 1; }; # endif |