1 1.1 christos /* 2 1.1 christos * Copyright 2024-2025 The OpenSSL Project Authors. All Rights Reserved. 3 1.1 christos * 4 1.1 christos * Licensed under the Apache License 2.0 (the "License"). You may not use 5 1.1 christos * this file except in compliance with the License. You can obtain a copy 6 1.1 christos * in the file LICENSE in the source distribution or at 7 1.1 christos * https://www.openssl.org/source/license.html 8 1.1 christos */ 9 1.1 christos 10 1.1 christos /** 11 1.1 christos * @file quic-hq-interop-server.c 12 1.1 christos * @brief Minimal QUIC HTTP/0.9 server implementation. 13 1.1 christos * 14 1.1 christos * This file implements a lightweight QUIC server supporting the HTTP/0.9 15 1.1 christos * protocol for interoperability testing. It includes functions for setting 16 1.1 christos * up a secure QUIC connection, handling ALPN negotiation, and serving client 17 1.1 christos * requests. Intended for use with the quic-interop-runner 18 1.1 christos * available at https://interop.seemann.io 19 1.1 christos * 20 1.1 christos * Key functionalities: 21 1.1 christos * - Setting up SSL_CTX with QUIC support. 22 1.1 christos * - Negotiating ALPN strings during the TLS handshake. 23 1.1 christos * - Listening and accepting incoming QUIC connections. 24 1.1 christos * - Handling client requests via HTTP/0.9 protocol. 25 1.1 christos * 26 1.1 christos * Usage: 27 1.1 christos * <port> <server.crt> <server.key> 28 1.1 christos * The server binds to the specified port and serves files using the given 29 1.1 christos * certificate and private key. 30 1.1 christos * 31 1.1 christos * Environment variables: 32 1.1 christos * - FILEPREFIX: Specifies the directory containing files to serve. 33 1.1 christos * Defaults to "./downloads" if not set. 34 1.1.1.2 christos * - SSLKEYLOGFILE: specifies that keylogging should be performed on the server 35 1.1 christos * should be set to a file name to record keylog data to 36 1.1 christos * - NO_ADDR_VALIDATE: Disables server address validation of clients 37 1.1 christos * 38 1.1 christos */ 39 1.1 christos 40 1.1 christos #include <string.h> 41 1.1 christos 42 1.1 christos /* Include the appropriate header file for SOCK_STREAM */ 43 1.1 christos #ifdef _WIN32 44 1.1.1.2 christos #include <stdarg.h> 45 1.1.1.2 christos #include <winsock2.h> 46 1.1.1.2 christos #include <ws2tcpip.h> 47 1.1 christos #else 48 1.1.1.2 christos #include <sys/socket.h> 49 1.1.1.2 christos #include <netinet/in.h> 50 1.1.1.2 christos #include <unistd.h> 51 1.1 christos #endif 52 1.1 christos 53 1.1 christos #include <openssl/bio.h> 54 1.1 christos #include <openssl/ssl.h> 55 1.1 christos #include <openssl/err.h> 56 1.1 christos #include <openssl/quic.h> 57 1.1 christos 58 1.1 christos #define BUF_SIZE 4096 59 1.1 christos 60 1.1 christos /** 61 1.1 christos * @brief ALPN (Application-Layer Protocol Negotiation) identifier for QUIC. 62 1.1 christos * 63 1.1 christos * This constant defines the ALPN string used during the TLS handshake 64 1.1 christos * to negotiate the application-layer protocol between the client and 65 1.1 christos * the server. It specifies "hq-interop" as the supported protocol. 66 1.1 christos * 67 1.1 christos * Format: 68 1.1 christos * - The first byte represents the length of the ALPN string. 69 1.1 christos * - Subsequent bytes represent the ASCII characters of the protocol name. 70 1.1 christos * 71 1.1 christos * Value: 72 1.1 christos * - Protocol: "hq-interop" 73 1.1 christos * - Length: 10 bytes 74 1.1 christos * 75 1.1 christos * Usage: 76 1.1 christos * This is passed to the ALPN callback function to validate and 77 1.1 christos * negotiate the desired protocol during the TLS handshake. 78 1.1 christos */ 79 1.1 christos static const unsigned char alpn_ossltest[] = { 80 1.1.1.2 christos 10, 81 1.1.1.2 christos 'h', 82 1.1.1.2 christos 'q', 83 1.1.1.2 christos '-', 84 1.1.1.2 christos 'i', 85 1.1.1.2 christos 'n', 86 1.1.1.2 christos 't', 87 1.1.1.2 christos 'e', 88 1.1.1.2 christos 'r', 89 1.1.1.2 christos 'o', 90 1.1.1.2 christos 'p', 91 1.1 christos }; 92 1.1 christos 93 1.1 christos /** 94 1.1 christos * @brief Directory prefix for serving requested files. 95 1.1 christos * 96 1.1 christos * This variable specifies the directory path used as the base location 97 1.1 christos * for serving files in response to client requests. It is used to construct 98 1.1 christos * the full file path for requested resources. 99 1.1 christos * 100 1.1 christos * Default: 101 1.1 christos * - If not set via the FILEPREFIX environment variable, it defaults to 102 1.1 christos * "./downloads". 103 1.1 christos * 104 1.1 christos * Usage: 105 1.1 christos * - Updated at runtime based on the FILEPREFIX environment variable. 106 1.1 christos * - Used to locate and serve files during incoming requests. 107 1.1 christos */ 108 1.1 christos static char *fileprefix = NULL; 109 1.1 christos 110 1.1 christos /** 111 1.1 christos * @brief Callback for ALPN (Application-Layer Protocol Negotiation) selection. 112 1.1 christos * 113 1.1 christos * This function is invoked during the TLS handshake on the server side to 114 1.1 christos * validate and negotiate the desired ALPN (Application-Layer Protocol 115 1.1 christos * Negotiation) protocol with the client. It ensures that the negotiated 116 1.1 christos * protocol matches the predefined "hq-interop" string. 117 1.1 christos * 118 1.1 christos * @param ssl Pointer to the SSL connection object. 119 1.1 christos * @param[out] out Pointer to the negotiated ALPN protocol string. 120 1.1 christos * @param[out] out_len Length of the negotiated ALPN protocol string. 121 1.1 christos * @param in Pointer to the client-provided ALPN protocol list. 122 1.1 christos * @param in_len Length of the client-provided ALPN protocol list. 123 1.1 christos * @param arg Optional user-defined argument (unused in this context). 124 1.1 christos * 125 1.1 christos * @return SSL_TLSEXT_ERR_OK on successful ALPN negotiation, 126 1.1 christos * SSL_TLSEXT_ERR_ALERT_FATAL otherwise. 127 1.1 christos * 128 1.1 christos * Usage: 129 1.1 christos * - This function is set as the ALPN selection callback in the SSL_CTX 130 1.1 christos * using `SSL_CTX_set_alpn_select_cb`. 131 1.1 christos * - Ensures that only the predefined ALPN protocol is accepted. 132 1.1 christos * 133 1.1 christos * Note: 134 1.1 christos * - The predefined protocol is specified in the `alpn_ossltest` array. 135 1.1 christos */ 136 1.1 christos static int select_alpn(SSL *ssl, const unsigned char **out, 137 1.1.1.2 christos unsigned char *out_len, const unsigned char *in, 138 1.1.1.2 christos unsigned int in_len, void *arg) 139 1.1 christos { 140 1.1 christos /* 141 1.1 christos * Use the next_proto helper function here. 142 1.1 christos * This scans the list of alpns we support and matches against 143 1.1 christos * what the client is requesting 144 1.1 christos */ 145 1.1 christos if (SSL_select_next_proto((unsigned char **)out, out_len, alpn_ossltest, 146 1.1.1.2 christos sizeof(alpn_ossltest), in, 147 1.1.1.2 christos in_len) 148 1.1.1.2 christos == OPENSSL_NPN_NEGOTIATED) 149 1.1 christos return SSL_TLSEXT_ERR_OK; 150 1.1 christos return SSL_TLSEXT_ERR_ALERT_FATAL; 151 1.1 christos } 152 1.1 christos 153 1.1 christos /** 154 1.1 christos * @brief Creates and configures an SSL_CTX for a QUIC server. 155 1.1 christos * 156 1.1 christos * This function initializes an SSL_CTX object with the QUIC server method 157 1.1 christos * and configures it using the provided certificate and private key. The 158 1.1 christos * context is prepared for handling secure QUIC connections and performing 159 1.1 christos * ALPN (Application-Layer Protocol Negotiation). 160 1.1 christos * 161 1.1 christos * @param cert_path Path to the server's certificate chain file in PEM format. 162 1.1 christos * The chain file must include the server's leaf certificate 163 1.1 christos * followed by intermediate CA certificates. 164 1.1 christos * @param key_path Path to the server's private key file in PEM format. The 165 1.1 christos * private key must correspond to the leaf certificate in 166 1.1 christos * the chain file. 167 1.1 christos * 168 1.1 christos * @return Pointer to the initialized SSL_CTX on success, or NULL on failure. 169 1.1 christos * 170 1.1 christos * Configuration: 171 1.1 christos * - Loads the certificate chain and private key into the context. 172 1.1 christos * - Disables client certificate verification (no mutual TLS). 173 1.1 christos * - Sets up the ALPN selection callback for protocol negotiation. 174 1.1 christos * 175 1.1 christos * Error Handling: 176 1.1 christos * - If any step fails (e.g., loading the certificate or key), the function 177 1.1 christos * frees the SSL_CTX and returns NULL. 178 1.1 christos * 179 1.1 christos * Usage: 180 1.1 christos * - Call this function to create an SSL_CTX before starting the QUIC server. 181 1.1 christos * - Ensure valid paths for the certificate and private key are provided. 182 1.1 christos * 183 1.1 christos * Note: 184 1.1 christos * - The ALPN callback only supports the predefined protocol defined in 185 1.1 christos * `alpn_ossltest`. 186 1.1 christos */ 187 1.1 christos static SSL_CTX *create_ctx(const char *cert_path, const char *key_path) 188 1.1 christos { 189 1.1 christos SSL_CTX *ctx; 190 1.1 christos 191 1.1 christos /* 192 1.1 christos * An SSL_CTX holds shared configuration information for multiple 193 1.1 christos * subsequent per-client connections. We specifically load a QUIC 194 1.1 christos * server method here. 195 1.1 christos */ 196 1.1 christos ctx = SSL_CTX_new(OSSL_QUIC_server_method()); 197 1.1 christos if (ctx == NULL) 198 1.1 christos goto err; 199 1.1 christos 200 1.1 christos /* 201 1.1 christos * Load the server's certificate *chain* file (PEM format), which includes 202 1.1 christos * not only the leaf (end-entity) server certificate, but also any 203 1.1 christos * intermediate issuer-CA certificates. The leaf certificate must be the 204 1.1 christos * first certificate in the file. 205 1.1 christos * 206 1.1 christos * In advanced use-cases this can be called multiple times, once per public 207 1.1 christos * key algorithm for which the server has a corresponding certificate. 208 1.1 christos * However, the corresponding private key (see below) must be loaded first, 209 1.1 christos * *before* moving on to the next chain file. 210 1.1 christos * 211 1.1 christos * The requisite files "chain.pem" and "pkey.pem" can be generated by running 212 1.1 christos * "make chain" in this directory. If the server will be executed from some 213 1.1 christos * other directory, move or copy the files there. 214 1.1 christos */ 215 1.1 christos if (SSL_CTX_use_certificate_chain_file(ctx, cert_path) <= 0) { 216 1.1 christos fprintf(stderr, "couldn't load certificate file: %s\n", cert_path); 217 1.1 christos goto err; 218 1.1 christos } 219 1.1 christos 220 1.1 christos /* 221 1.1 christos * Load the corresponding private key, this also checks that the private 222 1.1 christos * key matches the just loaded end-entity certificate. It does not check 223 1.1 christos * whether the certificate chain is valid, the certificates could be 224 1.1 christos * expired, or may otherwise fail to form a chain that a client can validate. 225 1.1 christos */ 226 1.1 christos if (SSL_CTX_use_PrivateKey_file(ctx, key_path, SSL_FILETYPE_PEM) <= 0) { 227 1.1 christos fprintf(stderr, "couldn't load key file: %s\n", key_path); 228 1.1 christos goto err; 229 1.1 christos } 230 1.1 christos 231 1.1 christos /* 232 1.1 christos * Since we're not soliciting or processing client certificates, we don't 233 1.1 christos * need to configure a trusted-certificate store, so no call to 234 1.1 christos * SSL_CTX_set_default_verify_paths() is needed. The server's own 235 1.1 christos * certificate chain is assumed valid. 236 1.1 christos */ 237 1.1 christos SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); 238 1.1 christos 239 1.1 christos /* Setup ALPN negotiation callback to decide which ALPN is accepted. */ 240 1.1 christos SSL_CTX_set_alpn_select_cb(ctx, select_alpn, NULL); 241 1.1 christos 242 1.1 christos return ctx; 243 1.1 christos 244 1.1 christos err: 245 1.1 christos SSL_CTX_free(ctx); 246 1.1 christos return NULL; 247 1.1 christos } 248 1.1 christos 249 1.1 christos /** 250 1.1 christos * @brief Creates and binds a UDP socket to the specified port. 251 1.1 christos * 252 1.1 christos * This function initializes a new UDP socket, binds it to the specified 253 1.1 christos * port on the local host, and returns the socket file descriptor for 254 1.1 christos * further use. 255 1.1 christos * 256 1.1 christos * @param port The port number to which the UDP socket should be bound. 257 1.1 christos * 258 1.1 christos * @return On success, returns the BIO of the created socket. 259 1.1 christos * On failure, returns NULL. 260 1.1 christos * 261 1.1 christos * Steps: 262 1.1 christos * - Creates a new UDP socket using the `socket` system call. 263 1.1 christos * - Configures the socket address structure to bind to the specified port 264 1.1 christos * on the local host. 265 1.1 christos * - Binds the socket to the port using the `bind` system call. 266 1.1 christos * 267 1.1 christos * Error Handling: 268 1.1 christos * - If socket creation or binding fails, an error message is printed to 269 1.1 christos * `stderr`, the socket (if created) is closed, and -1 is returned. 270 1.1 christos * 271 1.1 christos * Usage: 272 1.1 christos * - Call this function to set up a socket for handling incoming QUIC 273 1.1 christos * connections. 274 1.1 christos * 275 1.1 christos * Notes: 276 1.1 christos * - This function assumes UDP (`SOCK_DGRAM`). 277 1.1 christos * - This function accepts on both IPv4 and IPv6. 278 1.1 christos * - The specified port is converted to network byte order using `htons`. 279 1.1 christos */ 280 1.1 christos static BIO *create_socket(uint16_t port) 281 1.1 christos { 282 1.1 christos int fd = -1; 283 1.1 christos BIO *sock = NULL; 284 1.1 christos BIO_ADDR *addr = NULL; 285 1.1 christos int opt = 0; 286 1.1 christos #ifdef _WIN32 287 1.1 christos struct in6_addr in6addr_any; 288 1.1 christos 289 1.1 christos memset(&in6addr_any, 0, sizeof(in6addr_any)); 290 1.1 christos #endif 291 1.1 christos 292 1.1 christos /* Retrieve the file descriptor for a new UDP socket */ 293 1.1 christos if ((fd = BIO_socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP, 0)) < 0) { 294 1.1 christos fprintf(stderr, "cannot create socket"); 295 1.1 christos goto err; 296 1.1 christos } 297 1.1 christos 298 1.1 christos /* 299 1.1 christos * IPv6_V6ONLY is only available on some platforms. If it is defined, 300 1.1 christos * disable it to accept both IPv4 and IPv6 connections. Otherwise, the 301 1.1 christos * server will only accept IPv6 connections. 302 1.1 christos */ 303 1.1 christos #ifdef IPV6_V6ONLY 304 1.1 christos if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)) < 0) { 305 1.1 christos fprintf(stderr, "setsockopt IPV6_V6ONLY failed"); 306 1.1 christos goto err; 307 1.1 christos } 308 1.1 christos #endif 309 1.1 christos 310 1.1 christos /* 311 1.1 christos * Create a new BIO_ADDR 312 1.1 christos */ 313 1.1 christos addr = BIO_ADDR_new(); 314 1.1 christos if (addr == NULL) { 315 1.1 christos fprintf(stderr, "Unable to create BIO_ADDR\n"); 316 1.1 christos goto err; 317 1.1 christos } 318 1.1 christos 319 1.1 christos /* 320 1.1 christos * Build an INADDR_ANY BIO_ADDR 321 1.1 christos */ 322 1.1 christos if (!BIO_ADDR_rawmake(addr, AF_INET6, &in6addr_any, sizeof(in6addr_any), htons(port))) { 323 1.1 christos fprintf(stderr, "unable to bind to port %d\n", port); 324 1.1 christos goto err; 325 1.1 christos } 326 1.1 christos 327 1.1 christos /* Bind to the new UDP socket */ 328 1.1 christos if (!BIO_bind(fd, addr, 0)) { 329 1.1 christos fprintf(stderr, "cannot bind to %u\n", port); 330 1.1 christos goto err; 331 1.1 christos } 332 1.1 christos 333 1.1 christos /* 334 1.1 christos * Create a new datagram socket 335 1.1 christos */ 336 1.1 christos sock = BIO_new(BIO_s_datagram()); 337 1.1 christos if (sock == NULL) { 338 1.1 christos fprintf(stderr, "cannot create dgram bio\n"); 339 1.1 christos goto err; 340 1.1 christos } 341 1.1 christos 342 1.1 christos /* 343 1.1 christos * associate the underlying socket with the dgram BIO 344 1.1 christos */ 345 1.1 christos if (!BIO_set_fd(sock, fd, BIO_CLOSE)) { 346 1.1 christos fprintf(stderr, "Unable to set fd of dgram sock\n"); 347 1.1 christos goto err; 348 1.1 christos } 349 1.1 christos 350 1.1 christos /* 351 1.1 christos * Free our allocated addr 352 1.1 christos */ 353 1.1 christos BIO_ADDR_free(addr); 354 1.1 christos return sock; 355 1.1 christos 356 1.1 christos err: 357 1.1 christos BIO_ADDR_free(addr); 358 1.1 christos BIO_free(sock); 359 1.1 christos BIO_closesocket(fd); 360 1.1 christos return NULL; 361 1.1 christos } 362 1.1 christos 363 1.1 christos /** 364 1.1 christos * @brief Handles I/O failures on an SSL stream based on the result code. 365 1.1 christos * 366 1.1 christos * This function processes the result of an SSL I/O operation and handles 367 1.1 christos * different types of errors that may occur during the operation. It takes 368 1.1 christos * appropriate actions such as retrying the operation, reporting errors, or 369 1.1 christos * returning specific status codes based on the error type. 370 1.1 christos * 371 1.1 christos * @param ssl A pointer to the SSL object representing the stream. 372 1.1 christos * @param res The result code from the SSL I/O operation. 373 1.1 christos * @return An integer indicating the outcome: 374 1.1 christos * - 0: EOF, indicating the stream has been closed. 375 1.1 christos * - -1: A fatal error occurred or the stream has been reset. 376 1.1 christos * 377 1.1 christos * 378 1.1 christos * @note If the failure is due to an SSL verification error, additional 379 1.1 christos * information will be logged to stderr. 380 1.1 christos */ 381 1.1 christos static int handle_io_failure(SSL *ssl, int res) 382 1.1 christos { 383 1.1 christos switch (SSL_get_error(ssl, res)) { 384 1.1 christos case SSL_ERROR_ZERO_RETURN: 385 1.1 christos /* EOF */ 386 1.1 christos return 0; 387 1.1 christos 388 1.1 christos case SSL_ERROR_SYSCALL: 389 1.1 christos return -1; 390 1.1 christos 391 1.1 christos case SSL_ERROR_SSL: 392 1.1 christos /* 393 1.1 christos * Some stream fatal error occurred. This could be because of a 394 1.1 christos * stream reset - or some failure occurred on the underlying 395 1.1 christos * connection. 396 1.1 christos */ 397 1.1 christos switch (SSL_get_stream_read_state(ssl)) { 398 1.1 christos case SSL_STREAM_STATE_RESET_REMOTE: 399 1.1 christos fprintf(stderr, "Stream reset occurred\n"); 400 1.1 christos /* 401 1.1 christos * The stream has been reset but the connection is still 402 1.1 christos * healthy. 403 1.1 christos */ 404 1.1 christos break; 405 1.1 christos 406 1.1 christos case SSL_STREAM_STATE_CONN_CLOSED: 407 1.1 christos fprintf(stderr, "Connection closed\n"); 408 1.1 christos /* Connection is already closed. */ 409 1.1 christos break; 410 1.1 christos 411 1.1 christos default: 412 1.1 christos fprintf(stderr, "Unknown stream failure\n"); 413 1.1 christos break; 414 1.1 christos } 415 1.1 christos /* 416 1.1 christos * If the failure is due to a verification error we can get more 417 1.1 christos * information about it from SSL_get_verify_result(). 418 1.1 christos */ 419 1.1 christos if (SSL_get_verify_result(ssl) != X509_V_OK) 420 1.1 christos fprintf(stderr, "Verify error: %s\n", 421 1.1.1.2 christos X509_verify_cert_error_string(SSL_get_verify_result(ssl))); 422 1.1 christos return -1; 423 1.1 christos 424 1.1 christos default: 425 1.1 christos return -1; 426 1.1 christos } 427 1.1 christos } 428 1.1 christos 429 1.1 christos /** 430 1.1 christos * @brief Processes a new incoming QUIC stream for an HTTP/0.9 GET request. 431 1.1 christos * 432 1.1 christos * This function reads an HTTP/0.9 GET request from the provided QUIC stream, 433 1.1 christos * retrieves the requested file from the server's file system, and sends the 434 1.1 christos * file contents back to the client over the stream. 435 1.1 christos * 436 1.1 christos * @param Pointer to the SSL object representing the QUIC stream. 437 1.1 christos * 438 1.1 christos * Operation: 439 1.1 christos * - Reads the HTTP/0.9 GET request from the client. 440 1.1 christos * - Parses the request to extract the requested file name. 441 1.1 christos * - Constructs the file path using the `fileprefix` directory. 442 1.1 christos * - Reads the requested file in chunks and sends it to the client. 443 1.1 christos * - Concludes the QUIC stream once the file is fully sent. 444 1.1 christos * 445 1.1 christos * Error Handling: 446 1.1 christos * - If the request is invalid or the file cannot be opened, appropriate 447 1.1 christos * error messages are logged, and the function exits without sending data. 448 1.1 christos * - Errors during file reading or writing to the stream are handled, with 449 1.1 christos * retries for buffer-related issues (e.g., full send buffer). 450 1.1 christos * 451 1.1 christos * Notes: 452 1.1 christos * - The request is expected to be a valid HTTP/0.9 GET request. 453 1.1 christos * - File paths are sanitized to prevent path traversal vulnerabilities. 454 1.1 christos * - The function uses blocking operations for reading and writing data. 455 1.1 christos * 456 1.1 christos * Usage: 457 1.1 christos * - Called for each accepted QUIC stream to handle client requests. 458 1.1 christos */ 459 1.1 christos static void process_new_stream(SSL *stream) 460 1.1 christos { 461 1.1 christos unsigned char buf[BUF_SIZE]; 462 1.1 christos char path[BUF_SIZE]; 463 1.1 christos char *req = (char *)buf; 464 1.1 christos char *reqname; 465 1.1 christos char *creturn; 466 1.1 christos size_t nread; 467 1.1 christos BIO *readbio; 468 1.1 christos size_t bytes_read = 0; 469 1.1 christos size_t bytes_written = 0; 470 1.1 christos size_t offset = 0; 471 1.1 christos int rc; 472 1.1 christos int ret; 473 1.1 christos size_t total_read = 0; 474 1.1 christos 475 1.1 christos memset(buf, 0, BUF_SIZE); 476 1.1 christos for (;;) { 477 1.1 christos nread = 0; 478 1.1 christos ret = SSL_read_ex(stream, &buf[total_read], 479 1.1.1.2 christos sizeof(buf) - total_read - 1, &nread); 480 1.1 christos total_read += nread; 481 1.1 christos if (ret <= 0) { 482 1.1 christos ret = handle_io_failure(stream, ret); 483 1.1 christos if (ret == 0) { 484 1.1 christos /* EOF condition, fin bit set, we got the whole request */ 485 1.1 christos break; 486 1.1 christos } else { 487 1.1 christos /* permanent failure, abort */ 488 1.1 christos fprintf(stderr, "Failure on stream\n"); 489 1.1 christos return; 490 1.1 christos } 491 1.1 christos } 492 1.1 christos } 493 1.1 christos 494 1.1 christos /* We should have a valid http 0.9 GET request here */ 495 1.1 christos fprintf(stderr, "Request is %s\n", req); 496 1.1 christos 497 1.1 christos /* Look for the last '/' char in the request */ 498 1.1 christos reqname = strrchr(req, '/'); 499 1.1 christos if (reqname == NULL) 500 1.1 christos return; 501 1.1 christos reqname++; 502 1.1 christos 503 1.1 christos /* Requests have a trailing \r\n, eliminate them */ 504 1.1 christos creturn = strchr(reqname, '\r'); 505 1.1 christos if (creturn != NULL) 506 1.1 christos *creturn = '\0'; 507 1.1 christos 508 1.1 christos snprintf(path, BUF_SIZE, "%s/%s", fileprefix, reqname); 509 1.1 christos 510 1.1 christos fprintf(stderr, "Serving %s\n", path); 511 1.1 christos readbio = BIO_new_file(path, "r"); 512 1.1 christos if (readbio == NULL) { 513 1.1 christos fprintf(stderr, "Unable to open %s\n", path); 514 1.1 christos ERR_print_errors_fp(stderr); 515 1.1 christos goto done; 516 1.1 christos } 517 1.1 christos 518 1.1 christos /* Read the readbio file into a buffer, and just send it to the requestor */ 519 1.1 christos while (BIO_eof(readbio) <= 0) { 520 1.1 christos bytes_read = 0; 521 1.1 christos if (!BIO_read_ex(readbio, buf, BUF_SIZE, &bytes_read)) { 522 1.1 christos if (BIO_eof(readbio) <= 0) { 523 1.1 christos fprintf(stderr, "Failed to read from %s\n", path); 524 1.1 christos ERR_print_errors_fp(stderr); 525 1.1 christos goto out; 526 1.1 christos } else { 527 1.1 christos break; 528 1.1 christos } 529 1.1 christos } 530 1.1 christos 531 1.1 christos offset = 0; 532 1.1 christos for (;;) { 533 1.1 christos bytes_written = 0; 534 1.1 christos rc = SSL_write_ex(stream, &buf[offset], bytes_read, &bytes_written); 535 1.1 christos if (rc <= 0) { 536 1.1 christos rc = SSL_get_error(stream, rc); 537 1.1 christos switch (rc) { 538 1.1 christos case SSL_ERROR_WANT_WRITE: 539 1.1 christos fprintf(stderr, "Send buffer full, retrying\n"); 540 1.1 christos continue; 541 1.1 christos break; 542 1.1 christos default: 543 1.1 christos fprintf(stderr, "Unhandled error cause %d\n", rc); 544 1.1 christos goto done; 545 1.1 christos break; 546 1.1 christos } 547 1.1 christos } 548 1.1 christos bytes_read -= bytes_written; 549 1.1 christos offset += bytes_written; 550 1.1 christos bytes_written = 0; 551 1.1 christos if (bytes_read == 0) 552 1.1 christos break; 553 1.1 christos } 554 1.1 christos } 555 1.1 christos 556 1.1 christos done: 557 1.1 christos if (!SSL_stream_conclude(stream, 0)) 558 1.1 christos fprintf(stderr, "Failed to conclude stream\n"); 559 1.1 christos 560 1.1 christos out: 561 1.1 christos BIO_free(readbio); 562 1.1 christos return; 563 1.1 christos } 564 1.1 christos 565 1.1 christos /** 566 1.1 christos * @brief Runs the QUIC server to accept and handle client connections. 567 1.1 christos * 568 1.1 christos * This function initializes a QUIC listener, binds it to the provided UDP 569 1.1 christos * socket, and enters a loop to accept client connections and process incoming 570 1.1 christos * QUIC streams. Each connection is handled until termination, and streams are 571 1.1 christos * processed individually using the `process_new_stream` function. 572 1.1 christos * 573 1.1 christos * @param ctx Pointer to the SSL_CTX object configured for QUIC. 574 1.1 christos * @param sock BIO of the bound UDP socket. 575 1.1 christos * 576 1.1 christos * @return Returns 0 on error; otherwise, the server runs indefinitely. 577 1.1 christos * 578 1.1 christos * Operation: 579 1.1 christos * - Creates a QUIC listener using the provided SSL_CTX and associates it 580 1.1 christos * with the specified UDP socket. 581 1.1 christos * - Waits for incoming QUIC connections and accepts them. 582 1.1 christos * - For each connection: 583 1.1 christos * - Accepts incoming streams. 584 1.1 christos * - Processes each stream using `process_new_stream`. 585 1.1 christos * - Shuts down the connection upon completion. 586 1.1 christos * 587 1.1 christos * Error Handling: 588 1.1 christos * - If listener creation or connection acceptance fails, the function logs 589 1.1 christos * an error message and exits the loop. 590 1.1 christos * - Cleans up allocated resources (e.g., listener, connection) on failure. 591 1.1 christos * 592 1.1 christos * Usage: 593 1.1 christos * - Call this function in the main server loop after setting up the 594 1.1 christos * SSL_CTX and binding a UDP socket. 595 1.1 christos * 596 1.1 christos * Notes: 597 1.1 christos * - Uses blocking operations for listener, connection, and stream handling. 598 1.1 christos * - Incoming streams are processed based on the configured stream policy. 599 1.1 christos * - The server runs in an infinite loop unless a fatal error occurs. 600 1.1 christos */ 601 1.1 christos static int run_quic_server(SSL_CTX *ctx, BIO *sock) 602 1.1 christos { 603 1.1 christos int ok = 0; 604 1.1 christos SSL *listener, *conn, *stream; 605 1.1 christos unsigned long errcode; 606 1.1 christos uint64_t flags = 0; 607 1.1 christos 608 1.1 christos /* 609 1.1 christos * If NO_ADDR_VALIDATE exists in our environment 610 1.1 christos * then disable address validation on our listener 611 1.1 christos */ 612 1.1 christos if (getenv("NO_ADDR_VALIDATE") != NULL) 613 1.1 christos flags |= SSL_LISTENER_FLAG_NO_VALIDATE; 614 1.1 christos 615 1.1 christos /* 616 1.1 christos * Create a new QUIC listener. Listeners, and other QUIC objects, default 617 1.1 christos * to operating in blocking mode. The configured behaviour is inherited by 618 1.1 christos * child objects. 619 1.1 christos */ 620 1.1 christos if ((listener = SSL_new_listener(ctx, flags)) == NULL) 621 1.1 christos goto err; 622 1.1 christos 623 1.1 christos /* Provide the listener with our UDP socket. */ 624 1.1 christos SSL_set_bio(listener, sock, sock); 625 1.1 christos 626 1.1 christos /* Begin listening. */ 627 1.1 christos if (!SSL_listen(listener)) 628 1.1 christos goto err; 629 1.1 christos 630 1.1 christos /* 631 1.1 christos * Begin an infinite loop of listening for connections. We will only 632 1.1 christos * exit this loop if we encounter an error. 633 1.1 christos */ 634 1.1 christos for (;;) { 635 1.1 christos /* Pristine error stack for each new connection */ 636 1.1 christos ERR_clear_error(); 637 1.1 christos 638 1.1 christos /* Block while waiting for a client connection */ 639 1.1 christos printf("Waiting for connection\n"); 640 1.1 christos conn = SSL_accept_connection(listener, 0); 641 1.1 christos if (conn == NULL) { 642 1.1 christos fprintf(stderr, "error while accepting connection\n"); 643 1.1 christos goto err; 644 1.1 christos } 645 1.1 christos printf("Accepted new connection\n"); 646 1.1 christos 647 1.1 christos /* 648 1.1 christos * QUIC requires that we inform the connection that 649 1.1 christos * we always want to accept new streams, rather than reject them 650 1.1 christos * Additionally, while we don't make an explicit call here, we 651 1.1 christos * are using the default stream mode, as would be specified by 652 1.1 christos * a call to SSL_set_default_stream_mode 653 1.1 christos */ 654 1.1 christos if (!SSL_set_incoming_stream_policy(conn, 655 1.1.1.2 christos SSL_INCOMING_STREAM_POLICY_ACCEPT, 656 1.1.1.2 christos 0)) { 657 1.1.1.2 christos fprintf(stderr, "Failed to set incoming stream policy\n"); 658 1.1 christos goto close_conn; 659 1.1 christos } 660 1.1 christos 661 1.1 christos /* 662 1.1.1.2 christos * Until the connection is closed, accept incoming stream 663 1.1 christos * requests and serve them 664 1.1 christos */ 665 1.1 christos for (;;) { 666 1.1 christos /* 667 1.1 christos * Note that SSL_accept_stream is blocking here, as the 668 1.1.1.2 christos * conn SSL object inherited the default blocking property 669 1.1 christos * from its parent, the listener SSL object. As such there 670 1.1 christos * is no need to handle retry failures here. 671 1.1 christos */ 672 1.1 christos stream = SSL_accept_stream(conn, 0); 673 1.1 christos if (stream == NULL) { 674 1.1 christos /* 675 1.1 christos * If we don't get a stream, either we 676 1.1 christos * Hit a legitimate error, and should bail out 677 1.1 christos * or 678 1.1 christos * The Client closed the connection, and there are no 679 1.1.1.2 christos * more incoming streams expected 680 1.1 christos * 681 1.1 christos * Filter on the shutdown error, and only print an error 682 1.1 christos * message if the cause is not SHUTDOWN 683 1.1 christos */ 684 1.1 christos ERR_print_errors_fp(stderr); 685 1.1 christos errcode = ERR_get_error(); 686 1.1 christos if (ERR_GET_REASON(errcode) != SSL_R_PROTOCOL_IS_SHUTDOWN) 687 1.1 christos fprintf(stderr, "Failure in accept stream, error %s\n", 688 1.1.1.2 christos ERR_reason_error_string(errcode)); 689 1.1 christos break; 690 1.1 christos } 691 1.1 christos process_new_stream(stream); 692 1.1 christos SSL_free(stream); 693 1.1 christos } 694 1.1 christos 695 1.1 christos /* 696 1.1 christos * Shut down the connection. We may need to call this multiple times 697 1.1 christos * to ensure the connection is shutdown completely. 698 1.1 christos */ 699 1.1.1.2 christos close_conn: 700 1.1 christos while (SSL_shutdown(conn) != 1) 701 1.1 christos continue; 702 1.1 christos 703 1.1 christos SSL_free(conn); 704 1.1 christos } 705 1.1 christos 706 1.1 christos err: 707 1.1 christos SSL_free(listener); 708 1.1 christos return ok; 709 1.1 christos } 710 1.1 christos 711 1.1 christos /** 712 1.1 christos * @brief Entry point for the minimal QUIC HTTP/0.9 server. 713 1.1 christos * 714 1.1 christos * This function initializes the server, sets up a QUIC context, binds a UDP 715 1.1 christos * socket to the specified port, and starts the main QUIC server loop to handle 716 1.1 christos * client connections and requests. 717 1.1 christos * 718 1.1 christos * @param argc Number of command-line arguments. 719 1.1 christos * @param argv Array of command-line arguments: 720 1.1 christos * - argv[0]: Program name. 721 1.1 christos * - argv[1]: Port number to bind the server. 722 1.1 christos * - argv[2]: Path to the server's certificate file (PEM format). 723 1.1 christos * - argv[3]: Path to the server's private key file (PEM format). 724 1.1 christos * 725 1.1 christos * @return Returns EXIT_SUCCESS on successful execution, or EXIT_FAILURE 726 1.1 christos * on error. 727 1.1 christos * 728 1.1 christos * Operation: 729 1.1 christos * - Validates the command-line arguments. 730 1.1 christos * - Reads the FILEPREFIX environment variable to set the file prefix for 731 1.1 christos * serving files (default is "./downloads"). 732 1.1 christos * - Creates an SSL_CTX with QUIC support using the provided certificate and 733 1.1 christos * key files. 734 1.1 christos * - Parses and validates the port number. 735 1.1 christos * - Creates and binds a UDP socket to the specified port. 736 1.1 christos * - Starts the server loop using `run_quic_server` to accept and process 737 1.1 christos * client connections. 738 1.1 christos * 739 1.1 christos * Error Handling: 740 1.1 christos * - If any initialization step fails (e.g., invalid arguments, socket 741 1.1 christos * creation, context setup), appropriate error messages are logged, and 742 1.1 christos * the program exits with EXIT_FAILURE. 743 1.1 christos * 744 1.1 christos * Usage: 745 1.1 christos * - Run the program with the required arguments to start the server: 746 1.1 christos * `./server <port> <server.crt> <server.key>` 747 1.1 christos * 748 1.1 christos * Notes: 749 1.1 christos * - Ensure that the certificate and key files exist and are valid. 750 1.1 christos * - The server serves files from the directory specified by FILEPREFIX. 751 1.1 christos */ 752 1.1 christos int main(int argc, char *argv[]) 753 1.1 christos { 754 1.1 christos int res = EXIT_FAILURE; 755 1.1 christos SSL_CTX *ctx = NULL; 756 1.1 christos BIO *sock = NULL; 757 1.1 christos unsigned long port; 758 1.1 christos 759 1.1 christos if (argc != 4) { 760 1.1 christos fprintf(stderr, "usage: %s <port> <server.crt> <server.key>\n", argv[0]); 761 1.1 christos goto out; 762 1.1 christos } 763 1.1 christos 764 1.1 christos fileprefix = getenv("FILEPREFIX"); 765 1.1 christos if (fileprefix == NULL) 766 1.1 christos fileprefix = "./downloads"; 767 1.1 christos 768 1.1 christos fprintf(stderr, "Fileprefix is %s\n", fileprefix); 769 1.1 christos 770 1.1 christos /* Create SSL_CTX that supports QUIC. */ 771 1.1 christos if ((ctx = create_ctx(argv[2], argv[3])) == NULL) { 772 1.1 christos ERR_print_errors_fp(stderr); 773 1.1 christos fprintf(stderr, "Failed to create context\n"); 774 1.1 christos goto out; 775 1.1 christos } 776 1.1 christos 777 1.1 christos /* Parse port number from command line arguments. */ 778 1.1 christos port = strtoul(argv[1], NULL, 0); 779 1.1 christos if (port == 0 || port > UINT16_MAX) { 780 1.1 christos fprintf(stderr, "Failed to parse port number\n"); 781 1.1 christos goto out; 782 1.1 christos } 783 1.1 christos fprintf(stderr, "Binding to port %lu\n", port); 784 1.1 christos 785 1.1 christos /* Create and bind a UDP socket. */ 786 1.1 christos if ((sock = create_socket((uint16_t)port)) == NULL) { 787 1.1 christos ERR_print_errors_fp(stderr); 788 1.1 christos fprintf(stderr, "Failed to create socket\n"); 789 1.1 christos goto out; 790 1.1 christos } 791 1.1 christos 792 1.1 christos /* QUIC server connection acceptance loop. */ 793 1.1 christos if (!run_quic_server(ctx, sock)) { 794 1.1 christos ERR_print_errors_fp(stderr); 795 1.1 christos fprintf(stderr, "Failed to run quic server\n"); 796 1.1 christos goto out; 797 1.1 christos } 798 1.1 christos 799 1.1 christos res = EXIT_SUCCESS; 800 1.1 christos out: 801 1.1 christos /* Free resources. */ 802 1.1 christos SSL_CTX_free(ctx); 803 1.1 christos BIO_free(sock); 804 1.1 christos return res; 805 1.1 christos } 806