QUIC PORT, CHANNEL: Move ticking code into QUIC_PORT

Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/22674)
This commit is contained in:
Hugo Landau 2023-11-09 10:27:13 +00:00
parent 4ed6b48d9d
commit 632b0c7e8c
8 changed files with 128 additions and 163 deletions

View File

@ -267,6 +267,13 @@ int ossl_quic_channel_on_new_conn(QUIC_CHANNEL *ch, const BIO_ADDR *peer,
const QUIC_CONN_ID *peer_scid,
const QUIC_CONN_ID *peer_dcid);
/* For use by QUIC_PORT. You should not need to call this directly. */
void ossl_quic_channel_subtick(QUIC_CHANNEL *ch, QUIC_TICK_RESULT *r,
uint32_t flags);
/* For use by QUIC_PORT only. */
void ossl_quic_channel_raise_net_error(QUIC_CHANNEL *ch);
/*
* Queries and Accessors
* =====================
@ -294,12 +301,6 @@ BIO *ossl_quic_channel_get_net_wbio(QUIC_CHANNEL *ch);
int ossl_quic_channel_set_net_rbio(QUIC_CHANNEL *ch, BIO *net_rbio);
int ossl_quic_channel_set_net_wbio(QUIC_CHANNEL *ch, BIO *net_wbio);
/*
* Re-poll the network BIOs already set to determine if their support
* for polling has changed.
*/
int ossl_quic_channel_update_poll_descriptors(QUIC_CHANNEL *ch);
/*
* Returns an existing stream by stream ID. Returns NULL if the stream does not
* exist.
@ -396,9 +397,6 @@ int ossl_quic_channel_has_pending(const QUIC_CHANNEL *ch);
/* Force transmission of an ACK-eliciting packet. */
int ossl_quic_channel_ping(QUIC_CHANNEL *ch);
/* For testing use. While enabled, ticking is not performed. */
void ossl_quic_channel_set_inhibit_tick(QUIC_CHANNEL *ch, int inhibit);
/*
* These queries exist for diagnostic purposes only. They may roll over.
* Do not rely on them for non-testing purposes.

View File

@ -97,6 +97,10 @@ BIO *ossl_quic_port_get_net_wbio(QUIC_PORT *port);
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.
*/
int ossl_quic_port_update_poll_descriptors(QUIC_PORT *port);
/* Gets the reactor which can be used to tick/poll on the port. */
@ -114,6 +118,20 @@ OSSL_TIME ossl_quic_port_get_time(QUIC_PORT *port);
int ossl_quic_port_get_rx_short_dcid_len(const QUIC_PORT *port);
int ossl_quic_port_get_tx_init_dcid_len(const QUIC_PORT *port);
/* For testing use. While enabled, ticking is not performed. */
void ossl_quic_port_set_inhibit_tick(QUIC_PORT *port, int inhibit);
/*
* Events
* ======
*/
/*
* Called if a permanent network error occurs. Terminates all channels
* immediately.
*/
void ossl_quic_port_raise_net_error(QUIC_PORT *port);
# endif
#endif

View File

@ -48,10 +48,8 @@
DEFINE_LIST_OF_IMPL(ch, QUIC_CHANNEL);
static void ch_save_err_state(QUIC_CHANNEL *ch);
static void ch_rx_pre(QUIC_CHANNEL *ch);
static int ch_rx(QUIC_CHANNEL *ch, int channel_only);
static int ch_tx(QUIC_CHANNEL *ch);
static void ch_tick(QUIC_TICK_RESULT *res, void *arg, uint32_t flags);
static int ch_tick_tls(QUIC_CHANNEL *ch, int channel_only);
static void ch_rx_handle_packet(QUIC_CHANNEL *ch, int channel_only);
static OSSL_TIME ch_determine_next_tick_deadline(QUIC_CHANNEL *ch);
@ -92,7 +90,6 @@ static void ch_on_idle_timeout(QUIC_CHANNEL *ch);
static void ch_update_idle(QUIC_CHANNEL *ch);
static void ch_update_ping_deadline(QUIC_CHANNEL *ch);
static void ch_stateless_reset(QUIC_CHANNEL *ch);
static void ch_raise_net_error(QUIC_CHANNEL *ch);
static void ch_on_terminating_timeout(QUIC_CHANNEL *ch);
static void ch_start_terminating(QUIC_CHANNEL *ch,
const QUIC_TERMINATE_CAUSE *tcause,
@ -467,8 +464,6 @@ static int ch_init(QUIC_CHANNEL *ch)
goto err;
ch_update_idle(ch);
ossl_quic_reactor_init(&ch->rtor, ch_tick, ch,
ch_determine_next_tick_deadline(ch));
ossl_list_ch_insert_tail(&ch->port->channel_list, ch);
ch->on_port_list = 1;
return 1;
@ -601,7 +596,7 @@ int ossl_quic_channel_set_peer_addr(QUIC_CHANNEL *ch, const BIO_ADDR *peer_addr)
QUIC_REACTOR *ossl_quic_channel_get_reactor(QUIC_CHANNEL *ch)
{
return &ch->rtor;
return ossl_quic_port_get0_reactor(ch->port);
}
QUIC_STREAM_MAP *ossl_quic_channel_get_qsm(QUIC_CHANNEL *ch)
@ -1839,20 +1834,20 @@ err:
* at least everything network I/O related. Best effort - not allowed to fail
* "loudly".
*/
static void ch_tick(QUIC_TICK_RESULT *res, void *arg, uint32_t flags)
void ossl_quic_channel_subtick(QUIC_CHANNEL *ch, QUIC_TICK_RESULT *res,
uint32_t flags)
{
OSSL_TIME now, deadline;
QUIC_CHANNEL *ch = arg;
int channel_only = (flags & QUIC_REACTOR_TICK_FLAG_CHANNEL_ONLY) != 0;
/*
* When we tick the QUIC connection, we do everything we need to do
* periodically. In order, we:
* periodically. Network I/O handling will already have been performed
* as necessary by the QUIC port. Thus, in order, we:
*
* - handle any incoming data from the network;
* - handle any timer events which are due to fire (ACKM, etc.)
* - write any data to the network due to be sent, to the extent
* possible;
* - handle any packets the DEMUX has queued up for us;
* - handle any timer events which are due to fire (ACKM, etc.);
* - generate any packets which need to be sent;
* - determine the time at which we should next be ticked.
*/
@ -1880,13 +1875,10 @@ static void ch_tick(QUIC_TICK_RESULT *res, void *arg, uint32_t flags)
}
}
if (!ch->inhibit_tick) {
if (!ch->port->inhibit_tick) {
/* Handle RXKU timeouts. */
ch_rxku_tick(ch);
/* Handle any incoming data from network. */
ch_rx_pre(ch);
do {
/* Process queued incoming packets. */
ch->did_tls_tick = 0;
@ -1923,7 +1915,7 @@ static void ch_tick(QUIC_TICK_RESULT *res, void *arg, uint32_t flags)
* Idle timeout differs from normal protocol violation because we do
* not send a CONN_CLOSE frame; go straight to TERMINATED.
*/
if (!ch->inhibit_tick)
if (!ch->port->inhibit_tick)
ch_on_idle_timeout(ch);
res->net_read_desired = 0;
@ -1932,7 +1924,7 @@ static void ch_tick(QUIC_TICK_RESULT *res, void *arg, uint32_t flags)
return;
}
if (!ch->inhibit_tick) {
if (!ch->port->inhibit_tick) {
deadline = ossl_ackm_get_loss_detection_deadline(ch->ackm);
if (!ossl_time_is_zero(deadline)
&& ossl_time_compare(now, deadline) >= 0)
@ -1954,7 +1946,7 @@ static void ch_tick(QUIC_TICK_RESULT *res, void *arg, uint32_t flags)
ch_update_ping_deadline(ch);
}
/* Write any data to the network due to be sent. */
/* Queue any data to be sent for transmission. */
ch_tx(ch);
/* Do stream GC. */
@ -1965,14 +1957,13 @@ static void ch_tick(QUIC_TICK_RESULT *res, void *arg, uint32_t flags)
res->tick_deadline = ch_determine_next_tick_deadline(ch);
/*
* Always process network input unless we are now terminated.
* Although we had not terminated at the beginning of this tick, network
* errors in ch_rx_pre() or ch_tx() may have caused us to transition to the
* Terminated state.
* Always process network input unless we are now terminated. Although we
* had not terminated at the beginning of this tick, network errors in
* ch_tx() may have caused us to transition to the Terminated state.
*/
res->net_read_desired = !ossl_quic_channel_is_terminated(ch);
/* We want to write to the network if we have any in our queue. */
/* We want to write to the network if we have any data in our TX queue. */
res->net_write_desired
= (!ossl_quic_channel_is_terminated(ch)
&& ossl_qtx_get_queue_len_datagrams(ch->qtx) > 0);
@ -2000,32 +1991,6 @@ static int ch_tick_tls(QUIC_CHANNEL *ch, int channel_only)
return 1;
}
/* Process incoming datagrams, if any. */
static void ch_rx_pre(QUIC_CHANNEL *ch)
{
int ret;
if (!ch->is_server && !ch->have_sent_any_pkt)
return;
/*
* Get DEMUX to BIO_recvmmsg from the network and queue incoming datagrams
* to the appropriate QRX instance.
*/
ret = ossl_quic_demux_pump(ch->port->demux);
if (ret == QUIC_DEMUX_PUMP_RES_STATELESS_RESET)
ch_stateless_reset(ch);
else if (ret == QUIC_DEMUX_PUMP_RES_PERMANENT_FAIL)
/*
* We don't care about transient failure, but permanent failure means we
* should tear down the connection as though a protocol violation
* occurred. Skip straight to the Terminating state as there is no point
* trying to send CONNECTION_CLOSE frames if the network BIO is not
* operating correctly.
*/
ch_raise_net_error(ch);
}
/* Check incoming forged packet limit and terminate connection if needed. */
static void ch_rx_check_forged_pkt_limit(QUIC_CHANNEL *ch)
{
@ -2508,7 +2473,7 @@ static int ch_tx(QUIC_CHANNEL *ch)
case QTX_FLUSH_NET_RES_PERMANENT_FAIL:
default:
/* Permanent underlying network BIO, start terminating. */
ch_raise_net_error(ch);
ossl_quic_port_raise_net_error(ch->port);
break;
}
@ -2571,91 +2536,14 @@ static OSSL_TIME ch_determine_next_tick_deadline(QUIC_CHANNEL *ch)
* =======================================
*/
/* Determines whether we can support a given poll descriptor. */
static int validate_poll_descriptor(const BIO_POLL_DESCRIPTOR *d)
{
if (d->type == BIO_POLL_DESCRIPTOR_TYPE_SOCK_FD && d->value.fd < 0) {
ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT);
return 0;
}
return 1;
}
BIO *ossl_quic_channel_get_net_rbio(QUIC_CHANNEL *ch)
{
return ch->net_rbio;
}
BIO *ossl_quic_channel_get_net_wbio(QUIC_CHANNEL *ch)
{
return ch->net_wbio;
}
static int ch_update_poll_desc(QUIC_CHANNEL *ch, BIO *net_bio, int for_write)
{
BIO_POLL_DESCRIPTOR d = {0};
if (net_bio == NULL
|| (!for_write && !BIO_get_rpoll_descriptor(net_bio, &d))
|| (for_write && !BIO_get_wpoll_descriptor(net_bio, &d)))
/* Non-pollable BIO */
d.type = BIO_POLL_DESCRIPTOR_TYPE_NONE;
if (!validate_poll_descriptor(&d))
return 0;
if (for_write)
ossl_quic_reactor_set_poll_w(&ch->rtor, &d);
else
ossl_quic_reactor_set_poll_r(&ch->rtor, &d);
return 1;
}
int ossl_quic_channel_update_poll_descriptors(QUIC_CHANNEL *ch)
{
int ok = 1;
if (!ch_update_poll_desc(ch, ch->net_rbio, /*for_write=*/0))
ok = 0;
if (!ch_update_poll_desc(ch, ch->net_wbio, /*for_write=*/1))
ok = 0;
return ok;
}
/*
* QUIC_CHANNEL does not ref any BIO it is provided with, nor is any ref
* transferred to it. The caller (i.e., QUIC_CONNECTION) is responsible for
* ensuring the BIO lasts until the channel is freed or the BIO is switched out
* for another BIO by a subsequent successful call to this function.
*/
int ossl_quic_channel_set_net_rbio(QUIC_CHANNEL *ch, BIO *net_rbio)
{
if (ch->net_rbio == net_rbio)
return 1;
if (!ch_update_poll_desc(ch, net_rbio, /*for_write=*/0))
return 0;
ossl_quic_demux_set_bio(ch->port->demux, net_rbio);
ch->net_rbio = net_rbio;
return 1;
return ossl_quic_port_set_net_rbio(ch->port, net_rbio);
}
int ossl_quic_channel_set_net_wbio(QUIC_CHANNEL *ch, BIO *net_wbio)
{
if (ch->net_wbio == net_wbio)
return 1;
if (!ch_update_poll_desc(ch, net_wbio, /*for_write=*/1))
return 0;
ossl_qtx_set_bio(ch->qtx, net_wbio);
ch->net_wbio = net_wbio;
return 1;
return ossl_quic_port_set_net_wbio(ch->port, net_wbio);
}
/*
@ -2695,7 +2583,7 @@ int ossl_quic_channel_start(QUIC_CHANNEL *ch)
if (!ch_tick_tls(ch, /*channel_only=*/0))
return 0;
ossl_quic_reactor_tick(&ch->rtor, 0); /* best effort */
ossl_quic_reactor_tick(ossl_quic_port_get0_reactor(ch->port), 0); /* best effort */
return 1;
}
@ -3199,7 +3087,7 @@ static void ch_save_err_state(QUIC_CHANNEL *ch)
OSSL_ERR_STATE_save(ch->err_state);
}
static void ch_stateless_reset(QUIC_CHANNEL *ch)
static void ossl_unused ch_stateless_reset(QUIC_CHANNEL *ch)
{
QUIC_TERMINATE_CAUSE tcause = {0};
@ -3207,7 +3095,7 @@ static void ch_stateless_reset(QUIC_CHANNEL *ch)
ch_start_terminating(ch, &tcause, 1);
}
static void ch_raise_net_error(QUIC_CHANNEL *ch)
void ossl_quic_channel_raise_net_error(QUIC_CHANNEL *ch)
{
QUIC_TERMINATE_CAUSE tcause = {0};
@ -3698,11 +3586,6 @@ int ossl_quic_channel_ping(QUIC_CHANNEL *ch)
return 1;
}
void ossl_quic_channel_set_inhibit_tick(QUIC_CHANNEL *ch, int inhibit)
{
ch->inhibit_tick = (inhibit != 0);
}
uint16_t ossl_quic_channel_get_diag_num_rx_ack(QUIC_CHANNEL *ch)
{
return ch->diag_num_rx_ack;

View File

@ -58,15 +58,9 @@ struct quic_channel_st {
*/
unsigned char *local_transport_params;
/* Asynchronous I/O reactor. */
QUIC_REACTOR rtor;
/* Our current L4 peer address, if any. */
BIO_ADDR cur_peer_addr;
/* Network-side read and write BIOs. */
BIO *net_rbio, *net_wbio;
/*
* Subcomponents of the connection. All of these components are instantiated
* and owned by us.
@ -442,9 +436,6 @@ struct quic_channel_st {
*/
unsigned int protocol_error : 1;
/* Inhibit tick for testing purposes? */
unsigned int inhibit_tick : 1;
/* Are we using addressed mode? */
unsigned int addressed_mode : 1;

View File

@ -64,7 +64,7 @@ static int block_until_pred(QUIC_CONNECTION *qc,
* Any attempt to block auto-disables tick inhibition as otherwise we will
* hang around forever.
*/
ossl_quic_channel_set_inhibit_tick(qc->ch, 0);
ossl_quic_port_set_inhibit_tick(qc->port, 0);
rtor = ossl_quic_channel_get_reactor(qc->ch);
return ossl_quic_reactor_block_until_pred(rtor, pred, pred_arg, flags,
@ -856,7 +856,7 @@ static int qc_can_support_blocking_cached(QUIC_CONNECTION *qc)
static void qc_update_can_support_blocking(QUIC_CONNECTION *qc)
{
ossl_quic_channel_update_poll_descriptors(qc->ch); /* best effort */
ossl_quic_port_update_poll_descriptors(qc->port); /* best effort */
}
static void qc_update_blocking_mode(QUIC_CONNECTION *qc)

View File

@ -24,6 +24,7 @@ static void port_cleanup(QUIC_PORT *port);
static OSSL_TIME get_time(void *arg);
static void port_tick(QUIC_TICK_RESULT *res, void *arg, uint32_t flags);
static void port_default_packet_handler(QUIC_URXE *e, void *arg);
static void port_rx_pre(QUIC_PORT *port);
DEFINE_LIST_OF_IMPL(ch, QUIC_CHANNEL);
@ -217,13 +218,17 @@ 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)
{
QUIC_CHANNEL *ch;
if (port->net_wbio == net_wbio)
return 1;
if (!port_update_poll_desc(port, net_wbio, /*for_write=*/1))
return 0;
//ossl_qtx_set_bio(port->qtx, net_wbio);
LIST_FOREACH(ch, ch, &port->channel_list)
ossl_qtx_set_bio(ch->qtx, net_wbio);
port->net_wbio = net_wbio;
return 1;
}
@ -301,7 +306,49 @@ QUIC_CHANNEL *ossl_quic_port_create_incoming(QUIC_PORT *port, SSL *tls)
*/
static void port_tick(QUIC_TICK_RESULT *res, void *arg, uint32_t flags)
{
/* TODO */
QUIC_PORT *port = arg;
QUIC_CHANNEL *ch;
res->net_read_desired = 0;
res->net_write_desired = 0;
res->tick_deadline = ossl_time_infinite();
if (!port->inhibit_tick) {
/* Handle any incoming data from network. */
port_rx_pre(port);
/* Iterate through all channels and service them. */
LIST_FOREACH(ch, ch, &port->channel_list) {
QUIC_TICK_RESULT subr = {0};
ossl_quic_channel_subtick(ch, &subr, flags);
ossl_quic_tick_result_merge_into(res, &subr);
}
}
}
/* Process incoming datagrams, if any. */
static void port_rx_pre(QUIC_PORT *port)
{
int ret;
// TODO !have_sent_any_pkt
/*
* Get DEMUX to BIO_recvmmsg from the network and queue incoming datagrams
* to the appropriate QRX instances.
*/
ret = ossl_quic_demux_pump(port->demux);
// TODO: handle ret, stateless reset
if (ret == QUIC_DEMUX_PUMP_RES_PERMANENT_FAIL)
/*
* We don't care about transient failure, but permanent failure means we
* should tear down the port. All connections skip straight to the
* Terminated state as there is no point trying to send CONNECTION_CLOSE
* frames if the network BIO is not operating correctly.
*/
ossl_quic_port_raise_net_error(port);
}
/*
@ -336,9 +383,17 @@ static void port_default_packet_handler(QUIC_URXE *e, void *arg)
QUIC_PKT_HDR hdr;
QUIC_CHANNEL *new_ch = NULL;
// TODO review this
if (port->tserver_ch == NULL)
goto undesirable;
// TODO allow_incoming
//if (!ossl_assert(ch->is_server))
// goto undesirable;
//TODO if (ch->state != QUIC_CHANNEL_STATE_IDLE)
// goto undesirable;
/*
* We have got a packet for an unknown DCID. This might be an attempt to
* open a new connection.
@ -395,3 +450,18 @@ static void port_default_packet_handler(QUIC_URXE *e, void *arg)
undesirable:
ossl_quic_demux_release_urxe(port->demux, e);
}
void ossl_quic_port_set_inhibit_tick(QUIC_PORT *port, int inhibit)
{
port->inhibit_tick = (inhibit != 0);
}
void ossl_quic_port_raise_net_error(QUIC_PORT *port)
{
QUIC_CHANNEL *ch;
// TODO fsm
LIST_FOREACH(ch, ch, &port->channel_list)
ossl_quic_channel_raise_net_error(ch);
}

View File

@ -59,6 +59,9 @@ struct quic_port_st {
/* Is this port created to support multiple connections? */
unsigned int is_multi_conn : 1;
/* Inhibit tick for testing purposes? */
unsigned int inhibit_tick : 1;
};
# endif

View File

@ -14,6 +14,7 @@
#include "internal/quic_ssl.h"
#include "internal/quic_error.h"
#include "internal/quic_stream_map.h"
#include "internal/quic_port.h"
#include "testutil.h"
#include "helpers/quictestlib.h"
#if defined(OPENSSL_THREADS)
@ -1594,7 +1595,7 @@ static int run_script_worker(struct helper *h, const struct script_op *script,
QUIC_CHANNEL *ch = ossl_quic_conn_get_channel(h->c_conn);
SSL_SHUTDOWN_EX_ARGS args = {0};
ossl_quic_channel_set_inhibit_tick(ch, 0);
ossl_quic_port_set_inhibit_tick(ossl_quic_channel_get0_port(ch), 0);
if (!TEST_ptr(c_tgt))
goto out;
@ -1918,7 +1919,8 @@ static int run_script_worker(struct helper *h, const struct script_op *script,
{
QUIC_CHANNEL *ch = ossl_quic_conn_get_channel(h->c_conn);
ossl_quic_channel_set_inhibit_tick(ch, op->arg1);
ossl_quic_port_set_inhibit_tick(ossl_quic_channel_get0_port(ch),
op->arg1);
}
break;