Add a packet splitting BIO
authorMatt Caswell <matt@openssl.org>
Tue, 19 Sep 2023 10:52:42 +0000 (11:52 +0100)
committerMatt Caswell <matt@openssl.org>
Fri, 22 Sep 2023 12:56:43 +0000 (13:56 +0100)
Provide a BIO filter that can split QUIC datagrams containing multiple
packets, such that each packet is in its own datagram.

Reviewed-by: Tim Hudson <tjh@openssl.org>
Reviewed-by: Paul Dale <pauli@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/22157)

test/build.info
test/helpers/noisydgrambio.c
test/helpers/pktsplitbio.c [new file with mode: 0644]
test/helpers/quictestlib.c
test/helpers/quictestlib.h

index 4736e28c8b0bb10059bf08d83eadb515a2e62368..4c81a2b77984c708217a39f3de11ff809a041f6d 100644 (file)
@@ -339,7 +339,9 @@ IF[{- !$disabled{tests} -}]
   INCLUDE[quic_client_test]=../include ../apps/include
   DEPEND[quic_client_test]=../libcrypto.a ../libssl.a libtestutil.a
 
-  SOURCE[quic_multistream_test]=quic_multistream_test.c helpers/ssltestlib.c helpers/quictestlib.c helpers/noisydgrambio.c
+  $QUICTESTHELPERS=helpers/quictestlib.c helpers/noisydgrambio.c helpers/pktsplitbio.c
+
+  SOURCE[quic_multistream_test]=quic_multistream_test.c helpers/ssltestlib.c $QUICTESTHELPERS
   INCLUDE[quic_multistream_test]=../include ../apps/include
   DEPEND[quic_multistream_test]=../libcrypto.a ../libssl.a libtestutil.a
 
@@ -818,15 +820,15 @@ IF[{- !$disabled{tests} -}]
       INCLUDE[event_queue_test]=../include ../apps/include
       DEPEND[event_queue_test]=../libcrypto ../libssl.a libtestutil.a
 
-      SOURCE[quicfaultstest]=quicfaultstest.c helpers/ssltestlib.c helpers/quictestlib.c helpers/noisydgrambio.c
+      SOURCE[quicfaultstest]=quicfaultstest.c helpers/ssltestlib.c $QUICTESTHELPERS
       INCLUDE[quicfaultstest]=../include ../apps/include ..
       DEPEND[quicfaultstest]=../libcrypto.a ../libssl.a libtestutil.a
 
-      SOURCE[quicapitest]=quicapitest.c helpers/ssltestlib.c helpers/quictestlib.c helpers/noisydgrambio.c
+      SOURCE[quicapitest]=quicapitest.c helpers/ssltestlib.c $QUICTESTHELPERS
       INCLUDE[quicapitest]=../include ../apps/include
       DEPEND[quicapitest]=../libcrypto.a ../libssl.a libtestutil.a
 
-      SOURCE[quic_newcid_test]=quic_newcid_test.c helpers/ssltestlib.c helpers/quictestlib.c helpers/noisydgrambio.c
+      SOURCE[quic_newcid_test]=quic_newcid_test.c helpers/ssltestlib.c $QUICTESTHELPERS
       INCLUDE[quic_newcid_test]=../include ../apps/include ..
       DEPEND[quic_newcid_test]=../libcrypto.a ../libssl.a libtestutil.a
     ENDIF
index b42dbe6f4b50e12f1bf5bbd3f2f2f3df5c6b8b60..7dc6a9cf35f40b7d41024a0ec8d1c649672db20b 100644 (file)
@@ -105,67 +105,6 @@ static void get_noise(uint64_t *delay, int *should_drop)
     *delay += (uint64_t)(*should_drop);
 }
 
-/* There isn't a public function to do BIO_ADDR_copy() so we create one */
-static int bio_addr_copy(BIO_ADDR *dst, BIO_ADDR *src)
-{
-    size_t len;
-    void *data = NULL;
-    int res = 0;
-    int family;
-
-    if (src == NULL || dst == NULL)
-        return 0;
-
-    family = BIO_ADDR_family(src);
-    if (family == AF_UNSPEC) {
-        BIO_ADDR_clear(dst);
-        return 1;
-    }
-
-    if (!BIO_ADDR_rawaddress(src, NULL, &len))
-        return 0;
-
-    if (len > 0) {
-        data = OPENSSL_malloc(len);
-        if (!TEST_ptr(data))
-            return 0;
-    }
-
-    if (!BIO_ADDR_rawaddress(src, data, &len))
-        goto err;
-
-    if (!BIO_ADDR_rawmake(src, family, data, len, BIO_ADDR_rawport(src)))
-        goto err;
-
-    res = 1;
- err:
-    OPENSSL_free(data);
-    return res;
-}
-
-static int bio_msg_copy(BIO_MSG *dst, BIO_MSG *src)
-{
-    /*
-     * Note it is assumed that the originally allocated data sizes for dst and
-     * src are the same
-     */
-    memcpy(dst->data, src->data, src->data_len);
-    dst->data_len = src->data_len;
-    dst->flags = src->flags;
-    if (dst->local != NULL) {
-        if (src->local != NULL) {
-            if (!TEST_true(bio_addr_copy(dst->local, src->local)))
-                return 0;
-        } else {
-            BIO_ADDR_clear(dst->local);
-        }
-    }
-    if (!TEST_true(bio_addr_copy(dst->peer, src->peer)))
-        return 0;
-
-    return 1;
-}
-
 static int noisy_dgram_recvmmsg(BIO *bio, BIO_MSG *msg, size_t stride,
                                 size_t num_msg, uint64_t flags,
                                 size_t *msgs_processed)
diff --git a/test/helpers/pktsplitbio.c b/test/helpers/pktsplitbio.c
new file mode 100644 (file)
index 0000000..a3c01b9
--- /dev/null
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2023 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License").  You may not use
+ * this file except in compliance with the License.  You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#include <openssl/bio.h>
+#include "quictestlib.h"
+#include "../testutil.h"
+
+static int pkt_split_dgram_read(BIO *bio, char *out, int outl)
+{
+    /* We don't support this - not needed anyway */
+    return -1;
+}
+
+static int pkt_split_dgram_write(BIO *bio, const char *in, int inl)
+{
+    /* We don't support this - not needed anyway */
+    return -1;
+}
+
+static long pkt_split_dgram_ctrl(BIO *bio, int cmd, long num, void *ptr)
+{
+    long ret;
+    BIO *next = BIO_next(bio);
+
+    if (next == NULL)
+        return 0;
+
+    switch (cmd) {
+    case BIO_CTRL_DUP:
+        ret = 0L;
+        break;
+    default:
+        ret = BIO_ctrl(next, cmd, num, ptr);
+        break;
+    }
+    return ret;
+}
+
+static int pkt_split_dgram_gets(BIO *bio, char *buf, int size)
+{
+    /* We don't support this - not needed anyway */
+    return -1;
+}
+
+static int pkt_split_dgram_puts(BIO *bio, const char *str)
+{
+    /* We don't support this - not needed anyway */
+    return -1;
+}
+
+static int pkt_split_dgram_sendmmsg(BIO *bio, BIO_MSG *msg, size_t stride,
+                                    size_t num_msg, uint64_t flags,
+                                    size_t *msgs_processed)
+{
+    BIO *next = BIO_next(bio);
+
+    if (next == NULL)
+        return 0;
+
+    /*
+     * We only introduce noise when receiving messages. We just pass this on
+     * to the underlying BIO.
+     */
+    return BIO_sendmmsg(next, msg, stride, num_msg, flags, msgs_processed);
+}
+
+static int pkt_split_dgram_recvmmsg(BIO *bio, BIO_MSG *msg, size_t stride,
+                                    size_t num_msg, uint64_t flags,
+                                    size_t *msgs_processed)
+{
+    BIO *next = BIO_next(bio);
+    size_t i, j, data_len = 0, msg_cnt = 0;
+    BIO_MSG *thismsg;
+
+    if (!TEST_ptr(next))
+        return 0;
+
+    /*
+     * For simplicity we assume that all elements in the msg array have the
+     * same data_len. They are not required to by the API, but it would be quite
+     * strange for that not to be the case - and our code that calls
+     * BIO_recvmmsg does do this (which is all that is important for this test
+     * code). We test the invariant here.
+     */
+    for (i = 0; i < num_msg; i++) {
+        if (i == 0)
+            data_len = msg[i].data_len;
+        else if (!TEST_size_t_eq(msg[i].data_len, data_len))
+            return 0;
+    }
+
+    if (!BIO_recvmmsg(next, msg, stride, num_msg, flags, msgs_processed))
+        return 0;
+
+    msg_cnt = *msgs_processed;
+    if (msg_cnt == num_msg)
+        return 1; /* We've used all our slots and can't split any more */
+    assert(msg_cnt < num_msg);
+
+    for (i = 0, thismsg = msg; i < msg_cnt; i++, thismsg++) {
+        QUIC_PKT_HDR hdr;
+        PACKET pkt;
+        size_t remain;
+
+        if (!PACKET_buf_init(&pkt, thismsg->data, thismsg->data_len))
+            return 0;
+
+        /* Decode the packet header */
+        /*
+         * TODO(QUIC SERVER): We need to query the short connection id len
+         * here, e.g. via some API SSL_get_short_conn_id_len()
+         */
+        if (ossl_quic_wire_decode_pkt_hdr(&pkt, 0, 0, 0, &hdr, NULL) != 1)
+            return 0;
+        remain = PACKET_remaining(&pkt);
+        if (remain > 0) {
+            for (j = msg_cnt; j > i; j--) {
+                if (!bio_msg_copy(&msg[j], &msg[j - 1]))
+                    return 0;
+            }
+            thismsg->data_len -= remain;
+            msg[i + 1].data_len = remain;
+            memmove(msg[i + 1].data,
+                    (unsigned char *)msg[i + 1].data + thismsg->data_len,
+                    remain);
+            msg_cnt++;
+        }
+    }
+
+    *msgs_processed = msg_cnt;
+    return 1;
+}
+
+/* Choose a sufficiently large type likely to be unused for this custom BIO */
+#define BIO_TYPE_PKT_SPLIT_DGRAM_FILTER  (0x81 | BIO_TYPE_FILTER)
+
+static BIO_METHOD *method_pkt_split_dgram = NULL;
+
+/* Note: Not thread safe! */
+const BIO_METHOD *bio_f_pkt_split_dgram_filter(void)
+{
+    if (method_pkt_split_dgram == NULL) {
+        method_pkt_split_dgram = BIO_meth_new(BIO_TYPE_PKT_SPLIT_DGRAM_FILTER,
+                                              "Packet splitting datagram filter");
+        if (method_pkt_split_dgram == NULL
+            || !BIO_meth_set_write(method_pkt_split_dgram, pkt_split_dgram_write)
+            || !BIO_meth_set_read(method_pkt_split_dgram, pkt_split_dgram_read)
+            || !BIO_meth_set_puts(method_pkt_split_dgram, pkt_split_dgram_puts)
+            || !BIO_meth_set_gets(method_pkt_split_dgram, pkt_split_dgram_gets)
+            || !BIO_meth_set_ctrl(method_pkt_split_dgram, pkt_split_dgram_ctrl)
+            || !BIO_meth_set_sendmmsg(method_pkt_split_dgram,
+                                      pkt_split_dgram_sendmmsg)
+            || !BIO_meth_set_recvmmsg(method_pkt_split_dgram,
+                                      pkt_split_dgram_recvmmsg))
+            return NULL;
+    }
+    return method_pkt_split_dgram;
+}
+
+void bio_f_pkt_split_dgram_filter_free(void)
+{
+    BIO_meth_free(method_pkt_split_dgram);
+}
index 28791267edd00fefb130dcc303582185a5d0b172..6381d720fff2b93eb092d7f34253d97536cbff54 100644 (file)
@@ -1044,3 +1044,64 @@ int qtest_fault_resize_datagram(QTEST_FAULT *fault, size_t newlen)
 
     return 1;
 }
+
+/* There isn't a public function to do BIO_ADDR_copy() so we create one */
+int bio_addr_copy(BIO_ADDR *dst, BIO_ADDR *src)
+{
+    size_t len;
+    void *data = NULL;
+    int res = 0;
+    int family;
+
+    if (src == NULL || dst == NULL)
+        return 0;
+
+    family = BIO_ADDR_family(src);
+    if (family == AF_UNSPEC) {
+        BIO_ADDR_clear(dst);
+        return 1;
+    }
+
+    if (!BIO_ADDR_rawaddress(src, NULL, &len))
+        return 0;
+
+    if (len > 0) {
+        data = OPENSSL_malloc(len);
+        if (!TEST_ptr(data))
+            return 0;
+    }
+
+    if (!BIO_ADDR_rawaddress(src, data, &len))
+        goto err;
+
+    if (!BIO_ADDR_rawmake(src, family, data, len, BIO_ADDR_rawport(src)))
+        goto err;
+
+    res = 1;
+ err:
+    OPENSSL_free(data);
+    return res;
+}
+
+int bio_msg_copy(BIO_MSG *dst, BIO_MSG *src)
+{
+    /*
+     * Note it is assumed that the originally allocated data sizes for dst and
+     * src are the same
+     */
+    memcpy(dst->data, src->data, src->data_len);
+    dst->data_len = src->data_len;
+    dst->flags = src->flags;
+    if (dst->local != NULL) {
+        if (src->local != NULL) {
+            if (!TEST_true(bio_addr_copy(dst->local, src->local)))
+                return 0;
+        } else {
+            BIO_ADDR_clear(dst->local);
+        }
+    }
+    if (!TEST_true(bio_addr_copy(dst->peer, src->peer)))
+        return 0;
+
+    return 1;
+}
index 7a72e352d9a867a6f91b56f52aae6c6e2d2e78ff..f18cd29481163557ad1a5703f815acf3995483ba 100644 (file)
@@ -233,8 +233,23 @@ int qtest_fault_set_datagram_listener(QTEST_FAULT *fault,
  */
 int qtest_fault_resize_datagram(QTEST_FAULT *fault, size_t newlen);
 
+/* Copy a BIO_ADDR */
+int bio_addr_copy(BIO_ADDR *dst, BIO_ADDR *src);
+
+/* Copy a BIO_MSG */
+int bio_msg_copy(BIO_MSG *dst, BIO_MSG *src);
+
 /* BIO filter for simulating a noisy UDP socket */
 const BIO_METHOD *bio_f_noisy_dgram_filter(void);
 
 /* Free the BIO filter method object */
-void bio_f_noisy_dgram_filter_free(void);
\ No newline at end of file
+void bio_f_noisy_dgram_filter_free(void);
+
+/*
+ * BIO filter for splitting QUIC datagrams containing multiple packets into
+ * individual datagrams.
+ */
+const BIO_METHOD *bio_f_pkt_split_dgram_filter(void);
+
+/* Free the BIO filter method object */
+void bio_f_pkt_split_dgram_filter_free(void);