From: Neil Horman Date: Mon, 11 Nov 2024 22:12:19 +0000 (-0500) Subject: Augment quic interop harness to support server side interop tests X-Git-Tag: openssl-3.5.0-alpha1~315 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=fd50924d0107f77e0d220a0eb9c6216285fa5f39;p=thirdparty%2Fopenssl.git Augment quic interop harness to support server side interop tests the quic-interop-runner that we use for interop testing currently only supports openssl client testing, as we had previously not had a server to test with. This PR rectifies that by doing the following: 1) Adding a quic-hq-interop-server.c file in demos/guide 2) Augmenting our interop Dockerfile and entrypoint to support our interop containter running in a server role With these changes we are able to do server side interop testing Reviewed-by: Saša Nedvědický Reviewed-by: Tim Hudson (Merged from https://github.com/openssl/openssl/pull/26000) --- diff --git a/demos/guide/build.info b/demos/guide/build.info index 77675b69ecc..307b1506fb5 100644 --- a/demos/guide/build.info +++ b/demos/guide/build.info @@ -12,7 +12,8 @@ PROGRAMS{noinst} = tls-client-block \ quic-hq-interop \ quic-server-block \ quic-server-non-block \ - quic-client-non-block + quic-client-non-block \ + quic-hq-interop-server INCLUDE[tls-client-block]=../../include SOURCE[tls-client-block]=tls-client-block.c @@ -45,3 +46,7 @@ DEPEND[quic-client-non-block]=../../libcrypto ../../libssl INCLUDE[quic-hq-interop]=../../include SOURCE[quic-hq-interop]=quic-hq-interop.c DEPEND[quic-hq-interop]=../../libcrypto ../../libssl + +INCLUDE[quic-hq-interop-server]=../../include +SOURCE[quic-hq-interop-server]=quic-hq-interop-server.c +DEPEND[quic-hq-interop-server]=../../libcrypto ../../libssl diff --git a/demos/guide/quic-hq-interop-server.c b/demos/guide/quic-hq-interop-server.c new file mode 100644 index 00000000000..3f28a0ec97a --- /dev/null +++ b/demos/guide/quic-hq-interop-server.c @@ -0,0 +1,684 @@ +/* + * Copyright 2024 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 + */ + +/** + * @file quic-hq-interop-server.c + * @brief Minimal QUIC HTTP/0.9 server implementation. + * + * This file implements a lightweight QUIC server supporting the HTTP/0.9 + * protocol for interoperability testing. It includes functions for setting + * up a secure QUIC connection, handling ALPN negotiation, and serving client + * requests. Intended for use with the quic-interop-runner + * available at https://interop.seemann.io + * + * Key functionalities: + * - Setting up SSL_CTX with QUIC support. + * - Negotiating ALPN strings during the TLS handshake. + * - Listening and accepting incoming QUIC connections. + * - Handling client requests via HTTP/0.9 protocol. + * + * Usage: + * + * The server binds to the specified port and serves files using the given + * certificate and private key. + * + * Environment variables: + * - FILEPREFIX: Specifies the directory containing files to serve. + * Defaults to "./downloads" if not set. + * - SSLKEYLOGFILE: specifies that keylogging should be preformed on the server + * should be set to a file name to record keylog data to + * + */ + +#include + +/* Include the appropriate header file for SOCK_STREAM */ +#ifdef _WIN32 +# include +# include +#else +# include +# include +# include +#endif + +#include +#include +#include +#include + +#define BUF_SIZE 4096 + +/** + * @brief ALPN (Application-Layer Protocol Negotiation) identifier for QUIC. + * + * This constant defines the ALPN string used during the TLS handshake + * to negotiate the application-layer protocol between the client and + * the server. It specifies "hq-interop" as the supported protocol. + * + * Format: + * - The first byte represents the length of the ALPN string. + * - Subsequent bytes represent the ASCII characters of the protocol name. + * + * Value: + * - Protocol: "hq-interop" + * - Length: 10 bytes + * + * Usage: + * This is passed to the ALPN callback function to validate and + * negotiate the desired protocol during the TLS handshake. + */ +static const unsigned char alpn_ossltest[] = { + 10, 'h', 'q', '-', 'i', 'n', 't', 'e', 'r', 'o', 'p', +}; + +/** + * @brief Directory prefix for serving requested files. + * + * This variable specifies the directory path used as the base location + * for serving files in response to client requests. It is used to construct + * the full file path for requested resources. + * + * Default: + * - If not set via the FILEPREFIX environment variable, it defaults to + * "./downloads". + * + * Usage: + * - Updated at runtime based on the FILEPREFIX environment variable. + * - Used to locate and serve files during incoming requests. + */ +static char *fileprefix = NULL; + +/** + * @brief Callback for ALPN (Application-Layer Protocol Negotiation) selection. + * + * This function is invoked during the TLS handshake on the server side to + * validate and negotiate the desired ALPN (Application-Layer Protocol + * Negotiation) protocol with the client. It ensures that the negotiated + * protocol matches the predefined "hq-interop" string. + * + * @param ssl Pointer to the SSL connection object. + * @param[out] out Pointer to the negotiated ALPN protocol string. + * @param[out] out_len Length of the negotiated ALPN protocol string. + * @param in Pointer to the client-provided ALPN protocol list. + * @param in_len Length of the client-provided ALPN protocol list. + * @param arg Optional user-defined argument (unused in this context). + * + * @return SSL_TLSEXT_ERR_OK on successful ALPN negotiation, + * SSL_TLSEXT_ERR_ALERT_FATAL otherwise. + * + * Usage: + * - This function is set as the ALPN selection callback in the SSL_CTX + * using `SSL_CTX_set_alpn_select_cb`. + * - Ensures that only the predefined ALPN protocol is accepted. + * + * Note: + * - The predefined protocol is specified in the `alpn_ossltest` array. + */ +static int select_alpn(SSL *ssl, const unsigned char **out, + unsigned char *out_len, const unsigned char *in, + unsigned int in_len, void *arg) +{ + /* + * Use the next_proto helper function here. + * This scans the list of alpns we support and matches against + * what the client is requesting + */ + if (SSL_select_next_proto((unsigned char **)out, out_len, alpn_ossltest, + sizeof(alpn_ossltest), in, + in_len) == OPENSSL_NPN_NEGOTIATED) + return SSL_TLSEXT_ERR_OK; + return SSL_TLSEXT_ERR_ALERT_FATAL; +} + +/** + * @brief Creates and configures an SSL_CTX for a QUIC server. + * + * This function initializes an SSL_CTX object with the QUIC server method + * and configures it using the provided certificate and private key. The + * context is prepared for handling secure QUIC connections and performing + * ALPN (Application-Layer Protocol Negotiation). + * + * @param cert_path Path to the server's certificate chain file in PEM format. + * The chain file must include the server's leaf certificate + * followed by intermediate CA certificates. + * @param key_path Path to the server's private key file in PEM format. The + * private key must correspond to the leaf certificate in + * the chain file. + * + * @return Pointer to the initialized SSL_CTX on success, or NULL on failure. + * + * Configuration: + * - Loads the certificate chain and private key into the context. + * - Disables client certificate verification (no mutual TLS). + * - Sets up the ALPN selection callback for protocol negotiation. + * + * Error Handling: + * - If any step fails (e.g., loading the certificate or key), the function + * frees the SSL_CTX and returns NULL. + * + * Usage: + * - Call this function to create an SSL_CTX before starting the QUIC server. + * - Ensure valid paths for the certificate and private key are provided. + * + * Note: + * - The ALPN callback only supports the predefined protocol defined in + * `alpn_ossltest`. + */ +static SSL_CTX *create_ctx(const char *cert_path, const char *key_path) +{ + SSL_CTX *ctx; + + /* + * An SSL_CTX holds shared configuration information for multiple + * subsequent per-client connections. We specifically load a QUIC + * server method here. + */ + ctx = SSL_CTX_new(OSSL_QUIC_server_method()); + if (ctx == NULL) + goto err; + + /* + * Load the server's certificate *chain* file (PEM format), which includes + * not only the leaf (end-entity) server certificate, but also any + * intermediate issuer-CA certificates. The leaf certificate must be the + * first certificate in the file. + * + * In advanced use-cases this can be called multiple times, once per public + * key algorithm for which the server has a corresponding certificate. + * However, the corresponding private key (see below) must be loaded first, + * *before* moving on to the next chain file. + * + * The requisite files "chain.pem" and "pkey.pem" can be generated by running + * "make chain" in this directory. If the server will be executed from some + * other directory, move or copy the files there. + */ + if (SSL_CTX_use_certificate_chain_file(ctx, cert_path) <= 0) { + fprintf(stderr, "couldn't load certificate file: %s\n", cert_path); + goto err; + } + + /* + * Load the corresponding private key, this also checks that the private + * key matches the just loaded end-entity certificate. It does not check + * whether the certificate chain is valid, the certificates could be + * expired, or may otherwise fail to form a chain that a client can validate. + */ + if (SSL_CTX_use_PrivateKey_file(ctx, key_path, SSL_FILETYPE_PEM) <= 0) { + fprintf(stderr, "couldn't load key file: %s\n", key_path); + goto err; + } + + /* + * Since we're not soliciting or processing client certificates, we don't + * need to configure a trusted-certificate store, so no call to + * SSL_CTX_set_default_verify_paths() is needed. The server's own + * certificate chain is assumed valid. + */ + SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); + + /* Setup ALPN negotiation callback to decide which ALPN is accepted. */ + SSL_CTX_set_alpn_select_cb(ctx, select_alpn, NULL); + + return ctx; + +err: + SSL_CTX_free(ctx); + return NULL; +} + +/** + * @brief Creates and binds a UDP socket to the specified port. + * + * This function initializes a new UDP socket, binds it to the specified + * port on the local host, and returns the socket file descriptor for + * further use. + * + * @param port The port number to which the UDP socket should be bound. + * + * @return On success, returns the BIO of the created socket. + * On failure, returns NULL. + * + * Steps: + * - Creates a new UDP socket using the `socket` system call. + * - Configures the socket address structure to bind to the specified port + * on the local host. + * - Binds the socket to the port using the `bind` system call. + * + * Error Handling: + * - If socket creation or binding fails, an error message is printed to + * `stderr`, the socket (if created) is closed, and -1 is returned. + * + * Usage: + * - Call this function to set up a socket for handling incoming QUIC + * connections. + * + * Notes: + * - This function assumes the use of IPv4 (`AF_INET`) and UDP (`SOCK_DGRAM`). + * - The specified port is converted to network byte order using `htons`. + */ +static BIO *create_socket(uint16_t port) +{ + int fd = -1; + BIO *sock = NULL; + BIO_ADDR *addr = NULL; + struct in_addr ina; + + ina.s_addr = INADDR_ANY; + + /* Retrieve the file descriptor for a new UDP socket */ + if ((fd = BIO_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP, 0)) < 0) { + fprintf(stderr, "cannot create socket"); + goto err; + } + + /* + * Create a new BIO_ADDR + */ + addr = BIO_ADDR_new(); + if (addr == NULL) { + fprintf(stderr, "Unable to create BIO_ADDR\n"); + goto err; + } + + /* + * Build a INADDR_ANY BIO_ADDR + */ + if (!BIO_ADDR_rawmake(addr, AF_INET, &ina, sizeof(ina), htons(port))) { + fprintf(stderr, "unable to bind to port %d\n", port); + goto err; + } + + /* Bind to the new UDP socket */ + if (!BIO_bind(fd, addr, 0)) { + fprintf(stderr, "cannot bind to %u\n", port); + goto err; + } + + /* + * Create a new datagram socket + */ + sock = BIO_new(BIO_s_datagram()); + if (sock == NULL) { + fprintf(stderr, "cannot create dgram bio\n"); + goto err; + } + + /* + * associate the underlying socket with the dgram BIO + */ + if (!BIO_set_fd(sock, fd, BIO_CLOSE)) { + fprintf(stderr, "Unable to set fd of dgram sock\n"); + goto err; + } + + /* + * Free our allocated addr + */ + BIO_ADDR_free(addr); + return sock; + +err: + BIO_ADDR_free(addr); + BIO_free(sock); + BIO_closesocket(fd); + return NULL; +} + +/** + * @brief Processes a new incoming QUIC stream for an HTTP/0.9 GET request. + * + * This function reads an HTTP/0.9 GET request from the provided QUIC stream, + * retrieves the requested file from the server's file system, and sends the + * file contents back to the client over the stream. + * + * @param Pointer to the SSL object representing the QUIC stream. + * + * Operation: + * - Reads the HTTP/0.9 GET request from the client. + * - Parses the request to extract the requested file name. + * - Constructs the file path using the `fileprefix` directory. + * - Reads the requested file in chunks and sends it to the client. + * - Concludes the QUIC stream once the file is fully sent. + * + * Error Handling: + * - If the request is invalid or the file cannot be opened, appropriate + * error messages are logged, and the function exits without sending data. + * - Errors during file reading or writing to the stream are handled, with + * retries for buffer-related issues (e.g., full send buffer). + * + * Notes: + * - The request is expected to be a valid HTTP/0.9 GET request. + * - File paths are sanitized to prevent path traversal vulnerabilities. + * - The function uses blocking operations for reading and writing data. + * + * Usage: + * - Called for each accepted QUIC stream to handle client requests. + */ +static void process_new_stream(SSL *stream) +{ + unsigned char buf[BUF_SIZE]; + char path[BUF_SIZE]; + char *req = (char *)buf; + char *reqname; + char *creturn; + size_t nread; + BIO *readbio; + size_t bytes_read = 0; + size_t bytes_written = 0; + size_t offset = 0; + int rc; + + memset(buf, 0, BUF_SIZE); + if (SSL_read_ex(stream, buf, sizeof(buf) - 1, &nread) <= 0) + return; + + /* We should have a valid http 0.9 GET request here */ + fprintf(stderr, "Request is %s\n", req); + + /* Look for the last '/' char in the request */ + reqname = strrchr(req, '/'); + if (reqname == NULL) + return; + reqname++; + + /* Requests have a trailing \r\n, eliminate them */ + creturn = strchr(reqname, '\r'); + if (creturn != NULL) + *creturn = '\0'; + + snprintf(path, BUF_SIZE, "%s/%s", fileprefix, reqname); + + fprintf(stderr, "Serving %s\n", path); + readbio = BIO_new_file(path, "r"); + if (readbio == NULL) { + fprintf(stderr, "Unable to open %s\n", path); + ERR_print_errors_fp(stderr); + goto done; + } + + /* Read the readbio file into a buffer, and just send it to the requestor */ + while (BIO_eof(readbio) <= 0) { + bytes_read = 0; + if (!BIO_read_ex(readbio, buf, BUF_SIZE, &bytes_read)) { + if (BIO_eof(readbio) <= 0) { + fprintf(stderr, "Failed to read from %s\n", path); + ERR_print_errors_fp(stderr); + goto out; + } else { + break; + } + } + + offset = 0; + for (;;) { + bytes_written = 0; + rc = SSL_write_ex(stream, &buf[offset], bytes_read, &bytes_written); + if (rc <= 0) { + rc = SSL_get_error(stream, rc); + switch (rc) { + case SSL_ERROR_WANT_WRITE: + fprintf(stderr, "Send buffer full, retrying\n"); + continue; + break; + default: + fprintf(stderr, "Unhandled error cause %d\n", rc); + goto done; + break; + } + } + bytes_read -= bytes_written; + offset += bytes_written; + bytes_written = 0; + if (bytes_read == 0) + break; + } + } + +done: + if (!SSL_stream_conclude(stream, 0)) + fprintf(stderr, "Failed to conclude stream\n"); + +out: + BIO_free(readbio); + return; +} + +/** + * @brief Runs the QUIC server to accept and handle client connections. + * + * This function initializes a QUIC listener, binds it to the provided UDP + * socket, and enters a loop to accept client connections and process incoming + * QUIC streams. Each connection is handled until termination, and streams are + * processed individually using the `process_new_stream` function. + * + * @param ctx Pointer to the SSL_CTX object configured for QUIC. + * @param sock BIO of the bound UDP socket. + * + * @return Returns 0 on error; otherwise, the server runs indefinitely. + * + * Operation: + * - Creates a QUIC listener using the provided SSL_CTX and associates it + * with the specified UDP socket. + * - Waits for incoming QUIC connections and accepts them. + * - For each connection: + * - Accepts incoming streams. + * - Processes each stream using `process_new_stream`. + * - Shuts down the connection upon completion. + * + * Error Handling: + * - If listener creation or connection acceptance fails, the function logs + * an error message and exits the loop. + * - Cleans up allocated resources (e.g., listener, connection) on failure. + * + * Usage: + * - Call this function in the main server loop after setting up the + * SSL_CTX and binding a UDP socket. + * + * Notes: + * - Uses blocking operations for listener, connection, and stream handling. + * - Incoming streams are processed based on the configured stream policy. + * - The server runs in an infinite loop unless a fatal error occurs. + */ +static int run_quic_server(SSL_CTX *ctx, BIO *sock) +{ + int ok = 0; + SSL *listener, *conn, *stream; + unsigned long errcode; + + /* + * Create a new QUIC listener. Listeners, and other QUIC objects, default + * to operating in blocking mode. The configured behaviour is inherited by + * child objects. + */ + if ((listener = SSL_new_listener(ctx, 0)) == NULL) + goto err; + + /* Provide the listener with our UDP socket. */ + SSL_set_bio(listener, sock, sock); + + /* Begin listening. */ + if (!SSL_listen(listener)) + goto err; + + /* + * Begin an infinite loop of listening for connections. We will only + * exit this loop if we encounter an error. + */ + for (;;) { + /* Pristine error stack for each new connection */ + ERR_clear_error(); + + /* Block while waiting for a client connection */ + printf("Waiting for connection\n"); + conn = SSL_accept_connection(listener, 0); + if (conn == NULL) { + fprintf(stderr, "error while accepting connection\n"); + goto err; + } + printf("Accepted new connection\n"); + + /* + * QUIC requires that we inform the connection that + * we always want to accept new streams, rather than reject them + * Additionally, while we don't make an explicit call here, we + * are using the default stream mode, as would be specified by + * a call to SSL_set_default_stream_mode + */ + if (!SSL_set_incoming_stream_policy(conn, + SSL_INCOMING_STREAM_POLICY_ACCEPT, + 0)) { + fprintf(stderr, "Failed to set incomming stream policy\n"); + goto close_conn; + } + + /* + * Until the connection is closed, accept incomming stream + * requests and serve them + */ + for (;;) { + /* + * Note that SSL_accept_stream is blocking here, as the + * conn SSL object inherited the deafult blocking property + * from its parent, the listener SSL object. As such there + * is no need to handle retry failures here. + */ + stream = SSL_accept_stream(conn, 0); + if (stream == NULL) { + /* + * If we don't get a stream, either we + * Hit a legitimate error, and should bail out + * or + * The Client closed the connection, and there are no + * more incomming streams expected + * + * Filter on the shutdown error, and only print an error + * message if the cause is not SHUTDOWN + */ + errcode = ERR_get_error(); + if (ERR_GET_REASON(errcode) != SSL_R_PROTOCOL_IS_SHUTDOWN) + fprintf(stderr, "Failure in accept stream, error %s\n", + ERR_reason_error_string(errcode)); + break; + } + process_new_stream(stream); + SSL_free(stream); + } + + /* + * Shut down the connection. We may need to call this multiple times + * to ensure the connection is shutdown completely. + */ +close_conn: + while (SSL_shutdown(conn) != 1) + continue; + + SSL_free(conn); + } + +err: + SSL_free(listener); + return ok; +} + +/** + * @brief Entry point for the minimal QUIC HTTP/0.9 server. + * + * This function initializes the server, sets up a QUIC context, binds a UDP + * socket to the specified port, and starts the main QUIC server loop to handle + * client connections and requests. + * + * @param argc Number of command-line arguments. + * @param argv Array of command-line arguments: + * - argv[0]: Program name. + * - argv[1]: Port number to bind the server. + * - argv[2]: Path to the server's certificate file (PEM format). + * - argv[3]: Path to the server's private key file (PEM format). + * + * @return Returns EXIT_SUCCESS on successful execution, or EXIT_FAILURE + * on error. + * + * Operation: + * - Validates the command-line arguments. + * - Reads the FILEPREFIX environment variable to set the file prefix for + * serving files (default is "./downloads"). + * - Creates an SSL_CTX with QUIC support using the provided certificate and + * key files. + * - Parses and validates the port number. + * - Creates and binds a UDP socket to the specified port. + * - Starts the server loop using `run_quic_server` to accept and process + * client connections. + * + * Error Handling: + * - If any initialization step fails (e.g., invalid arguments, socket + * creation, context setup), appropriate error messages are logged, and + * the program exits with EXIT_FAILURE. + * + * Usage: + * - Run the program with the required arguments to start the server: + * `./server ` + * + * Notes: + * - Ensure that the certificate and key files exist and are valid. + * - The server serves files from the directory specified by FILEPREFIX. + */ +int main(int argc, char *argv[]) +{ + int res = EXIT_FAILURE; + SSL_CTX *ctx = NULL; + BIO *sock = NULL; + unsigned long port; + + if (argc != 4) { + fprintf(stderr, "usage: %s \n", argv[0]); + goto out; + } + + fileprefix = getenv("FILEPREFIX"); + if (fileprefix == NULL) + fileprefix = "./downloads"; + + fprintf(stderr, "Fileprefix is %s\n", fileprefix); + + /* Create SSL_CTX that supports QUIC. */ + if ((ctx = create_ctx(argv[2], argv[3])) == NULL) { + ERR_print_errors_fp(stderr); + fprintf(stderr, "Failed to create context\n"); + goto out; + } + + /* Parse port number from command line arguments. */ + port = strtoul(argv[1], NULL, 0); + if (port == 0 || port > UINT16_MAX) { + fprintf(stderr, "Failed to parse port number\n"); + goto out; + } + fprintf(stderr, "Binding to port %lu\n", port); + + /* Create and bind a UDP socket. */ + if ((sock = create_socket((uint16_t)port)) == NULL) { + ERR_print_errors_fp(stderr); + fprintf(stderr, "Failed to create socket\n"); + goto out; + } + + /* QUIC server connection acceptance loop. */ + if (!run_quic_server(ctx, sock)) { + ERR_print_errors_fp(stderr); + fprintf(stderr, "Failed to run quic server\n"); + goto out; + } + + res = EXIT_SUCCESS; +out: + /* Free resources. */ + SSL_CTX_free(ctx); + BIO_free(sock); + return res; +} diff --git a/test/quic-openssl-docker/Dockerfile b/test/quic-openssl-docker/Dockerfile index 5a65cbf2823..7f3609e84bc 100644 --- a/test/quic-openssl-docker/Dockerfile +++ b/test/quic-openssl-docker/Dockerfile @@ -28,8 +28,9 @@ RUN git clone --depth 1 https://github.com/ngtcp2/nghttp3.git && \ # download and build openssl RUN git clone --depth 1 -b $OPENSSL_BRANCH $OPENSSL_URL && \ cd openssl && \ - ./Configure enable-fips enable-demos disable-docs --prefix=/usr --openssldir=/etc/pki/tls && \ + ./Configure enable-sslkeylog enable-fips enable-demos disable-docs --prefix=/usr --openssldir=/etc/pki/tls && \ make -j 4 && make install && cp demos/guide/quic-hq-interop /usr/local/bin && \ + cp demos/guide/quic-hq-interop-server /usr/local/bin && \ rm -rf /openssl # Build curl diff --git a/test/quic-openssl-docker/run_endpoint.sh b/test/quic-openssl-docker/run_endpoint.sh index 5cd006ab4d5..e8729c30dd4 100644 --- a/test/quic-openssl-docker/run_endpoint.sh +++ b/test/quic-openssl-docker/run_endpoint.sh @@ -85,8 +85,20 @@ if [ "$ROLE" == "client" ]; then ;; esac elif [ "$ROLE" == "server" ]; then - echo "UNSUPPORTED" - exit 127 + echo "TESTCASE is $TESTCASE" + rm -f $CURLRC + case "$TESTCASE" in + "handshake"|"transfer"|"retry"|"resumption") + SSLKEYLOGFILE=/logs/keys.log FILEPREFIX=/www quic-hq-interop-server 443 /certs/cert.pem /certs/priv.key + ;; + "chacha20") + SSL_CIPHER_SUITES=TLS_CHACHA20_POLY1305_SHA256 SSLKEYLOGFILE=/logs/keys.log FILEPREFIX=/www quic-hq-interop-server 443 /certs/cert.pem /certs/priv.key + ;; + *) + echo "UNSUPPORTED TESTCASE $TESTCASE" + exit 127 + ;; + esac else echo "Unknown ROLE $ROLE" exit 127