| /* |
| * Copyright 2024-2025 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: |
| * <port> <server.crt> <server.key> |
| * 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 |
| * - NO_ADDR_VALIDATE: Disables server address validation of clients |
| * |
| */ |
| |
| #include <string.h> |
| |
| /* Include the appropriate header file for SOCK_STREAM */ |
| #ifdef _WIN32 |
| # include <stdarg.h> |
| # include <winsock2.h> |
| # include <ws2tcpip.h> |
| #else |
| # include <sys/socket.h> |
| # include <netinet/in.h> |
| # include <unistd.h> |
| #endif |
| |
| #include <openssl/bio.h> |
| #include <openssl/ssl.h> |
| #include <openssl/err.h> |
| #include <openssl/quic.h> |
| |
| #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 UDP (`SOCK_DGRAM`). |
| * - This function accepts on both IPv4 and IPv6. |
| * - 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; |
| int opt = 0; |
| #ifdef _WIN32 |
| struct in6_addr in6addr_any; |
| |
| memset(&in6addr_any, 0, sizeof(in6addr_any)); |
| #endif |
| |
| /* Retrieve the file descriptor for a new UDP socket */ |
| if ((fd = BIO_socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP, 0)) < 0) { |
| fprintf(stderr, "cannot create socket"); |
| goto err; |
| } |
| |
| /* |
| * IPv6_V6ONLY is only available on some platforms. If it is defined, |
| * disable it to accept both IPv4 and IPv6 connections. Otherwise, the |
| * server will only accept IPv6 connections. |
| */ |
| #ifdef IPV6_V6ONLY |
| if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)) < 0) { |
| fprintf(stderr, "setsockopt IPV6_V6ONLY failed"); |
| goto err; |
| } |
| #endif |
| |
| /* |
| * Create a new BIO_ADDR |
| */ |
| addr = BIO_ADDR_new(); |
| if (addr == NULL) { |
| fprintf(stderr, "Unable to create BIO_ADDR\n"); |
| goto err; |
| } |
| |
| /* |
| * Build an INADDR_ANY BIO_ADDR |
| */ |
| if (!BIO_ADDR_rawmake(addr, AF_INET6, &in6addr_any, sizeof(in6addr_any), 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 Handles I/O failures on an SSL stream based on the result code. |
| * |
| * This function processes the result of an SSL I/O operation and handles |
| * different types of errors that may occur during the operation. It takes |
| * appropriate actions such as retrying the operation, reporting errors, or |
| * returning specific status codes based on the error type. |
| * |
| * @param ssl A pointer to the SSL object representing the stream. |
| * @param res The result code from the SSL I/O operation. |
| * @return An integer indicating the outcome: |
| * - 0: EOF, indicating the stream has been closed. |
| * - -1: A fatal error occurred or the stream has been reset. |
| * |
| * |
| * @note If the failure is due to an SSL verification error, additional |
| * information will be logged to stderr. |
| */ |
| static int handle_io_failure(SSL *ssl, int res) |
| { |
| switch (SSL_get_error(ssl, res)) { |
| case SSL_ERROR_ZERO_RETURN: |
| /* EOF */ |
| return 0; |
| |
| case SSL_ERROR_SYSCALL: |
| return -1; |
| |
| case SSL_ERROR_SSL: |
| /* |
| * Some stream fatal error occurred. This could be because of a |
| * stream reset - or some failure occurred on the underlying |
| * connection. |
| */ |
| switch (SSL_get_stream_read_state(ssl)) { |
| case SSL_STREAM_STATE_RESET_REMOTE: |
| fprintf(stderr, "Stream reset occurred\n"); |
| /* |
| * The stream has been reset but the connection is still |
| * healthy. |
| */ |
| break; |
| |
| case SSL_STREAM_STATE_CONN_CLOSED: |
| fprintf(stderr, "Connection closed\n"); |
| /* Connection is already closed. */ |
| break; |
| |
| default: |
| fprintf(stderr, "Unknown stream failure\n"); |
| break; |
| } |
| /* |
| * If the failure is due to a verification error we can get more |
| * information about it from SSL_get_verify_result(). |
| */ |
| if (SSL_get_verify_result(ssl) != X509_V_OK) |
| fprintf(stderr, "Verify error: %s\n", |
| X509_verify_cert_error_string(SSL_get_verify_result(ssl))); |
| return -1; |
| |
| default: |
| return -1; |
| } |
| } |
| |
| /** |
| * @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; |
| int ret; |
| size_t total_read = 0; |
| |
| memset(buf, 0, BUF_SIZE); |
| for (;;) { |
| nread = 0; |
| ret = SSL_read_ex(stream, &buf[total_read], |
| sizeof(buf) - total_read - 1, &nread); |
| total_read += nread; |
| if (ret <= 0) { |
| ret = handle_io_failure(stream, ret); |
| if (ret == 0) { |
| /* EOF condition, fin bit set, we got the whole request */ |
| break; |
| } else { |
| /* permanent failure, abort */ |
| fprintf(stderr, "Failure on stream\n"); |
| 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; |
| uint64_t flags = 0; |
| |
| /* |
| * If NO_ADDR_VALIDATE exists in our environment |
| * then disable address validation on our listener |
| */ |
| if (getenv("NO_ADDR_VALIDATE") != NULL) |
| flags |= SSL_LISTENER_FLAG_NO_VALIDATE; |
| |
| /* |
| * 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, flags)) == 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 |
| */ |
| ERR_print_errors_fp(stderr); |
| 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 <port> <server.crt> <server.key>` |
| * |
| * 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 <port> <server.crt> <server.key>\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; |
| } |