QUIC PORT: Partially move stateless reset handling to port
authorHugo Landau <hlandau@openssl.org>
Thu, 9 Nov 2023 10:27:13 +0000 (10:27 +0000)
committerHugo Landau <hlandau@openssl.org>
Thu, 21 Dec 2023 08:11:59 +0000 (08:11 +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_channel.h
ssl/quic/quic_channel.c
ssl/quic/quic_port.c

index c696f933245aba05e23852333d4afd93da658ab3..bdc48e50840a8f70d9059115c66a5dd1e0298339 100644 (file)
@@ -273,6 +273,9 @@ void ossl_quic_channel_subtick(QUIC_CHANNEL *ch, QUIC_TICK_RESULT *r,
 /* For use by QUIC_PORT only. */
 void ossl_quic_channel_raise_net_error(QUIC_CHANNEL *ch);
 
+/* For use by QUIC_PORT only. */
+void ossl_quic_channel_on_stateless_reset(QUIC_CHANNEL *ch);
+
 /*
  * Queries and Accessors
  * =====================
index aa5ea0971ec89aa255c347bc7e3578ed4642962e..c363b3bdde32f04b924e828055ff81d526bbb346 100644 (file)
@@ -89,12 +89,10 @@ static int ch_discard_el(QUIC_CHANNEL *ch,
 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_on_terminating_timeout(QUIC_CHANNEL *ch);
 static void ch_start_terminating(QUIC_CHANNEL *ch,
                                  const QUIC_TERMINATE_CAUSE *tcause,
                                  int force_immediate);
-static int ch_stateless_reset_token_handler(const unsigned char *data, size_t datalen, void *arg);
 static void ch_on_txp_ack_tx(const OSSL_QUIC_FRAME_ACK *ack, uint32_t pn_space,
                              void *arg);
 static void ch_rx_handle_version_neg(QUIC_CHANNEL *ch, OSSL_QRX_PKT *pkt);
@@ -207,47 +205,6 @@ static void chan_remove_reset_token(QUIC_CHANNEL *ch, uint64_t seq_num)
     }
 }
 
-/*
- * This is called by the demux whenever a new datagram arrives
- *
- * TODO(QUIC FUTURE): optimise this to only be called for unparsable packets
- */
-static int ossl_unused ch_stateless_reset_token_handler(const unsigned char *data,
-                                            size_t datalen, void *arg)
-{
-    QUIC_SRT_ELEM srte;
-    QUIC_CHANNEL *ch = (QUIC_CHANNEL *)arg;
-
-    /*
-     * Perform some fast and cheap checks for a packet not being a stateless
-     * reset token.  RFC 9000 s. 10.3 specifies this layout for stateless
-     * reset packets:
-     *
-     *  Stateless Reset {
-     *      Fixed Bits (2) = 1,
-     *      Unpredictable Bits (38..),
-     *      Stateless Reset Token (128),
-     *  }
-     *
-     * It also specifies:
-     *      However, endpoints MUST treat any packet ending in a valid
-     *      stateless reset token as a Stateless Reset, as other QUIC
-     *      versions might allow the use of a long header.
-     *
-     * We can rapidly check for the minimum length and that the first pair
-     * of bits in the first byte are 01 or 11.
-     *
-     * The function returns 1 if it is a stateless reset packet, 0 if it isn't
-     * and -1 if an error was encountered.
-     */
-    if (datalen < QUIC_STATELESS_RESET_TOKEN_LEN + 5 || (0100 & *data) != 0100)
-        return 0;
-    memset(&srte, 0, sizeof(srte));
-    if (!reset_token_obfuscate(&srte, data + datalen - sizeof(srte.token)))
-        return -1;
-    return lh_QUIC_SRT_ELEM_retrieve(ch->srt_hash_tok, &srte) != NULL;
-}
-
 /*
  * QUIC Channel Initialization and Teardown
  * ========================================
@@ -3072,12 +3029,13 @@ static void ch_save_err_state(QUIC_CHANNEL *ch)
     OSSL_ERR_STATE_save(ch->err_state);
 }
 
-static void ossl_unused ch_stateless_reset(QUIC_CHANNEL *ch)
+void ossl_quic_channel_on_stateless_reset(QUIC_CHANNEL *ch)
 {
     QUIC_TERMINATE_CAUSE tcause = {0};
 
-    tcause.error_code = QUIC_ERR_NO_ERROR;
-    ch_start_terminating(ch, &tcause, 1);
+    tcause.error_code   = QUIC_ERR_NO_ERROR;
+    tcause.remote       = 1;
+    ch_start_terminating(ch, &tcause, 0);
 }
 
 void ossl_quic_channel_raise_net_error(QUIC_CHANNEL *ch)
index b128477c0a846f245bde208d6c925d18f6a3d2d6..bf351e923c5e9e48a850ebf6698539d8c06262e1 100644 (file)
@@ -381,6 +381,52 @@ static void port_on_new_conn(QUIC_PORT *port, const BIO_ADDR *peer,
     }
 }
 
+static int port_try_handle_stateless_reset(QUIC_PORT *port, const QUIC_URXE *e)
+{
+    size_t i;
+    const unsigned char *data = ossl_quic_urxe_data(e);
+    void *opaque = NULL;
+
+    /*
+     * Perform some fast and cheap checks for a packet not being a stateless
+     * reset token.  RFC 9000 s. 10.3 specifies this layout for stateless
+     * reset packets:
+     *
+     *  Stateless Reset {
+     *      Fixed Bits (2) = 1,
+     *      Unpredictable Bits (38..),
+     *      Stateless Reset Token (128),
+     *  }
+     *
+     * It also specifies:
+     *      However, endpoints MUST treat any packet ending in a valid
+     *      stateless reset token as a Stateless Reset, as other QUIC
+     *      versions might allow the use of a long header.
+     *
+     * We can rapidly check for the minimum length and that the first pair
+     * of bits in the first byte are 01 or 11.
+     *
+     * The function returns 1 if it is a stateless reset packet, 0 if it isn't
+     * and -1 if an error was encountered.
+     */
+    if (e->data_len < QUIC_STATELESS_RESET_TOKEN_LEN + 5
+        || (0100 & *data) != 0100)
+        return 0;
+
+    for (i = 0;; ++i) {
+        if (!ossl_quic_srtm_lookup(port->srtm,
+                                   (QUIC_STATELESS_RESET_TOKEN *)(data + e->data_len
+                                       - sizeof(QUIC_STATELESS_RESET_TOKEN)),
+                                   i, &opaque, NULL))
+            break;
+
+        assert(opaque != NULL);
+        ossl_quic_channel_on_stateless_reset((QUIC_CHANNEL *)opaque);
+    }
+
+    return i > 0;
+}
+
 /*
  * This is called by the demux when we get a packet not destined for any known
  * DCID.
@@ -392,6 +438,9 @@ static void port_default_packet_handler(QUIC_URXE *e, void *arg)
     QUIC_PKT_HDR hdr;
     QUIC_CHANNEL *new_ch = NULL;
 
+    if (port_try_handle_stateless_reset(port, e))
+        goto undesirable;
+
     // TODO review this
     if (port->tserver_ch == NULL)
         goto undesirable;