summaryrefslogtreecommitdiff
path: root/ssl/quic
diff options
context:
space:
mode:
authorHugo Landau <hlandau@openssl.org>2023-01-09 15:47:47 +0000
committerHugo Landau <hlandau@openssl.org>2023-01-13 13:20:37 +0000
commitdf15e990ff2557fd43fe4d661c8e1988a3d0ffcc (patch)
tree551063099e942942e4c9d0c8aee258e144ec57ad /ssl/quic
parent0550829f53fe74f884e382ec0ec323342f77d181 (diff)
downloadopenssl-new-df15e990ff2557fd43fe4d661c8e1988a3d0ffcc.tar.gz
QUIC CHANNEL: Handle network errors as connection-fatal events
Reviewed-by: Tomas Mraz <tomas@openssl.org> Reviewed-by: Matt Caswell <matt@openssl.org> (Merged from https://github.com/openssl/openssl/pull/19703)
Diffstat (limited to 'ssl/quic')
-rw-r--r--ssl/quic/quic_channel.c135
1 files changed, 103 insertions, 32 deletions
diff --git a/ssl/quic/quic_channel.c b/ssl/quic/quic_channel.c
index e8f86f251f..0fb5c6d5d3 100644
--- a/ssl/quic/quic_channel.c
+++ b/ssl/quic/quic_channel.c
@@ -57,9 +57,11 @@ static int ch_discard_el(QUIC_CHANNEL *ch,
uint32_t enc_level);
static void ch_on_idle_timeout(QUIC_CHANNEL *ch);
static void ch_update_idle(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);
+ const QUIC_TERMINATE_CAUSE *tcause,
+ int force_immediate);
static int gen_rand_conn_id(OSSL_LIB_CTX *libctx, size_t len, QUIC_CONN_ID *cid)
{
@@ -1071,16 +1073,25 @@ static void ch_tick(QUIC_TICK_RESULT *res, void *arg)
/* Determine the time at which we should next be ticked. */
res->tick_deadline = ch_determine_next_tick_deadline(ch);
- /* Always process network input. */
- res->want_net_read = 1;
+ /*
+ * 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.
+ */
+ res->want_net_read = !ossl_quic_channel_is_terminated(ch);
/* We want to write to the network if we have any in our queue. */
- res->want_net_write = (ossl_qtx_get_queue_len_datagrams(ch->qtx) > 0);
+ res->want_net_write
+ = (!ossl_quic_channel_is_terminated(ch)
+ && ossl_qtx_get_queue_len_datagrams(ch->qtx) > 0);
}
/* Process incoming datagrams, if any. */
static void ch_rx_pre(QUIC_CHANNEL *ch)
{
+ int ret;
+
if (!ch->have_sent_any_pkt)
return;
@@ -1088,7 +1099,16 @@ static void ch_rx_pre(QUIC_CHANNEL *ch)
* Get DEMUX to BIO_recvmmsg from the network and queue incoming datagrams
* to the appropriate QRX instance.
*/
- ossl_quic_demux_pump(ch->demux); /* best effort */
+ ret = ossl_quic_demux_pump(ch->demux);
+ 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);
}
/* Process queued incoming packets and handle frames, if any. */
@@ -1245,12 +1265,33 @@ static int ch_tx(QUIC_CHANNEL *ch)
* Best effort. In particular if TXP fails for some reason we should still
* flush any queued packets which we already generated.
*/
- if (ossl_quic_tx_packetiser_generate(ch->txp,
- TX_PACKETISER_ARCHETYPE_NORMAL)
- == TX_PACKETISER_RES_SENT_PKT)
- ch->have_sent_any_pkt = 1;
+ switch (ossl_quic_tx_packetiser_generate(ch->txp,
+ TX_PACKETISER_ARCHETYPE_NORMAL)) {
+ case TX_PACKETISER_RES_SENT_PKT:
+ ch->have_sent_any_pkt = 1; /* Packet was sent */
+ break;
+ case TX_PACKETISER_RES_NO_PKT:
+ break; /* No packet was sent */
+ default:
+ ossl_quic_channel_raise_protocol_error(ch, QUIC_ERR_INTERNAL_ERROR, 0,
+ "internal error");
+ break; /* Internal failure (e.g. allocation, assertion) */
+ }
+
+ /* Flush packets to network. */
+ switch (ossl_qtx_flush_net(ch->qtx)) {
+ case QTX_FLUSH_NET_RES_OK:
+ case QTX_FLUSH_NET_RES_TRANSIENT_FAIL:
+ /* Best effort, done for now. */
+ break;
+
+ case QTX_FLUSH_NET_RES_PERMANENT_FAIL:
+ default:
+ /* Permanent underlying network BIO, start terminating. */
+ ch_raise_net_error(ch);
+ break;
+ }
- ossl_qtx_flush_net(ch->qtx); /* best effort */
return 1;
}
@@ -1260,6 +1301,9 @@ static OSSL_TIME ch_determine_next_tick_deadline(QUIC_CHANNEL *ch)
OSSL_TIME deadline;
uint32_t pn_space;
+ if (ossl_quic_channel_is_terminated(ch))
+ return ossl_time_infinite();
+
deadline = ossl_ackm_get_loss_detection_deadline(ch->ackm);
if (ossl_time_is_zero(deadline))
deadline = ossl_time_infinite();
@@ -1404,7 +1448,7 @@ void ossl_quic_channel_local_close(QUIC_CHANNEL *ch)
return;
tcause.app = 1;
- ch_start_terminating(ch, &tcause);
+ ch_start_terminating(ch, &tcause, 0);
}
static void free_token(const unsigned char *buf, size_t buf_len, void *arg)
@@ -1552,7 +1596,8 @@ int ossl_quic_channel_on_handshake_confirmed(QUIC_CHANNEL *ch)
* in it.
*/
static void ch_start_terminating(QUIC_CHANNEL *ch,
- const QUIC_TERMINATE_CAUSE *tcause)
+ const QUIC_TERMINATE_CAUSE *tcause,
+ int force_immediate)
{
switch (ch->state) {
default:
@@ -1562,34 +1607,47 @@ static void ch_start_terminating(QUIC_CHANNEL *ch,
break;
case QUIC_CHANNEL_STATE_ACTIVE:
- ch->state = tcause->remote ? QUIC_CHANNEL_STATE_TERMINATING_DRAINING
- : QUIC_CHANNEL_STATE_TERMINATING_CLOSING;
ch->terminate_cause = *tcause;
- ch->terminate_deadline
- = ossl_time_add(ossl_time_now(),
- ossl_time_multiply(ossl_ackm_get_pto_duration(ch->ackm),
- 3));
-
- if (!tcause->remote) {
- OSSL_QUIC_FRAME_CONN_CLOSE f = {0};
-
- /* best effort */
- f.error_code = ch->terminate_cause.error_code;
- f.frame_type = ch->terminate_cause.frame_type;
- f.is_app = ch->terminate_cause.app;
- ossl_quic_tx_packetiser_schedule_conn_close(ch->txp, &f);
- ch->conn_close_queued = 1;
+
+ if (!force_immediate) {
+ ch->state = tcause->remote ? QUIC_CHANNEL_STATE_TERMINATING_DRAINING
+ : QUIC_CHANNEL_STATE_TERMINATING_CLOSING;
+ ch->terminate_deadline
+ = ossl_time_add(ossl_time_now(),
+ ossl_time_multiply(ossl_ackm_get_pto_duration(ch->ackm),
+ 3));
+
+ if (!tcause->remote) {
+ OSSL_QUIC_FRAME_CONN_CLOSE f = {0};
+
+ /* best effort */
+ f.error_code = ch->terminate_cause.error_code;
+ f.frame_type = ch->terminate_cause.frame_type;
+ f.is_app = ch->terminate_cause.app;
+ ossl_quic_tx_packetiser_schedule_conn_close(ch->txp, &f);
+ ch->conn_close_queued = 1;
+ }
+ } else {
+ ch_on_terminating_timeout(ch);
}
break;
case QUIC_CHANNEL_STATE_TERMINATING_CLOSING:
- if (tcause->remote)
+ if (force_immediate)
+ ch_on_terminating_timeout(ch);
+ else if (tcause->remote)
ch->state = QUIC_CHANNEL_STATE_TERMINATING_DRAINING;
break;
case QUIC_CHANNEL_STATE_TERMINATING_DRAINING:
- /* We remain here until the timout expires. */
+ /*
+ * Other than in the force-immediate case, we remain here until the
+ * timout expires.
+ */
+ if (force_immediate)
+ ch_on_terminating_timeout(ch);
+
break;
case QUIC_CHANNEL_STATE_TERMINATED:
@@ -1612,7 +1670,20 @@ void ossl_quic_channel_on_remote_conn_close(QUIC_CHANNEL *ch,
tcause.error_code = f->error_code;
tcause.frame_type = f->frame_type;
- ch_start_terminating(ch, &tcause);
+ ch_start_terminating(ch, &tcause, 0);
+}
+
+static void ch_raise_net_error(QUIC_CHANNEL *ch)
+{
+ QUIC_TERMINATE_CAUSE tcause = {0};
+
+ tcause.error_code = QUIC_ERR_INTERNAL_ERROR;
+
+ /*
+ * Skip Terminating state and go directly to Terminated, no point trying to
+ * send CONNECTION_CLOSE if we cannot communicate.
+ */
+ ch_start_terminating(ch, &tcause, 1);
}
void ossl_quic_channel_raise_protocol_error(QUIC_CHANNEL *ch,
@@ -1625,7 +1696,7 @@ void ossl_quic_channel_raise_protocol_error(QUIC_CHANNEL *ch,
tcause.error_code = error_code;
tcause.frame_type = frame_type;
- ch_start_terminating(ch, &tcause);
+ ch_start_terminating(ch, &tcause, 0);
}
/*