Browse Source

Add simple interoperability test with Cloudflare quiche

This is an external test which requires recursive checkout
of the cloudflare-quiche submodule.

We simply run a client against the example quiche-server
serving HTTP/0.9 requests.

Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Paul Dale <pauli@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/20527)
Tomas Mraz 1 year ago
parent
commit
fc11028089

+ 18 - 0
.github/workflows/ci.yml

@@ -410,3 +410,21 @@ jobs:
         default: true
     - name: test external pyca
       run: make test TESTS="test_external_pyca" VERBOSE=1
+
+  external-test-cf-quiche:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v3
+      with:
+        submodules: recursive
+    - name: Configure OpenSSL
+      run: ./config --banner=Configured --strict-warnings enable-external-tests enable-quic && perl configdata.pm --dump
+    - name: make
+      run: make -s -j4
+    - uses: actions-rs/toolchain@v1
+      with:
+        profile: default
+        toolchain: stable
+        default: true
+    - name: test external Cloudflare quiche
+      run: make test TESTS="test_external_cf_quiche" VERBOSE=1

+ 3 - 0
.gitmodules

@@ -25,3 +25,6 @@
 [submodule "oqs-provider"]
 	path = oqs-provider
 	url = https://github.com/open-quantum-safe/oqs-provider.git
+[submodule "cloudflare-quiche"]
+	path = cloudflare-quiche
+	url = https://github.com/cloudflare/quiche

+ 1 - 0
cloudflare-quiche

@@ -0,0 +1 @@
+Subproject commit 24a959abf115923910ce18985aa199d85fb602d7

+ 5 - 0
test/build.info

@@ -332,6 +332,10 @@ IF[{- !$disabled{tests} -}]
   INCLUDE[quic_tserver_test]=../include ../apps/include
   DEPEND[quic_tserver_test]=../libcrypto.a ../libssl.a libtestutil.a
 
+  SOURCE[quic_client_test]=quic_client_test.c
+  INCLUDE[quic_client_test]=../include ../apps/include
+  DEPEND[quic_client_test]=../libcrypto.a ../libssl.a libtestutil.a
+
   SOURCE[asynctest]=asynctest.c
   INCLUDE[asynctest]=../include ../apps/include
   DEPEND[asynctest]=../libcrypto
@@ -1076,6 +1080,7 @@ ENDIF
     PROGRAMS{noinst}=quic_wire_test quic_ackm_test quic_record_test
     PROGRAMS{noinst}=quic_fc_test quic_stream_test quic_cfq_test quic_txpim_test
     PROGRAMS{noinst}=quic_fifd_test quic_txp_test quic_tserver_test
+    PROGRAMS{noinst}=quic_client_test
   ENDIF
 
   SOURCE[quic_ackm_test]=quic_ackm_test.c

+ 176 - 0
test/quic_client_test.c

@@ -0,0 +1,176 @@
+/*
+ * 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 <stdio.h>
+#include <openssl/ssl.h>
+#include <openssl/quic.h>
+#include <openssl/bio.h>
+#include "internal/common.h"
+#include "internal/sockets.h"
+#include "internal/time.h"
+#include "testutil.h"
+
+static const char msg1[] = "GET LICENSE.txt\r\n";
+static char msg2[16000];
+
+static int is_want(SSL *s, int ret)
+{
+    int ec = SSL_get_error(s, ret);
+
+    return ec == SSL_ERROR_WANT_READ || ec == SSL_ERROR_WANT_WRITE;
+}
+
+static int test_quic_client(void)
+{
+    int testresult = 0, ret;
+    int c_fd = INVALID_SOCKET;
+    BIO *c_net_bio = NULL, *c_net_bio_own = NULL;
+    BIO_ADDR *s_addr_ = NULL;
+    struct in_addr ina = {0};
+    SSL_CTX *c_ctx = NULL;
+    SSL *c_ssl = NULL;
+    short port = 4433;
+    int c_connected = 0, c_write_done = 0, c_shutdown = 0;
+    size_t l = 0, c_total_read = 0;
+    OSSL_TIME start_time;
+    unsigned char alpn[] = { 8, 'h', 't', 't', 'p', '/', '0', '.', '9' };
+
+    ina.s_addr = htonl(0x7f000001UL);
+
+    /* Setup test client. */
+    c_fd = BIO_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP, 0);
+    if (!TEST_int_ne(c_fd, INVALID_SOCKET))
+        goto err;
+
+    if (!TEST_true(BIO_socket_nbio(c_fd, 1)))
+        goto err;
+
+    if (!TEST_ptr(s_addr_ = BIO_ADDR_new()))
+        goto err;
+
+    if (!TEST_true(BIO_ADDR_rawmake(s_addr_, AF_INET, &ina, sizeof(ina),
+                                    htons(port))))
+        goto err;
+
+    if (!TEST_ptr(c_net_bio = c_net_bio_own = BIO_new_dgram(c_fd, 0)))
+        goto err;
+
+    if (!BIO_dgram_set_peer(c_net_bio, s_addr_))
+        goto err;
+
+    if (!TEST_ptr(c_ctx = SSL_CTX_new(OSSL_QUIC_client_method())))
+        goto err;
+
+    if (!TEST_ptr(c_ssl = SSL_new(c_ctx)))
+        goto err;
+
+    /* 0 is a success for SSL_set_alpn_protos() */
+    if (!TEST_false(SSL_set_alpn_protos(c_ssl, alpn, sizeof(alpn))))
+        goto err;
+
+    /* Takes ownership of our reference to the BIO. */
+    SSL_set0_rbio(c_ssl, c_net_bio);
+
+    /* Get another reference to be transferred in the SSL_set0_wbio call. */
+    if (!TEST_true(BIO_up_ref(c_net_bio))) {
+        c_net_bio_own = NULL; /* SSL_free will free the first reference. */
+        goto err;
+    }
+
+    SSL_set0_wbio(c_ssl, c_net_bio);
+    c_net_bio_own = NULL;
+
+    if (!TEST_true(SSL_set_blocking_mode(c_ssl, 0)))
+        goto err;
+
+    start_time = ossl_time_now();
+
+    for (;;) {
+        if (ossl_time_compare(ossl_time_subtract(ossl_time_now(), start_time),
+                              ossl_ms2time(3000)) >= 0) {
+            TEST_error("timeout while attempting QUIC client test");
+            goto err;
+        }
+
+        if (!c_connected) {
+            ret = SSL_connect(c_ssl);
+            if (!TEST_true(ret == 1 || is_want(c_ssl, ret)))
+                goto err;
+
+            if (ret == 1) {
+                c_connected = 1;
+                TEST_info("Connected!");
+            }
+        }
+
+        if (c_connected && !c_write_done) {
+            if (!TEST_int_eq(SSL_write(c_ssl, msg1, sizeof(msg1) - 1),
+                             (int)sizeof(msg1) - 1))
+                goto err;
+
+            if (!TEST_true(SSL_stream_conclude(c_ssl, 0)))
+                goto err;
+
+            c_write_done = 1;
+        }
+
+        if (c_write_done && !c_shutdown && c_total_read < sizeof(msg2) - 1) {
+            ret = SSL_read_ex(c_ssl, msg2 + c_total_read,
+                              sizeof(msg2) - 1 - c_total_read, &l);
+            if (ret != 1) {
+                if (SSL_get_error(c_ssl, ret) == SSL_ERROR_ZERO_RETURN) {
+                    c_shutdown = 1;
+                    TEST_info("Message: \n%s\n", msg2);
+                } else if (!TEST_true(is_want(c_ssl, ret))) {
+                    goto err;
+                }
+            } else {
+                c_total_read += l;
+
+                if (!TEST_size_t_lt(c_total_read, sizeof(msg2) - 1))
+                    goto err;
+            }
+        }
+
+        if (c_shutdown) {
+            ret = SSL_shutdown(c_ssl);
+            if (ret == 1)
+                break;
+        }
+
+        /*
+         * This is inefficient because we spin until things work without
+         * blocking but this is just a test.
+         */
+        OSSL_sleep(0);
+        SSL_tick(c_ssl);
+    }
+
+    testresult = 1;
+err:
+    SSL_free(c_ssl);
+    SSL_CTX_free(c_ctx);
+    BIO_ADDR_free(s_addr_);
+    BIO_free(c_net_bio_own);
+    if (c_fd != INVALID_SOCKET)
+        BIO_closesocket(c_fd);
+    return testresult;
+}
+
+OPT_TEST_DECLARE_USAGE("certfile privkeyfile\n")
+
+int setup_tests(void)
+{
+    if (!test_skip_common_options()) {
+        TEST_error("Error parsing test options\n");
+        return 0;
+    }
+
+    ADD_TEST(test_quic_client);
+    return 1;
+}

+ 44 - 0
test/recipes/95-test_external_cf_quiche.t

@@ -0,0 +1,44 @@
+#! /usr/bin/env perl
+# 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
+
+
+use OpenSSL::Test;
+use OpenSSL::Test::Utils;
+use OpenSSL::Test qw/:DEFAULT data_file bldtop_dir srctop_dir srctop_file/;
+
+setup("test_external_cf_quiche");
+
+plan skip_all => "No external tests in this configuration"
+    if disabled("external-tests");
+plan skip_all => "Cloudflare quiche tests not available on Windows or VMS"
+    if $^O =~ /^(VMS|MSWin32)$/;
+plan skip_all => "Cloudflare quiche tests only available with QUIC support"
+    if disabled("quic");
+plan skip_all => "Cloudflare & Boringssl not checked out"
+    if ! -f srctop_file("cloudflare-quiche", "quiche", "deps", "boringssl", "LICENSE");
+
+plan tests => 3;
+
+ok(run(cmd(["sh", data_file("quiche-build.sh")])),
+   "running Cloudflare quiche build");
+
+ok(run(cmd(["sh", data_file("quiche-server.sh")])),
+   "running Cloudflare quiche server");
+
+ok(run(test(["quic_client_test"])),
+   "running quic_client_test");
+
+open my $fh, '<', "server.pid"
+    or die "Error opening server.pid - $!\n";
+$serverpid = <$fh>;
+close($fh);
+
+kill('TERM', $serverpid);
+sleep(1);
+kill('KILL', $serverpid);
+waitpid($serverpid, 0);

+ 26 - 0
test/recipes/95-test_external_cf_quiche_data/quiche-build.sh

@@ -0,0 +1,26 @@
+#!/bin/sh
+#
+# 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
+
+#
+# Build the Cloudflare quiche
+#
+set -e
+
+SRCTOP="$(cd $SRCTOP; pwd)"
+BLDTOP="$(cd $BLDTOP; pwd)"
+
+echo "------------------------------------------------------------------"
+echo "Building Cloudflare quiche"
+echo "------------------------------------------------------------------"
+
+QUICHE_TARGET_PATH="$BLDTOP/quiche"
+test -d "$QUICHE_TARGET_PATH" || mkdir "$QUICHE_TARGET_PATH"
+echo "   QUICHE_TARGET_PATH: $QUICHE_TARGET_PATH"
+
+(cd "$SRCTOP/cloudflare-quiche" && cargo build --verbose --target-dir "$QUICHE_TARGET_PATH")

+ 29 - 0
test/recipes/95-test_external_cf_quiche_data/quiche-server.sh

@@ -0,0 +1,29 @@
+#!/bin/sh
+#
+# 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
+
+#
+# Run the quiche server
+#
+set -e
+
+SRCTOP="$(cd $SRCTOP; pwd)"
+BLDTOP="$(cd $BLDTOP; pwd)"
+
+echo "------------------------------------------------------------------"
+echo "Running Cloudflare quiche-server"
+echo "------------------------------------------------------------------"
+
+QUICHE_TARGET_PATH="$BLDTOP/quiche"
+test -d "$QUICHE_TARGET_PATH" || exit 1
+
+"$QUICHE_TARGET_PATH/debug/quiche-server" --cert "$SRCTOP/test/certs/servercert.pem" \
+    --key "$SRCTOP/test/certs/serverkey.pem" --disable-gso \
+    --http-version HTTP/0.9 --root "$SRCTOP" --no-grease --disable-hystart &
+
+echo $! >server.pid