QUIC PORT: Allow errors to be tracked at port level
authorHugo Landau <hlandau@openssl.org>
Thu, 9 Nov 2023 10:27:14 +0000 (10:27 +0000)
committerHugo Landau <hlandau@openssl.org>
Thu, 21 Dec 2023 08:12:06 +0000 (08:12 +0000)
Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/22674)

include/internal/quic_port.h
ssl/quic/quic_channel.c
ssl/quic/quic_port.c
ssl/quic/quic_port_local.h

index ea97b9339211050437b75c60c87aed0e76bb7ea5..be08e213ee4efc96b72e0691e6d39839ec4676a7 100644 (file)
@@ -133,6 +133,12 @@ void ossl_quic_port_set_inhibit_tick(QUIC_PORT *port, int inhibit);
 /* Returns 1 if the port is running/healthy, 0 if it has failed. */
 int ossl_quic_port_is_running(const QUIC_PORT *port);
 
+/*
+ * Restores port-level error to the error stack. To be called only if
+ * the port is no longer running.
+ */
+void ossl_quic_port_restore_err_state(const QUIC_PORT *port);
+
 /*
  * Events
  * ======
@@ -140,9 +146,11 @@ int ossl_quic_port_is_running(const QUIC_PORT *port);
 
 /*
  * Called if a permanent network error occurs. Terminates all channels
- * immediately.
+ * immediately. triggering_ch is an optional argument designating
+ * a channel which encountered the network error.
  */
-void ossl_quic_port_raise_net_error(QUIC_PORT *port);
+void ossl_quic_port_raise_net_error(QUIC_PORT *port,
+                                    QUIC_CHANNEL *triggering_ch);
 
 # endif
 
index aed911ad9fdcc03a92e4c479f03fd40535cdede5..5bcd66c9e45ac750db13fe4ae12c7b30fde73e9b 100644 (file)
@@ -2310,7 +2310,7 @@ static int ch_tx(QUIC_CHANNEL *ch)
     case QTX_FLUSH_NET_RES_PERMANENT_FAIL:
     default:
         /* Permanent underlying network BIO, start terminating. */
-        ossl_quic_port_raise_net_error(ch->port);
+        ossl_quic_port_raise_net_error(ch->port, ch);
         break;
     }
 
@@ -2927,13 +2927,14 @@ void ossl_quic_channel_raise_net_error(QUIC_CHANNEL *ch)
 {
     QUIC_TERMINATE_CAUSE tcause = {0};
 
-    ch->net_error = 1;
+    if (ch->net_error)
+        return;
 
-    ERR_raise_data(ERR_LIB_SSL, SSL_R_QUIC_NETWORK_ERROR,
-                   "connection terminated due to network error");
-    ch_save_err_state(ch);
+    ch->net_error = 1;
 
     tcause.error_code = QUIC_ERR_INTERNAL_ERROR;
+    tcause.reason     = "network BIO I/O error";
+    tcause.reason_len = strlen(tcause.reason);
 
     /*
      * Skip Terminating state and go directly to Terminated, no point trying to
@@ -2952,7 +2953,10 @@ void ossl_quic_channel_restore_err_state(QUIC_CHANNEL *ch)
     if (ch == NULL)
         return;
 
-    OSSL_ERR_STATE_restore(ch->err_state);
+    if (!ossl_quic_port_is_running(ch->port))
+        ossl_quic_port_restore_err_state(ch->port);
+    else
+        OSSL_ERR_STATE_restore(ch->err_state);
 }
 
 void ossl_quic_channel_raise_protocol_error_loc(QUIC_CHANNEL *ch,
index db1a352adffe935e07673bfea21fdb394502c51a..e6dba46bf4aba5aac1d409c66866ff7c16b5bded 100644 (file)
@@ -70,6 +70,9 @@ static int port_init(QUIC_PORT *port)
     if (port->channel_ctx == NULL)
         goto err;
 
+    if ((port->err_state = OSSL_ERR_STATE_new()) == NULL)
+        goto err;
+
     if ((port->demux = ossl_quic_demux_new(/*BIO=*/NULL,
                                            /*Short CID Len=*/rx_short_dcid_len,
                                            get_time, port)) == NULL)
@@ -108,6 +111,9 @@ static void port_cleanup(QUIC_PORT *port)
 
     ossl_quic_lcidm_free(port->lcidm);
     port->lcidm = NULL;
+
+    OSSL_ERR_STATE_free(port->err_state);
+    port->err_state = NULL;
 }
 
 static void port_transition_failed(QUIC_PORT *port)
@@ -341,7 +347,8 @@ static void port_tick(QUIC_TICK_RESULT *res, void *arg, uint32_t flags)
 
     if (!port->inhibit_tick) {
         /* Handle any incoming data from network. */
-        port_rx_pre(port);
+        if (ossl_quic_port_is_running(port))
+            port_rx_pre(port);
 
         /* Iterate through all channels and service them. */
         LIST_FOREACH(ch, ch, &port->channel_list) {
@@ -370,7 +377,7 @@ static void port_rx_pre(QUIC_PORT *port)
          * 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);
+        ossl_quic_port_raise_net_error(port, NULL);
 }
 
 /*
@@ -538,12 +545,35 @@ 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)
+void ossl_quic_port_raise_net_error(QUIC_PORT *port,
+                                    QUIC_CHANNEL *triggering_ch)
 {
     QUIC_CHANNEL *ch;
 
+    if (!ossl_quic_port_is_running(port))
+        return;
+
+    /*
+     * Immediately capture any triggering error on the error stack, with a
+     * cover error.
+     */
+    ERR_raise_data(ERR_LIB_SSL, SSL_R_QUIC_NETWORK_ERROR,
+                   "port failed due to network BIO I/O error");
+    OSSL_ERR_STATE_save(port->err_state);
+
     port_transition_failed(port);
 
+    /* Give the triggering channel (if any) the first notification. */
+    if (triggering_ch != NULL)
+        ossl_quic_channel_raise_net_error(triggering_ch);
+
     LIST_FOREACH(ch, ch, &port->channel_list)
-        ossl_quic_channel_raise_net_error(ch);
+        if (ch != triggering_ch)
+            ossl_quic_channel_raise_net_error(ch);
+}
+
+void ossl_quic_port_restore_err_state(const QUIC_PORT *port)
+{
+    ERR_clear_error();
+    OSSL_ERR_STATE_restore(port->err_state);
 }
index a2937beeaf9184dff61eb4774f99769f3c4ec7b0..38bb0193d8ac9bf11360a3fe46193e634a1c3466 100644 (file)
@@ -71,6 +71,9 @@ struct quic_port_st {
     /* SRTM used for incoming packet routing by SRT. */
     QUIC_SRTM                       *srtm;
 
+    /* Port-level permanent errors (causing failure state) are stored here. */
+    ERR_STATE                       *err_state;
+
     /* DCID length used for incoming short header packets. */
     unsigned char                   rx_short_dcid_len;
     /* For clients, CID length used for outgoing Initial packets. */