QUIC DEMUX: Allow MTU to vary over time and autodetect MTU
authorHugo Landau <hlandau@openssl.org>
Fri, 18 Nov 2022 17:20:20 +0000 (17:20 +0000)
committerHugo Landau <hlandau@openssl.org>
Fri, 13 Jan 2023 13:20:17 +0000 (13:20 +0000)
Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/19703)

include/internal/quic_demux.h
ssl/quic/quic_channel.c
ssl/quic/quic_demux.c
test/quic_record_test.c
test/quic_txp_test.c

index 84a90b09b1b3d555d839a77511e8c217d318e361..ee3ec6e0afe67a8c8ba2ab438963ebc8c6ed2e12 100644 (file)
@@ -126,6 +126,12 @@ struct quic_urxe_st {
      * QRX only; not used by the demuxer.
      */
     char            deferred;
+
+    /*
+     * Used by the DEMUX to track if a URXE has been handed out. Used primarily
+     * for debugging purposes.
+     */
+    char            demux_state;
 };
 
 /* Accessors for URXE buffer. */
@@ -185,7 +191,6 @@ typedef void (ossl_quic_demux_cb_fn)(QUIC_URXE *e, void *arg);
  */
 QUIC_DEMUX *ossl_quic_demux_new(BIO *net_bio,
                                 size_t short_conn_id_len,
-                                size_t default_urxe_alloc_len,
                                 OSSL_TIME (*now)(void *arg),
                                 void *now_arg);
 
@@ -196,10 +201,16 @@ QUIC_DEMUX *ossl_quic_demux_new(BIO *net_bio,
 void ossl_quic_demux_free(QUIC_DEMUX *demux);
 
 /*
- * Changes the BIO which the demuxer reads from.
+ * Changes the BIO which the demuxer reads from. This also sets the MTU if the
+ * BIO supports querying the MTU.
  */
 void ossl_quic_demux_set_bio(QUIC_DEMUX *demux, BIO *net_bio);
 
+/*
+ * Changes the MTU in bytes we use to receive datagrams.
+ */
+int ossl_quic_demux_set_mtu(QUIC_DEMUX *demux, unsigned int mtu);
+
 /*
  * Register a datagram handler callback for a connection ID.
  *
index 795dca23f7f10f22f93a2ff4cb6a59152961ef13..7da3095ee119f435d7e11b1c1bedcf6fab633c0e 100644 (file)
@@ -159,7 +159,7 @@ static int ch_init(QUIC_CHANNEL *ch)
         goto err;
 
     if ((ch->demux = ossl_quic_demux_new(/*BIO=*/NULL, /*Short CID Len=*/0,
-                                         1200, get_time, NULL)) == NULL)
+                                         get_time, NULL)) == NULL)
         goto err;
 
     qrx_args.demux              = ch->demux;
index 9ac4a1a415ec83e862ee529d4f4d098832b5745c..810be8af03b822a746c5e930a5fa12e6f3211ad7 100644 (file)
 #include "internal/common.h"
 #include <openssl/lhash.h>
 
+#define URXE_DEMUX_STATE_FREE       0 /* on urx_free list */
+#define URXE_DEMUX_STATE_PENDING    1 /* on urx_pending list */
+#define URXE_DEMUX_STATE_ISSUED     2 /* on neither list */
+
 #define DEMUX_MAX_MSGS_PER_CALL    32
 
+#define DEMUX_DEFAULT_MTU        1500
+
 /* Structure used to track a given connection ID. */
 typedef struct quic_demux_conn_st QUIC_DEMUX_CONN;
 
@@ -56,8 +62,11 @@ struct quic_demux_st {
      */
     size_t                      short_conn_id_len;
 
-    /* Default URXE buffer size in bytes. */
-    size_t                      default_urxe_alloc_len;
+    /*
+     * Our current understanding of the upper bound on an incoming datagram size
+     * in bytes.
+     */
+    size_t                      mtu;
 
     /* Time retrieval callback. */
     OSSL_TIME                 (*now)(void *arg);
@@ -87,7 +96,6 @@ struct quic_demux_st {
 
 QUIC_DEMUX *ossl_quic_demux_new(BIO *net_bio,
                                 size_t short_conn_id_len,
-                                size_t default_urxe_alloc_len,
                                 OSSL_TIME (*now)(void *arg),
                                 void *now_arg)
 {
@@ -99,7 +107,8 @@ QUIC_DEMUX *ossl_quic_demux_new(BIO *net_bio,
 
     demux->net_bio                  = net_bio;
     demux->short_conn_id_len        = short_conn_id_len;
-    demux->default_urxe_alloc_len   = default_urxe_alloc_len;
+    /* We update this if possible when we get a BIO. */
+    demux->mtu                      = DEMUX_DEFAULT_MTU;
     demux->now                      = now;
     demux->now_arg                  = now_arg;
 
@@ -152,7 +161,29 @@ void ossl_quic_demux_free(QUIC_DEMUX *demux)
 
 void ossl_quic_demux_set_bio(QUIC_DEMUX *demux, BIO *net_bio)
 {
+    unsigned int mtu;
+
     demux->net_bio = net_bio;
+
+    if (net_bio != NULL) {
+        /*
+         * Try to determine our MTU if possible. The BIO is not required to
+         * support this, in which case we remain at the last known MTU, or our
+         * initial default.
+         */
+        mtu = BIO_dgram_get_mtu(net_bio);
+        if (mtu >= QUIC_MIN_INITIAL_DGRAM_LEN)
+            ossl_quic_demux_set_mtu(demux, mtu); /* best effort */
+    }
+}
+
+int ossl_quic_demux_set_mtu(QUIC_DEMUX *demux, unsigned int mtu)
+{
+    if (mtu < QUIC_MIN_INITIAL_DGRAM_LEN)
+        return 0;
+
+    demux->mtu = mtu;
+    return 1;
 }
 
 static QUIC_DEMUX_CONN *demux_get_by_conn_id(QUIC_DEMUX *demux,
@@ -265,21 +296,60 @@ static QUIC_URXE *demux_alloc_urxe(size_t alloc_len)
         return NULL;
 
     ossl_list_urxe_init_elem(e);
-    e->alloc_len        = alloc_len;
-    e->data_len = 0;
+    e->alloc_len   = alloc_len;
+    e->data_len    = 0;
     return e;
 }
 
+static QUIC_URXE *demux_resize_urxe(QUIC_DEMUX *demux, QUIC_URXE *e,
+                                    size_t new_alloc_len)
+{
+    QUIC_URXE *e2, *prev;
+
+    if (!ossl_assert(e->demux_state == URXE_DEMUX_STATE_FREE))
+        /* Never attempt to resize a URXE which is not on the free list. */
+        return NULL;
+
+    prev = ossl_list_urxe_prev(e);
+    ossl_list_urxe_remove(&demux->urx_free, e);
+
+    e2 = OPENSSL_realloc(e, sizeof(QUIC_URXE) + new_alloc_len);
+    if (e2 == NULL) {
+        /* Failed to resize, abort. */
+        if (prev == NULL)
+            ossl_list_urxe_insert_head(&demux->urx_free, e);
+        else
+            ossl_list_urxe_insert_after(&demux->urx_free, prev, e);
+
+        return NULL;
+    }
+
+    if (prev == NULL)
+        ossl_list_urxe_insert_head(&demux->urx_free, e2);
+    else
+        ossl_list_urxe_insert_after(&demux->urx_free, prev, e2);
+
+    e2->alloc_len = new_alloc_len;
+    return e2;
+}
+
+static QUIC_URXE *demux_reserve_urxe(QUIC_DEMUX *demux, QUIC_URXE *e,
+                                     size_t alloc_len)
+{
+    return e->alloc_len < alloc_len ? demux_resize_urxe(demux, e, alloc_len) : e;
+}
+
 static int demux_ensure_free_urxe(QUIC_DEMUX *demux, size_t min_num_free)
 {
     QUIC_URXE *e;
 
     while (ossl_list_urxe_num(&demux->urx_free) < min_num_free) {
-        e = demux_alloc_urxe(demux->default_urxe_alloc_len);
+        e = demux_alloc_urxe(demux->mtu);
         if (e == NULL)
             return 0;
 
         ossl_list_urxe_insert_tail(&demux->urx_free, e);
+        e->demux_state = URXE_DEMUX_STATE_FREE;
     }
 
     return 1;
@@ -302,6 +372,7 @@ static int demux_recv(QUIC_DEMUX *demux)
 
     /* This should never be called when we have any pending URXE. */
     assert(ossl_list_urxe_head(&demux->urx_pending) == NULL);
+    assert(urxe->demux_state == URXE_DEMUX_STATE_FREE);
 
     if (demux->net_bio == NULL)
         return 0;
@@ -320,6 +391,12 @@ static int demux_recv(QUIC_DEMUX *demux)
             break;
         }
 
+        /* Ensure the URXE is big enough. */
+        urxe = demux_reserve_urxe(demux, urxe, demux->mtu);
+        if (urxe == NULL)
+            /* Allocation error, fail. */
+            return 0;
+
         /* Ensure we zero any fields added to BIO_MSG at a later date. */
         memset(&msg[i], 0, sizeof(BIO_MSG));
         msg[i].data     = ossl_quic_urxe_data(urxe);
@@ -346,6 +423,7 @@ static int demux_recv(QUIC_DEMUX *demux)
         /* Move from free list to pending list. */
         ossl_list_urxe_remove(&demux->urx_free, urxe);
         ossl_list_urxe_insert_tail(&demux->urx_pending, urxe);
+        urxe->demux_state = URXE_DEMUX_STATE_PENDING;
     }
 
     return 1;
@@ -386,6 +464,8 @@ static int demux_process_pending_urxe(QUIC_DEMUX *demux, QUIC_URXE *e)
     if (!ossl_assert(e == ossl_list_urxe_head(&demux->urx_pending)))
         return 0;
 
+    assert(e->demux_state == URXE_DEMUX_STATE_PENDING);
+
     conn = demux_identify_conn(demux, e);
     if (conn == NULL) {
         /*
@@ -394,6 +474,7 @@ static int demux_process_pending_urxe(QUIC_DEMUX *demux, QUIC_URXE *e)
          */
         ossl_list_urxe_remove(&demux->urx_pending, e);
         ossl_list_urxe_insert_tail(&demux->urx_free, e);
+        e->demux_state = URXE_DEMUX_STATE_FREE;
         return 1; /* keep processing pending URXEs */
     }
 
@@ -402,6 +483,7 @@ static int demux_process_pending_urxe(QUIC_DEMUX *demux, QUIC_URXE *e)
      * callback. (QUIC_DEMUX_CONN never has non-NULL cb.)
      */
     ossl_list_urxe_remove(&demux->urx_pending, e);
+    e->demux_state = URXE_DEMUX_STATE_ISSUED;
     conn->cb(e, conn->cb_arg);
     return 1;
 }
@@ -462,6 +544,12 @@ int ossl_quic_demux_inject(QUIC_DEMUX *demux,
     if (buf_len > urxe->alloc_len)
         return 0;
 
+    assert(urxe->demux_state == URXE_DEMUX_STATE_FREE);
+
+    urxe = demux_reserve_urxe(demux, urxe, buf_len);
+    if (urxe == NULL)
+        return 0;
+
     memcpy(ossl_quic_urxe_data(urxe), buf, buf_len);
     urxe->data_len = buf_len;
 
@@ -478,6 +566,7 @@ int ossl_quic_demux_inject(QUIC_DEMUX *demux,
     /* Move from free list to pending list. */
     ossl_list_urxe_remove(&demux->urx_free, urxe);
     ossl_list_urxe_insert_tail(&demux->urx_pending, urxe);
+    urxe->demux_state = URXE_DEMUX_STATE_PENDING;
 
     return demux_process_pending_urxl(demux);
 }
@@ -487,5 +576,7 @@ void ossl_quic_demux_release_urxe(QUIC_DEMUX *demux,
                                   QUIC_URXE *e)
 {
     assert(ossl_list_urxe_prev(e) == NULL && ossl_list_urxe_next(e) == NULL);
+    assert(e->demux_state == URXE_DEMUX_STATE_ISSUED);
     ossl_list_urxe_insert_tail(&demux->urx_free, e);
+    e->demux_state = URXE_DEMUX_STATE_FREE;
 }
index 18d8711afad116931f38dfd3b1bb523b73547915..1dc0eec5e598eb364fe03e5553af321bf116f181 100644 (file)
@@ -1704,7 +1704,6 @@ static int rx_state_ensure(struct rx_state *s)
     if (s->demux == NULL
         && !TEST_ptr(s->demux = ossl_quic_demux_new(NULL,
                                                     s->args.short_conn_id_len,
-                                                    1500,
                                                     fake_time,
                                                     NULL)))
         return 0;
index 8d2361af18b7a9b82cc509b8b85be77f570827ab..241645a57b0d73ff9e87c4271e45b03b277a09c6 100644 (file)
@@ -183,7 +183,7 @@ static int helper_init(struct helper *h)
     if (!TEST_ptr(h->txp = ossl_quic_tx_packetiser_new(&h->args)))
         goto err;
 
-    if (!TEST_ptr(h->demux = ossl_quic_demux_new(h->bio2, 8, 1200,
+    if (!TEST_ptr(h->demux = ossl_quic_demux_new(h->bio2, 8,
                                                  fake_now, NULL)))
         goto err;