diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 299dc64..3ec2018 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -2,7 +2,7 @@ # The dependencies of this project have been moved into another Dockerfile that can be built # separately. This image can be pushed to the HUB, speeding up codespace creation considerably. # -FROM docker.io/psedoc/anyhttp:0.27 +FROM docker.io/psedoc/anyhttp:0.28 # # install some more interactive utils in the devcontainer diff --git a/.devcontainer/base/Dockerfile b/.devcontainer/base/Dockerfile index 6536404..3b285c8 100644 --- a/.devcontainer/base/Dockerfile +++ b/.devcontainer/base/Dockerfile @@ -2,7 +2,7 @@ # https://github.com/microsoft/vscode-dev-containers/tree/main/containers/debian # https://github.com/pgit/cpp-devcontainer # -FROM docker.io/psedoc/cpp-devcontainer:0.27 +FROM docker.io/psedoc/cpp-devcontainer:0.28 # # GDB libc++ pretty printer (works for string, but not for map with llvm-18) @@ -66,10 +66,15 @@ ENV LD_LIBRARY_PATH=/usr/local/lib # With debian trixie, we now have OpenSSL 3 with QUIC support. Whether or not that is any good, # seems to be up for debate: https://www.haproxy.com/blog/state-of-ssl-stacks # -ARG AWS_LC_VERSION=v5.5.0 +ARG AWS_LC_VERSION=v5.6.0 RUN git clone --depth 1 -b ${AWS_LC_VERSION} https://github.com/aws/aws-lc && \ cd aws-lc && \ - cmake -B build -DDISABLE_GO=ON --install-prefix=/opt/boringssl && \ + cmake -B build \ + -DCMAKE_BUILD_TYPE=Release \ + -DDISABLE_GO=ON \ + -DBUILD_TESTING=OFF \ + -DBUILD_TOOL=OFF \ + --install-prefix=/opt/boringssl && \ make -j$(nproc) -C build && \ cmake --install build && \ cd .. && \ @@ -86,6 +91,14 @@ RUN git clone --recursive --depth 1 --branch ${NGHTTP3_VERSION} https://github.c # # nghtcp2 is built with both 'boringssl' (actually, aws-lc) and 'ossl' support # +# The example client/server are built as well. There is no --enable-examples switch: they are +# enabled implicitly by configure as long as we don't pass --enable-lib-only and nghttp3, libev +# and a C++23 compiler are present -- see the 'Examples:' line in the configure summary. +# +# They are noinst_PROGRAMS, so 'make install' ignores them and we have to install them by hand. +# 'osslclient'/'osslserver' are the OpenSSL variants, replacing the copy of the example sources +# that used to live in src/ngtcp2/ as 'ngtcp-client'/'ngtcp-server'. +# ARG NGTCP2_VERSION=v1.25.0 RUN git clone --recursive --depth 1 --branch ${NGTCP2_VERSION} https://github.com/ngtcp2/ngtcp2 && \ cd ngtcp2 && \ @@ -95,12 +108,15 @@ RUN git clone --recursive --depth 1 --branch ${NGTCP2_VERSION} https://github.co BORINGSSL_LIBS="-L/opt/boringssl/lib -lssl -lcrypto" \ --with-boringssl --with-ossl && \ make -j$(nproc) check && make install && \ + make -j$(nproc) -C examples osslclient osslserver && \ + install -m 755 -t /usr/local/bin examples/osslclient examples/osslserver && \ cd third-party/urlparse && \ autoreconf -i && \ ./configure --prefix=/usr/local && \ make -j$(nproc) && make install && \ cd ../.. && \ - cd .. && rm -rf ngctp2 + cd .. && rm -rf ngtcp2 && \ + ldconfig # ================================================================================================== diff --git a/.github/instructions/anyhttp.instructions.md b/.github/instructions/anyhttp.instructions.md index d872846..1db6ef2 100644 --- a/.github/instructions/anyhttp.instructions.md +++ b/.github/instructions/anyhttp.instructions.md @@ -2,7 +2,7 @@ applyTo: '**' --- AnyHTTP is a type-erased interface for implementing HTTP Servers and Clients. It offers common -interfaces for HTTP/1.1, HTTP/2 and HTTP/3 (QUIC, not implemented yet). +interfaces for HTTP/1.1, HTTP/2 and HTTP/3 (QUIC). AnyHTTP is completely asynchronous and intended to be used with C++20 coroutines. It uses Boost ASIO as the underlying async runtime and tries to adhere to it's diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2587400..74faa72 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,7 +15,7 @@ jobs: name: RelWithDebInfo runs-on: ubuntu-latest container: - image: docker.io/psedoc/anyhttp:0.27 + image: docker.io/psedoc/anyhttp:0.28 steps: - &checkout @@ -37,7 +37,7 @@ jobs: name: ASAN runs-on: ubuntu-latest container: - image: docker.io/psedoc/anyhttp:0.27 + image: docker.io/psedoc/anyhttp:0.28 steps: - *checkout @@ -56,7 +56,7 @@ jobs: name: TSAN runs-on: ubuntu-latest container: - image: docker.io/psedoc/anyhttp:0.27 + image: docker.io/psedoc/anyhttp:0.28 steps: - *checkout @@ -75,7 +75,7 @@ jobs: name: Coverage runs-on: ubuntu-latest container: - image: docker.io/psedoc/anyhttp:0.27 + image: docker.io/psedoc/anyhttp:0.28 permissions: contents: read pages: write diff --git a/CMakeLists.txt b/CMakeLists.txt index b696593..c3233b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,6 +42,14 @@ add_link_options(-stdlib=libc++ -lstdc++fs) # use std::filesystem instead of boost::filesystem add_compile_definitions(BOOST_PROCESS_USE_STD_FS) +# Boost.Process v2's posix launcher calls asio's execution_context::notify_fork() in the forked +# child, between fork() and execve(). That walks the io_context's services and locks their +# mutexes -- and with more than one thread running the context, another thread may well have +# been holding one of them at the moment of the fork, so the child inherits it locked and hangs +# there forever, before it ever gets to dup2() its stdio or exec. The child does nothing but +# exec, so it has no use for the notification in the first place. +add_compile_definitions(BOOST_PROCESS_V2_DISABLE_NOTIFY_FORK) + # we don't use fmtlib any more add_compile_definitions(SPDLOG_USE_STD_FORMAT) @@ -86,21 +94,6 @@ find_package(spdlog REQUIRED) # -------------------------------------------------------------------------------------------------- -# Checks for header files, used by ngtcp2 code. -include(CheckIncludeFile) -check_include_file("arpa/inet.h" HAVE_ARPA_INET_H) -check_include_file("netinet/in.h" HAVE_NETINET_IN_H) -check_include_file("netinet/ip.h" HAVE_NETINET_IP_H) -check_include_file("unistd.h" HAVE_UNISTD_H) -check_include_file("sys/endian.h" HAVE_SYS_ENDIAN_H) -check_include_file("endian.h" HAVE_ENDIAN_H) -check_include_file("byteswap.h" HAVE_BYTESWAP_H) -check_include_file("asm/types.h" HAVE_ASM_TYPES_H) -check_include_file("linux/netlink.h" HAVE_LINUX_NETLINK_H) -check_include_file("linux/rtnetlink.h" HAVE_LINUX_RTNETLINK_H) - -# -------------------------------------------------------------------------------------------------- - enable_testing(false) include(cmake/pki.cmake) add_subdirectory(src) diff --git a/HTTP3.md b/HTTP3.md index 47bccdf..01ffb65 100644 --- a/HTTP3.md +++ b/HTTP3.md @@ -1,7 +1,7 @@ # HTTP/3 Manual Testing ```sh -cmake --build build && build/src/ngtcp2/ngtcp-server ::1 8080 pki/out/server-key.pem pki/out/server-chain.pem +osslserver ::1 8080 pki/out/server-key.pem pki/out/server-chain.pem ``` ```sh diff --git a/README.md b/README.md index 9ef343f..3846a29 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ None of those protocols are implemented from scratch. Instead, it is a wrapper a * Boost Beast * nghttp2 -* nghttp3 - not done yet. +* ngtcp2/nghttp3 ## Synopsis ### Server diff --git a/include/anyhttp/literals.hpp b/include/anyhttp/literals.hpp index e2b5b96..91a5382 100644 --- a/include/anyhttp/literals.hpp +++ b/include/anyhttp/literals.hpp @@ -1 +1,7 @@ -#include "../src/ngtcp2/template.h" +#pragma once + +// +// User-defined literals for K and M (powers of 1024), as in the ngtcp2 examples. +// +constexpr unsigned long long operator""_k(unsigned long long k) { return k * 1024; } +constexpr unsigned long long operator""_m(unsigned long long m) { return m * 1024 * 1024; } diff --git a/include/anyhttp/server_impl.hpp b/include/anyhttp/server_impl.hpp index 2063fb4..6f1e49e 100644 --- a/include/anyhttp/server_impl.hpp +++ b/include/anyhttp/server_impl.hpp @@ -53,6 +53,7 @@ class Response::Impl : public impl::Writer struct Endpoint; class Http3Session; +struct QuicBatch; class Server::Impl : public std::enable_shared_from_this { @@ -69,7 +70,7 @@ class Server::Impl : public std::enable_shared_from_this const Config& config() const { return m_config; } boost::asio::any_io_executor get_executor() const noexcept { return m_executor; } - asio::awaitable listen_loop(); + asio::awaitable tcp_listen_loop(); asio::awaitable handleConnection(asio::ip::tcp::socket socket); asio::ip::tcp::endpoint local_endpoint() const @@ -83,10 +84,13 @@ class Server::Impl : public std::enable_shared_from_this asio::awaitable udp_receive_loop(); int udp_on_read(Endpoint& ep); + void process_quic_batch(const std::shared_ptr& session, QuicBatch&& batch); // // QUIC connection-ID demux table. Populated by QuicHandler as new source CIDs are minted, - // consulted by udp_on_read() to route packets to the right connection. + // consulted by udp_on_read() to route packets to the right connection. Guarded by + // m_quicMutex: the receive loop reads it while sessions mutate it from their own strands + // (get_new_connection_id/remove_connection_id callbacks, close timers). // void associate_quic_cid(const ngtcp2_cid& cid, Http3Session* session); void dissociate_quic_cid(const ngtcp2_cid& cid); @@ -102,10 +106,11 @@ class Server::Impl : public std::enable_shared_from_this std::mutex m_sessionMutex; std::set> m_sessions; + std::mutex m_quicMutex; std::unordered_map> m_quic_handlers; RequestHandler m_requestHandler; - bool m_stopped = false; + bool m_destroyed = false; }; // ================================================================================================= diff --git a/include/anyhttp/tls.hpp b/include/anyhttp/tls.hpp new file mode 100644 index 0000000..f6bab22 --- /dev/null +++ b/include/anyhttp/tls.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include + +struct ssl_st; + +// ================================================================================================= + +namespace anyhttp +{ + +/** + * One-line summary of a completed TLS handshake, in the spirit of what h2load prints: + * protocol version, cipher, key exchange group and the negotiated ALPN protocol. + * + * Used for both TLS over TCP and QUIC, so that the log looks the same for all protocols. + */ +std::string tls_handshake_info(ssl_st* ssl); + +} // namespace anyhttp + +// ================================================================================================= diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 562e614..cc79a01 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,8 +4,7 @@ include_directories(${CMAKE_SOURCE_DIR}/include) # https://github.com/andrewmd5/libwtf/blob/main/CMakeLists.txt # -# QUIC / HTTP3 libraries -- shared between the anyhttp library below and the -# stand-alone ngtcp2 example binaries in the ngtcp2/ subdirectory. +# QUIC / HTTP3 libraries # pkg_check_modules(NGTCP2 REQUIRED IMPORTED_TARGET libngtcp2) pkg_check_modules(NGHTTP3 REQUIRED IMPORTED_TARGET libnghttp3) @@ -22,14 +21,16 @@ target_sources(anyhttp PRIVATE ${anyhttp_sources}) target_include_directories(anyhttp PUBLIC ../include) target_include_directories(anyhttp PRIVATE "/opt/nghttp3/build/include") -target_sources(anyhttp PRIVATE ngtcp2/shared.cc) +# +# Small helpers taken from the ngtcp2 examples (see ngtcp2/README.md). +# +target_sources(anyhttp PRIVATE ngtcp2/shared.cc ngtcp2/util.cc) target_link_libraries(anyhttp PRIVATE Threads::Threads) target_link_libraries(anyhttp PRIVATE OpenSSL::SSL) target_link_libraries(anyhttp PRIVATE Boost::thread Boost::atomic Boost::url Boost::filesystem) target_link_libraries(anyhttp PRIVATE spdlog::spdlog_header_only) target_link_libraries(anyhttp PRIVATE PkgConfig::NGHTTP2) -target_link_libraries(anyhttp PRIVATE ngtcp2_common) target_link_libraries(anyhttp PRIVATE PkgConfig::NGTCP2 PkgConfig::NGHTTP3 PkgConfig::NGTCP2_CRYPTO_OSSL) # @@ -53,14 +54,3 @@ target_link_libraries(client PRIVATE anyhttp) # add_executable(beast_issue_3032) target_sources(beast_issue_3032 PRIVATE "beast_issue_3032.cpp") - -# -# ngtcp-test -# -add_executable(ngtcp-test) -target_sources(ngtcp-test PRIVATE "ngtcp-test_main.cpp") -target_include_directories(ngtcp-test PUBLIC "/opt/boringssl/include") -target_link_directories(ngtcp-test PRIVATE "/opt/boringssl/lib") -target_link_libraries(ngtcp-test ssl crypto ev ngtcp2 libngtcp2_crypto_boringssl.a) - -add_subdirectory(ngtcp2) diff --git a/src/client_impl_udp.cpp b/src/client_impl_udp.cpp index 00d501d..0769a00 100644 --- a/src/client_impl_udp.cpp +++ b/src/client_impl_udp.cpp @@ -16,6 +16,7 @@ #include "anyhttp/formatter.hpp" // IWYU pragma: keep #include "anyhttp/literals.hpp" #include "anyhttp/session_impl.hpp" +#include "anyhttp/tls.hpp" #include #include @@ -95,15 +96,6 @@ struct TlsClientContext static constexpr unsigned char alpn[] = "\x02h3"; SSL_CTX_set_alpn_protos(ctx, alpn, sizeof(alpn) - 1); - // - // Same order as the server, so AES-128 GCM is also picked against peers that leave the - // choice to the client. - // - if (SSL_CTX_set_ciphersuites(ctx, "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:" - "TLS_CHACHA20_POLY1305_SHA256") != 1) - throw std::runtime_error(std::string{"SSL_CTX_set_ciphersuites: "} + - ERR_error_string(ERR_get_error(), nullptr)); - // // TODO: verify the server certificate (e.g. against pki/out/root.pem) instead of accepting // anything. @@ -183,8 +175,8 @@ class Http3ClientStream; // // Bound on how much of the caller's async_write() buffer we copy into write_chunk at a time (see // Http3ClientStream's write_* members) -- copying is paced by how much nghttp3/ngtcp2 actually -// drains, rather than copying a huge caller buffer (e.g. 50MB) in one synchronous allocation+memcpy, -// mirroring nghttp2's own per-call copy into its frame buffer. +// drains, rather than copying a huge caller buffer (e.g. 50MB) in one synchronous +// allocation+memcpy, mirroring nghttp2's own per-call copy into its frame buffer. // inline constexpr size_t kWriteChunkSize = 16 * 1024; @@ -1016,7 +1008,6 @@ void Http3ClientStream::finish_active_write() if (handler) swap_and_invoke(handler, boost::system::error_code{}); - } // ------------------------------------------------------------------------------------------------- @@ -1346,6 +1337,17 @@ awaitable Http3ClientSession::do_session(Buffer&&) if (on_read({buf.data(), n}) != 0) break; // handle_error() already tore things down. + + // + // close() may have run from inside on_read(): handing a response chunk or EOF to the + // application resumes its coroutine, which may drop the last reference to the Session + // right there. Its socket_.cancel() then found no receive pending -- we are between two + // of them -- so nothing would stop us from arming a fresh one that no peer will ever + // complete. The server, already draining because it got our CONNECTION_CLOSE, does not + // even answer it. + // + if (closed_) + break; } // @@ -1674,7 +1676,8 @@ int Http3ClientSession::handle_error(int /*rv*/) int Http3ClientSession::cb_handshake_completed(ngtcp2_conn*, void* user) { auto self = static_cast(user); - logi("[{}] TLS handshake complete", self->log_prefix_); + logi("[{}] TLS handshake completed: {}", self->log_prefix_, + tls_handshake_info(ngtcp2_crypto_ossl_ctx_get_ssl(self->ossl_ctx_))); if (!self->h3_ && self->setup_http3() != 0) return NGTCP2_ERR_CALLBACK_FAILURE; return 0; diff --git a/src/ngtcp-test_main.cpp b/src/ngtcp-test_main.cpp deleted file mode 100644 index fae8f25..0000000 --- a/src/ngtcp-test_main.cpp +++ /dev/null @@ -1,682 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2021 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#ifdef HAVE_CONFIG_H -# include -#endif /* defined(HAVE_CONFIG_H) */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include - -#include - -// #define REMOTE_HOST "127.0.0.1" -// #define REMOTE_PORT "4433" -// #define ALPN "\xahq-interop" -// #define MESSAGE "GET /\r\n" - -/* - * Example 1: Handshake with www.google.com - * - * #define REMOTE_HOST "www.google.com" - * #define REMOTE_PORT "443" - * #define ALPN "\x2h3" - * - * and undefine MESSAGE macro. - */ -#define REMOTE_HOST "localhost" -#define REMOTE_PORT "8080" -#define ALPN "\x2h3" -#undef MESSAGE - -static uint64_t timestamp(void) { - struct timespec tp; - - if (clock_gettime(CLOCK_MONOTONIC, &tp) != 0) { - fprintf(stderr, "clock_gettime: %s\n", strerror(errno)); - exit(EXIT_FAILURE); - } - - return (uint64_t)tp.tv_sec * NGTCP2_SECONDS + (uint64_t)tp.tv_nsec; -} - -static int create_sock(struct sockaddr *addr, socklen_t *paddrlen, - const char *host, const char *port) { - struct addrinfo hints = {0}; - struct addrinfo *res, *rp; - int rv; - int fd = -1; - - hints.ai_flags = AF_UNSPEC; - hints.ai_socktype = SOCK_DGRAM; - - rv = getaddrinfo(host, port, &hints, &res); - if (rv != 0) { - fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); - return -1; - } - - for (rp = res; rp; rp = rp->ai_next) { - fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); - if (fd == -1) { - continue; - } - - break; - } - - if (fd == -1) { - goto end; - } - - *paddrlen = rp->ai_addrlen; - memcpy(addr, rp->ai_addr, rp->ai_addrlen); - -end: - freeaddrinfo(res); - - return fd; -} - -static int connect_sock(struct sockaddr *local_addr, socklen_t *plocal_addrlen, - int fd, const struct sockaddr *remote_addr, - size_t remote_addrlen) { - socklen_t len; - - if (connect(fd, remote_addr, (socklen_t)remote_addrlen) != 0) { - fprintf(stderr, "connect: %s\n", strerror(errno)); - return -1; - } - - len = *plocal_addrlen; - - if (getsockname(fd, local_addr, &len) == -1) { - fprintf(stderr, "getsockname: %s\n", strerror(errno)); - return -1; - } - - *plocal_addrlen = len; - - return 0; -} - -struct client { - ngtcp2_crypto_conn_ref conn_ref; - int fd; - struct sockaddr_storage local_addr; - socklen_t local_addrlen; - SSL_CTX *ssl_ctx; - SSL *ssl; - ngtcp2_conn *conn; - - struct { - int64_t stream_id; - const uint8_t *data; - size_t datalen; - size_t nwrite; - } stream; - - ngtcp2_ccerr last_error; - - ev_io rev; - ev_timer timer; -}; - -static int numeric_host_family(const char *hostname, int family) { - uint8_t dst[sizeof(struct in6_addr)]; - return inet_pton(family, hostname, dst) == 1; -} - -static int numeric_host(const char *hostname) { - return numeric_host_family(hostname, AF_INET) || - numeric_host_family(hostname, AF_INET6); -} - -static int client_ssl_init(struct client *c) { - c->ssl_ctx = SSL_CTX_new(TLS_client_method()); - if (!c->ssl_ctx) { - fprintf(stderr, "SSL_CTX_new: %s\n", - ERR_error_string(ERR_get_error(), NULL)); - return -1; - } - - if (ngtcp2_crypto_boringssl_configure_client_context(c->ssl_ctx) != 0) { - fprintf(stderr, "ngtcp2_crypto_quictls_configure_client_context failed\n"); - return -1; - } - - c->ssl = SSL_new(c->ssl_ctx); - if (!c->ssl) { - fprintf(stderr, "SSL_new: %s\n", ERR_error_string(ERR_get_error(), NULL)); - return -1; - } - - SSL_set_app_data(c->ssl, &c->conn_ref); - SSL_set_connect_state(c->ssl); - SSL_set_alpn_protos(c->ssl, (const unsigned char *)ALPN, sizeof(ALPN) - 1); - if (!numeric_host(REMOTE_HOST)) { - SSL_set_tlsext_host_name(c->ssl, REMOTE_HOST); - } - - return 0; -} - -static void rand_cb(uint8_t *dest, size_t destlen, - const ngtcp2_rand_ctx *rand_ctx) { - size_t i; - (void)rand_ctx; - - for (i = 0; i < destlen; ++i) { - *dest = (uint8_t)random(); - } -} - -static int get_new_connection_id_cb(ngtcp2_conn *conn, ngtcp2_cid *cid, - uint8_t *token, size_t cidlen, - void *user_data) { - (void)conn; - (void)user_data; - - if (RAND_bytes(cid->data, (int)cidlen) != 1) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - cid->datalen = cidlen; - - if (RAND_bytes(token, NGTCP2_STATELESS_RESET_TOKENLEN) != 1) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} - -static int extend_max_local_streams_bidi(ngtcp2_conn *conn, - uint64_t max_streams, - void *user_data) { -#ifdef MESSAGE - struct client *c = static_cast(user_data); - int rv; - int64_t stream_id; - (void)max_streams; - - if (c->stream.stream_id != -1) { - return 0; - } - - rv = ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); - if (rv != 0) { - return 0; - } - - c->stream.stream_id = stream_id; - c->stream.data = (const uint8_t *)MESSAGE; - c->stream.datalen = sizeof(MESSAGE) - 1; - - return 0; -#else /* !defined(MESSAGE) */ - (void)conn; - (void)max_streams; - (void)user_data; - - return 0; -#endif /* !defined(MESSAGE) */ -} - -static void log_printf(void *user_data, const char *fmt, ...) { - va_list ap; - (void)user_data; - - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - - fprintf(stderr, "\n"); -} - -static int client_quic_init(struct client *c, - const struct sockaddr *remote_addr, - socklen_t remote_addrlen, - const struct sockaddr *local_addr, - socklen_t local_addrlen) { - ngtcp2_path path = { - { - (struct sockaddr *)local_addr, - local_addrlen, - }, - { - (struct sockaddr *)remote_addr, - remote_addrlen, - }, - NULL, - }; - ngtcp2_callbacks callbacks = { - ngtcp2_crypto_client_initial_cb, - NULL, /* recv_client_initial */ - ngtcp2_crypto_recv_crypto_data_cb, - NULL, /* handshake_completed */ - NULL, /* recv_version_negotiation */ - ngtcp2_crypto_encrypt_cb, - ngtcp2_crypto_decrypt_cb, - ngtcp2_crypto_hp_mask_cb, - NULL, /* recv_stream_data */ - NULL, /* acked_stream_data_offset */ - NULL, /* stream_open */ - NULL, /* stream_close */ - NULL, /* recv_stateless_reset */ - ngtcp2_crypto_recv_retry_cb, - extend_max_local_streams_bidi, - NULL, /* extend_max_local_streams_uni */ - rand_cb, - get_new_connection_id_cb, - NULL, /* remove_connection_id */ - ngtcp2_crypto_update_key_cb, - NULL, /* path_validation */ - NULL, /* select_preferred_address */ - NULL, /* stream_reset */ - NULL, /* extend_max_remote_streams_bidi */ - NULL, /* extend_max_remote_streams_uni */ - NULL, /* extend_max_stream_data */ - NULL, /* dcid_status */ - NULL, /* handshake_confirmed */ - NULL, /* recv_new_token */ - ngtcp2_crypto_delete_crypto_aead_ctx_cb, - ngtcp2_crypto_delete_crypto_cipher_ctx_cb, - NULL, /* recv_datagram */ - NULL, /* ack_datagram */ - NULL, /* lost_datagram */ - ngtcp2_crypto_get_path_challenge_data_cb, - NULL, /* stream_stop_sending */ - ngtcp2_crypto_version_negotiation_cb, - NULL, /* recv_rx_key */ - NULL, /* recv_tx_key */ - NULL, /* early_data_rejected */ - }; - ngtcp2_cid dcid, scid; - ngtcp2_settings settings; - ngtcp2_transport_params params; - int rv; - - dcid.datalen = NGTCP2_MIN_INITIAL_DCIDLEN; - if (RAND_bytes(dcid.data, (int)dcid.datalen) != 1) { - fprintf(stderr, "RAND_bytes failed\n"); - return -1; - } - - scid.datalen = 8; - if (RAND_bytes(scid.data, (int)scid.datalen) != 1) { - fprintf(stderr, "RAND_bytes failed\n"); - return -1; - } - - ngtcp2_settings_default(&settings); - - settings.initial_ts = timestamp(); - settings.log_printf = log_printf; - - ngtcp2_transport_params_default(¶ms); - - params.initial_max_streams_uni = 3; - params.initial_max_stream_data_bidi_local = 128 * 1024; - params.initial_max_data = 1024 * 1024; - - rv = - ngtcp2_conn_client_new(&c->conn, &dcid, &scid, &path, NGTCP2_PROTO_VER_V1, - &callbacks, &settings, ¶ms, NULL, c); - if (rv != 0) { - fprintf(stderr, "ngtcp2_conn_client_new: %s\n", ngtcp2_strerror(rv)); - return -1; - } - - ngtcp2_conn_set_tls_native_handle(c->conn, c->ssl); - - return 0; -} - -static int client_read(struct client *c) { - uint8_t buf[65536]; - struct sockaddr_storage addr; - struct iovec iov = {buf, sizeof(buf)}; - struct msghdr msg = {0}; - ssize_t nread; - ngtcp2_path path; - ngtcp2_pkt_info pi = {0}; - int rv; - - msg.msg_name = &addr; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - for (;;) { - msg.msg_namelen = sizeof(addr); - - nread = recvmsg(c->fd, &msg, MSG_DONTWAIT); - - if (nread == -1) { - if (errno != EAGAIN && errno != EWOULDBLOCK) { - fprintf(stderr, "recvmsg: %s\n", strerror(errno)); - } - - break; - } - - path.local.addrlen = c->local_addrlen; - path.local.addr = (struct sockaddr *)&c->local_addr; - path.remote.addrlen = msg.msg_namelen; - path.remote.addr = static_cast(msg.msg_name); - - rv = ngtcp2_conn_read_pkt(c->conn, &path, &pi, buf, (size_t)nread, - timestamp()); - if (rv != 0) { - fprintf(stderr, "ngtcp2_conn_read_pkt: %s\n", ngtcp2_strerror(rv)); - if (!c->last_error.error_code) { - if (rv == NGTCP2_ERR_CRYPTO) { - ngtcp2_ccerr_set_tls_alert( - &c->last_error, ngtcp2_conn_get_tls_alert(c->conn), NULL, 0); - } else { - ngtcp2_ccerr_set_liberr(&c->last_error, rv, NULL, 0); - } - } - return -1; - } - } - - return 0; -} - -static int client_send_packet(struct client *c, const uint8_t *data, - size_t datalen) { - struct iovec iov = {(uint8_t *)data, datalen}; - struct msghdr msg = {0}; - ssize_t nwrite; - - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - do { - nwrite = sendmsg(c->fd, &msg, 0); - } while (nwrite == -1 && errno == EINTR); - - if (nwrite == -1) { - fprintf(stderr, "sendmsg: %s\n", strerror(errno)); - - return -1; - } - - return 0; -} - -static size_t client_get_message(struct client *c, int64_t *pstream_id, - int *pfin, ngtcp2_vec *datav, - size_t datavcnt) { - if (datavcnt == 0) { - return 0; - } - - if (c->stream.stream_id != -1 && c->stream.nwrite < c->stream.datalen) { - *pstream_id = c->stream.stream_id; - *pfin = 1; - datav->base = (uint8_t *)c->stream.data + c->stream.nwrite; - datav->len = c->stream.datalen - c->stream.nwrite; - return 1; - } - - *pstream_id = -1; - *pfin = 0; - datav->base = NULL; - datav->len = 0; - - return 0; -} - -static int client_write_streams(struct client *c) { - ngtcp2_tstamp ts = timestamp(); - ngtcp2_pkt_info pi; - ngtcp2_ssize nwrite; - uint8_t buf[1452]; - ngtcp2_path_storage ps; - ngtcp2_vec datav; - size_t datavcnt; - int64_t stream_id; - ngtcp2_ssize wdatalen; - uint32_t flags; - int fin; - - ngtcp2_path_storage_zero(&ps); - - for (;;) { - datavcnt = client_get_message(c, &stream_id, &fin, &datav, 1); - - flags = NGTCP2_WRITE_STREAM_FLAG_MORE; - if (fin) { - flags |= NGTCP2_WRITE_STREAM_FLAG_FIN; - } - - nwrite = ngtcp2_conn_writev_stream(c->conn, &ps.path, &pi, buf, sizeof(buf), - &wdatalen, flags, stream_id, &datav, - datavcnt, ts); - if (nwrite < 0) { - switch (nwrite) { - case NGTCP2_ERR_WRITE_MORE: - c->stream.nwrite += (size_t)wdatalen; - continue; - default: - fprintf(stderr, "ngtcp2_conn_writev_stream: %s\n", - ngtcp2_strerror((int)nwrite)); - ngtcp2_ccerr_set_liberr(&c->last_error, (int)nwrite, NULL, 0); - return -1; - } - } - - if (nwrite == 0) { - return 0; - } - - if (wdatalen > 0) { - c->stream.nwrite += (size_t)wdatalen; - } - - if (client_send_packet(c, buf, (size_t)nwrite) != 0) { - break; - } - } - - return 0; -} - -static int client_write(struct client *c) { - ngtcp2_tstamp expiry, now; - ev_tstamp t; - - if (client_write_streams(c) != 0) { - return -1; - } - - expiry = ngtcp2_conn_get_expiry(c->conn); - now = timestamp(); - - t = expiry < now ? 1e-9 : (ev_tstamp)(expiry - now) / NGTCP2_SECONDS; - - c->timer.repeat = t; - ev_timer_again(EV_DEFAULT, &c->timer); - - return 0; -} - -static int client_handle_expiry(struct client *c) { - int rv = ngtcp2_conn_handle_expiry(c->conn, timestamp()); - if (rv != 0) { - fprintf(stderr, "ngtcp2_conn_handle_expiry: %s\n", ngtcp2_strerror(rv)); - return -1; - } - - return 0; -} - -static void client_close(struct client *c) { - ngtcp2_ssize nwrite; - ngtcp2_pkt_info pi; - ngtcp2_path_storage ps; - uint8_t buf[1280]; - - if (ngtcp2_conn_in_closing_period(c->conn) || - ngtcp2_conn_in_draining_period(c->conn)) { - goto fin; - } - - ngtcp2_path_storage_zero(&ps); - - nwrite = ngtcp2_conn_write_connection_close( - c->conn, &ps.path, &pi, buf, sizeof(buf), &c->last_error, timestamp()); - if (nwrite < 0) { - fprintf(stderr, "ngtcp2_conn_write_connection_close: %s\n", - ngtcp2_strerror((int)nwrite)); - goto fin; - } - - client_send_packet(c, buf, (size_t)nwrite); - -fin: - ev_break(EV_DEFAULT, EVBREAK_ALL); -} - -static void read_cb(struct ev_loop *loop, ev_io *w, int revents) { - struct client *c = static_cast(w->data); - (void)loop; - (void)revents; - - if (client_read(c) != 0) { - client_close(c); - return; - } - - if (client_write(c) != 0) { - client_close(c); - } -} - -static void timer_cb(struct ev_loop *loop, ev_timer *w, int revents) { - struct client *c = static_cast(w->data); - (void)loop; - (void)revents; - - if (client_handle_expiry(c) != 0) { - client_close(c); - return; - } - - if (client_write(c) != 0) { - client_close(c); - } -} - -static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) { - struct client *c = static_cast(conn_ref->user_data); - return c->conn; -} - -static int client_init(struct client *c) { - struct sockaddr_storage remote_addr, local_addr; - socklen_t remote_addrlen, local_addrlen = sizeof(local_addr); - - memset(c, 0, sizeof(*c)); - - ngtcp2_ccerr_default(&c->last_error); - - c->fd = create_sock((struct sockaddr *)&remote_addr, &remote_addrlen, - REMOTE_HOST, REMOTE_PORT); - if (c->fd == -1) { - return -1; - } - - if (connect_sock((struct sockaddr *)&local_addr, &local_addrlen, c->fd, - (struct sockaddr *)&remote_addr, remote_addrlen) != 0) { - return -1; - } - - memcpy(&c->local_addr, &local_addr, sizeof(c->local_addr)); - c->local_addrlen = local_addrlen; - - if (client_ssl_init(c) != 0) { - return -1; - } - - if (client_quic_init(c, (struct sockaddr *)&remote_addr, remote_addrlen, - (struct sockaddr *)&local_addr, local_addrlen) != 0) { - return -1; - } - - c->stream.stream_id = -1; - - c->conn_ref.get_conn = get_conn; - c->conn_ref.user_data = c; - - ev_io_init(&c->rev, read_cb, c->fd, EV_READ); - c->rev.data = c; - ev_io_start(EV_DEFAULT, &c->rev); - - ev_timer_init(&c->timer, timer_cb, 0., 0.); - c->timer.data = c; - - return 0; -} - -static void client_free(struct client *c) { - ngtcp2_conn_del(c->conn); - SSL_free(c->ssl); - SSL_CTX_free(c->ssl_ctx); -} - -int main(void) { - struct client c; - - srandom((unsigned int)timestamp()); - - if (client_init(&c) != 0) { - exit(EXIT_FAILURE); - } - - if (client_write(&c) != 0) { - exit(EXIT_FAILURE); - } - - ev_run(EV_DEFAULT, 0); - - client_free(&c); - - return 0; -} \ No newline at end of file diff --git a/src/ngtcp2/CMakeLists.txt b/src/ngtcp2/CMakeLists.txt deleted file mode 100644 index b923e3c..0000000 --- a/src/ngtcp2/CMakeLists.txt +++ /dev/null @@ -1,55 +0,0 @@ -# NGTCP2, NGHTTP3, NGTCP2_CRYPTO_OSSL are declared at parent scope in ../CMakeLists.txt -# so they can be linked into the main anyhttp library as well. -pkg_check_modules(NGTCP2_CRYPTO_BORINGSSL REQUIRED IMPORTED_TARGET libngtcp2_crypto_boringssl) - -# -# Create a static library from supplementary ngtcp2/example code. -# The following definitions apply to both the lib and the example programs below. -# -# add_compile_definitions(ENABLE_EXAMPLE_BORINGSSL WITH_EXAMPLE_BORINGSSL) -add_compile_definitions(ENABLE_EXAMPLE_OSSL WITH_EXAMPLE_OSSL) -add_compile_definitions(HAVE_NETINET_UDP_H) -add_compile_definitions(HAVE_NETINET_IN_H) -add_compile_definitions(HAVE_ARPA_INET_H) -add_compile_definitions(UDP_GRO) -# include_directories("." "/opt/boringssl/include") -# link_directories("/opt/boringssl/lib") - -add_library(ngtcp2_common STATIC) -target_sources(ngtcp2_common PRIVATE - debug.cc - util.cc - shared.cc - tls_shared_boringssl.cc - tls_session_base_ossl.cc - util_openssl.cc - siphash.cc -) -target_link_libraries(ngtcp2_common PRIVATE - PkgConfig::NGTCP2 -) - -# -# Create example client and server binaries. The following link_libraries() directives apply only -# to those. -# -link_libraries(ngtcp2_common) -link_libraries(PkgConfig::NGHTTP3 PkgConfig::NGTCP2 PkgConfig::NGTCP2_CRYPTO_OSSL) -link_libraries(liburlparse.a ssl crypto ev nghttp3) - -add_executable(ngtcp-client) -target_sources(ngtcp-client PRIVATE - client.cc - client_base.cc - tls_client_context_ossl.cc - tls_client_session_ossl.cc -) - -add_executable(ngtcp-server) -target_sources(ngtcp-server PRIVATE - server.cc - server_base.cc - http.cc - tls_server_context_ossl.cc - tls_server_session_ossl.cc -) diff --git a/src/ngtcp2/README.md b/src/ngtcp2/README.md index 70d8110..040c0b8 100644 --- a/src/ngtcp2/README.md +++ b/src/ngtcp2/README.md @@ -1,10 +1,12 @@ -## Server -```bash -build/src/ngtcp2/ngtcp-server ::1 8080 pki/out/server-key.pem pki/out/server.pem -``` +# ngtcp2 example helpers -## Client -```bash -export SSLKEYLOGFILE=secrets -build/src/ngtcp2/ngtcp-client ::1 8080 'https://[::1]:8080/README.md -``` +A few small helpers extracted from the [ngtcp2](https://github.com/ngtcp2/ngtcp2) examples +(`examples/`, v1.25.0), reduced to what the anyhttp library actually uses: + +* `network.h` -- `sockaddr_union` and `Address` +* `util.{h,cc}` -- `format_hex()`, `timestamp()`, `straddr()` +* `shared.{h,cc}` -- `msghdr_get_local_addr()`, `set_port()` + +The full example client and server used to be vendored here as `ngtcp-client`/`ngtcp-server`. +They are gone; the devcontainer image builds the upstream examples instead and installs them +as `/usr/local/bin/osslclient` and `/usr/local/bin/osslserver` -- see [HTTP3.md](../../HTTP3.md). diff --git a/src/ngtcp2/client.cc b/src/ngtcp2/client.cc deleted file mode 100644 index 90e0aec..0000000 --- a/src/ngtcp2/client.cc +++ /dev/null @@ -1,3414 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2017 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "client.h" -#include "network.h" -#include "debug.h" -#include "util.h" -#include "shared.h" - -using namespace ngtcp2; -using namespace std::literals; - -namespace { -auto randgen = util::make_mt19937(); -} // namespace - -namespace { -constexpr size_t max_preferred_versionslen = 4; -} // namespace - -Config config{}; - -Stream::Stream(const Request &req, int64_t stream_id) - : req(req), stream_id(stream_id), fd(-1) {} - -Stream::~Stream() { - if (fd != -1) { - close(fd); - } -} - -int Stream::open_file(const std::string_view &path) { - assert(fd == -1); - - std::string_view filename; - - auto it = std::ranges::find(std::rbegin(path), std::rend(path), '/').base(); - if (it == std::ranges::end(path)) { - filename = "index.html"sv; - } else { - filename = std::string_view{it, std::ranges::end(path)}; - if (filename == ".."sv || filename == "."sv) { - std::cerr << "Invalid file name: " << filename << std::endl; - return -1; - } - } - - auto fname = std::string{config.download}; - fname += '/'; - fname += filename; - - fd = open(fname.c_str(), O_WRONLY | O_CREAT | O_TRUNC, - S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - if (fd == -1) { - std::cerr << "open: Could not open file " << fname << ": " - << strerror(errno) << std::endl; - return -1; - } - - return 0; -} - -namespace { -void writecb(struct ev_loop *loop, ev_io *w, int revents) { - auto c = static_cast(w->data); - - c->on_write(); -} -} // namespace - -namespace { -void readcb(struct ev_loop *loop, ev_io *w, int revents) { - auto ep = static_cast(w->data); - auto c = ep->client; - - if (c->on_read(*ep) != 0) { - return; - } - - c->on_write(); -} -} // namespace - -namespace { -void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { - int rv; - auto c = static_cast(w->data); - - rv = c->handle_expiry(); - if (rv != 0) { - return; - } - - c->on_write(); -} -} // namespace - -namespace { -void change_local_addrcb(struct ev_loop *loop, ev_timer *w, int revents) { - auto c = static_cast(w->data); - - c->change_local_addr(); -} -} // namespace - -namespace { -void key_updatecb(struct ev_loop *loop, ev_timer *w, int revents) { - auto c = static_cast(w->data); - - if (c->initiate_key_update() != 0) { - c->disconnect(); - } -} -} // namespace - -namespace { -void delay_streamcb(struct ev_loop *loop, ev_timer *w, int revents) { - auto c = static_cast(w->data); - - ev_timer_stop(loop, w); - c->on_extend_max_streams(); - c->on_write(); -} -} // namespace - -namespace { -void siginthandler(struct ev_loop *loop, ev_signal *w, int revents) { - ev_break(loop, EVBREAK_ALL); -} -} // namespace - -Client::Client(struct ev_loop *loop, uint32_t client_chosen_version, - uint32_t original_version) - : remote_addr_{}, - loop_(loop), - httpconn_(nullptr), - addr_(nullptr), - port_(nullptr), - nstreams_done_(0), - nstreams_closed_(0), - nkey_update_(0), - client_chosen_version_(client_chosen_version), - original_version_(original_version), - early_data_(false), - handshake_confirmed_(false), - no_gso_{ -#ifdef UDP_SEGMENT - config.no_gso -#else // !defined(UDP_SEGMENT) - true -#endif // !defined(UDP_SEGMENT) - }, - tx_{} { - ev_io_init(&wev_, writecb, 0, EV_WRITE); - wev_.data = this; - ev_timer_init(&timer_, timeoutcb, 0., 0.); - timer_.data = this; - ev_timer_init(&change_local_addr_timer_, change_local_addrcb, - static_cast(config.change_local_addr) / NGTCP2_SECONDS, - 0.); - change_local_addr_timer_.data = this; - ev_timer_init(&key_update_timer_, key_updatecb, - static_cast(config.key_update) / NGTCP2_SECONDS, 0.); - key_update_timer_.data = this; - ev_timer_init(&delay_stream_timer_, delay_streamcb, - static_cast(config.delay_stream) / NGTCP2_SECONDS, 0.); - delay_stream_timer_.data = this; - ev_signal_init(&sigintev_, siginthandler, SIGINT); -} - -Client::~Client() { - disconnect(); - - if (httpconn_) { - nghttp3_conn_del(httpconn_); - httpconn_ = nullptr; - } -} - -void Client::disconnect() { - tx_.send_blocked = false; - - handle_error(); - - config.tx_loss_prob = 0; - - ev_timer_stop(loop_, &delay_stream_timer_); - ev_timer_stop(loop_, &key_update_timer_); - ev_timer_stop(loop_, &change_local_addr_timer_); - ev_timer_stop(loop_, &timer_); - - ev_io_stop(loop_, &wev_); - - for (auto &ep : endpoints_) { - ev_io_stop(loop_, &ep.rev); - close(ep.fd); - } - - endpoints_.clear(); - - ev_signal_stop(loop_, &sigintev_); -} - -namespace { -int recv_crypto_data(ngtcp2_conn *conn, - ngtcp2_encryption_level encryption_level, uint64_t offset, - const uint8_t *data, size_t datalen, void *user_data) { - if (!config.quiet && !config.no_quic_dump) { - debug::print_crypto_data(encryption_level, {data, datalen}); - } - - return ngtcp2_crypto_recv_crypto_data_cb(conn, encryption_level, offset, data, - datalen, user_data); -} -} // namespace - -namespace { -int recv_stream_data(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id, - uint64_t offset, const uint8_t *data, size_t datalen, - void *user_data, void *stream_user_data) { - if (!config.quiet && !config.no_quic_dump) { - debug::print_stream_data(stream_id, {data, datalen}); - } - - auto c = static_cast(user_data); - - if (c->recv_stream_data(flags, stream_id, {data, datalen}) != 0) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} -} // namespace - -namespace { -int acked_stream_data_offset(ngtcp2_conn *conn, int64_t stream_id, - uint64_t offset, uint64_t datalen, void *user_data, - void *stream_user_data) { - auto c = static_cast(user_data); - if (c->acked_stream_data_offset(stream_id, datalen) != 0) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - return 0; -} -} // namespace - -namespace { -int handshake_completed(ngtcp2_conn *conn, void *user_data) { - auto c = static_cast(user_data); - - if (!config.quiet) { - debug::handshake_completed(conn, user_data); - } - - if (c->handshake_completed() != 0) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} -} // namespace - -int Client::handshake_completed() { - if (early_data_ && !tls_session_.get_early_data_accepted()) { - if (!config.quiet) { - std::cerr << "Early data was rejected by server" << std::endl; - } - - // Some TLS backends only report early data rejection after - // handshake completion (e.g., OpenSSL). For TLS backends which - // report it early (e.g., BoringSSL and PicoTLS), the following - // functions are noop. - if (auto rv = ngtcp2_conn_tls_early_data_rejected(conn_); rv != 0) { - std::cerr << "ngtcp2_conn_tls_early_data_rejected: " - << ngtcp2_strerror(rv) << std::endl; - return -1; - } - - if (setup_httpconn() != 0) { - return -1; - } - } - - if (!config.quiet) { - std::cerr << "Negotiated cipher suite is " << tls_session_.get_cipher_name() - << std::endl; - if (auto group = tls_session_.get_negotiated_group(); !group.empty()) { - std::cerr << "Negotiated group is " << group << std::endl; - } - std::cerr << "Negotiated ALPN is " << tls_session_.get_selected_alpn() - << std::endl; - - if (!config.ech_config_list.empty() && tls_session_.get_ech_accepted()) { - std::cerr << "ECH was accepted" << std::endl; - } - } - - if (config.tp_file) { - std::array data; - auto datalen = - ngtcp2_conn_encode_0rtt_transport_params(conn_, data.data(), data.size()); - if (datalen < 0) { - std::cerr << "Could not encode 0-RTT transport parameters: " - << ngtcp2_strerror(static_cast(datalen)) << std::endl; - } else if (util::write_transport_params( - config.tp_file, {data.data(), static_cast(datalen)}) != - 0) { - std::cerr << "Could not write transport parameters in " << config.tp_file - << std::endl; - } - } - - return 0; -} - -namespace { -int handshake_confirmed(ngtcp2_conn *conn, void *user_data) { - auto c = static_cast(user_data); - - if (!config.quiet) { - debug::handshake_confirmed(conn, user_data); - } - - if (c->handshake_confirmed() != 0) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} -} // namespace - -bool Client::should_exit() const { - return handshake_confirmed_ && - (!config.wait_for_ticket || ticket_received_) && - ((config.exit_on_first_stream_close && - (config.nstreams == 0 || nstreams_closed_)) || - (config.exit_on_all_streams_close && - config.nstreams == nstreams_done_ && - nstreams_closed_ == nstreams_done_)); -} - -int Client::handshake_confirmed() { - handshake_confirmed_ = true; - - if (config.change_local_addr) { - start_change_local_addr_timer(); - } - if (config.key_update) { - start_key_update_timer(); - } - if (config.delay_stream) { - start_delay_stream_timer(); - } - - return 0; -} - -namespace { -int recv_version_negotiation(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd, - const uint32_t *sv, size_t nsv, void *user_data) { - auto c = static_cast(user_data); - - c->recv_version_negotiation(sv, nsv); - - return 0; -} -} // namespace - -void Client::recv_version_negotiation(const uint32_t *sv, size_t nsv) { - offered_versions_.resize(nsv); - std::ranges::copy_n(sv, as_signed(nsv), - std::ranges::begin(offered_versions_)); -} - -namespace { -int stream_close(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id, - uint64_t app_error_code, void *user_data, - void *stream_user_data) { - auto c = static_cast(user_data); - - if (!(flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)) { - app_error_code = NGHTTP3_H3_NO_ERROR; - } - - if (c->on_stream_close(stream_id, app_error_code) != 0) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} -} // namespace - -namespace { -int stream_reset(ngtcp2_conn *conn, int64_t stream_id, uint64_t final_size, - uint64_t app_error_code, void *user_data, - void *stream_user_data) { - auto c = static_cast(user_data); - - if (c->on_stream_reset(stream_id) != 0) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} -} // namespace - -namespace { -int stream_stop_sending(ngtcp2_conn *conn, int64_t stream_id, - uint64_t app_error_code, void *user_data, - void *stream_user_data) { - auto c = static_cast(user_data); - - if (c->on_stream_stop_sending(stream_id) != 0) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} -} // namespace - -namespace { -int extend_max_local_streams_bidi(ngtcp2_conn *conn, uint64_t max_streams, - void *user_data) { - auto c = static_cast(user_data); - - if (c->on_extend_max_streams() != 0) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} -} // namespace - -namespace { -void rand_bytes(uint8_t *dest, size_t destlen) { - auto rv = util::generate_secure_random({dest, destlen}); - if (rv != 0) { - assert(0); - abort(); - } -} -} // namespace - -namespace { -void rand(uint8_t *dest, size_t destlen, const ngtcp2_rand_ctx *rand_ctx) { - rand_bytes(dest, destlen); -} -} // namespace - -namespace { -int get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, uint8_t *token, - size_t cidlen, void *user_data) { - if (util::generate_secure_random({cid->data, cidlen}) != 0) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - cid->datalen = cidlen; - if (ngtcp2_crypto_generate_stateless_reset_token( - token, config.static_secret.data(), config.static_secret.size(), cid) != - 0) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} -} // namespace - -namespace { -int do_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp, - const ngtcp2_crypto_cipher_ctx *hp_ctx, const uint8_t *sample) { - if (ngtcp2_crypto_hp_mask(dest, hp, hp_ctx, sample) != 0) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - if (!config.quiet && config.show_secret) { - debug::print_hp_mask({dest, NGTCP2_HP_MASKLEN}, - {sample, NGTCP2_HP_SAMPLELEN}); - } - - return 0; -} -} // namespace - -namespace { -int update_key(ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret, - ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv, - ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv, - const uint8_t *current_rx_secret, - const uint8_t *current_tx_secret, size_t secretlen, - void *user_data) { - auto c = static_cast(user_data); - - if (c->update_key(rx_secret, tx_secret, rx_aead_ctx, rx_iv, tx_aead_ctx, - tx_iv, current_rx_secret, current_tx_secret, - secretlen) != 0) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} -} // namespace - -namespace { -int path_validation(ngtcp2_conn *conn, uint32_t flags, const ngtcp2_path *path, - const ngtcp2_path *old_path, - ngtcp2_path_validation_result res, void *user_data) { - if (!config.quiet) { - debug::path_validation(path, res); - } - - if (flags & NGTCP2_PATH_VALIDATION_FLAG_PREFERRED_ADDR) { - auto c = static_cast(user_data); - - c->set_remote_addr(path->remote); - } - - return 0; -} -} // namespace - -void Client::set_remote_addr(const ngtcp2_addr &remote_addr) { - memcpy(&remote_addr_.su, remote_addr.addr, remote_addr.addrlen); - remote_addr_.len = remote_addr.addrlen; -} - -namespace { -int select_preferred_address(ngtcp2_conn *conn, ngtcp2_path *dest, - const ngtcp2_preferred_addr *paddr, - void *user_data) { - auto c = static_cast(user_data); - Address remote_addr; - - if (config.no_preferred_addr) { - return 0; - } - - if (c->select_preferred_address(remote_addr, paddr) != 0) { - return 0; - } - - auto ep = c->endpoint_for(remote_addr); - if (!ep) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - ngtcp2_addr_copy_byte(&dest->local, &(*ep)->addr.su.sa, (*ep)->addr.len); - ngtcp2_addr_copy_byte(&dest->remote, &remote_addr.su.sa, remote_addr.len); - dest->user_data = *ep; - - return 0; -} -} // namespace - -namespace { -int extend_max_stream_data(ngtcp2_conn *conn, int64_t stream_id, - uint64_t max_data, void *user_data, - void *stream_user_data) { - auto c = static_cast(user_data); - if (c->extend_max_stream_data(stream_id, max_data) != 0) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - return 0; -} -} // namespace - -int Client::extend_max_stream_data(int64_t stream_id, uint64_t max_data) { - if (auto rv = nghttp3_conn_unblock_stream(httpconn_, stream_id); rv != 0) { - std::cerr << "nghttp3_conn_unblock_stream: " << nghttp3_strerror(rv) - << std::endl; - return -1; - } - return 0; -} - -namespace { -int recv_new_token(ngtcp2_conn *conn, const uint8_t *token, size_t tokenlen, - void *user_data) { - if (config.token_file.empty()) { - return 0; - } - - util::write_token(config.token_file, {token, tokenlen}); - - return 0; -} -} // namespace - -namespace { -int recv_rx_key(ngtcp2_conn *conn, ngtcp2_encryption_level level, - void *user_data) { - if (level != NGTCP2_ENCRYPTION_LEVEL_1RTT) { - return 0; - } - - auto c = static_cast(user_data); - if (c->setup_httpconn() != 0) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} -} // namespace - -namespace { -int early_data_rejected(ngtcp2_conn *conn, void *user_data) { - auto c = static_cast(user_data); - - c->early_data_rejected(); - - return 0; -} -} // namespace - -void Client::early_data_rejected() { - nghttp3_conn_del(httpconn_); - httpconn_ = nullptr; - - nstreams_done_ = 0; - streams_.clear(); -} - -int Client::init(int fd, const Address &local_addr, const Address &remote_addr, - const char *addr, const char *port, - TLSClientContext &tls_ctx) { - endpoints_.reserve(4); - - endpoints_.emplace_back(); - auto &ep = endpoints_.back(); - ep.addr = local_addr; - ep.client = this; - ep.fd = fd; - ev_io_init(&ep.rev, readcb, fd, EV_READ); - ep.rev.data = &ep; - - remote_addr_ = remote_addr; - addr_ = addr; - port_ = port; - - auto callbacks = ngtcp2_callbacks{ - .client_initial = ngtcp2_crypto_client_initial_cb, - .recv_crypto_data = ::recv_crypto_data, - .handshake_completed = ::handshake_completed, - .recv_version_negotiation = ::recv_version_negotiation, - .encrypt = ngtcp2_crypto_encrypt_cb, - .decrypt = ngtcp2_crypto_decrypt_cb, - .hp_mask = do_hp_mask, - .recv_stream_data = ::recv_stream_data, - .acked_stream_data_offset = ::acked_stream_data_offset, - .stream_close = stream_close, - .recv_retry = ngtcp2_crypto_recv_retry_cb, - .extend_max_local_streams_bidi = extend_max_local_streams_bidi, - .rand = rand, - .get_new_connection_id = get_new_connection_id, - .update_key = ::update_key, - .path_validation = path_validation, - .select_preferred_addr = ::select_preferred_address, - .stream_reset = stream_reset, - .extend_max_stream_data = ::extend_max_stream_data, - .handshake_confirmed = ::handshake_confirmed, - .recv_new_token = ::recv_new_token, - .delete_crypto_aead_ctx = ngtcp2_crypto_delete_crypto_aead_ctx_cb, - .delete_crypto_cipher_ctx = ngtcp2_crypto_delete_crypto_cipher_ctx_cb, - .get_path_challenge_data = ngtcp2_crypto_get_path_challenge_data_cb, - .stream_stop_sending = stream_stop_sending, - .version_negotiation = ngtcp2_crypto_version_negotiation_cb, - .recv_rx_key = ::recv_rx_key, - .tls_early_data_rejected = ::early_data_rejected, - }; - - ngtcp2_cid scid, dcid; - if (config.scid_present) { - scid = config.scid; - } else { - scid.datalen = 17; - if (util::generate_secure_random({scid.data, scid.datalen}) != 0) { - std::cerr << "Could not generate source connection ID" << std::endl; - return -1; - } - } - if (config.dcid.datalen == 0) { - dcid.datalen = 18; - if (util::generate_secure_random({dcid.data, dcid.datalen}) != 0) { - std::cerr << "Could not generate destination connection ID" << std::endl; - return -1; - } - } else { - dcid = config.dcid; - } - - ngtcp2_settings settings; - ngtcp2_settings_default(&settings); - settings.log_printf = config.quiet ? nullptr : debug::log_printf; - if (!config.qlog_file.empty() || !config.qlog_dir.empty()) { - std::string path; - if (!config.qlog_file.empty()) { - path = config.qlog_file; - } else { - path = std::string{config.qlog_dir}; - path += '/'; - path += util::format_hex(scid.data, as_signed(scid.datalen)); - path += ".sqlog"; - } - qlog_ = fopen(path.c_str(), "w"); - if (qlog_ == nullptr) { - std::cerr << "Could not open qlog file " << std::quoted(path) << ": " - << strerror(errno) << std::endl; - return -1; - } - settings.qlog_write = qlog_write_cb; - } - - settings.cc_algo = config.cc_algo; - settings.initial_ts = util::timestamp(); - settings.initial_rtt = config.initial_rtt; - settings.max_window = config.max_window; - settings.max_stream_window = config.max_stream_window; - if (config.max_udp_payload_size) { - settings.max_tx_udp_payload_size = config.max_udp_payload_size; - settings.no_tx_udp_payload_size_shaping = 1; - } - settings.handshake_timeout = config.handshake_timeout; - settings.no_pmtud = config.no_pmtud; - settings.ack_thresh = config.ack_thresh; - if (config.initial_pkt_num == UINT32_MAX) { - auto dis = std::uniform_int_distribution(0, INT32_MAX); - settings.initial_pkt_num = dis(randgen); - } else { - settings.initial_pkt_num = config.initial_pkt_num; - } - - std::vector token; - - if (!config.token_file.empty()) { - std::cerr << "Reading token file " << config.token_file << std::endl; - - auto t = util::read_token(config.token_file); - if (t) { - token = std::move(*t); - settings.token = token.data(); - settings.tokenlen = token.size(); - } - } - - if (!config.available_versions.empty()) { - settings.available_versions = config.available_versions.data(); - settings.available_versionslen = config.available_versions.size(); - } - - if (!config.preferred_versions.empty()) { - settings.preferred_versions = config.preferred_versions.data(); - settings.preferred_versionslen = config.preferred_versions.size(); - } - - settings.original_version = original_version_; - - if (!config.pmtud_probes.empty()) { - settings.pmtud_probes = config.pmtud_probes.data(); - settings.pmtud_probeslen = config.pmtud_probes.size(); - - if (!config.max_udp_payload_size) { - settings.max_tx_udp_payload_size = - *std::ranges::max_element(config.pmtud_probes); - } - } - - ngtcp2_transport_params params; - ngtcp2_transport_params_default(¶ms); - params.initial_max_stream_data_bidi_local = config.max_stream_data_bidi_local; - params.initial_max_stream_data_bidi_remote = - config.max_stream_data_bidi_remote; - params.initial_max_stream_data_uni = config.max_stream_data_uni; - params.initial_max_data = config.max_data; - params.initial_max_streams_bidi = config.max_streams_bidi; - params.initial_max_streams_uni = config.max_streams_uni; - params.max_idle_timeout = config.timeout; - params.active_connection_id_limit = 7; - params.grease_quic_bit = 1; - - auto path = ngtcp2_path{ - .local = - { - .addr = const_cast(&ep.addr.su.sa), - .addrlen = ep.addr.len, - }, - .remote = - { - .addr = const_cast(&remote_addr.su.sa), - .addrlen = remote_addr.len, - }, - .user_data = &ep, - }; - auto rv = - ngtcp2_conn_client_new(&conn_, &dcid, &scid, &path, client_chosen_version_, - &callbacks, &settings, ¶ms, nullptr, this); - - if (rv != 0) { - std::cerr << "ngtcp2_conn_client_new: " << ngtcp2_strerror(rv) << std::endl; - return -1; - } - - if (tls_session_.init(early_data_, tls_ctx, addr_, this, - client_chosen_version_, AppProtocol::H3) != 0) { - return -1; - } - - ngtcp2_conn_set_tls_native_handle(conn_, tls_session_.get_native_handle()); - - if (early_data_ && config.tp_file) { - auto params = util::read_transport_params(config.tp_file); - if (!params) { - early_data_ = false; - } else { - auto rv = ngtcp2_conn_decode_and_set_0rtt_transport_params( - conn_, params->data(), params->size()); - if (rv != 0) { - std::cerr << "ngtcp2_conn_decode_and_set_0rtt_transport_params: " - << ngtcp2_strerror(rv) << std::endl; - early_data_ = false; - } else if (make_stream_early() != 0) { - return -1; - } - } - } - - ev_io_start(loop_, &ep.rev); - - ev_signal_start(loop_, &sigintev_); - - return 0; -} - -int Client::feed_data(const Endpoint &ep, const sockaddr *sa, socklen_t salen, - const ngtcp2_pkt_info *pi, - std::span data) { - auto path = ngtcp2_path{ - .local = - { - .addr = const_cast(&ep.addr.su.sa), - .addrlen = ep.addr.len, - }, - .remote = - { - .addr = const_cast(sa), - .addrlen = salen, - }, - .user_data = const_cast(&ep), - }; - if (auto rv = ngtcp2_conn_read_pkt(conn_, &path, pi, data.data(), data.size(), - util::timestamp()); - rv != 0) { - std::cerr << "ngtcp2_conn_read_pkt: " << ngtcp2_strerror(rv) << std::endl; - if (!last_error_.error_code) { - if (rv == NGTCP2_ERR_CRYPTO) { - auto alert = ngtcp2_conn_get_tls_alert(conn_); - ngtcp2_ccerr_set_tls_alert(&last_error_, alert, nullptr, 0); - - if (alert == TLS_ALERT_ECH_REQUIRED && config.ech_config_list_file && - tls_session_.write_ech_config_list(config.ech_config_list_file) != - 0) { - std::cerr << "Could not write ECH retry configs in " - << config.ech_config_list_file << std::endl; - } - } else { - ngtcp2_ccerr_set_liberr(&last_error_, rv, nullptr, 0); - } - } - disconnect(); - return -1; - } - return 0; -} - -int Client::on_read(const Endpoint &ep) { - std::array buf; - sockaddr_union su; - size_t pktcnt = 0; - ngtcp2_pkt_info pi; - - iovec msg_iov{ - .iov_base = buf.data(), - .iov_len = buf.size(), - }; - - uint8_t msg_ctrl[CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(int))]; - - msghdr msg{ - .msg_name = &su, - .msg_iov = &msg_iov, - .msg_iovlen = 1, - .msg_control = msg_ctrl, - }; - - auto start = util::timestamp(); - - for (; pktcnt < MAX_RECV_PKTS;) { - if (util::recv_pkt_time_threshold_exceeded( - config.cc_algo == NGTCP2_CC_ALGO_BBR, start, pktcnt)) { - break; - } - - msg.msg_namelen = sizeof(su); - msg.msg_controllen = sizeof(msg_ctrl); - - auto nread = recvmsg(ep.fd, &msg, 0); - - if (nread == -1) { - if (errno != EAGAIN && errno != EWOULDBLOCK) { - std::cerr << "recvmsg: " << strerror(errno) << std::endl; - } - break; - } - - // Packets less than 21 bytes never be a valid QUIC packet. - if (nread < 21) { - ++pktcnt; - - continue; - } - - pi.ecn = msghdr_get_ecn(&msg, su.storage.ss_family); - auto gso_size = msghdr_get_udp_gro(&msg); - if (gso_size == 0) { - gso_size = static_cast(nread); - } - - auto data = std::span{buf.data(), static_cast(nread)}; - - for (;;) { - auto datalen = std::min(data.size(), gso_size); - - ++pktcnt; - - if (!config.quiet) { - std::cerr << "Received packet: local=" - << util::straddr(&ep.addr.su.sa, ep.addr.len) - << " remote=" << util::straddr(&su.sa, msg.msg_namelen) - << " ecn=0x" << std::hex << static_cast(pi.ecn) - << std::dec << " " << datalen << " bytes" << std::endl; - } - - // Packets less than 21 bytes never be a valid QUIC packet. - if (datalen < 21) { - break; - } - - if (debug::packet_lost(config.rx_loss_prob)) { - if (!config.quiet) { - std::cerr << "** Simulated incoming packet loss **" << std::endl; - } - } else if (feed_data(ep, &su.sa, msg.msg_namelen, &pi, - {data.data(), datalen}) != 0) { - return -1; - } - - data = data.subspan(datalen); - - if (data.empty()) { - break; - } - } - } - - if (should_exit()) { - ngtcp2_ccerr_set_application_error( - &last_error_, nghttp3_err_infer_quic_app_error_code(0), nullptr, 0); - disconnect(); - return -1; - } - - update_timer(); - - return 0; -} - -int Client::handle_expiry() { - auto now = util::timestamp(); - if (auto rv = ngtcp2_conn_handle_expiry(conn_, now); rv != 0) { - std::cerr << "ngtcp2_conn_handle_expiry: " << ngtcp2_strerror(rv) - << std::endl; - ngtcp2_ccerr_set_liberr(&last_error_, rv, nullptr, 0); - disconnect(); - return -1; - } - - return 0; -} - -int Client::on_write() { - if (tx_.send_blocked) { - if (auto rv = send_blocked_packet(); rv != 0) { - return rv; - } - - if (tx_.send_blocked) { - return 0; - } - } - - ev_io_stop(loop_, &wev_); - - if (auto rv = write_streams(); rv != 0) { - return rv; - } - - if (should_exit()) { - ngtcp2_ccerr_set_application_error( - &last_error_, nghttp3_err_infer_quic_app_error_code(0), nullptr, 0); - disconnect(); - return -1; - } - - update_timer(); - return 0; -} - -namespace { -ngtcp2_ssize write_pkt(ngtcp2_conn *conn, ngtcp2_path *path, - ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, - ngtcp2_tstamp ts, void *user_data) { - auto c = static_cast(user_data); - - return c->write_pkt(path, pi, dest, destlen, ts); -} -} // namespace - -ngtcp2_ssize Client::write_pkt(ngtcp2_path *path, ngtcp2_pkt_info *pi, - uint8_t *dest, size_t destlen, - ngtcp2_tstamp ts) { - std::array vec; - - for (;;) { - int64_t stream_id = -1; - int fin = 0; - nghttp3_ssize sveccnt = 0; - - if (httpconn_ && ngtcp2_conn_get_max_data_left(conn_)) { - sveccnt = nghttp3_conn_writev_stream(httpconn_, &stream_id, &fin, - vec.data(), vec.size()); - if (sveccnt < 0) { - std::cerr << "nghttp3_conn_writev_stream: " - << nghttp3_strerror(static_cast(sveccnt)) << std::endl; - ngtcp2_ccerr_set_application_error( - &last_error_, - nghttp3_err_infer_quic_app_error_code(static_cast(sveccnt)), - nullptr, 0); - return NGTCP2_ERR_CALLBACK_FAILURE; - } - } - - ngtcp2_ssize ndatalen; - auto v = vec.data(); - auto vcnt = static_cast(sveccnt); - - uint32_t flags = NGTCP2_WRITE_STREAM_FLAG_MORE; - if (fin) { - flags |= NGTCP2_WRITE_STREAM_FLAG_FIN; - } - - auto nwrite = ngtcp2_conn_writev_stream( - conn_, path, pi, dest, destlen, &ndatalen, flags, stream_id, - reinterpret_cast(v), vcnt, ts); - if (nwrite < 0) { - switch (nwrite) { - case NGTCP2_ERR_STREAM_DATA_BLOCKED: - assert(ndatalen == -1); - nghttp3_conn_block_stream(httpconn_, stream_id); - continue; - case NGTCP2_ERR_STREAM_SHUT_WR: - assert(ndatalen == -1); - nghttp3_conn_shutdown_stream_write(httpconn_, stream_id); - continue; - case NGTCP2_ERR_WRITE_MORE: - assert(ndatalen >= 0); - if (auto rv = nghttp3_conn_add_write_offset(httpconn_, stream_id, - as_unsigned(ndatalen)); - rv != 0) { - std::cerr << "nghttp3_conn_add_write_offset: " << nghttp3_strerror(rv) - << std::endl; - ngtcp2_ccerr_set_application_error( - &last_error_, nghttp3_err_infer_quic_app_error_code(rv), nullptr, - 0); - return NGTCP2_ERR_CALLBACK_FAILURE; - } - continue; - } - - assert(ndatalen == -1); - - std::cerr << "ngtcp2_conn_write_stream: " - << ngtcp2_strerror(static_cast(nwrite)) << std::endl; - ngtcp2_ccerr_set_liberr(&last_error_, static_cast(nwrite), nullptr, - 0); - - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - if (ndatalen >= 0) { - if (auto rv = nghttp3_conn_add_write_offset(httpconn_, stream_id, - as_unsigned(ndatalen)); - rv != 0) { - std::cerr << "nghttp3_conn_add_write_offset: " << nghttp3_strerror(rv) - << std::endl; - ngtcp2_ccerr_set_application_error( - &last_error_, nghttp3_err_infer_quic_app_error_code(rv), nullptr, 0); - - return NGTCP2_ERR_CALLBACK_FAILURE; - } - } - - return nwrite; - } -} - -int Client::write_streams() { - ngtcp2_path_storage ps; - ngtcp2_pkt_info pi; - size_t gso_size; - auto ts = util::timestamp(); - auto txbuf = std::span{tx_.data}; - auto buflen = util::clamp_buffer_size(conn_, txbuf.size(), config.gso_burst); - - ngtcp2_path_storage_zero(&ps); - - auto nwrite = ngtcp2_conn_write_aggregate_pkt2( - conn_, &ps.path, &pi, txbuf.data(), buflen, &gso_size, ::write_pkt, - config.gso_burst, ts); - if (nwrite < 0) { - disconnect(); - return -1; - } - - ngtcp2_conn_update_pkt_tx_time(conn_, ts); - - if (nwrite == 0) { - return 0; - } - - send_packet_or_blocked(ps.path, pi.ecn, - txbuf.first(static_cast(nwrite)), gso_size); - - return 0; -} - -int Client::send_packet_or_blocked(const ngtcp2_path &path, unsigned int ecn, - std::span data, - size_t gso_size) { - auto &ep = *static_cast(path.user_data); - - auto [rest, rv] = send_packet(ep, path.remote, ecn, data, gso_size); - if (rv != 0) { - assert(NETWORK_ERR_SEND_BLOCKED == rv); - - on_send_blocked(path, ecn, rest, gso_size); - - return rv; - } - - return 0; -} - -void Client::update_timer() { - auto expiry = ngtcp2_conn_get_expiry(conn_); - auto now = util::timestamp(); - - if (expiry <= now) { - if (!config.quiet) { - auto t = static_cast(now - expiry) / NGTCP2_SECONDS; - std::cerr << "Timer has already expired: " << std::fixed << t << "s" - << std::defaultfloat << std::endl; - } - - ev_feed_event(loop_, &timer_, EV_TIMER); - - return; - } - - auto t = static_cast(expiry - now) / NGTCP2_SECONDS; - if (!config.quiet) { - std::cerr << "Set timer=" << std::fixed << t << "s" << std::defaultfloat - << std::endl; - } - timer_.repeat = t; - ev_timer_again(loop_, &timer_); -} - -#ifdef HAVE_LINUX_RTNETLINK_H -namespace { -int bind_addr(Address &local_addr, int fd, const in_addr_union *iau, - int family) { - addrinfo hints{ - .ai_flags = AI_PASSIVE, - .ai_family = family, - .ai_socktype = SOCK_DGRAM, - }; - addrinfo *res, *rp; - char *node; - std::array nodebuf; - - if (iau) { - if (inet_ntop(family, iau, nodebuf.data(), nodebuf.size()) == nullptr) { - std::cerr << "inet_ntop: " << strerror(errno) << std::endl; - return -1; - } - - node = nodebuf.data(); - } else { - node = nullptr; - } - - if (auto rv = getaddrinfo(node, "0", &hints, &res); rv != 0) { - std::cerr << "getaddrinfo: " << gai_strerror(rv) << std::endl; - return -1; - } - - auto res_d = defer(freeaddrinfo, res); - - for (rp = res; rp; rp = rp->ai_next) { - if (bind(fd, rp->ai_addr, rp->ai_addrlen) != -1) { - break; - } - } - - if (!rp) { - std::cerr << "Could not bind" << std::endl; - return -1; - } - - socklen_t len = sizeof(local_addr.su.storage); - if (getsockname(fd, &local_addr.su.sa, &len) == -1) { - std::cerr << "getsockname: " << strerror(errno) << std::endl; - return -1; - } - local_addr.len = len; - local_addr.ifindex = 0; - - return 0; -} -} // namespace -#endif // defined(HAVE_LINUX_RTNETLINK_H) - -#ifndef HAVE_LINUX_RTNETLINK_H -namespace { -int connect_sock(Address &local_addr, int fd, const Address &remote_addr) { - if (connect(fd, &remote_addr.su.sa, remote_addr.len) != 0) { - std::cerr << "connect: " << strerror(errno) << std::endl; - return -1; - } - - socklen_t len = sizeof(local_addr.su.storage); - if (getsockname(fd, &local_addr.su.sa, &len) == -1) { - std::cerr << "getsockname: " << strerror(errno) << std::endl; - return -1; - } - local_addr.len = len; - local_addr.ifindex = 0; - - return 0; -} -} // namespace -#endif // !defined(HAVE_LINUX_RTNETLINK_H) - -namespace { -int udp_sock(int family) { - auto fd = util::create_nonblock_socket(family, SOCK_DGRAM, IPPROTO_UDP); - if (fd == -1) { - return -1; - } - - fd_set_recv_ecn(fd, family); - fd_set_ip_mtu_discover(fd, family); - fd_set_ip_dontfrag(fd, family); - fd_set_udp_gro(fd); - - return fd; -} -} // namespace - -namespace { -int create_sock(Address &remote_addr, const char *addr, const char *port) { - addrinfo hints{ - .ai_family = AF_UNSPEC, - .ai_socktype = SOCK_DGRAM, - }; - addrinfo *res, *rp; - - if (auto rv = getaddrinfo(addr, port, &hints, &res); rv != 0) { - std::cerr << "getaddrinfo: " << gai_strerror(rv) << std::endl; - return -1; - } - - auto res_d = defer(freeaddrinfo, res); - - int fd = -1; - - for (rp = res; rp; rp = rp->ai_next) { - fd = udp_sock(rp->ai_family); - if (fd == -1) { - continue; - } - - break; - } - - if (!rp) { - std::cerr << "Could not create socket" << std::endl; - return -1; - } - - remote_addr.len = rp->ai_addrlen; - memcpy(&remote_addr.su, rp->ai_addr, rp->ai_addrlen); - remote_addr.ifindex = 0; - - return fd; -} -} // namespace - -std::optional Client::endpoint_for(const Address &remote_addr) { -#ifdef HAVE_LINUX_RTNETLINK_H - in_addr_union iau; - - if (get_local_addr(iau, remote_addr) != 0) { - std::cerr << "Could not get local address for a selected preferred address" - << std::endl; - return nullptr; - } - - auto current_path = ngtcp2_conn_get_path(conn_); - auto current_ep = static_cast(current_path->user_data); - if (addreq(¤t_ep->addr.su.sa, iau)) { - return current_ep; - } -#endif // defined(HAVE_LINUX_RTNETLINK_H) - - auto fd = udp_sock(remote_addr.su.sa.sa_family); - if (fd == -1) { - return nullptr; - } - - Address local_addr; - -#ifdef HAVE_LINUX_RTNETLINK_H - if (bind_addr(local_addr, fd, &iau, remote_addr.su.sa.sa_family) != 0) { - close(fd); - return nullptr; - } -#else // !defined(HAVE_LINUX_RTNETLINK_H) - if (connect_sock(local_addr, fd, remote_addr) != 0) { - close(fd); - return nullptr; - } -#endif // !defined(HAVE_LINUX_RTNETLINK_H) - - endpoints_.emplace_back(); - auto &ep = endpoints_.back(); - ep.addr = local_addr; - ep.client = this; - ep.fd = fd; - ev_io_init(&ep.rev, readcb, fd, EV_READ); - ep.rev.data = &ep; - - ev_io_start(loop_, &ep.rev); - - return &ep; -} - -void Client::start_change_local_addr_timer() { - ev_timer_start(loop_, &change_local_addr_timer_); -} - -int Client::change_local_addr() { - Address local_addr; - - if (!config.quiet) { - std::cerr << "Changing local address" << std::endl; - } - - auto nfd = udp_sock(remote_addr_.su.sa.sa_family); - if (nfd == -1) { - return -1; - } - -#ifdef HAVE_LINUX_RTNETLINK_H - in_addr_union iau; - - if (get_local_addr(iau, remote_addr_) != 0) { - std::cerr << "Could not get local address" << std::endl; - close(nfd); - return -1; - } - - if (bind_addr(local_addr, nfd, &iau, remote_addr_.su.sa.sa_family) != 0) { - close(nfd); - return -1; - } -#else // !defined(HAVE_LINUX_RTNETLINK_H) - if (connect_sock(local_addr, nfd, remote_addr_) != 0) { - close(nfd); - return -1; - } -#endif // !defined(HAVE_LINUX_RTNETLINK_H) - - if (!config.quiet) { - std::cerr << "Local address is now " - << util::straddr(&local_addr.su.sa, local_addr.len) << std::endl; - } - - endpoints_.emplace_back(); - auto &ep = endpoints_.back(); - ep.addr = local_addr; - ep.client = this; - ep.fd = nfd; - ev_io_init(&ep.rev, readcb, nfd, EV_READ); - ep.rev.data = &ep; - - ngtcp2_addr addr; - ngtcp2_addr_init(&addr, &local_addr.su.sa, local_addr.len); - - if (config.nat_rebinding) { - ngtcp2_conn_set_local_addr(conn_, &addr); - ngtcp2_conn_set_path_user_data(conn_, &ep); - } else { - auto path = ngtcp2_path{ - .local = addr, - .remote = - { - .addr = const_cast(&remote_addr_.su.sa), - .addrlen = remote_addr_.len, - }, - .user_data = &ep, - }; - if (auto rv = ngtcp2_conn_initiate_immediate_migration(conn_, &path, - util::timestamp()); - rv != 0) { - std::cerr << "ngtcp2_conn_initiate_immediate_migration: " - << ngtcp2_strerror(rv) << std::endl; - } - } - - ev_io_start(loop_, &ep.rev); - - return 0; -} - -void Client::start_key_update_timer() { - ev_timer_start(loop_, &key_update_timer_); -} - -int Client::update_key(uint8_t *rx_secret, uint8_t *tx_secret, - ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv, - ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv, - const uint8_t *current_rx_secret, - const uint8_t *current_tx_secret, size_t secretlen) { - if (!config.quiet) { - std::cerr << "Updating traffic key" << std::endl; - } - - auto crypto_ctx = ngtcp2_conn_get_crypto_ctx(conn_); - auto aead = &crypto_ctx->aead; - auto keylen = ngtcp2_crypto_aead_keylen(aead); - auto ivlen = ngtcp2_crypto_packet_protection_ivlen(aead); - - ++nkey_update_; - - std::array rx_key, tx_key; - - if (ngtcp2_crypto_update_key(conn_, rx_secret, tx_secret, rx_aead_ctx, - rx_key.data(), rx_iv, tx_aead_ctx, tx_key.data(), - tx_iv, current_rx_secret, current_tx_secret, - secretlen) != 0) { - return -1; - } - - if (!config.quiet && config.show_secret) { - std::cerr << "application_traffic rx secret " << nkey_update_ << std::endl; - debug::print_secrets({rx_secret, secretlen}, {rx_key.data(), keylen}, - {rx_iv, ivlen}); - std::cerr << "application_traffic tx secret " << nkey_update_ << std::endl; - debug::print_secrets({tx_secret, secretlen}, {tx_key.data(), keylen}, - {tx_iv, ivlen}); - } - - return 0; -} - -int Client::initiate_key_update() { - if (!config.quiet) { - std::cerr << "Initiate key update" << std::endl; - } - - if (auto rv = ngtcp2_conn_initiate_key_update(conn_, util::timestamp()); - rv != 0) { - std::cerr << "ngtcp2_conn_initiate_key_update: " << ngtcp2_strerror(rv) - << std::endl; - return -1; - } - - return 0; -} - -void Client::start_delay_stream_timer() { - ev_timer_start(loop_, &delay_stream_timer_); -} - -int Client::send_packet(const Endpoint &ep, const ngtcp2_addr &remote_addr, - unsigned int ecn, std::span data) { - auto [_, rv] = send_packet(ep, remote_addr, ecn, data, data.size()); - - return rv; -} - -std::pair, int> -Client::send_packet(const Endpoint &ep, const ngtcp2_addr &remote_addr, - unsigned int ecn, std::span data, - size_t gso_size) { - assert(gso_size); - - if (debug::packet_lost(config.tx_loss_prob)) { - if (!config.quiet) { - std::cerr << "** Simulated outgoing packet loss **" << std::endl; - } - return {{}, NETWORK_ERR_OK}; - } - - if (no_gso_ && data.size() > gso_size) { - for (; !data.empty();) { - auto len = std::min(gso_size, data.size()); - - auto [_, rv] = send_packet(ep, remote_addr, ecn, data.first(len), len); - if (rv != 0) { - return {data, rv}; - } - - data = data.subspan(len); - } - - return {{}, 0}; - } - - iovec msg_iov{ - .iov_base = const_cast(data.data()), - .iov_len = data.size(), - }; - - uint8_t msg_ctrl[CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(uint16_t))]{}; - - msghdr msg{ -#ifdef HAVE_LINUX_RTNETLINK_H - .msg_name = const_cast(remote_addr.addr), - .msg_namelen = remote_addr.addrlen, -#endif // defined(HAVE_LINUX_RTNETLINK_H) - .msg_iov = &msg_iov, - .msg_iovlen = 1, - .msg_control = msg_ctrl, - .msg_controllen = sizeof(msg_ctrl), - }; - - size_t controllen = 0; - - auto cm = CMSG_FIRSTHDR(&msg); - controllen += CMSG_SPACE(sizeof(int)); - cm->cmsg_len = CMSG_LEN(sizeof(int)); - memcpy(CMSG_DATA(cm), &ecn, sizeof(ecn)); - - switch (remote_addr.addr->sa_family) { - case AF_INET: - cm->cmsg_level = IPPROTO_IP; - cm->cmsg_type = IP_TOS; - - break; - case AF_INET6: - cm->cmsg_level = IPPROTO_IPV6; - cm->cmsg_type = IPV6_TCLASS; - - break; - default: - assert(0); - } - -#ifdef UDP_SEGMENT - if (data.size() > gso_size) { - controllen += CMSG_SPACE(sizeof(uint16_t)); - cm = CMSG_NXTHDR(&msg, cm); - cm->cmsg_level = SOL_UDP; - cm->cmsg_type = UDP_SEGMENT; - cm->cmsg_len = CMSG_LEN(sizeof(uint16_t)); - auto n = static_cast(gso_size); - memcpy(CMSG_DATA(cm), &n, sizeof(n)); - } -#endif // defined(UDP_SEGMENT) - - msg.msg_controllen = -#ifndef __APPLE__ - controllen -#else // defined(__APPLE__) - static_cast(controllen) -#endif // defined(__APPLE__) - ; - - ssize_t nwrite = 0; - - do { - nwrite = sendmsg(ep.fd, &msg, 0); - } while (nwrite == -1 && errno == EINTR); - - if (nwrite == -1) { - switch (errno) { - case EAGAIN: -#if EAGAIN != EWOULDBLOCK - case EWOULDBLOCK: -#endif // EAGAIN != EWOULDBLOCK - return {data, NETWORK_ERR_SEND_BLOCKED}; -#ifdef UDP_SEGMENT - case EIO: - if (data.size() > gso_size) { - // GSO failure; send each packet in a separate sendmsg call. - std::cerr << "sendmsg: disabling GSO due to " << strerror(errno) - << std::endl; - - no_gso_ = true; - - return send_packet(ep, remote_addr, ecn, data, gso_size); - } - break; -#endif // defined(UDP_SEGMENT) - } - - std::cerr << "sendmsg: " << strerror(errno) << std::endl; - - // TODO We have packet which is expected to fail to send (e.g., - // path validation to old path). - return {{}, NETWORK_ERR_OK}; - } - - assert(static_cast(nwrite) == data.size()); - - if (!config.quiet) { - std::cerr << "Sent packet: local=" - << util::straddr(&ep.addr.su.sa, ep.addr.len) << " remote=" - << util::straddr(remote_addr.addr, remote_addr.addrlen) - << " ecn=0x" << std::hex << ecn << std::dec << " " << nwrite - << " bytes" << std::endl; - } - - return {{}, NETWORK_ERR_OK}; -} - -void Client::on_send_blocked(const ngtcp2_path &path, unsigned int ecn, - std::span data, size_t gso_size) { - assert(!tx_.send_blocked); - assert(gso_size); - - tx_.send_blocked = true; - - auto &p = tx_.blocked; - - memcpy(&p.remote_addr.su, path.remote.addr, path.remote.addrlen); - - auto &ep = *static_cast(path.user_data); - - p.remote_addr.len = path.remote.addrlen; - p.endpoint = &ep; - p.ecn = ecn; - p.data = data; - p.gso_size = gso_size; - - start_wev_endpoint(ep); -} - -void Client::start_wev_endpoint(const Endpoint &ep) { - // We do not close ep.fd, so we can expect that each Endpoint has - // unique fd. - if (ep.fd != wev_.fd) { - if (ev_is_active(&wev_)) { - ev_io_stop(loop_, &wev_); - } - - ev_io_set(&wev_, ep.fd, EV_WRITE); - } - - ev_io_start(loop_, &wev_); -} - -int Client::send_blocked_packet() { - assert(tx_.send_blocked); - - auto &p = tx_.blocked; - - ngtcp2_addr remote_addr{ - .addr = &p.remote_addr.su.sa, - .addrlen = p.remote_addr.len, - }; - - auto [rest, rv] = - send_packet(*p.endpoint, remote_addr, p.ecn, p.data, p.gso_size); - if (rv != 0) { - assert(NETWORK_ERR_SEND_BLOCKED == rv); - - p.data = rest; - - start_wev_endpoint(*p.endpoint); - - return 0; - } - - tx_.send_blocked = false; - - return 0; -} - -int Client::handle_error() { - if (!conn_ || ngtcp2_conn_in_closing_period(conn_) || - ngtcp2_conn_in_draining_period(conn_)) { - return 0; - } - - std::array buf; - - ngtcp2_path_storage ps; - - ngtcp2_path_storage_zero(&ps); - - ngtcp2_pkt_info pi; - - auto nwrite = ngtcp2_conn_write_connection_close( - conn_, &ps.path, &pi, buf.data(), buf.size(), &last_error_, - util::timestamp()); - if (nwrite < 0) { - std::cerr << "ngtcp2_conn_write_connection_close: " - << ngtcp2_strerror(static_cast(nwrite)) << std::endl; - return -1; - } - - if (nwrite == 0) { - return 0; - } - - return send_packet(*static_cast(ps.path.user_data), - ps.path.remote, pi.ecn, - {buf.data(), static_cast(nwrite)}); -} - -int Client::on_stream_close(int64_t stream_id, uint64_t app_error_code) { - if (httpconn_) { - if (app_error_code == 0) { - app_error_code = NGHTTP3_H3_NO_ERROR; - } - auto rv = nghttp3_conn_close_stream(httpconn_, stream_id, app_error_code); - switch (rv) { - case 0: - http_stream_close(stream_id, app_error_code); - break; - case NGHTTP3_ERR_STREAM_NOT_FOUND: - // We have to handle the case when stream opened but no data is - // transferred. In this case, nghttp3_conn_close_stream might - // return error. - if (!ngtcp2_is_bidi_stream(stream_id)) { - assert(!ngtcp2_conn_is_local_stream(conn_, stream_id)); - ngtcp2_conn_extend_max_streams_uni(conn_, 1); - } - break; - default: - std::cerr << "nghttp3_conn_close_stream: " << nghttp3_strerror(rv) - << std::endl; - ngtcp2_ccerr_set_application_error( - &last_error_, nghttp3_err_infer_quic_app_error_code(rv), nullptr, 0); - return -1; - } - } - - return 0; -} - -int Client::on_stream_reset(int64_t stream_id) { - if (httpconn_) { - if (auto rv = nghttp3_conn_shutdown_stream_read(httpconn_, stream_id); - rv != 0) { - std::cerr << "nghttp3_conn_shutdown_stream_read: " << nghttp3_strerror(rv) - << std::endl; - return -1; - } - } - return 0; -} - -int Client::on_stream_stop_sending(int64_t stream_id) { - if (!httpconn_) { - return 0; - } - - if (auto rv = nghttp3_conn_shutdown_stream_read(httpconn_, stream_id); - rv != 0) { - std::cerr << "nghttp3_conn_shutdown_stream_read: " << nghttp3_strerror(rv) - << std::endl; - return -1; - } - - return 0; -} - -int Client::make_stream_early() { - if (setup_httpconn() != 0) { - return -1; - } - - return on_extend_max_streams(); -} - -int Client::on_extend_max_streams() { - int64_t stream_id; - - if ((config.delay_stream && !handshake_confirmed_) || - ev_is_active(&delay_stream_timer_)) { - return 0; - } - - for (; nstreams_done_ < config.nstreams; ++nstreams_done_) { - if (auto rv = ngtcp2_conn_open_bidi_stream(conn_, &stream_id, nullptr); - rv != 0) { - assert(NGTCP2_ERR_STREAM_ID_BLOCKED == rv); - break; - } - - auto stream = std::make_unique( - config.requests[nstreams_done_ % config.requests.size()], stream_id); - - if (submit_http_request(stream.get()) != 0) { - break; - } - - if (!config.download.empty()) { - stream->open_file(stream->req.path); - } - streams_.emplace(stream_id, std::move(stream)); - } - return 0; -} - -namespace { -nghttp3_ssize read_data(nghttp3_conn *conn, int64_t stream_id, nghttp3_vec *vec, - size_t veccnt, uint32_t *pflags, void *user_data, - void *stream_user_data) { - vec[0].base = config.data; - vec[0].len = config.datalen; - *pflags |= NGHTTP3_DATA_FLAG_EOF; - - return 1; -} -} // namespace - -int Client::submit_http_request(const Stream *stream) { - std::string content_length_str; - - const auto &req = stream->req; - - std::array nva{ - util::make_nv_nn(":method", config.http_method), - util::make_nv_nn(":scheme", req.scheme), - util::make_nv_nn(":authority", req.authority), - util::make_nv_nn(":path", req.path), - util::make_nv_nn("user-agent", "nghttp3/ngtcp2 client"), - }; - size_t nvlen = 5; - if (config.fd != -1) { - content_length_str = util::format_uint(config.datalen); - nva[nvlen++] = util::make_nv_nc("content-length", content_length_str); - } - - if (!config.quiet) { - debug::print_http_request_headers(stream->stream_id, nva.data(), nvlen); - } - - nghttp3_data_reader dr{ - .read_data = read_data, - }; - - if (auto rv = nghttp3_conn_submit_request( - httpconn_, stream->stream_id, nva.data(), nvlen, - config.fd == -1 ? nullptr : &dr, nullptr); - rv != 0) { - std::cerr << "nghttp3_conn_submit_request: " << nghttp3_strerror(rv) - << std::endl; - return -1; - } - - return 0; -} - -int Client::recv_stream_data(uint32_t flags, int64_t stream_id, - std::span data) { - auto nconsumed = nghttp3_conn_read_stream2( - httpconn_, stream_id, data.data(), data.size(), - flags & NGTCP2_STREAM_DATA_FLAG_FIN, ngtcp2_conn_get_timestamp(conn_)); - if (nconsumed < 0) { - std::cerr << "nghttp3_conn_read_stream2: " - << nghttp3_strerror(static_cast(nconsumed)) << std::endl; - ngtcp2_ccerr_set_application_error( - &last_error_, - nghttp3_err_infer_quic_app_error_code(static_cast(nconsumed)), - nullptr, 0); - return -1; - } - - ngtcp2_conn_extend_max_stream_offset(conn_, stream_id, - static_cast(nconsumed)); - ngtcp2_conn_extend_max_offset(conn_, static_cast(nconsumed)); - - return 0; -} - -int Client::acked_stream_data_offset(int64_t stream_id, uint64_t datalen) { - if (auto rv = nghttp3_conn_add_ack_offset(httpconn_, stream_id, datalen); - rv != 0) { - std::cerr << "nghttp3_conn_add_ack_offset: " << nghttp3_strerror(rv) - << std::endl; - return -1; - } - - return 0; -} - -int Client::select_preferred_address(Address &selected_addr, - const ngtcp2_preferred_addr *paddr) { - auto path = ngtcp2_conn_get_path(conn_); - - switch (path->local.addr->sa_family) { - case AF_INET: - if (!paddr->ipv4_present) { - return -1; - } - selected_addr.su.in = paddr->ipv4; - selected_addr.len = sizeof(paddr->ipv4); - break; - case AF_INET6: - if (!paddr->ipv6_present) { - return -1; - } - selected_addr.su.in6 = paddr->ipv6; - selected_addr.len = sizeof(paddr->ipv6); - break; - default: - return -1; - } - - if (!config.quiet) { - char host[NI_MAXHOST], service[NI_MAXSERV]; - if (auto rv = getnameinfo(&selected_addr.su.sa, selected_addr.len, host, - sizeof(host), service, sizeof(service), - NI_NUMERICHOST | NI_NUMERICSERV); - rv != 0) { - std::cerr << "getnameinfo: " << gai_strerror(rv) << std::endl; - return -1; - } - - std::cerr << "selected server preferred_address is [" << host - << "]:" << service << std::endl; - } - - return 0; -} - -namespace { -int http_recv_data(nghttp3_conn *conn, int64_t stream_id, const uint8_t *data, - size_t datalen, void *user_data, void *stream_user_data) { - if (!config.quiet && !config.no_http_dump) { - debug::print_http_data(stream_id, {data, datalen}); - } - auto c = static_cast(user_data); - c->http_consume(stream_id, datalen); - c->http_write_data(stream_id, {data, datalen}); - return 0; -} -} // namespace - -namespace { -int http_deferred_consume(nghttp3_conn *conn, int64_t stream_id, - size_t nconsumed, void *user_data, - void *stream_user_data) { - auto c = static_cast(user_data); - c->http_consume(stream_id, nconsumed); - return 0; -} -} // namespace - -void Client::http_consume(int64_t stream_id, size_t nconsumed) { - ngtcp2_conn_extend_max_stream_offset(conn_, stream_id, nconsumed); - ngtcp2_conn_extend_max_offset(conn_, nconsumed); -} - -void Client::http_write_data(int64_t stream_id, std::span data) { - auto it = streams_.find(stream_id); - if (it == std::ranges::end(streams_)) { - return; - } - - auto &stream = (*it).second; - - if (stream->fd == -1) { - return; - } - - ssize_t nwrite; - do { - nwrite = write(stream->fd, data.data(), data.size()); - } while (nwrite == -1 && errno == EINTR); -} - -namespace { -int http_begin_headers(nghttp3_conn *conn, int64_t stream_id, void *user_data, - void *stream_user_data) { - if (!config.quiet) { - debug::print_http_begin_response_headers(stream_id); - } - return 0; -} -} // namespace - -namespace { -int http_recv_header(nghttp3_conn *conn, int64_t stream_id, int32_t token, - nghttp3_rcbuf *name, nghttp3_rcbuf *value, uint8_t flags, - void *user_data, void *stream_user_data) { - if (!config.quiet) { - debug::print_http_header(stream_id, name, value, flags); - } - return 0; -} -} // namespace - -namespace { -int http_end_headers(nghttp3_conn *conn, int64_t stream_id, int fin, - void *user_data, void *stream_user_data) { - if (!config.quiet) { - debug::print_http_end_headers(stream_id); - } - return 0; -} -} // namespace - -namespace { -int http_begin_trailers(nghttp3_conn *conn, int64_t stream_id, void *user_data, - void *stream_user_data) { - if (!config.quiet) { - debug::print_http_begin_trailers(stream_id); - } - return 0; -} -} // namespace - -namespace { -int http_recv_trailer(nghttp3_conn *conn, int64_t stream_id, int32_t token, - nghttp3_rcbuf *name, nghttp3_rcbuf *value, uint8_t flags, - void *user_data, void *stream_user_data) { - if (!config.quiet) { - debug::print_http_header(stream_id, name, value, flags); - } - return 0; -} -} // namespace - -namespace { -int http_end_trailers(nghttp3_conn *conn, int64_t stream_id, int fin, - void *user_data, void *stream_user_data) { - if (!config.quiet) { - debug::print_http_end_trailers(stream_id); - } - return 0; -} -} // namespace - -namespace { -int http_stop_sending(nghttp3_conn *conn, int64_t stream_id, - uint64_t app_error_code, void *user_data, - void *stream_user_data) { - auto c = static_cast(user_data); - if (c->stop_sending(stream_id, app_error_code) != 0) { - return NGHTTP3_ERR_CALLBACK_FAILURE; - } - return 0; -} -} // namespace - -int Client::stop_sending(int64_t stream_id, uint64_t app_error_code) { - if (auto rv = - ngtcp2_conn_shutdown_stream_read(conn_, 0, stream_id, app_error_code); - rv != 0) { - std::cerr << "ngtcp2_conn_shutdown_stream_read: " << ngtcp2_strerror(rv) - << std::endl; - return -1; - } - return 0; -} - -namespace { -int http_reset_stream(nghttp3_conn *conn, int64_t stream_id, - uint64_t app_error_code, void *user_data, - void *stream_user_data) { - auto c = static_cast(user_data); - if (c->reset_stream(stream_id, app_error_code) != 0) { - return NGHTTP3_ERR_CALLBACK_FAILURE; - } - return 0; -} -} // namespace - -int Client::reset_stream(int64_t stream_id, uint64_t app_error_code) { - if (auto rv = - ngtcp2_conn_shutdown_stream_write(conn_, 0, stream_id, app_error_code); - rv != 0) { - std::cerr << "ngtcp2_conn_shutdown_stream_write: " << ngtcp2_strerror(rv) - << std::endl; - return -1; - } - return 0; -} - -void Client::http_stream_close(int64_t stream_id, uint64_t app_error_code) { - if (!ngtcp2_is_bidi_stream(stream_id)) { - return; - } - - assert(ngtcp2_conn_is_local_stream(conn_, stream_id)); - - ++nstreams_closed_; - - auto it = streams_.find(stream_id); - if (it == std::ranges::end(streams_)) { - return; - } - - if (!config.quiet) { - std::cerr << "HTTP stream " << stream_id << " closed with error code " - << app_error_code << std::endl; - } - - streams_.erase(it); -} - -namespace { -int http_recv_settings(nghttp3_conn *conn, const nghttp3_settings *settings, - void *conn_user_data) { - if (!config.quiet) { - debug::print_http_settings(settings); - } - - return 0; -} -} // namespace - -namespace { -int http_recv_origin(nghttp3_conn *conn, const uint8_t *origin, - size_t originlen, void *conn_user_data) { - if (!config.quiet) { - debug::print_http_origin(origin, originlen); - } - - return 0; -} -} // namespace - -namespace { -int http_end_origin(nghttp3_conn *conn, void *conn_user_data) { - if (!config.quiet) { - debug::print_http_end_origin(); - } - - return 0; -} -} // namespace - -int Client::setup_httpconn() { - if (httpconn_) { - return 0; - } - - if (ngtcp2_conn_get_streams_uni_left(conn_) < 3) { - std::cerr << "peer does not allow at least 3 unidirectional streams." - << std::endl; - return -1; - } - - nghttp3_callbacks callbacks{ - .recv_data = ::http_recv_data, - .deferred_consume = ::http_deferred_consume, - .begin_headers = ::http_begin_headers, - .recv_header = ::http_recv_header, - .end_headers = ::http_end_headers, - .begin_trailers = ::http_begin_trailers, - .recv_trailer = ::http_recv_trailer, - .end_trailers = ::http_end_trailers, - .stop_sending = ::http_stop_sending, - .reset_stream = ::http_reset_stream, - .recv_settings = ::http_recv_settings, - .recv_origin = ::http_recv_origin, - .end_origin = ::http_end_origin, - .rand = rand_bytes, - }; - nghttp3_settings settings; - nghttp3_settings_default(&settings); - settings.qpack_max_dtable_capacity = 4_k; - settings.qpack_blocked_streams = 100; - - auto mem = nghttp3_mem_default(); - - if (auto rv = - nghttp3_conn_client_new(&httpconn_, &callbacks, &settings, mem, this); - rv != 0) { - std::cerr << "nghttp3_conn_client_new: " << nghttp3_strerror(rv) - << std::endl; - return -1; - } - - int64_t ctrl_stream_id; - - if (auto rv = ngtcp2_conn_open_uni_stream(conn_, &ctrl_stream_id, nullptr); - rv != 0) { - std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv) - << std::endl; - return -1; - } - - if (auto rv = nghttp3_conn_bind_control_stream(httpconn_, ctrl_stream_id); - rv != 0) { - std::cerr << "nghttp3_conn_bind_control_stream: " << nghttp3_strerror(rv) - << std::endl; - return -1; - } - - if (!config.quiet) { - fprintf(stderr, "http: control stream=%" PRIx64 "\n", ctrl_stream_id); - } - - int64_t qpack_enc_stream_id, qpack_dec_stream_id; - - if (auto rv = - ngtcp2_conn_open_uni_stream(conn_, &qpack_enc_stream_id, nullptr); - rv != 0) { - std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv) - << std::endl; - return -1; - } - - if (auto rv = - ngtcp2_conn_open_uni_stream(conn_, &qpack_dec_stream_id, nullptr); - rv != 0) { - std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv) - << std::endl; - return -1; - } - - if (auto rv = nghttp3_conn_bind_qpack_streams(httpconn_, qpack_enc_stream_id, - qpack_dec_stream_id); - rv != 0) { - std::cerr << "nghttp3_conn_bind_qpack_streams: " << nghttp3_strerror(rv) - << std::endl; - return -1; - } - - if (!config.quiet) { - fprintf(stderr, - "http: QPACK streams encoder=%" PRIx64 " decoder=%" PRIx64 "\n", - qpack_enc_stream_id, qpack_dec_stream_id); - } - - return 0; -} - -const std::vector &Client::get_offered_versions() const { - return offered_versions_; -} - -bool Client::get_early_data() const { return early_data_; } - -namespace { -int run(Client &c, const char *addr, const char *port, - TLSClientContext &tls_ctx) { - Address remote_addr, local_addr; - - auto fd = create_sock(remote_addr, addr, port); - if (fd == -1) { - return -1; - } - -#ifdef HAVE_LINUX_RTNETLINK_H - in_addr_union iau; - - if (get_local_addr(iau, remote_addr) != 0) { - std::cerr << "Could not get local address" << std::endl; - close(fd); - return -1; - } - - if (bind_addr(local_addr, fd, &iau, remote_addr.su.sa.sa_family) != 0) { - close(fd); - return -1; - } -#else // !defined(HAVE_LINUX_RTNETLINK_H) - if (connect_sock(local_addr, fd, remote_addr) != 0) { - close(fd); - return -1; - } -#endif // !defined(HAVE_LINUX_RTNETLINK_H) - - if (c.init(fd, local_addr, remote_addr, addr, port, tls_ctx) != 0) { - return -1; - } - - // TODO Do we need this ? - if (auto rv = c.on_write(); rv != 0) { - return rv; - } - - ev_run(EV_DEFAULT, 0); - - return 0; -} -} // namespace - -namespace { -int parse_uri(Request &req, const std::string_view &uri) { - urlparse_url u; - - if (urlparse_parse_url(uri.data(), uri.size(), /* is_connect = */ 0, &u) != - 0) { - return -1; - } - - if (!(u.field_set & (1 << URLPARSE_SCHEMA)) || - !(u.field_set & (1 << URLPARSE_HOST))) { - return -1; - } - - req.scheme = util::get_string(uri, u, URLPARSE_SCHEMA); - - auto host = std::string(util::get_string(uri, u, URLPARSE_HOST)); - if (util::numeric_host(host.c_str(), AF_INET6)) { - req.authority = '['; - req.authority += host; - req.authority += ']'; - } else { - req.authority = std::move(host); - } - - if (u.field_set & (1 << URLPARSE_PORT)) { - req.authority += ':'; - req.authority += util::get_string(uri, u, URLPARSE_PORT); - } - - if (u.field_set & (1 << URLPARSE_PATH)) { - req.path = util::get_string(uri, u, URLPARSE_PATH); - } else { - req.path = "/"; - } - - if (u.field_set & (1 << URLPARSE_QUERY)) { - req.path += '?'; - req.path += util::get_string(uri, u, URLPARSE_QUERY); - } - - return 0; -} -} // namespace - -namespace { -int parse_requests(char **argv, size_t argvlen) { - for (size_t i = 0; i < argvlen; ++i) { - auto uri = std::string_view{argv[i]}; - Request req; - if (parse_uri(req, uri) != 0) { - std::cerr << "Could not parse URI: " << uri << std::endl; - return -1; - } - config.requests.emplace_back(std::move(req)); - } - return 0; -} -} // namespace - -std::ofstream keylog_file; - -namespace { -const char *prog = "client"; -} // namespace - -namespace { -void print_usage() { - std::cerr << "Usage: " << prog << " [OPTIONS] [...]" - << std::endl; -} -} // namespace - -namespace { -void config_set_default(Config &config) { - config = Config{ - .tx_loss_prob = 0., - .rx_loss_prob = 0., - .fd = -1, - .ciphers = util::crypto_default_ciphers(), - .groups = util::crypto_default_groups(), - .version = NGTCP2_PROTO_VER_V1, - .timeout = 30 * NGTCP2_SECONDS, - .http_method = "GET"sv, - .max_data = 24_m, - .max_stream_data_bidi_local = 16_m, - .max_stream_data_uni = 16_m, - .max_streams_uni = 100, - .cc_algo = NGTCP2_CC_ALGO_CUBIC, - .initial_rtt = NGTCP2_DEFAULT_INITIAL_RTT, - .handshake_timeout = UINT64_MAX, - .ack_thresh = 2, - .initial_pkt_num = UINT32_MAX, - }; -} -} // namespace - -namespace { -void print_help() { - print_usage(); - - config_set_default(config); - - std::cout << R"( - Remote server host (DNS name or IP address). In case of - DNS name, it will be sent in TLS SNI extension. - Remote server port - Remote URI -Options: - -t, --tx-loss=

- The probability of losing outgoing packets.

must be - [0.0, 1.0], inclusive. 0.0 means no packet loss. 1.0 - means 100% packet loss. - -r, --rx-loss=

- The probability of losing incoming packets.

must be - [0.0, 1.0], inclusive. 0.0 means no packet loss. 1.0 - means 100% packet loss. - -d, --data= - Read data from , and send them as STREAM data. - -n, --nstreams= - The number of requests. s are used in the order of - appearance in the command-line. If the number of - list is less than , list is wrapped. It - defaults to 0 which means the number of specified. - -v, --version= - Specify QUIC version to use in hex string. If the given - version is not supported by libngtcp2, client will use - QUIC v1 long packet types. Instead of specifying hex - string, there are special aliases available: "v1" - indicates QUIC v1, and "v2" indicates QUIC v2. - Default: )" - << std::hex << "0x" << config.version << std::dec << R"( - --preferred-versions=[[,]...] - Specify QUIC versions in hex string in the order of - preference. Client chooses one of those versions if - client received Version Negotiation packet from server. - These versions must be supported by libngtcp2. Instead - of specifying hex string, there are special aliases - available: "v1" indicates QUIC v1, and "v2" indicates - QUIC v2. - --available-versions=[[,]...] - Specify QUIC versions in hex string that are sent in - available_versions field of version_information - transport parameter. This list can include a version - which is not supported by libngtcp2. Instead of - specifying hex string, there are special aliases - available: "v1" indicates QUIC v1, and "v2" indicates - QUIC v2. - -q, --quiet Suppress debug output. - -s, --show-secret - Print out secrets unless --quiet is used. - --timeout= - Specify idle timeout. - Default: )" - << util::format_duration(config.timeout) << R"( - --ciphers= - Specify the cipher suite list to enable. - Default: )" - << config.ciphers << R"( - --groups= - Specify the supported groups. - Default: )" - << config.groups << R"( - --session-file= - Read/write TLS session from/to . To resume a - session, the previous session must be supplied with this - option. - --tp-file= - Read/write QUIC transport parameters from/to . To - send 0-RTT data, the transport parameters received from - the previous session must be supplied with this option. - --dcid= - Specify initial DCID. is hex string. After - decoded as binary, it should be at least 8 bytes and at - most 20 bytes long. - --scid= - Specify source connection ID. is hex string. If - an empty string is given, zero length connection ID is - assumed. - --change-local-addr= - Client changes local address when elapse - after handshake completes. - --nat-rebinding - When used with --change-local-addr, simulate NAT - rebinding. In other words, client changes local - address, but it does not start path validation. - --key-update= - Client initiates key update when elapse after - handshake completes. - -m, --http-method= - Specify HTTP method. Default: )" - << config.http_method << R"( - --delay-stream= - Delay sending STREAM data in 1-RTT for after - handshake completes. - --no-preferred-addr - Do not try to use preferred address offered by server. - --key= - The path to client private key PEM file. - --cert= - The path to client certificate PEM file. - --download= - The path to the directory to save a downloaded content. - It is undefined if 2 concurrent requests write to the - same file. If a request path does not contain a path - component usable as a file name, it defaults to - "index.html". - --no-quic-dump - Disables printing QUIC STREAM and CRYPTO frame data out. - --no-http-dump - Disables printing HTTP response body out. - --qlog-file= - The path to write qlog. This option and --qlog-dir are - mutually exclusive. - --qlog-dir= - Path to the directory where qlog file is stored. The - file name of each qlog is the Source Connection ID of - client. This option and --qlog-file are mutually - exclusive. - --max-data= - The initial connection-level flow control window. - Default: )" - << util::format_uint_iec(config.max_data) << R"( - --max-stream-data-bidi-local= - The initial stream-level flow control window for a - bidirectional stream that the local endpoint initiates. - Default: )" - << util::format_uint_iec(config.max_stream_data_bidi_local) << R"( - --max-stream-data-bidi-remote= - The initial stream-level flow control window for a - bidirectional stream that the remote endpoint initiates. - Default: )" - << util::format_uint_iec(config.max_stream_data_bidi_remote) << R"( - --max-stream-data-uni= - The initial stream-level flow control window for a - unidirectional stream. - Default: )" - << util::format_uint_iec(config.max_stream_data_uni) << R"( - --max-streams-bidi= - The number of the concurrent bidirectional streams that - the remote endpoint initiates. - Default: )" - << config.max_streams_bidi << R"( - --max-streams-uni= - The number of the concurrent unidirectional streams that - the remote endpoint initiates. - Default: )" - << config.max_streams_uni << R"( - --exit-on-first-stream-close - Exit when a first client initiated HTTP stream is - closed. - --exit-on-all-streams-close - Exit when all client initiated HTTP streams are closed. - --wait-for-ticket - Wait for a ticket to be received before exiting on - --exit-on-first-stream-close or - --exit-on-all-streams-close. --session-file must be - specified. - --disable-early-data - Disable early data. - --cc=(cubic|reno|bbr) - The name of congestion controller algorithm. - Default: )" - << util::strccalgo(config.cc_algo) << R"( - --token-file= - Read/write token from/to . Token is obtained from - NEW_TOKEN frame from server. - --sni= - Send in TLS SNI, overriding the DNS name - specified in . - --initial-rtt= - Set an initial RTT. - Default: )" - << util::format_duration(config.initial_rtt) << R"( - --max-window= - Maximum connection-level flow control window size. The - window auto-tuning is enabled if nonzero value is given, - and window size is scaled up to this value. - Default: )" - << util::format_uint_iec(config.max_window) << R"( - --max-stream-window= - Maximum stream-level flow control window size. The - window auto-tuning is enabled if nonzero value is given, - and window size is scaled up to this value. - Default: )" - << util::format_uint_iec(config.max_stream_window) << R"( - --max-udp-payload-size= - Override maximum UDP payload size that client transmits. - With this option, client assumes that a path supports - byte of UDP datagram payload, without performing - Path MTU Discovery. - --handshake-timeout= - Set the QUIC handshake timeout. It defaults to no - timeout. - --no-pmtud Disables Path MTU Discovery. - --ack-thresh= - The minimum number of the received ACK eliciting packets - that triggers immediate acknowledgement. - Default: )" - << config.ack_thresh << R"( - --initial-pkt-num= - The initial packet number that is used for each packet - number space. It must be in range [0, (1 << 31) - 1], - inclusive. By default, the initial packet number is - chosen randomly. - --pmtud-probes=[[,]...] - Specify UDP datagram payload sizes to probe in Path MTU - Discovery. must be strictly larger than 1200. - --ech-config-list-file= - Read/write ECHConfigList from/to . ECH is only - attempted if an underlying TLS stack supports it. If - the handshake fails with ech_required alert, ECH retry - configs, if provided by server, will be written to - . - --no-gso Disables GSO. - --show-stat Print the connection statistics when the connection is - closed. - --gso-burst= - The maximum number of packets to aggregate for GSO. If - GSO is disabled, this is the maximum number of packets - to send per an event loop in a single connection. It - defaults to 0, which means it is not limited by the - configuration. - -h, --help Display this help and exit. - ---- - - The argument is an integer and an optional unit (e.g., 10K is - 10 * 1024). Units are K, M and G (powers of 1024). - - The argument is an integer and an optional unit (e.g., 1s - is 1 second and 500ms is 500 milliseconds). Units are h, m, s, ms, - us, or ns (hours, minutes, seconds, milliseconds, microseconds, and - nanoseconds respectively). If a unit is omitted, a second is used - as unit. - - The argument is an hex string which must start with "0x" - (e.g., 0x00000001).)" - << std::endl; -} -} // namespace - -int main(int argc, char **argv) { - config_set_default(config); - char *data_path = nullptr; - const char *private_key_file = nullptr; - const char *cert_file = nullptr; - - if (argc) { - prog = basename(argv[0]); - } - - for (;;) { - static int flag = 0; - constexpr static option long_opts[] = { - {"help", no_argument, nullptr, 'h'}, - {"tx-loss", required_argument, nullptr, 't'}, - {"rx-loss", required_argument, nullptr, 'r'}, - {"data", required_argument, nullptr, 'd'}, - {"http-method", required_argument, nullptr, 'm'}, - {"nstreams", required_argument, nullptr, 'n'}, - {"version", required_argument, nullptr, 'v'}, - {"quiet", no_argument, nullptr, 'q'}, - {"show-secret", no_argument, nullptr, 's'}, - {"ciphers", required_argument, &flag, 1}, - {"groups", required_argument, &flag, 2}, - {"timeout", required_argument, &flag, 3}, - {"session-file", required_argument, &flag, 4}, - {"tp-file", required_argument, &flag, 5}, - {"dcid", required_argument, &flag, 6}, - {"change-local-addr", required_argument, &flag, 7}, - {"key-update", required_argument, &flag, 8}, - {"nat-rebinding", no_argument, &flag, 9}, - {"delay-stream", required_argument, &flag, 10}, - {"no-preferred-addr", no_argument, &flag, 11}, - {"key", required_argument, &flag, 12}, - {"cert", required_argument, &flag, 13}, - {"download", required_argument, &flag, 14}, - {"no-quic-dump", no_argument, &flag, 15}, - {"no-http-dump", no_argument, &flag, 16}, - {"qlog-file", required_argument, &flag, 17}, - {"max-data", required_argument, &flag, 18}, - {"max-stream-data-bidi-local", required_argument, &flag, 19}, - {"max-stream-data-bidi-remote", required_argument, &flag, 20}, - {"max-stream-data-uni", required_argument, &flag, 21}, - {"max-streams-bidi", required_argument, &flag, 22}, - {"max-streams-uni", required_argument, &flag, 23}, - {"exit-on-first-stream-close", no_argument, &flag, 24}, - {"disable-early-data", no_argument, &flag, 25}, - {"qlog-dir", required_argument, &flag, 26}, - {"cc", required_argument, &flag, 27}, - {"exit-on-all-streams-close", no_argument, &flag, 28}, - {"token-file", required_argument, &flag, 29}, - {"sni", required_argument, &flag, 30}, - {"initial-rtt", required_argument, &flag, 31}, - {"max-window", required_argument, &flag, 32}, - {"max-stream-window", required_argument, &flag, 33}, - {"scid", required_argument, &flag, 34}, - {"max-udp-payload-size", required_argument, &flag, 35}, - {"handshake-timeout", required_argument, &flag, 36}, - {"available-versions", required_argument, &flag, 37}, - {"no-pmtud", no_argument, &flag, 38}, - {"preferred-versions", required_argument, &flag, 39}, - {"ack-thresh", required_argument, &flag, 40}, - {"wait-for-ticket", no_argument, &flag, 41}, - {"initial-pkt-num", required_argument, &flag, 42}, - {"pmtud-probes", required_argument, &flag, 43}, - {"ech-config-list-file", required_argument, &flag, 44}, - {"no-gso", no_argument, &flag, 45}, - {"show-stat", no_argument, &flag, 46}, - {"gso-burst", required_argument, &flag, 47}, - {}, - }; - - auto optidx = 0; - auto c = getopt_long(argc, argv, "d:him:n:qr:st:v:", long_opts, &optidx); - if (c == -1) { - break; - } - switch (c) { - case 'd': - // --data - data_path = optarg; - break; - case 'h': - // --help - print_help(); - exit(EXIT_SUCCESS); - case 'm': - // --http-method - config.http_method = optarg; - break; - case 'n': - // --streams - if (auto n = util::parse_uint(optarg); !n) { - std::cerr << "streams: invalid argument" << std::endl; - exit(EXIT_FAILURE); - } else if (*n > NGTCP2_MAX_VARINT) { - std::cerr << "streams: must not exceed " << NGTCP2_MAX_VARINT - << std::endl; - exit(EXIT_FAILURE); - } else { - config.nstreams = *n; - } - break; - case 'q': - // --quiet - config.quiet = true; - break; - case 'r': - // --rx-loss - config.rx_loss_prob = strtod(optarg, nullptr); - break; - case 's': - // --show-secret - config.show_secret = true; - break; - case 't': - // --tx-loss - config.tx_loss_prob = strtod(optarg, nullptr); - break; - case 'v': { - // --version - if (optarg == "v1"sv) { - config.version = NGTCP2_PROTO_VER_V1; - break; - } - if (optarg == "v2"sv) { - config.version = NGTCP2_PROTO_VER_V2; - break; - } - auto rv = util::parse_version(optarg); - if (!rv) { - std::cerr << "version: invalid version " << std::quoted(optarg) - << std::endl; - exit(EXIT_FAILURE); - } - config.version = *rv; - break; - } - case '?': - print_usage(); - exit(EXIT_FAILURE); - case 0: - switch (flag) { - case 1: - // --ciphers - if (util::crypto_default_ciphers()[0] == '\0') { - std::cerr << "ciphers: not supported" << std::endl; - exit(EXIT_FAILURE); - } - config.ciphers = optarg; - break; - case 2: - // --groups - config.groups = optarg; - break; - case 3: - // --timeout - if (auto t = util::parse_duration(optarg); !t) { - std::cerr << "timeout: invalid argument" << std::endl; - exit(EXIT_FAILURE); - } else { - config.timeout = *t; - } - break; - case 4: - // --session-file - config.session_file = optarg; - break; - case 5: - // --tp-file - config.tp_file = optarg; - break; - case 6: { - // --dcid - auto hexcid = std::string_view{optarg}; - if (hexcid.size() < NGTCP2_MIN_INITIAL_DCIDLEN * 2 || - hexcid.size() > NGTCP2_MAX_CIDLEN * 2) { - std::cerr << "dcid: wrong length" << std::endl; - exit(EXIT_FAILURE); - } - - if (!util::is_hex_string(hexcid)) { - std::cerr << "dcid: not hex string" << std::endl; - exit(EXIT_FAILURE); - } - - auto dcid = util::decode_hex(hexcid); - ngtcp2_cid_init(&config.dcid, - reinterpret_cast(dcid.c_str()), - dcid.size()); - break; - } - case 7: - // --change-local-addr - if (auto t = util::parse_duration(optarg); !t) { - std::cerr << "change-local-addr: invalid argument" << std::endl; - exit(EXIT_FAILURE); - } else { - config.change_local_addr = *t; - } - break; - case 8: - // --key-update - if (auto t = util::parse_duration(optarg); !t) { - std::cerr << "key-update: invalid argument" << std::endl; - exit(EXIT_FAILURE); - } else { - config.key_update = *t; - } - break; - case 9: - // --nat-rebinding - config.nat_rebinding = true; - break; - case 10: - // --delay-stream - if (auto t = util::parse_duration(optarg); !t) { - std::cerr << "delay-stream: invalid argument" << std::endl; - exit(EXIT_FAILURE); - } else { - config.delay_stream = *t; - } - break; - case 11: - // --no-preferred-addr - config.no_preferred_addr = true; - break; - case 12: - // --key - private_key_file = optarg; - break; - case 13: - // --cert - cert_file = optarg; - break; - case 14: - // --download - config.download = optarg; - break; - case 15: - // --no-quic-dump - config.no_quic_dump = true; - break; - case 16: - // --no-http-dump - config.no_http_dump = true; - break; - case 17: - // --qlog-file - config.qlog_file = optarg; - break; - case 18: - // --max-data - if (auto n = util::parse_uint_iec(optarg); !n) { - std::cerr << "max-data: invalid argument" << std::endl; - exit(EXIT_FAILURE); - } else { - config.max_data = *n; - } - break; - case 19: - // --max-stream-data-bidi-local - if (auto n = util::parse_uint_iec(optarg); !n) { - std::cerr << "max-stream-data-bidi-local: invalid argument" - << std::endl; - exit(EXIT_FAILURE); - } else { - config.max_stream_data_bidi_local = *n; - } - break; - case 20: - // --max-stream-data-bidi-remote - if (auto n = util::parse_uint_iec(optarg); !n) { - std::cerr << "max-stream-data-bidi-remote: invalid argument" - << std::endl; - exit(EXIT_FAILURE); - } else { - config.max_stream_data_bidi_remote = *n; - } - break; - case 21: - // --max-stream-data-uni - if (auto n = util::parse_uint_iec(optarg); !n) { - std::cerr << "max-stream-data-uni: invalid argument" << std::endl; - exit(EXIT_FAILURE); - } else { - config.max_stream_data_uni = *n; - } - break; - case 22: - // --max-streams-bidi - if (auto n = util::parse_uint(optarg); !n) { - std::cerr << "max-streams-bidi: invalid argument" << std::endl; - exit(EXIT_FAILURE); - } else { - config.max_streams_bidi = *n; - } - break; - case 23: - // --max-streams-uni - if (auto n = util::parse_uint(optarg); !n) { - std::cerr << "max-streams-uni: invalid argument" << std::endl; - exit(EXIT_FAILURE); - } else { - config.max_streams_uni = *n; - } - break; - case 24: - // --exit-on-first-stream-close - config.exit_on_first_stream_close = true; - break; - case 25: - // --disable-early-data - config.disable_early_data = true; - break; - case 26: - // --qlog-dir - config.qlog_dir = optarg; - break; - case 27: - // --cc - if (strcmp("cubic", optarg) == 0) { - config.cc_algo = NGTCP2_CC_ALGO_CUBIC; - break; - } - if (strcmp("reno", optarg) == 0) { - config.cc_algo = NGTCP2_CC_ALGO_RENO; - break; - } - if (strcmp("bbr", optarg) == 0) { - config.cc_algo = NGTCP2_CC_ALGO_BBR; - break; - } - std::cerr << "cc: specify cubic, reno, or bbr" << std::endl; - exit(EXIT_FAILURE); - case 28: - // --exit-on-all-streams-close - config.exit_on_all_streams_close = true; - break; - case 29: - // --token-file - config.token_file = optarg; - break; - case 30: - // --sni - config.sni = optarg; - break; - case 31: - // --initial-rtt - if (auto t = util::parse_duration(optarg); !t) { - std::cerr << "initial-rtt: invalid argument" << std::endl; - exit(EXIT_FAILURE); - } else { - config.initial_rtt = *t; - } - break; - case 32: - // --max-window - if (auto n = util::parse_uint_iec(optarg); !n) { - std::cerr << "max-window: invalid argument" << std::endl; - exit(EXIT_FAILURE); - } else { - config.max_window = *n; - } - break; - case 33: - // --max-stream-window - if (auto n = util::parse_uint_iec(optarg); !n) { - std::cerr << "max-stream-window: invalid argument" << std::endl; - exit(EXIT_FAILURE); - } else { - config.max_stream_window = *n; - } - break; - case 34: { - // --scid - auto hexcid = std::string_view{optarg}; - if (hexcid.size() > NGTCP2_MAX_CIDLEN * 2) { - std::cerr << "scid: wrong length" << std::endl; - exit(EXIT_FAILURE); - } - - if (!util::is_hex_string(hexcid)) { - std::cerr << "scid: not hex string" << std::endl; - exit(EXIT_FAILURE); - } - - auto scid = util::decode_hex(hexcid); - ngtcp2_cid_init(&config.scid, - reinterpret_cast(scid.c_str()), - scid.size()); - config.scid_present = true; - break; - } - case 35: - // --max-udp-payload-size - if (auto n = util::parse_uint_iec(optarg); !n) { - std::cerr << "max-udp-payload-size: invalid argument" << std::endl; - exit(EXIT_FAILURE); - } else if (*n > NGTCP2_MAX_TX_UDP_PAYLOAD_SIZE) { - std::cerr << "max-udp-payload-size: must not exceed " - << NGTCP2_MAX_TX_UDP_PAYLOAD_SIZE << std::endl; - exit(EXIT_FAILURE); - } else if (*n == 0) { - std::cerr << "max-udp-payload-size: must not be 0" << std::endl; - } else { - config.max_udp_payload_size = *n; - } - break; - case 36: - // --handshake-timeout - if (auto t = util::parse_duration(optarg); !t) { - std::cerr << "handshake-timeout: invalid argument" << std::endl; - exit(EXIT_FAILURE); - } else { - config.handshake_timeout = *t; - } - break; - case 37: { - // --available-versions - if (strlen(optarg) == 0) { - config.available_versions.resize(0); - break; - } - auto l = util::split_str(optarg); - config.available_versions.resize(l.size()); - auto it = std::ranges::begin(config.available_versions); - for (const auto &k : l) { - if (k == "v1"sv) { - *it++ = NGTCP2_PROTO_VER_V1; - continue; - } - if (k == "v2"sv) { - *it++ = NGTCP2_PROTO_VER_V2; - continue; - } - auto rv = util::parse_version(k); - if (!rv) { - std::cerr << "available-versions: invalid version " - << std::quoted(k) << std::endl; - exit(EXIT_FAILURE); - } - *it++ = *rv; - } - break; - } - case 38: - // --no-pmtud - config.no_pmtud = true; - break; - case 39: { - // --preferred-versions - auto l = util::split_str(optarg); - if (l.size() > max_preferred_versionslen) { - std::cerr << "preferred-versions: too many versions > " - << max_preferred_versionslen << std::endl; - } - config.preferred_versions.resize(l.size()); - auto it = std::ranges::begin(config.preferred_versions); - for (const auto &k : l) { - if (k == "v1"sv) { - *it++ = NGTCP2_PROTO_VER_V1; - continue; - } - if (k == "v2"sv) { - *it++ = NGTCP2_PROTO_VER_V2; - continue; - } - auto rv = util::parse_version(k); - if (!rv) { - std::cerr << "preferred-versions: invalid version " - << std::quoted(k) << std::endl; - exit(EXIT_FAILURE); - } - if (!ngtcp2_is_supported_version(*rv)) { - std::cerr << "preferred-versions: unsupported version " - << std::quoted(k) << std::endl; - exit(EXIT_FAILURE); - } - *it++ = *rv; - } - break; - } - case 40: - // --ack-thresh - if (auto n = util::parse_uint(optarg); !n) { - std::cerr << "ack-thresh: invalid argument" << std::endl; - exit(EXIT_FAILURE); - } else if (*n > 100) { - std::cerr << "ack-thresh: must not exceed 100" << std::endl; - exit(EXIT_FAILURE); - } else { - config.ack_thresh = *n; - } - break; - case 41: - // --wait-for-ticket - config.wait_for_ticket = true; - break; - case 42: - // --initial-pkt-num - if (auto n = util::parse_uint(optarg); !n) { - std::cerr << "initial-pkt-num: invalid argument" << std::endl; - exit(EXIT_FAILURE); - } else if (*n > INT32_MAX) { - std::cerr << "initial-pkt-num: must not exceed (1 << 31) - 1" - << std::endl; - exit(EXIT_FAILURE); - } else { - config.initial_pkt_num = static_cast(*n); - } - break; - case 43: { - // --pmtud-probes - auto l = util::split_str(optarg); - for (auto &s : l) { - if (auto n = util::parse_uint_iec(s); !n) { - std::cerr << "pmtud-probes: invalid argument" << std::endl; - exit(EXIT_FAILURE); - } else if (*n <= NGTCP2_MAX_UDP_PAYLOAD_SIZE || - *n > NGTCP2_MAX_TX_UDP_PAYLOAD_SIZE) { - std::cerr << "pmtud-probes: must be in range [" - << NGTCP2_MAX_UDP_PAYLOAD_SIZE + 1 << ", " - << NGTCP2_MAX_TX_UDP_PAYLOAD_SIZE << "], inclusive." - << std::endl; - exit(EXIT_FAILURE); - } else { - config.pmtud_probes.push_back(static_cast(*n)); - } - } - break; - } - case 44: - // --ech-config-list-file - config.ech_config_list_file = optarg; - break; - case 45: - // --no-gso - config.no_gso = true; - break; - case 46: - // --show-stat - config.show_stat = true; - break; - case 47: { - // --gso-burst - auto n = util::parse_uint(optarg); - if (!n) { - std::cerr << "gso-burst: invalid argument" << std::endl; - exit(EXIT_FAILURE); - } - - if (*n > 64) { - std::cerr << "gso-burst: must be in range [0, 64], inclusive." - << std::endl; - exit(EXIT_FAILURE); - } - - config.gso_burst = static_cast(*n); - - break; - } - } - break; - default: - break; - } - } - - if (argc - optind < 2) { - std::cerr << "Too few arguments" << std::endl; - print_usage(); - exit(EXIT_FAILURE); - } - - if (!config.qlog_file.empty() && !config.qlog_dir.empty()) { - std::cerr << "qlog-file and qlog-dir are mutually exclusive" << std::endl; - exit(EXIT_FAILURE); - } - - if (config.exit_on_first_stream_close && config.exit_on_all_streams_close) { - std::cerr << "exit-on-first-stream-close and exit-on-all-streams-close are " - "mutually exclusive" - << std::endl; - exit(EXIT_FAILURE); - } - - if (config.wait_for_ticket && !config.session_file) { - std::cerr << "wait-for-ticket: session-file must be specified" << std::endl; - exit(EXIT_FAILURE); - } - - if (data_path) { - auto fd = open(data_path, O_RDONLY); - if (fd == -1) { - std::cerr << "data: Could not open file " << data_path << ": " - << strerror(errno) << std::endl; - exit(EXIT_FAILURE); - } - struct stat st; - if (fstat(fd, &st) != 0) { - std::cerr << "data: Could not stat file " << data_path << ": " - << strerror(errno) << std::endl; - exit(EXIT_FAILURE); - } - config.fd = fd; - config.datalen = static_cast(st.st_size); - if (config.datalen) { - auto addr = mmap(nullptr, config.datalen, PROT_READ, MAP_SHARED, fd, 0); - if (addr == MAP_FAILED) { - std::cerr << "data: Could not mmap file " << data_path << ": " - << strerror(errno) << std::endl; - exit(EXIT_FAILURE); - } - config.data = static_cast(addr); - } - } - - if (config.ech_config_list_file) { - auto ech_config = util::read_file(config.ech_config_list_file); - if (!ech_config) { - std::cerr << "ech-config-list-file: Could not read ECHConfigList" - << std::endl; - } else { - config.ech_config_list = std::move(*ech_config); - } - } - - auto addr = argv[optind++]; - auto port = argv[optind++]; - - if (parse_requests(&argv[optind], static_cast(argc - optind)) != 0) { - exit(EXIT_FAILURE); - } - - if (!ngtcp2_is_reserved_version(config.version)) { - if (!config.preferred_versions.empty() && - std::ranges::find(config.preferred_versions, config.version) == - std::ranges::end(config.preferred_versions)) { - std::cerr << "preferred-version: must include version " << std::hex - << "0x" << config.version << std::dec << std::endl; - exit(EXIT_FAILURE); - } - - if (!config.available_versions.empty() && - std::ranges::find(config.available_versions, config.version) == - std::ranges::end(config.available_versions)) { - std::cerr << "available-versions: must include version " << std::hex - << "0x" << config.version << std::dec << std::endl; - exit(EXIT_FAILURE); - } - } - - if (config.nstreams == 0) { - config.nstreams = config.requests.size(); - } - - TLSClientContext tls_ctx; - if (tls_ctx.init(private_key_file, cert_file) != 0) { - exit(EXIT_FAILURE); - } - - auto ev_loop_d = defer(ev_loop_destroy, EV_DEFAULT); - - auto keylog_filename = getenv("SSLKEYLOGFILE"); - if (keylog_filename) { - keylog_file.open(keylog_filename, std::ios_base::app); - if (keylog_file) { - tls_ctx.enable_keylog(); - } - } - - if (util::generate_secret(config.static_secret) != 0) { - std::cerr << "Unable to generate static secret" << std::endl; - exit(EXIT_FAILURE); - } - - auto client_chosen_version = config.version; - - for (;;) { - Client c(EV_DEFAULT, client_chosen_version, config.version); - - if (run(c, addr, port, tls_ctx) != 0) { - exit(EXIT_FAILURE); - } - - if (config.preferred_versions.empty()) { - break; - } - - auto &offered_versions = c.get_offered_versions(); - if (offered_versions.empty()) { - break; - } - - client_chosen_version = ngtcp2_select_version( - config.preferred_versions.data(), config.preferred_versions.size(), - offered_versions.data(), offered_versions.size()); - - if (client_chosen_version == 0) { - std::cerr << "Unable to select a version" << std::endl; - exit(EXIT_FAILURE); - } - - if (!config.quiet) { - std::cerr << "Client selected version " << std::hex << "0x" - << client_chosen_version << std::dec << std::endl; - } - } - - return EXIT_SUCCESS; -} diff --git a/src/ngtcp2/client.h b/src/ngtcp2/client.h deleted file mode 100644 index 61d894d..0000000 --- a/src/ngtcp2/client.h +++ /dev/null @@ -1,198 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2017 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#ifndef CLIENT_H -#define CLIENT_H - -#ifdef HAVE_CONFIG_H -# include -#endif // defined(HAVE_CONFIG_H) - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include - -#include "client_base.h" -#include "tls_client_context.h" -#include "tls_client_session.h" -#include "network.h" -#include "shared.h" -#include "template.h" - -using namespace ngtcp2; - -struct Stream { - Stream(const Request &req, int64_t stream_id); - ~Stream(); - - int open_file(const std::string_view &path); - - Request req; - int64_t stream_id; - int fd; -}; - -class Client; - -struct Endpoint { - Address addr; - ev_io rev; - Client *client; - int fd; -}; - -class Client : public ClientBase { -public: - Client(struct ev_loop *loop, uint32_t client_chosen_version, - uint32_t original_version); - ~Client(); - - int init(int fd, const Address &local_addr, const Address &remote_addr, - const char *addr, const char *port, TLSClientContext &tls_ctx); - void disconnect(); - - int on_read(const Endpoint &ep); - int on_write(); - int write_streams(); - int feed_data(const Endpoint &ep, const sockaddr *sa, socklen_t salen, - const ngtcp2_pkt_info *pi, std::span data); - int handle_expiry(); - void update_timer(); - int handshake_completed(); - int handshake_confirmed(); - void recv_version_negotiation(const uint32_t *sv, size_t nsv); - - int send_packet(const Endpoint &ep, const ngtcp2_addr &remote_addr, - unsigned int ecn, std::span data); - std::pair, int> - send_packet(const Endpoint &ep, const ngtcp2_addr &remote_addr, - unsigned int ecn, std::span data, size_t gso_size); - int send_packet_or_blocked(const ngtcp2_path &path, unsigned int ecn, - std::span data, size_t gso_size); - int on_stream_close(int64_t stream_id, uint64_t app_error_code); - int on_extend_max_streams(); - int handle_error(); - int make_stream_early(); - int change_local_addr(); - void start_change_local_addr_timer(); - int update_key(uint8_t *rx_secret, uint8_t *tx_secret, - ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv, - ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv, - const uint8_t *current_rx_secret, - const uint8_t *current_tx_secret, size_t secretlen); - int initiate_key_update(); - void start_key_update_timer(); - void start_delay_stream_timer(); - - int select_preferred_address(Address &selected_addr, - const ngtcp2_preferred_addr *paddr); - - std::optional endpoint_for(const Address &remote_addr); - - void set_remote_addr(const ngtcp2_addr &remote_addr); - - int setup_httpconn(); - int submit_http_request(const Stream *stream); - int recv_stream_data(uint32_t flags, int64_t stream_id, - std::span data); - int acked_stream_data_offset(int64_t stream_id, uint64_t datalen); - void http_consume(int64_t stream_id, size_t nconsumed); - void http_write_data(int64_t stream_id, std::span data); - int on_stream_reset(int64_t stream_id); - int on_stream_stop_sending(int64_t stream_id); - int extend_max_stream_data(int64_t stream_id, uint64_t max_data); - int stop_sending(int64_t stream_id, uint64_t app_error_code); - int reset_stream(int64_t stream_id, uint64_t app_error_code); - void http_stream_close(int64_t stream_id, uint64_t app_error_code); - - void on_send_blocked(const ngtcp2_path &path, unsigned int ecn, - std::span data, size_t gso_size); - void start_wev_endpoint(const Endpoint &ep); - int send_blocked_packet(); - ngtcp2_ssize write_pkt(ngtcp2_path *path, ngtcp2_pkt_info *pi, uint8_t *dest, - size_t destlen, ngtcp2_tstamp ts); - - const std::vector &get_offered_versions() const; - - bool get_early_data() const; - void early_data_rejected(); - - bool should_exit() const; - -private: - std::vector endpoints_; - Address remote_addr_; - ev_io wev_; - ev_timer timer_; - ev_timer change_local_addr_timer_; - ev_timer key_update_timer_; - ev_timer delay_stream_timer_; - ev_signal sigintev_; - struct ev_loop *loop_; - std::unordered_map> streams_; - std::vector offered_versions_; - nghttp3_conn *httpconn_; - // addr_ is the server host address. - const char *addr_; - // port_ is the server port. - const char *port_; - // nstreams_done_ is the number of streams opened. - size_t nstreams_done_; - // nstreams_closed_ is the number of streams get closed. - size_t nstreams_closed_; - // nkey_update_ is the number of key update occurred. - size_t nkey_update_; - uint32_t client_chosen_version_; - uint32_t original_version_; - // early_data_ is true if client attempts to do 0RTT data transfer. - bool early_data_; - // handshake_confirmed_ gets true after handshake has been - // confirmed. - bool handshake_confirmed_; - bool no_gso_; - - struct { - bool send_blocked; - // blocked field is effective only when send_blocked is true. - struct { - const Endpoint *endpoint; - Address remote_addr; - unsigned int ecn; - std::span data; - size_t gso_size; - } blocked; - std::array data; - } tx_; -}; - -#endif // !defined(CLIENT_H) diff --git a/src/ngtcp2/client_base.cc b/src/ngtcp2/client_base.cc deleted file mode 100644 index 81c2fe7..0000000 --- a/src/ngtcp2/client_base.cc +++ /dev/null @@ -1,211 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2020 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#include "client_base.h" - -#include -#include -#include -#include - -#include "debug.h" -#include "template.h" -#include "util.h" - -using namespace ngtcp2; -using namespace std::literals; - -extern Config config; - -static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) { - auto c = static_cast(conn_ref->user_data); - return c->conn(); -} - -ClientBase::ClientBase() - : conn_ref_{get_conn, this}, - qlog_(nullptr), - conn_(nullptr), - ticket_received_(false) { - ngtcp2_ccerr_default(&last_error_); -} - -ClientBase::~ClientBase() { - if (conn_) { - if (config.show_stat) { - debug::print_conn_info(conn_); - } - - ngtcp2_conn_del(conn_); - } - - if (qlog_) { - fclose(qlog_); - } -} - -int ClientBase::write_transport_params(const char *path, - const ngtcp2_transport_params *params) { - auto f = std::ofstream(path); - if (!f) { - return -1; - } - - f << "initial_max_streams_bidi=" << params->initial_max_streams_bidi << '\n' - << "initial_max_streams_uni=" << params->initial_max_streams_uni << '\n' - << "initial_max_stream_data_bidi_local=" - << params->initial_max_stream_data_bidi_local << '\n' - << "initial_max_stream_data_bidi_remote=" - << params->initial_max_stream_data_bidi_remote << '\n' - << "initial_max_stream_data_uni=" << params->initial_max_stream_data_uni - << '\n' - << "initial_max_data=" << params->initial_max_data << '\n' - << "active_connection_id_limit=" << params->active_connection_id_limit - << '\n' - << "max_datagram_frame_size=" << params->max_datagram_frame_size << '\n'; - - f.close(); - if (!f) { - return -1; - } - - return 0; -} - -int ClientBase::read_transport_params(const char *path, - ngtcp2_transport_params *params) { - auto f = std::ifstream(path); - if (!f) { - return -1; - } - - for (std::string line; std::getline(f, line);) { - if (util::istarts_with(line, "initial_max_streams_bidi="sv)) { - if (auto n = util::parse_uint(line.c_str() + - "initial_max_streams_bidi="sv.size()); - !n) { - return -1; - } else { - params->initial_max_streams_bidi = *n; - } - continue; - } - - if (util::istarts_with(line, "initial_max_streams_uni="sv)) { - if (auto n = util::parse_uint(line.c_str() + - "initial_max_streams_uni="sv.size()); - !n) { - return -1; - } else { - params->initial_max_streams_uni = *n; - } - continue; - } - - if (util::istarts_with(line, "initial_max_stream_data_bidi_local="sv)) { - if (auto n = util::parse_uint( - line.c_str() + "initial_max_stream_data_bidi_local="sv.size()); - !n) { - return -1; - } else { - params->initial_max_stream_data_bidi_local = *n; - } - continue; - } - - if (util::istarts_with(line, "initial_max_stream_data_bidi_remote="sv)) { - if (auto n = util::parse_uint( - line.c_str() + "initial_max_stream_data_bidi_remote="sv.size()); - !n) { - return -1; - } else { - params->initial_max_stream_data_bidi_remote = *n; - } - continue; - } - - if (util::istarts_with(line, "initial_max_stream_data_uni="sv)) { - if (auto n = util::parse_uint(line.c_str() + - "initial_max_stream_data_uni="sv.size()); - !n) { - return -1; - } else { - params->initial_max_stream_data_uni = *n; - } - continue; - } - - if (util::istarts_with(line, "initial_max_data="sv)) { - if (auto n = - util::parse_uint(line.c_str() + "initial_max_data="sv.size()); - !n) { - return -1; - } else { - params->initial_max_data = *n; - } - continue; - } - - if (util::istarts_with(line, "active_connection_id_limit="sv)) { - if (auto n = util::parse_uint(line.c_str() + - "active_connection_id_limit="sv.size()); - !n) { - return -1; - } else { - params->active_connection_id_limit = *n; - } - continue; - } - - if (util::istarts_with(line, "max_datagram_frame_size="sv)) { - if (auto n = util::parse_uint(line.c_str() + - "max_datagram_frame_size="sv.size()); - !n) { - return -1; - } else { - params->max_datagram_frame_size = *n; - } - continue; - } - } - - return 0; -} - -ngtcp2_conn *ClientBase::conn() const { return conn_; } - -void qlog_write_cb(void *user_data, uint32_t flags, const void *data, - size_t datalen) { - auto c = static_cast(user_data); - c->write_qlog(data, datalen); -} - -void ClientBase::write_qlog(const void *data, size_t datalen) { - assert(qlog_); - fwrite(data, 1, datalen, qlog_); -} - -ngtcp2_crypto_conn_ref *ClientBase::conn_ref() { return &conn_ref_; } - -void ClientBase::ticket_received() { ticket_received_ = true; } diff --git a/src/ngtcp2/client_base.h b/src/ngtcp2/client_base.h deleted file mode 100644 index fe50125..0000000 --- a/src/ngtcp2/client_base.h +++ /dev/null @@ -1,238 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2020 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#ifndef CLIENT_BASE_H -#define CLIENT_BASE_H - -#ifdef HAVE_CONFIG_H -# include -#endif // defined(HAVE_CONFIG_H) - -#include -#include -#include -#include -#include - -#include - -#include "tls_client_session.h" -#include "network.h" -#include "shared.h" - -using namespace ngtcp2; - -struct Request { - std::string_view scheme; - std::string authority; - std::string path; -}; - -struct Config { - ngtcp2_cid dcid; - ngtcp2_cid scid; - bool scid_present; - // tx_loss_prob is probability of losing outgoing packet. - double tx_loss_prob; - // rx_loss_prob is probability of losing incoming packet. - double rx_loss_prob; - // fd is a file descriptor to read input for streams. - int fd; - // ciphers is the list of enabled ciphers. - const char *ciphers; - // groups is the list of supported groups. - const char *groups; - // nstreams is the number of streams to open. - size_t nstreams; - // data is the pointer to memory region which maps file denoted by - // fd. - uint8_t *data; - // datalen is the length of file denoted by fd. - size_t datalen; - // version is a QUIC version to use. - uint32_t version; - // quiet suppresses the output normally shown except for the error - // messages. - bool quiet; - // timeout is an idle timeout for QUIC connection. - ngtcp2_duration timeout; - // session_file is a path to a file to write, and read TLS session. - const char *session_file; - // tp_file is a path to a file to write, and read QUIC transport - // parameters. - const char *tp_file; - // show_secret is true if transport secrets should be printed out. - bool show_secret; - // change_local_addr is the duration after which client changes - // local address. - ngtcp2_duration change_local_addr; - // key_update is the duration after which client initiates key - // update. - ngtcp2_duration key_update; - // delay_stream is the duration after which client sends the first - // 1-RTT stream. - ngtcp2_duration delay_stream; - // nat_rebinding is true if simulated NAT rebinding is enabled. - bool nat_rebinding; - // no_preferred_addr is true if client do not follow preferred - // address offered by server. - bool no_preferred_addr; - std::string_view http_method; - // download is a path to a directory where a downloaded file is - // saved. If it is empty, no file is saved. - std::string_view download; - // requests contains URIs to request. - std::vector requests; - // no_quic_dump is true if hexdump of QUIC STREAM and CRYPTO data - // should be disabled. - bool no_quic_dump; - // no_http_dump is true if hexdump of HTTP response body should be - // disabled. - bool no_http_dump; - // qlog_file is the path to write qlog. - std::string_view qlog_file; - // qlog_dir is the path to directory where qlog is stored. qlog_dir - // and qlog_file are mutually exclusive. - std::string_view qlog_dir; - // max_data is the initial connection-level flow control window. - uint64_t max_data; - // max_stream_data_bidi_local is the initial stream-level flow - // control window for a bidirectional stream that the local endpoint - // initiates. - uint64_t max_stream_data_bidi_local; - // max_stream_data_bidi_remote is the initial stream-level flow - // control window for a bidirectional stream that the remote - // endpoint initiates. - uint64_t max_stream_data_bidi_remote; - // max_stream_data_uni is the initial stream-level flow control - // window for a unidirectional stream. - uint64_t max_stream_data_uni; - // max_streams_bidi is the number of the concurrent bidirectional - // streams. - uint64_t max_streams_bidi; - // max_streams_uni is the number of the concurrent unidirectional - // streams. - uint64_t max_streams_uni; - // max_window is the maximum connection-level flow control window - // size if auto-tuning is enabled. - uint64_t max_window; - // max_stream_window is the maximum stream-level flow control window - // size if auto-tuning is enabled. - uint64_t max_stream_window; - // exit_on_first_stream_close is the flag that if it is true, client - // exits when a first HTTP stream gets closed. It is not - // necessarily the same time when the underlying QUIC stream closes - // due to the QPACK synchronization. - bool exit_on_first_stream_close; - // exit_on_all_streams_close is the flag that if it is true, client - // exits when all HTTP streams get closed. - bool exit_on_all_streams_close; - // disable_early_data disables early data. - bool disable_early_data; - // static_secret is used to derive keying materials for Stateless - // Retry token. - std::array static_secret; - // cc_algo is the congestion controller algorithm. - ngtcp2_cc_algo cc_algo; - // token_file is a path to file to read or write token from - // NEW_TOKEN frame. - std::string_view token_file; - // sni is the value sent in TLS SNI, overriding DNS name of the - // remote host. - std::string_view sni; - // initial_rtt is an initial RTT. - ngtcp2_duration initial_rtt; - // max_udp_payload_size is the maximum UDP payload size that client - // transmits. - size_t max_udp_payload_size; - // handshake_timeout is the period of time before giving up QUIC - // connection establishment. - ngtcp2_duration handshake_timeout; - // preferred_versions includes QUIC versions in the order of - // preference. Client uses this field to select a version from the - // version set offered in Version Negotiation packet. - std::vector preferred_versions; - // available_versions includes QUIC versions that are sent in - // available_versions field of version_information - // transport_parameter. - std::vector available_versions; - // no_pmtud disables Path MTU Discovery. - bool no_pmtud; - // ack_thresh is the minimum number of the received ACK eliciting - // packets that triggers immediate acknowledgement. - size_t ack_thresh; - // wait_for_ticket, if true, waits for a ticket to be received - // before exiting on exit_on_first_stream_close or - // exit_on_all_streams_close. - bool wait_for_ticket; - // initial_pkt_num is the initial packet number for each packet - // number space. If it is set to UINT32_MAX, it is chosen randomly. - uint32_t initial_pkt_num; - // pmtud_probes is the array of UDP datagram payload size to probes. - std::vector pmtud_probes; - // ech_config_list contains ECHConfigList. - std::vector ech_config_list; - // ech_config_list_file is a path to a file to read and write - // ECHConfigList. - const char *ech_config_list_file; - // no_gso disables GSO. - bool no_gso; - // show_stat, if true, displays the connection statistics when the - // connection is closed. - bool show_stat; - // gso_burst is the number of packets to aggregate in GSO. 0 means - // it is not limited by the configuration. - size_t gso_burst; -}; - -class ClientBase { -public: - ClientBase(); - ~ClientBase(); - - ngtcp2_conn *conn() const; - - int write_transport_params(const char *path, - const ngtcp2_transport_params *params); - int read_transport_params(const char *path, ngtcp2_transport_params *params); - - void write_qlog(const void *data, size_t datalen); - - ngtcp2_crypto_conn_ref *conn_ref(); - - void ticket_received(); - -protected: - ngtcp2_crypto_conn_ref conn_ref_; - TLSClientSession tls_session_; - FILE *qlog_; - ngtcp2_conn *conn_; - ngtcp2_ccerr last_error_; - bool ticket_received_; -}; - -void qlog_write_cb(void *user_data, uint32_t flags, const void *data, - size_t datalen); - -#endif // !defined(CLIENT_BASE_H) diff --git a/src/ngtcp2/debug.cc b/src/ngtcp2/debug.cc deleted file mode 100644 index baad9a2..0000000 --- a/src/ngtcp2/debug.cc +++ /dev/null @@ -1,345 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2017 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#include "debug.h" - -#include - -#include -#include -#include -#include - -#include "util.h" - -using namespace std::literals; - -namespace ngtcp2 { - -namespace debug { - -namespace { -auto randgen = util::make_mt19937(); -} // namespace - -namespace { -auto *outfile = stderr; -} // namespace - -int handshake_completed(ngtcp2_conn *conn, void *user_data) { - fprintf(outfile, "QUIC handshake has completed\n"); - return 0; -} - -int handshake_confirmed(ngtcp2_conn *conn, void *user_data) { - fprintf(outfile, "QUIC handshake has been confirmed\n"); - return 0; -} - -bool packet_lost(double prob) { - auto p = std::uniform_real_distribution<>(0, 1)(randgen); - return p < prob; -} - -void print_crypto_data(ngtcp2_encryption_level encryption_level, - std::span data) { - const char *encryption_level_str; - switch (encryption_level) { - case NGTCP2_ENCRYPTION_LEVEL_INITIAL: - encryption_level_str = "Initial"; - break; - case NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE: - encryption_level_str = "Handshake"; - break; - case NGTCP2_ENCRYPTION_LEVEL_1RTT: - encryption_level_str = "1-RTT"; - break; - default: - assert(0); - abort(); - } - fprintf(outfile, "Ordered CRYPTO data in %s crypto level\n", - encryption_level_str); - util::hexdump(outfile, data.data(), data.size()); -} - -void print_stream_data(int64_t stream_id, std::span data) { - fprintf(outfile, "Ordered STREAM data stream_id=0x%" PRIx64 "\n", stream_id); - util::hexdump(outfile, data.data(), data.size()); -} - -void print_initial_secret(std::span data) { - fprintf(outfile, "initial_secret=%s\n", util::format_hex(data).c_str()); -} - -void print_client_in_secret(std::span data) { - fprintf(outfile, "client_in_secret=%s\n", util::format_hex(data).c_str()); -} - -void print_server_in_secret(std::span data) { - fprintf(outfile, "server_in_secret=%s\n", util::format_hex(data).c_str()); -} - -void print_handshake_secret(std::span data) { - fprintf(outfile, "handshake_secret=%s\n", util::format_hex(data).c_str()); -} - -void print_client_hs_secret(std::span data) { - fprintf(outfile, "client_hs_secret=%s\n", util::format_hex(data).c_str()); -} - -void print_server_hs_secret(std::span data) { - fprintf(outfile, "server_hs_secret=%s\n", util::format_hex(data).c_str()); -} - -void print_client_0rtt_secret(std::span data) { - fprintf(outfile, "client_0rtt_secret=%s\n", util::format_hex(data).c_str()); -} - -void print_client_1rtt_secret(std::span data) { - fprintf(outfile, "client_1rtt_secret=%s\n", util::format_hex(data).c_str()); -} - -void print_server_1rtt_secret(std::span data) { - fprintf(outfile, "server_1rtt_secret=%s\n", util::format_hex(data).c_str()); -} - -void print_client_pp_key(std::span data) { - fprintf(outfile, "+ client_pp_key=%s\n", util::format_hex(data).c_str()); -} - -void print_server_pp_key(std::span data) { - fprintf(outfile, "+ server_pp_key=%s\n", util::format_hex(data).c_str()); -} - -void print_client_pp_iv(std::span data) { - fprintf(outfile, "+ client_pp_iv=%s\n", util::format_hex(data).c_str()); -} - -void print_server_pp_iv(std::span data) { - fprintf(outfile, "+ server_pp_iv=%s\n", util::format_hex(data).c_str()); -} - -void print_client_pp_hp(std::span data) { - fprintf(outfile, "+ client_pp_hp=%s\n", util::format_hex(data).c_str()); -} - -void print_server_pp_hp(std::span data) { - fprintf(outfile, "+ server_pp_hp=%s\n", util::format_hex(data).c_str()); -} - -void print_secrets(std::span secret, - std::span key, std::span iv, - std::span hp) { - std::cerr << "+ secret=" << util::format_hex(secret) << "\n" - << "+ key=" << util::format_hex(key) << "\n" - << "+ iv=" << util::format_hex(iv) << "\n" - << "+ hp=" << util::format_hex(hp) << std::endl; -} - -void print_secrets(std::span secret, - std::span key, std::span iv) { - std::cerr << "+ secret=" << util::format_hex(secret) << "\n" - << "+ key=" << util::format_hex(key) << "\n" - << "+ iv=" << util::format_hex(iv) << std::endl; -} - -void print_hp_mask(std::span mask, - std::span sample) { - fprintf(outfile, "mask=%s sample=%s\n", util::format_hex(mask).c_str(), - util::format_hex(sample).c_str()); -} - -void log_printf(void *user_data, const char *fmt, ...) { - va_list ap; - std::array buf; - - va_start(ap, fmt); - auto n = vsnprintf(buf.data(), buf.size(), fmt, ap); - va_end(ap); - - if (static_cast(n) >= buf.size()) { - n = buf.size() - 1; - } - - buf[static_cast(n++)] = '\n'; - - while (write(fileno(stderr), buf.data(), static_cast(n)) == -1 && - errno == EINTR) - ; -} - -void path_validation(const ngtcp2_path *path, - ngtcp2_path_validation_result res) { - auto local_addr = util::straddr( - reinterpret_cast(path->local.addr), path->local.addrlen); - auto remote_addr = util::straddr( - reinterpret_cast(path->remote.addr), path->remote.addrlen); - - std::cerr << "Path validation against path {local:" << local_addr - << ", remote:" << remote_addr << "} " - << (res == NGTCP2_PATH_VALIDATION_RESULT_SUCCESS ? "succeeded" - : "failed") - << std::endl; -} - -void print_http_begin_request_headers(int64_t stream_id) { - fprintf(outfile, "http: stream 0x%" PRIx64 " request headers started\n", - stream_id); -} - -void print_http_begin_response_headers(int64_t stream_id) { - fprintf(outfile, "http: stream 0x%" PRIx64 " response headers started\n", - stream_id); -} - -namespace { -void print_header(std::span name, std::span value, - uint8_t flags) { - fprintf(outfile, "[%.*s: %.*s]%s\n", static_cast(name.size()), - name.data(), static_cast(value.size()), value.data(), - (flags & NGHTTP3_NV_FLAG_NEVER_INDEX) ? "(sensitive)" : ""); -} -} // namespace - -namespace { -void print_header(const nghttp3_rcbuf *name, const nghttp3_rcbuf *value, - uint8_t flags) { - auto namebuf = nghttp3_rcbuf_get_buf(name); - auto valuebuf = nghttp3_rcbuf_get_buf(value); - print_header({namebuf.base, namebuf.len}, {valuebuf.base, valuebuf.len}, - flags); -} -} // namespace - -namespace { -void print_header(const nghttp3_nv &nv) { - print_header({nv.name, nv.namelen}, {nv.value, nv.valuelen}, nv.flags); -} -} // namespace - -void print_http_header(int64_t stream_id, const nghttp3_rcbuf *name, - const nghttp3_rcbuf *value, uint8_t flags) { - fprintf(outfile, "http: stream 0x%" PRIx64 " ", stream_id); - print_header(name, value, flags); -} - -void print_http_end_headers(int64_t stream_id) { - fprintf(outfile, "http: stream 0x%" PRIx64 " headers ended\n", stream_id); -} - -void print_http_data(int64_t stream_id, std::span data) { - fprintf(outfile, "http: stream 0x%" PRIx64 " body %zu bytes\n", stream_id, - data.size()); - util::hexdump(outfile, data.data(), data.size()); -} - -void print_http_begin_trailers(int64_t stream_id) { - fprintf(outfile, "http: stream 0x%" PRIx64 " trailers started\n", stream_id); -} - -void print_http_end_trailers(int64_t stream_id) { - fprintf(outfile, "http: stream 0x%" PRIx64 " trailers ended\n", stream_id); -} - -void print_http_request_headers(int64_t stream_id, const nghttp3_nv *nva, - size_t nvlen) { - fprintf(outfile, "http: stream 0x%" PRIx64 " submit request headers\n", - stream_id); - for (size_t i = 0; i < nvlen; ++i) { - auto &nv = nva[i]; - print_header(nv); - } -} - -void print_http_response_headers(int64_t stream_id, const nghttp3_nv *nva, - size_t nvlen) { - fprintf(outfile, "http: stream 0x%" PRIx64 " submit response headers\n", - stream_id); - for (size_t i = 0; i < nvlen; ++i) { - auto &nv = nva[i]; - print_header(nv); - } -} - -void print_http_settings(const nghttp3_settings *settings) { - fprintf(outfile, - "http: remote settings\n" - "http: SETTINGS_MAX_FIELD_SECTION_SIZE=%" PRIu64 "\n" - "http: SETTINGS_QPACK_MAX_TABLE_CAPACITY=%zu\n" - "http: SETTINGS_QPACK_BLOCKED_STREAMS=%zu\n" - "http: SETTINGS_ENABLE_CONNECT_PROTOCOL=%d\n" - "http: SETTINGS_H3_DATAGRAM=%d\n", - settings->max_field_section_size, settings->qpack_max_dtable_capacity, - settings->qpack_blocked_streams, settings->enable_connect_protocol, - settings->h3_datagram); -} - -void print_http_origin(const uint8_t *origin, size_t originlen) { - fprintf(outfile, "http: origin [%.*s]\n", static_cast(originlen), - origin); -} - -void print_http_end_origin() { fprintf(outfile, "http: origin ended\n"); } - -std::string_view secret_title(ngtcp2_encryption_level level) { - switch (level) { - case NGTCP2_ENCRYPTION_LEVEL_0RTT: - return "early_traffic"sv; - case NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE: - return "handshake_traffic"sv; - case NGTCP2_ENCRYPTION_LEVEL_1RTT: - return "application_traffic"sv; - default: - assert(0); - abort(); - } -} - -void print_conn_info(ngtcp2_conn *conn) { - ngtcp2_conn_info cinfo; - - ngtcp2_conn_get_conn_info(conn, &cinfo); - - std::cout << "# Connection Statistics (see ngtcp2_conn_info for details)\n" - "min_rtt=" - << util::format_durationf(cinfo.min_rtt) << '\n' - << "smoothed_rtt=" << util::format_durationf(cinfo.smoothed_rtt) - << '\n' - << "rttvar=" << util::format_durationf(cinfo.rttvar) << '\n' - << "cwnd=" << cinfo.cwnd << '\n' - << "ssthresh=" << cinfo.ssthresh << '\n' - << "pkt_sent=" << cinfo.pkt_sent << '\n' - << "bytes_sent=" << cinfo.bytes_sent << '\n' - << "pkt_recv=" << cinfo.pkt_recv << '\n' - << "bytes_recv=" << cinfo.bytes_recv << '\n' - << "pkt_lost=" << cinfo.pkt_lost << '\n' - << "bytes_lost=" << cinfo.bytes_lost << '\n' - << "ping_recv=" << cinfo.ping_recv << '\n' - << "pkt_discarded=" << cinfo.pkt_discarded << std::endl; -} - -} // namespace debug - -} // namespace ngtcp2 diff --git a/src/ngtcp2/debug.h b/src/ngtcp2/debug.h deleted file mode 100644 index 2705e76..0000000 --- a/src/ngtcp2/debug.h +++ /dev/null @@ -1,133 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2017 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#ifndef DEBUG_H -#define DEBUG_H - -#ifdef HAVE_CONFIG_H -# include -#endif // defined(HAVE_CONFIG_H) - -#ifndef __STDC_FORMAT_MACROS -// For travis and PRIu64 -# define __STDC_FORMAT_MACROS -#endif // !defined(__STDC_FORMAT_MACROS) - -#include -#include -#include - -#include -#include - -namespace ngtcp2 { - -namespace debug { - -int handshake_completed(ngtcp2_conn *conn, void *user_data); - -int handshake_confirmed(ngtcp2_conn *conn, void *user_data); - -bool packet_lost(double prob); - -void print_crypto_data(ngtcp2_encryption_level encryption_level, - std::span data); - -void print_stream_data(int64_t stream_id, std::span data); - -void print_initial_secret(std::span data); - -void print_client_in_secret(std::span data); -void print_server_in_secret(std::span data); - -void print_handshake_secret(std::span data); - -void print_client_hs_secret(std::span data); -void print_server_hs_secret(std::span data); - -void print_client_0rtt_secret(std::span data); - -void print_client_1rtt_secret(std::span data); -void print_server_1rtt_secret(std::span data); - -void print_client_pp_key(std::span data); -void print_server_pp_key(std::span data); - -void print_client_pp_iv(std::span data); -void print_server_pp_iv(std::span data); - -void print_client_pp_hp(std::span data); -void print_server_pp_hp(std::span data); - -void print_secrets(std::span secret, - std::span key, std::span iv, - std::span hp); - -void print_secrets(std::span secret, - std::span key, std::span iv); - -void print_hp_mask(std::span mask, - std::span sample); - -void log_printf(void *user_data, const char *fmt, ...); - -void path_validation(const ngtcp2_path *path, - ngtcp2_path_validation_result res); - -void print_http_begin_request_headers(int64_t stream_id); - -void print_http_begin_response_headers(int64_t stream_id); - -void print_http_header(int64_t stream_id, const nghttp3_rcbuf *name, - const nghttp3_rcbuf *value, uint8_t flags); - -void print_http_end_headers(int64_t stream_id); - -void print_http_data(int64_t stream_id, std::span data); - -void print_http_begin_trailers(int64_t stream_id); - -void print_http_end_trailers(int64_t stream_id); - -void print_http_request_headers(int64_t stream_id, const nghttp3_nv *nva, - size_t nvlen); - -void print_http_response_headers(int64_t stream_id, const nghttp3_nv *nva, - size_t nvlen); - -void print_http_settings(const nghttp3_settings *settings); - -void print_http_origin(const uint8_t *origin, size_t originlen); - -void print_http_end_origin(); - -std::string_view secret_title(ngtcp2_encryption_level level); - -void print_conn_info(ngtcp2_conn *conn); - -} // namespace debug - -} // namespace ngtcp2 - -#endif // !defined(DEBUG_H) diff --git a/src/ngtcp2/http.cc b/src/ngtcp2/http.cc deleted file mode 100644 index 9467a18..0000000 --- a/src/ngtcp2/http.cc +++ /dev/null @@ -1,140 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2017 ngtcp2 contributors - * Copyright (c) 2012 nghttp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#include "http.h" - -using namespace std::literals; - -namespace ngtcp2 { - -namespace http { - -std::string_view get_reason_phrase(unsigned int status_code) { - switch (status_code) { - case 100: - return "Continue"sv; - case 101: - return "Switching Protocols"sv; - case 200: - return "OK"sv; - case 201: - return "Created"sv; - case 202: - return "Accepted"sv; - case 203: - return "Non-Authoritative Information"sv; - case 204: - return "No Content"sv; - case 205: - return "Reset Content"sv; - case 206: - return "Partial Content"sv; - case 300: - return "Multiple Choices"sv; - case 301: - return "Moved Permanently"sv; - case 302: - return "Found"sv; - case 303: - return "See Other"sv; - case 304: - return "Not Modified"sv; - case 305: - return "Use Proxy"sv; - // case 306: return "(Unused)"sv; - case 307: - return "Temporary Redirect"sv; - case 308: - return "Permanent Redirect"sv; - case 400: - return "Bad Request"sv; - case 401: - return "Unauthorized"sv; - case 402: - return "Payment Required"sv; - case 403: - return "Forbidden"sv; - case 404: - return "Not Found"sv; - case 405: - return "Method Not Allowed"sv; - case 406: - return "Not Acceptable"sv; - case 407: - return "Proxy Authentication Required"sv; - case 408: - return "Request Timeout"sv; - case 409: - return "Conflict"sv; - case 410: - return "Gone"sv; - case 411: - return "Length Required"sv; - case 412: - return "Precondition Failed"sv; - case 413: - return "Payload Too Large"sv; - case 414: - return "URI Too Long"sv; - case 415: - return "Unsupported Media Type"sv; - case 416: - return "Requested Range Not Satisfiable"sv; - case 417: - return "Expectation Failed"sv; - case 421: - return "Misdirected Request"sv; - case 426: - return "Upgrade Required"sv; - case 428: - return "Precondition Required"sv; - case 429: - return "Too Many Requests"sv; - case 431: - return "Request Header Fields Too Large"sv; - case 451: - return "Unavailable For Legal Reasons"sv; - case 500: - return "Internal Server Error"sv; - case 501: - return "Not Implemented"sv; - case 502: - return "Bad Gateway"sv; - case 503: - return "Service Unavailable"sv; - case 504: - return "Gateway Timeout"sv; - case 505: - return "HTTP Version Not Supported"sv; - case 511: - return "Network Authentication Required"sv; - default: - return ""sv; - } -} - -} // namespace http - -} // namespace ngtcp2 diff --git a/src/ngtcp2/http.h b/src/ngtcp2/http.h deleted file mode 100644 index 789ba86..0000000 --- a/src/ngtcp2/http.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2017 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#ifndef HTTP_H -#define HTTP_H - -#ifdef HAVE_CONFIG_H -# include -#endif // defined(HAVE_CONFIG_H) - -#include - -namespace ngtcp2 { - -namespace http { - -std::string_view get_reason_phrase(unsigned int status_code); - -} // namespace http - -} // namespace ngtcp2 - -#endif // !defined(HTTP_H) diff --git a/src/ngtcp2/network.h b/src/ngtcp2/network.h index 2224864..857c887 100644 --- a/src/ngtcp2/network.h +++ b/src/ngtcp2/network.h @@ -26,44 +26,12 @@ #ifndef NETWORK_H #define NETWORK_H -#ifdef HAVE_CONFIG_H -# include -#endif // defined(HAVE_CONFIG_H) - #include -#ifdef HAVE_SYS_SOCKET_H -# include -#endif // defined(HAVE_SYS_SOCKET_H) -#include -#ifdef HAVE_NETINET_IN_H -# include -#endif // defined(HAVE_NETINET_IN_H) -#ifdef HAVE_ARPA_INET_H -# include -#endif // defined(HAVE_ARPA_INET_H) - -#include - -#include - -#include +#include +#include namespace ngtcp2 { -enum network_error { - NETWORK_ERR_OK = 0, - NETWORK_ERR_FATAL = -10, - NETWORK_ERR_SEND_BLOCKED = -11, - NETWORK_ERR_CLOSE_WAIT = -12, - NETWORK_ERR_RETRY = -13, - NETWORK_ERR_DROP_CONN = -14, -}; - -union in_addr_union { - in_addr in; - in6_addr in6; -}; - union sockaddr_union { sockaddr_storage storage; sockaddr sa; diff --git a/src/ngtcp2/server.cc b/src/ngtcp2/server.cc deleted file mode 100644 index a46f7b8..0000000 --- a/src/ngtcp2/server.cc +++ /dev/null @@ -1,4094 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2017 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "server.h" -#include "network.h" -#include "debug.h" -#include "util.h" -#include "shared.h" -#include "http.h" -#include "template.h" - -using namespace ngtcp2; -using namespace std::literals; - -namespace { -constexpr size_t NGTCP2_SV_SCIDLEN = 18; -} // namespace - -namespace { -constexpr size_t max_preferred_versionslen = 4; -} // namespace - -namespace { -constexpr size_t NGTCP2_STATELESS_RESET_BURST = 100; -} // namespace - -namespace { -constexpr size_t NGTCP2_TX_BUFLEN = 64_k; -} // namespace - -namespace { -auto randgen = util::make_mt19937(); -} // namespace - -Config config{}; - -Stream::Stream(int64_t stream_id, Handler *handler) - : stream_id(stream_id), - handler(handler), - data(nullptr), - datalen(0), - dynresp(false), - dyndataleft(0), - dynbuflen(0) {} - -namespace { -constexpr auto NGTCP2_SERVER = "nghttp3/ngtcp2 server"sv; -} // namespace - -namespace { -std::string make_status_body(unsigned int status_code) { - auto status_string = util::format_uint(status_code); - auto reason_phrase = http::get_reason_phrase(status_code); - - std::string body; - body = ""; - body += status_string; - body += ' '; - body += reason_phrase; - body += "

"; - body += status_string; - body += ' '; - body += reason_phrase; - body += "


"; - body += NGTCP2_SERVER; - body += " at port "; - body += util::format_uint(config.port); - body += "
"; - body += ""; - return body; -} -} // namespace - -struct Request { - std::string path; - struct { - int32_t urgency; - int inc; - } pri; -}; - -namespace { -Request request_path(const std::string_view &uri, bool is_connect) { - urlparse_url u; - Request req{ - .pri = - { - .urgency = -1, - .inc = -1, - }, - }; - - if (auto rv = urlparse_parse_url(uri.data(), uri.size(), is_connect, &u); - rv != 0) { - return req; - } - - if (u.field_set & (1 << URLPARSE_PATH)) { - req.path = util::get_string(uri, u, URLPARSE_PATH); - if (req.path.find('%') != std::string::npos) { - req.path = util::percent_decode(req.path); - } - if (!req.path.empty() && req.path.back() == '/') { - req.path += "index.html"; - } - } else { - req.path = "/index.html"; - } - - req.path = util::normalize_path(req.path); - if (req.path == "/") { - req.path = "/index.html"; - } - - if (u.field_set & (1 << URLPARSE_QUERY)) { - static constexpr auto urgency_prefix = "u="sv; - static constexpr auto inc_prefix = "i="sv; - auto q = util::get_string(uri, u, URLPARSE_QUERY); - for (auto p = std::ranges::begin(q); p != std::ranges::end(q);) { - if (util::istarts_with(std::string_view{p, std::ranges::end(q)}, - urgency_prefix)) { - auto urgency_start = p + urgency_prefix.size(); - auto urgency_end = - std::ranges::find(urgency_start, std::ranges::end(q), '&'); - if (urgency_start + 1 == urgency_end && '0' <= *urgency_start && - *urgency_start <= '7') { - req.pri.urgency = *urgency_start - '0'; - } - if (urgency_end == std::ranges::end(q)) { - break; - } - p = urgency_end + 1; - continue; - } - if (util::istarts_with(std::string_view{p, std::ranges::end(q)}, - inc_prefix)) { - auto inc_start = p + inc_prefix.size(); - auto inc_end = std::ranges::find(inc_start, std::ranges::end(q), '&'); - if (inc_start + 1 == inc_end && - (*inc_start == '0' || *inc_start == '1')) { - req.pri.inc = *inc_start - '0'; - } - if (inc_end == std::ranges::end(q)) { - break; - } - p = inc_end + 1; - continue; - } - - p = std::ranges::find(p, std::ranges::end(q), '&'); - if (p == std::ranges::end(q)) { - break; - } - ++p; - } - } - return req; -} -} // namespace - -enum FileEntryFlag { - FILE_ENTRY_TYPE_DIR = 0x1, -}; - -struct FileEntry { - uint64_t len; - void *map; - int fd; - uint8_t flags; -}; - -namespace { -std::unordered_map file_cache; -} // namespace - -std::pair Stream::open_file(const std::string &path) { - auto it = file_cache.find(path); - if (it != std::ranges::end(file_cache)) { - return {(*it).second, 0}; - } - - auto fd = open(path.c_str(), O_RDONLY); - if (fd == -1) { - return {{}, -1}; - } - - struct stat st{}; - if (fstat(fd, &st) != 0) { - close(fd); - return {{}, -1}; - } - - FileEntry fe{}; - if (st.st_mode & S_IFDIR) { - fe.flags |= FILE_ENTRY_TYPE_DIR; - fe.fd = -1; - close(fd); - } else { - fe.fd = fd; - fe.len = static_cast(st.st_size); - if (fe.len) { - fe.map = mmap(nullptr, fe.len, PROT_READ, MAP_SHARED, fd, 0); - if (fe.map == MAP_FAILED) { - std::cerr << "mmap: " << strerror(errno) << std::endl; - close(fd); - return {{}, -1}; - } - } - } - - file_cache.emplace(path, fe); - - return {std::move(fe), 0}; -} - -void Stream::map_file(const FileEntry &fe) { - data = static_cast(fe.map); - datalen = fe.len; -} - -int64_t Stream::find_dyn_length(const std::string_view &path) { - assert(path[0] == '/'); - - if (path.size() == 1) { - return -1; - } - - uint64_t n = 0; - - for (auto it = std::ranges::begin(path) + 1; it != std::ranges::end(path); - ++it) { - if (*it < '0' || '9' < *it) { - return -1; - } - auto d = static_cast(*it - '0'); - if (n > (((1ull << 62) - 1) - d) / 10) { - return -1; - } - n = n * 10 + d; - if (n > config.max_dyn_length) { - return -1; - } - } - - return static_cast(n); -} - -namespace { -nghttp3_ssize read_data(nghttp3_conn *conn, int64_t stream_id, nghttp3_vec *vec, - size_t veccnt, uint32_t *pflags, void *user_data, - void *stream_user_data) { - auto stream = static_cast(stream_user_data); - - vec[0].base = stream->data; - vec[0].len = stream->datalen; - *pflags |= NGHTTP3_DATA_FLAG_EOF; - if (config.send_trailers) { - *pflags |= NGHTTP3_DATA_FLAG_NO_END_STREAM; - } - - return 1; -} -} // namespace - -auto dyn_buf = std::make_unique>(); - -namespace { -nghttp3_ssize dyn_read_data(nghttp3_conn *conn, int64_t stream_id, - nghttp3_vec *vec, size_t veccnt, uint32_t *pflags, - void *user_data, void *stream_user_data) { - auto stream = static_cast(stream_user_data); - - auto len = - std::min(dyn_buf->size(), static_cast(stream->dyndataleft)); - - vec[0].base = dyn_buf->data(); - vec[0].len = len; - - stream->dynbuflen += len; - stream->dyndataleft -= len; - - if (stream->dyndataleft == 0) { - *pflags |= NGHTTP3_DATA_FLAG_EOF; - if (config.send_trailers) { - *pflags |= NGHTTP3_DATA_FLAG_NO_END_STREAM; - auto stream_id_str = util::format_uint(as_unsigned(stream_id)); - auto trailers = std::to_array({ - util::make_nv_nc("x-ngtcp2-stream-id"sv, stream_id_str), - }); - - if (auto rv = nghttp3_conn_submit_trailers( - conn, stream_id, trailers.data(), trailers.size()); - rv != 0) { - std::cerr << "nghttp3_conn_submit_trailers: " << nghttp3_strerror(rv) - << std::endl; - return NGHTTP3_ERR_CALLBACK_FAILURE; - } - } - } - - return 1; -} -} // namespace - -void Stream::http_acked_stream_data(uint64_t datalen) { - if (!dynresp) { - return; - } - - assert(dynbuflen >= datalen); - - dynbuflen -= datalen; -} - -int Stream::send_status_response(nghttp3_conn *httpconn, - unsigned int status_code, - const std::vector &extra_headers) { - status_resp_body = make_status_body(status_code); - - auto status_code_str = util::format_uint(status_code); - auto content_length_str = util::format_uint(status_resp_body.size()); - - std::vector nva(4 + extra_headers.size()); - nva[0] = util::make_nv_nc(":status"sv, status_code_str); - nva[1] = util::make_nv_nn("server"sv, NGTCP2_SERVER); - nva[2] = util::make_nv_nn("content-type"sv, "text/html; charset=utf-8"); - nva[3] = util::make_nv_nc("content-length"sv, content_length_str); - for (size_t i = 0; i < extra_headers.size(); ++i) { - auto &hdr = extra_headers[i]; - auto &nv = nva[4 + i]; - nv = util::make_nv_cc(hdr.name, hdr.value); - } - - data = const_cast( - reinterpret_cast(status_resp_body.data())); - datalen = status_resp_body.size(); - - nghttp3_data_reader dr{ - .read_data = read_data, - }; - - if (auto rv = nghttp3_conn_submit_response(httpconn, stream_id, nva.data(), - nva.size(), &dr); - rv != 0) { - std::cerr << "nghttp3_conn_submit_response: " << nghttp3_strerror(rv) - << std::endl; - return -1; - } - - if (config.send_trailers) { - auto stream_id_str = util::format_uint(as_unsigned(stream_id)); - auto trailers = std::to_array({ - util::make_nv_nc("x-ngtcp2-stream-id"sv, stream_id_str), - }); - - if (auto rv = nghttp3_conn_submit_trailers( - httpconn, stream_id, trailers.data(), trailers.size()); - rv != 0) { - std::cerr << "nghttp3_conn_submit_trailers: " << nghttp3_strerror(rv) - << std::endl; - return -1; - } - } - - handler->shutdown_read(stream_id, NGHTTP3_H3_NO_ERROR); - - return 0; -} - -int Stream::send_redirect_response(nghttp3_conn *httpconn, - unsigned int status_code, - const std::string_view &path) { - return send_status_response(httpconn, status_code, {{"location", path}}); -} - -int Stream::start_response(nghttp3_conn *httpconn) { - // TODO This should be handled by nghttp3 - if (uri.empty() || method.empty()) { - return send_status_response(httpconn, 400); - } - - auto req = request_path(uri, method == "CONNECT"); - if (req.path.empty()) { - return send_status_response(httpconn, 400); - } - - auto dyn_len = find_dyn_length(req.path); - - int64_t content_length = -1; - nghttp3_data_reader dr{}; - auto content_type = "text/plain"sv; - - if (dyn_len == -1) { - auto path = config.htdocs + req.path; - auto [fe, rv] = open_file(path); - if (rv != 0) { - send_status_response(httpconn, 404); - return 0; - } - - if (fe.flags & FILE_ENTRY_TYPE_DIR) { - send_redirect_response(httpconn, 308, - path.substr(config.htdocs.size() - 1) + '/'); - return 0; - } - - content_length = static_cast(fe.len); - - if (method != "HEAD") { - map_file(fe); - } - - dr.read_data = read_data; - - auto ext = std::ranges::end(req.path) - 1; - for (; ext != std::ranges::begin(req.path) && *ext != '.' && *ext != '/'; - --ext) - ; - if (*ext == '.') { - ++ext; - auto it = - config.mime_types.find(std::string{ext, std::ranges::end(req.path)}); - if (it != std::ranges::end(config.mime_types)) { - content_type = (*it).second; - } - } - } else { - content_length = dyn_len; - dynresp = true; - dr.read_data = dyn_read_data; - - if (method != "HEAD") { - datalen = as_unsigned(dyn_len); - dyndataleft = as_unsigned(dyn_len); - } - - content_type = "application/octet-stream"sv; - } - - auto content_length_str = util::format_uint(as_unsigned(content_length)); - - std::array nva{ - util::make_nv_nn(":status"sv, "200"sv), - util::make_nv_nn("server"sv, NGTCP2_SERVER), - util::make_nv_nn("content-type"sv, content_type), - util::make_nv_nc("content-length"sv, content_length_str), - }; - - size_t nvlen = 4; - - std::string prival; - - if (req.pri.urgency != -1 || req.pri.inc != -1) { - nghttp3_pri pri; - - if (auto rv = nghttp3_conn_get_stream_priority(httpconn, &pri, stream_id); - rv != 0) { - std::cerr << "nghttp3_conn_get_stream_priority: " << nghttp3_strerror(rv) - << std::endl; - return -1; - } - - if (req.pri.urgency != -1) { - pri.urgency = as_unsigned(req.pri.urgency); - } - if (req.pri.inc != -1) { - pri.inc = static_cast(req.pri.inc); - } - - if (auto rv = - nghttp3_conn_set_server_stream_priority(httpconn, stream_id, &pri); - rv != 0) { - std::cerr << "nghttp3_conn_set_stream_priority: " << nghttp3_strerror(rv) - << std::endl; - return -1; - } - - prival = "u="; - prival += static_cast(pri.urgency + '0'); - prival += ",i"; - if (!pri.inc) { - prival += "=?0"; - } - - nva[nvlen++] = util::make_nv_nc("priority"sv, prival); - } - - if (!config.quiet) { - debug::print_http_response_headers(stream_id, nva.data(), nvlen); - } - - if (auto rv = nghttp3_conn_submit_response(httpconn, stream_id, nva.data(), - nvlen, &dr); - rv != 0) { - std::cerr << "nghttp3_conn_submit_response: " << nghttp3_strerror(rv) - << std::endl; - return -1; - } - - if (config.send_trailers && dyn_len == -1) { - auto stream_id_str = util::format_uint(as_unsigned(stream_id)); - auto trailers = std::to_array({ - util::make_nv_nc("x-ngtcp2-stream-id"sv, stream_id_str), - }); - - if (auto rv = nghttp3_conn_submit_trailers( - httpconn, stream_id, trailers.data(), trailers.size()); - rv != 0) { - std::cerr << "nghttp3_conn_submit_trailers: " << nghttp3_strerror(rv) - << std::endl; - return -1; - } - } - - return 0; -} - -namespace { -void writecb(struct ev_loop *loop, ev_io *w, int revents) { - auto h = static_cast(w->data); - auto s = h->server(); - - switch (h->on_write()) { - case 0: - case NETWORK_ERR_CLOSE_WAIT: - return; - default: - s->remove(h); - } -} -} // namespace - -namespace { -void close_waitcb(struct ev_loop *loop, ev_timer *w, int revents) { - auto h = static_cast(w->data); - auto s = h->server(); - auto conn = h->conn(); - - if (ngtcp2_conn_in_closing_period(conn)) { - if (!config.quiet) { - std::cerr << "Closing Period is over" << std::endl; - } - - s->remove(h); - return; - } - if (ngtcp2_conn_in_draining_period(conn)) { - if (!config.quiet) { - std::cerr << "Draining Period is over" << std::endl; - } - - s->remove(h); - return; - } - - assert(0); -} -} // namespace - -namespace { -void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { - int rv; - - auto h = static_cast(w->data); - auto s = h->server(); - - if (!config.quiet) { - std::cerr << "Timer expired" << std::endl; - } - - rv = h->handle_expiry(); - if (rv != 0) { - goto fail; - } - - h->signal_write(); - - return; - -fail: - switch (rv) { - case NETWORK_ERR_CLOSE_WAIT: - ev_timer_stop(loop, w); - return; - default: - s->remove(h); - return; - } -} -} // namespace - -Handler::Handler(struct ev_loop *loop, Server *server) - : loop_(loop), - server_(server), - qlog_(nullptr), - scid_{}, - httpconn_{nullptr}, - nkey_update_(0), - no_gso_{ -#ifdef UDP_SEGMENT - config.no_gso -#else // !defined(UDP_SEGMENT) - true -#endif // !defined(UDP_SEGMENT) - }, - close_wait_{ - .next_pkts_recv = 1, - }, - tx_{ - .data = std::make_unique_for_overwrite(NGTCP2_TX_BUFLEN), - } { - ev_io_init(&wev_, writecb, 0, EV_WRITE); - wev_.data = this; - ev_timer_init(&timer_, timeoutcb, 0., 0.); - timer_.data = this; -} - -Handler::~Handler() { - if (!config.quiet) { - std::cerr << scid_ << " Closing QUIC connection " << std::endl; - } - - ev_timer_stop(loop_, &timer_); - ev_io_stop(loop_, &wev_); - - if (httpconn_) { - nghttp3_conn_del(httpconn_); - } - - if (qlog_) { - fclose(qlog_); - } -} - -namespace { -int handshake_completed(ngtcp2_conn *conn, void *user_data) { - auto h = static_cast(user_data); - - if (!config.quiet) { - debug::handshake_completed(conn, user_data); - } - - if (h->handshake_completed() != 0) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} -} // namespace - -int Handler::handshake_completed() { - if (!config.quiet) { - std::cerr << "Negotiated cipher suite is " << tls_session_.get_cipher_name() - << std::endl; - if (auto group = tls_session_.get_negotiated_group(); !group.empty()) { - std::cerr << "Negotiated group is " << group << std::endl; - } - std::cerr << "Negotiated ALPN is " << tls_session_.get_selected_alpn() - << std::endl; - } - - if (tls_session_.send_session_ticket() != 0) { - std::cerr << "Unable to send session ticket" << std::endl; - } - - std::array token; - - auto path = ngtcp2_conn_get_path(conn_); - auto t = util::system_clock_now(); - - auto tokenlen = ngtcp2_crypto_generate_regular_token( - token.data(), config.static_secret.data(), config.static_secret.size(), - path->remote.addr, path->remote.addrlen, t); - if (tokenlen < 0) { - if (!config.quiet) { - std::cerr << "Unable to generate token" << std::endl; - } - return 0; - } - - if (auto rv = ngtcp2_conn_submit_new_token(conn_, token.data(), - as_unsigned(tokenlen)); - rv != 0) { - if (!config.quiet) { - std::cerr << "ngtcp2_conn_submit_new_token: " << ngtcp2_strerror(rv) - << std::endl; - } - return -1; - } - - return 0; -} - -namespace { -int do_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp, - const ngtcp2_crypto_cipher_ctx *hp_ctx, const uint8_t *sample) { - if (ngtcp2_crypto_hp_mask(dest, hp, hp_ctx, sample) != 0) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - if (!config.quiet && config.show_secret) { - debug::print_hp_mask({dest, NGTCP2_HP_MASKLEN}, - {sample, NGTCP2_HP_SAMPLELEN}); - } - - return 0; -} -} // namespace - -namespace { -int recv_crypto_data(ngtcp2_conn *conn, - ngtcp2_encryption_level encryption_level, uint64_t offset, - const uint8_t *data, size_t datalen, void *user_data) { - if (!config.quiet && !config.no_quic_dump) { - debug::print_crypto_data(encryption_level, {data, datalen}); - } - - return ngtcp2_crypto_recv_crypto_data_cb(conn, encryption_level, offset, data, - datalen, user_data); -} -} // namespace - -namespace { -int recv_stream_data(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id, - uint64_t offset, const uint8_t *data, size_t datalen, - void *user_data, void *stream_user_data) { - auto h = static_cast(user_data); - - if (h->recv_stream_data(flags, stream_id, {data, datalen}) != 0) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} -} // namespace - -namespace { -int acked_stream_data_offset(ngtcp2_conn *conn, int64_t stream_id, - uint64_t offset, uint64_t datalen, void *user_data, - void *stream_user_data) { - auto h = static_cast(user_data); - if (h->acked_stream_data_offset(stream_id, datalen) != 0) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - return 0; -} -} // namespace - -int Handler::acked_stream_data_offset(int64_t stream_id, uint64_t datalen) { - if (!httpconn_) { - return 0; - } - - if (auto rv = nghttp3_conn_add_ack_offset(httpconn_, stream_id, datalen); - rv != 0) { - std::cerr << "nghttp3_conn_add_ack_offset: " << nghttp3_strerror(rv) - << std::endl; - return -1; - } - - return 0; -} - -namespace { -int stream_open(ngtcp2_conn *conn, int64_t stream_id, void *user_data) { - auto h = static_cast(user_data); - h->on_stream_open(stream_id); - return 0; -} -} // namespace - -void Handler::on_stream_open(int64_t stream_id) { - if (!ngtcp2_is_bidi_stream(stream_id)) { - return; - } - - assert(!streams_.contains(stream_id)); - - streams_.emplace(stream_id, std::make_unique(stream_id, this)); -} - -namespace { -int stream_close(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id, - uint64_t app_error_code, void *user_data, - void *stream_user_data) { - auto h = static_cast(user_data); - - if (!(flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)) { - app_error_code = NGHTTP3_H3_NO_ERROR; - } - - if (h->on_stream_close(stream_id, app_error_code) != 0) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - return 0; -} -} // namespace - -namespace { -int stream_reset(ngtcp2_conn *conn, int64_t stream_id, uint64_t final_size, - uint64_t app_error_code, void *user_data, - void *stream_user_data) { - auto h = static_cast(user_data); - if (h->on_stream_reset(stream_id) != 0) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - return 0; -} -} // namespace - -int Handler::on_stream_reset(int64_t stream_id) { - if (httpconn_) { - if (auto rv = nghttp3_conn_shutdown_stream_read(httpconn_, stream_id); - rv != 0) { - std::cerr << "nghttp3_conn_shutdown_stream_read: " << nghttp3_strerror(rv) - << std::endl; - return -1; - } - } - return 0; -} - -namespace { -int stream_stop_sending(ngtcp2_conn *conn, int64_t stream_id, - uint64_t app_error_code, void *user_data, - void *stream_user_data) { - auto h = static_cast(user_data); - if (h->on_stream_stop_sending(stream_id) != 0) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - return 0; -} -} // namespace - -int Handler::on_stream_stop_sending(int64_t stream_id) { - if (!httpconn_) { - return 0; - } - - if (auto rv = nghttp3_conn_shutdown_stream_read(httpconn_, stream_id); - rv != 0) { - std::cerr << "nghttp3_conn_shutdown_stream_read: " << nghttp3_strerror(rv) - << std::endl; - return -1; - } - - return 0; -} - -namespace { -void rand_bytes(uint8_t *dest, size_t destlen) { - auto rv = util::generate_secure_random({dest, destlen}); - if (rv != 0) { - assert(0); - abort(); - } -} -} // namespace - -namespace { -void rand(uint8_t *dest, size_t destlen, const ngtcp2_rand_ctx *rand_ctx) { - rand_bytes(dest, destlen); -} -} // namespace - -namespace { -int get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, uint8_t *token, - size_t cidlen, void *user_data) { - if (util::generate_secure_random({cid->data, cidlen}) != 0) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - cid->datalen = cidlen; - if (ngtcp2_crypto_generate_stateless_reset_token( - token, config.static_secret.data(), config.static_secret.size(), cid) != - 0) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - auto h = static_cast(user_data); - h->server()->associate_cid(cid, h); - - return 0; -} -} // namespace - -namespace { -int remove_connection_id(ngtcp2_conn *conn, const ngtcp2_cid *cid, - void *user_data) { - auto h = static_cast(user_data); - h->server()->dissociate_cid(cid); - return 0; -} -} // namespace - -namespace { -int update_key(ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret, - ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv, - ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv, - const uint8_t *current_rx_secret, - const uint8_t *current_tx_secret, size_t secretlen, - void *user_data) { - auto h = static_cast(user_data); - if (h->update_key(rx_secret, tx_secret, rx_aead_ctx, rx_iv, tx_aead_ctx, - tx_iv, current_rx_secret, current_tx_secret, - secretlen) != 0) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - return 0; -} -} // namespace - -namespace { -int path_validation(ngtcp2_conn *conn, uint32_t flags, const ngtcp2_path *path, - const ngtcp2_path *old_path, - ngtcp2_path_validation_result res, void *user_data) { - if (!config.quiet) { - debug::path_validation(path, res); - } - - if (res != NGTCP2_PATH_VALIDATION_RESULT_SUCCESS || - !(flags & NGTCP2_PATH_VALIDATION_FLAG_NEW_TOKEN)) { - return 0; - } - - std::array token; - auto t = util::system_clock_now(); - - auto tokenlen = ngtcp2_crypto_generate_regular_token( - token.data(), config.static_secret.data(), config.static_secret.size(), - path->remote.addr, path->remote.addrlen, t); - if (tokenlen < 0) { - if (!config.quiet) { - std::cerr << "Unable to generate token" << std::endl; - } - - return 0; - } - - if (auto rv = - ngtcp2_conn_submit_new_token(conn, token.data(), as_unsigned(tokenlen)); - rv != 0) { - if (!config.quiet) { - std::cerr << "ngtcp2_conn_submit_new_token: " << ngtcp2_strerror(rv) - << std::endl; - } - - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} -} // namespace - -namespace { -int extend_max_remote_streams_bidi(ngtcp2_conn *conn, uint64_t max_streams, - void *user_data) { - auto h = static_cast(user_data); - h->extend_max_remote_streams_bidi(max_streams); - return 0; -} -} // namespace - -void Handler::extend_max_remote_streams_bidi(uint64_t max_streams) { - if (!httpconn_) { - return; - } - - nghttp3_conn_set_max_client_streams_bidi(httpconn_, max_streams); -} - -namespace { -int http_recv_data(nghttp3_conn *conn, int64_t stream_id, const uint8_t *data, - size_t datalen, void *user_data, void *stream_user_data) { - if (!config.quiet && !config.no_http_dump) { - debug::print_http_data(stream_id, {data, datalen}); - } - auto h = static_cast(user_data); - h->http_consume(stream_id, datalen); - return 0; -} -} // namespace - -namespace { -int http_deferred_consume(nghttp3_conn *conn, int64_t stream_id, - size_t nconsumed, void *user_data, - void *stream_user_data) { - auto h = static_cast(user_data); - h->http_consume(stream_id, nconsumed); - return 0; -} -} // namespace - -void Handler::http_consume(int64_t stream_id, size_t nconsumed) { - ngtcp2_conn_extend_max_stream_offset(conn_, stream_id, nconsumed); - ngtcp2_conn_extend_max_offset(conn_, nconsumed); -} - -namespace { -int http_begin_request_headers(nghttp3_conn *conn, int64_t stream_id, - void *user_data, void *stream_user_data) { - if (!config.quiet) { - debug::print_http_begin_request_headers(stream_id); - } - - auto h = static_cast(user_data); - h->http_begin_request_headers(stream_id); - return 0; -} -} // namespace - -void Handler::http_begin_request_headers(int64_t stream_id) { - auto it = streams_.find(stream_id); - assert(it != std::ranges::end(streams_)); - auto &stream = (*it).second; - - nghttp3_conn_set_stream_user_data(httpconn_, stream_id, stream.get()); -} - -namespace { -int http_recv_request_header(nghttp3_conn *conn, int64_t stream_id, - int32_t token, nghttp3_rcbuf *name, - nghttp3_rcbuf *value, uint8_t flags, - void *user_data, void *stream_user_data) { - if (!config.quiet) { - debug::print_http_header(stream_id, name, value, flags); - } - - auto h = static_cast(user_data); - auto stream = static_cast(stream_user_data); - h->http_recv_request_header(stream, token, name, value); - return 0; -} -} // namespace - -void Handler::http_recv_request_header(Stream *stream, int32_t token, - nghttp3_rcbuf *name, - nghttp3_rcbuf *value) { - auto v = nghttp3_rcbuf_get_buf(value); - - switch (token) { - case NGHTTP3_QPACK_TOKEN__PATH: - stream->uri = std::string{v.base, v.base + v.len}; - break; - case NGHTTP3_QPACK_TOKEN__METHOD: - stream->method = std::string{v.base, v.base + v.len}; - break; - case NGHTTP3_QPACK_TOKEN__AUTHORITY: - stream->authority = std::string{v.base, v.base + v.len}; - break; - } -} - -namespace { -int http_end_request_headers(nghttp3_conn *conn, int64_t stream_id, int fin, - void *user_data, void *stream_user_data) { - if (!config.quiet) { - debug::print_http_end_headers(stream_id); - } - - auto h = static_cast(user_data); - auto stream = static_cast(stream_user_data); - if (h->http_end_request_headers(stream) != 0) { - return NGHTTP3_ERR_CALLBACK_FAILURE; - } - return 0; -} -} // namespace - -int Handler::http_end_request_headers(Stream *stream) { - if (config.early_response) { - if (start_response(stream) != 0) { - return -1; - } - - shutdown_read(stream->stream_id, NGHTTP3_H3_NO_ERROR); - } - return 0; -} - -namespace { -int http_end_stream(nghttp3_conn *conn, int64_t stream_id, void *user_data, - void *stream_user_data) { - auto h = static_cast(user_data); - auto stream = static_cast(stream_user_data); - if (h->http_end_stream(stream) != 0) { - return NGHTTP3_ERR_CALLBACK_FAILURE; - } - return 0; -} -} // namespace - -int Handler::http_end_stream(Stream *stream) { - if (!config.early_response) { - return start_response(stream); - } - return 0; -} - -int Handler::start_response(Stream *stream) { - return stream->start_response(httpconn_); -} - -namespace { -int http_acked_stream_data(nghttp3_conn *conn, int64_t stream_id, - uint64_t datalen, void *user_data, - void *stream_user_data) { - auto h = static_cast(user_data); - auto stream = static_cast(stream_user_data); - h->http_acked_stream_data(stream, datalen); - return 0; -} -} // namespace - -void Handler::http_acked_stream_data(Stream *stream, uint64_t datalen) { - stream->http_acked_stream_data(datalen); -} - -void Handler::http_stream_close(int64_t stream_id, uint64_t app_error_code) { - if (!ngtcp2_is_bidi_stream(stream_id)) { - return; - } - - assert(!ngtcp2_conn_is_local_stream(conn_, stream_id)); - ngtcp2_conn_extend_max_streams_bidi(conn_, 1); - - auto it = streams_.find(stream_id); - if (it == std::ranges::end(streams_)) { - return; - } - - if (!config.quiet) { - std::cerr << "HTTP stream " << stream_id << " closed with error code " - << app_error_code << std::endl; - } - - streams_.erase(it); -} - -namespace { -int http_stop_sending(nghttp3_conn *conn, int64_t stream_id, - uint64_t app_error_code, void *user_data, - void *stream_user_data) { - auto h = static_cast(user_data); - if (h->http_stop_sending(stream_id, app_error_code) != 0) { - return NGHTTP3_ERR_CALLBACK_FAILURE; - } - return 0; -} -} // namespace - -int Handler::http_stop_sending(int64_t stream_id, uint64_t app_error_code) { - if (auto rv = - ngtcp2_conn_shutdown_stream_read(conn_, 0, stream_id, app_error_code); - rv != 0) { - std::cerr << "ngtcp2_conn_shutdown_stream_read: " << ngtcp2_strerror(rv) - << std::endl; - return -1; - } - return 0; -} - -namespace { -int http_reset_stream(nghttp3_conn *conn, int64_t stream_id, - uint64_t app_error_code, void *user_data, - void *stream_user_data) { - auto h = static_cast(user_data); - if (h->http_reset_stream(stream_id, app_error_code) != 0) { - return NGHTTP3_ERR_CALLBACK_FAILURE; - } - return 0; -} -} // namespace - -int Handler::http_reset_stream(int64_t stream_id, uint64_t app_error_code) { - if (auto rv = - ngtcp2_conn_shutdown_stream_write(conn_, 0, stream_id, app_error_code); - rv != 0) { - std::cerr << "ngtcp2_conn_shutdown_stream_write: " << ngtcp2_strerror(rv) - << std::endl; - return -1; - } - return 0; -} - -namespace { -int http_recv_settings(nghttp3_conn *conn, const nghttp3_settings *settings, - void *conn_user_data) { - if (!config.quiet) { - debug::print_http_settings(settings); - } - - return 0; -} -} // namespace - -int Handler::setup_httpconn() { - if (httpconn_) { - return 0; - } - - if (ngtcp2_conn_get_streams_uni_left(conn_) < 3) { - std::cerr << "peer does not allow at least 3 unidirectional streams." - << std::endl; - return -1; - } - - nghttp3_callbacks callbacks{ - .acked_stream_data = ::http_acked_stream_data, - .recv_data = ::http_recv_data, - .deferred_consume = ::http_deferred_consume, - .begin_headers = ::http_begin_request_headers, - .recv_header = ::http_recv_request_header, - .end_headers = ::http_end_request_headers, - .stop_sending = ::http_stop_sending, - .end_stream = ::http_end_stream, - .reset_stream = ::http_reset_stream, - .recv_settings = ::http_recv_settings, - .rand = rand_bytes, - }; - nghttp3_settings settings; - nghttp3_settings_default(&settings); - settings.qpack_max_dtable_capacity = 4096; - settings.qpack_blocked_streams = 100; - - nghttp3_vec origin_list; - - if (config.origin_list) { - origin_list.base = config.origin_list->data(); - origin_list.len = config.origin_list->size(); - - settings.origin_list = &origin_list; - } - - auto mem = nghttp3_mem_default(); - - if (auto rv = - nghttp3_conn_server_new(&httpconn_, &callbacks, &settings, mem, this); - rv != 0) { - std::cerr << "nghttp3_conn_server_new: " << nghttp3_strerror(rv) - << std::endl; - return -1; - } - - auto params = ngtcp2_conn_get_local_transport_params(conn_); - - nghttp3_conn_set_max_client_streams_bidi(httpconn_, - params->initial_max_streams_bidi); - - int64_t ctrl_stream_id; - - if (auto rv = ngtcp2_conn_open_uni_stream(conn_, &ctrl_stream_id, nullptr); - rv != 0) { - std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv) - << std::endl; - return -1; - } - - if (auto rv = nghttp3_conn_bind_control_stream(httpconn_, ctrl_stream_id); - rv != 0) { - std::cerr << "nghttp3_conn_bind_control_stream: " << nghttp3_strerror(rv) - << std::endl; - return -1; - } - - if (!config.quiet) { - fprintf(stderr, "http: control stream=%" PRIx64 "\n", ctrl_stream_id); - } - - int64_t qpack_enc_stream_id, qpack_dec_stream_id; - - if (auto rv = - ngtcp2_conn_open_uni_stream(conn_, &qpack_enc_stream_id, nullptr); - rv != 0) { - std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv) - << std::endl; - return -1; - } - - if (auto rv = - ngtcp2_conn_open_uni_stream(conn_, &qpack_dec_stream_id, nullptr); - rv != 0) { - std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv) - << std::endl; - return -1; - } - - if (auto rv = nghttp3_conn_bind_qpack_streams(httpconn_, qpack_enc_stream_id, - qpack_dec_stream_id); - rv != 0) { - std::cerr << "nghttp3_conn_bind_qpack_streams: " << nghttp3_strerror(rv) - << std::endl; - return -1; - } - - if (!config.quiet) { - fprintf(stderr, - "http: QPACK streams encoder=%" PRIx64 " decoder=%" PRIx64 "\n", - qpack_enc_stream_id, qpack_dec_stream_id); - } - - return 0; -} - -namespace { -int extend_max_stream_data(ngtcp2_conn *conn, int64_t stream_id, - uint64_t max_data, void *user_data, - void *stream_user_data) { - auto h = static_cast(user_data); - if (h->extend_max_stream_data(stream_id, max_data) != 0) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - return 0; -} -} // namespace - -int Handler::extend_max_stream_data(int64_t stream_id, uint64_t max_data) { - if (auto rv = nghttp3_conn_unblock_stream(httpconn_, stream_id); rv != 0) { - std::cerr << "nghttp3_conn_unblock_stream: " << nghttp3_strerror(rv) - << std::endl; - return -1; - } - return 0; -} - -namespace { -int recv_tx_key(ngtcp2_conn *conn, ngtcp2_encryption_level level, - void *user_data) { - if (level != NGTCP2_ENCRYPTION_LEVEL_1RTT) { - return 0; - } - - auto h = static_cast(user_data); - if (h->setup_httpconn() != 0) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} -} // namespace - -namespace { -void write_qlog(void *user_data, uint32_t flags, const void *data, - size_t datalen) { - auto h = static_cast(user_data); - h->write_qlog(data, datalen); -} -} // namespace - -void Handler::write_qlog(const void *data, size_t datalen) { - assert(qlog_); - fwrite(data, 1, datalen, qlog_); -} - -int Handler::init(const Endpoint &ep, const Address &local_addr, - const sockaddr *sa, socklen_t salen, const ngtcp2_cid *dcid, - const ngtcp2_cid *scid, const ngtcp2_cid *ocid, - std::span token, ngtcp2_token_type token_type, - uint32_t version, TLSServerContext &tls_ctx) { - auto callbacks = ngtcp2_callbacks{ - .recv_client_initial = ngtcp2_crypto_recv_client_initial_cb, - .recv_crypto_data = ::recv_crypto_data, - .handshake_completed = ::handshake_completed, - .encrypt = ngtcp2_crypto_encrypt_cb, - .decrypt = ngtcp2_crypto_decrypt_cb, - .hp_mask = do_hp_mask, - .recv_stream_data = ::recv_stream_data, - .acked_stream_data_offset = ::acked_stream_data_offset, - .stream_open = stream_open, - .stream_close = stream_close, - .rand = rand, - .get_new_connection_id = get_new_connection_id, - .remove_connection_id = remove_connection_id, - .update_key = ::update_key, - .path_validation = path_validation, - .stream_reset = ::stream_reset, - .extend_max_remote_streams_bidi = ::extend_max_remote_streams_bidi, - .extend_max_stream_data = ::extend_max_stream_data, - .delete_crypto_aead_ctx = ngtcp2_crypto_delete_crypto_aead_ctx_cb, - .delete_crypto_cipher_ctx = ngtcp2_crypto_delete_crypto_cipher_ctx_cb, - .get_path_challenge_data = ngtcp2_crypto_get_path_challenge_data_cb, - .stream_stop_sending = stream_stop_sending, - .version_negotiation = ngtcp2_crypto_version_negotiation_cb, - .recv_tx_key = ::recv_tx_key, - }; - - scid_.datalen = NGTCP2_SV_SCIDLEN; - if (util::generate_secure_random({scid_.data, scid_.datalen}) != 0) { - std::cerr << "Could not generate connection ID" << std::endl; - return -1; - } - - ngtcp2_settings settings; - ngtcp2_settings_default(&settings); - settings.log_printf = config.quiet ? nullptr : debug::log_printf; - settings.initial_ts = util::timestamp(); - settings.token = token.data(); - settings.tokenlen = token.size(); - settings.token_type = token_type; - settings.cc_algo = config.cc_algo; - settings.initial_rtt = config.initial_rtt; - settings.max_window = config.max_window; - settings.max_stream_window = config.max_stream_window; - settings.handshake_timeout = config.handshake_timeout; - settings.no_pmtud = config.no_pmtud; - settings.ack_thresh = config.ack_thresh; - if (config.max_udp_payload_size) { - settings.max_tx_udp_payload_size = config.max_udp_payload_size; - settings.no_tx_udp_payload_size_shaping = 1; - } - if (!config.qlog_dir.empty()) { - auto path = std::string{config.qlog_dir}; - path += '/'; - path += util::format_hex(scid_.data, as_signed(scid_.datalen)); - path += ".sqlog"; - qlog_ = fopen(path.c_str(), "w"); - if (qlog_ == nullptr) { - std::cerr << "Could not open qlog file " << std::quoted(path) << ": " - << strerror(errno) << std::endl; - return -1; - } - settings.qlog_write = ::write_qlog; - } - if (!config.preferred_versions.empty()) { - settings.preferred_versions = config.preferred_versions.data(); - settings.preferred_versionslen = config.preferred_versions.size(); - } - if (!config.available_versions.empty()) { - settings.available_versions = config.available_versions.data(); - settings.available_versionslen = config.available_versions.size(); - } - if (config.initial_pkt_num == UINT32_MAX) { - auto dis = std::uniform_int_distribution(0, INT32_MAX); - settings.initial_pkt_num = dis(randgen); - } else { - settings.initial_pkt_num = config.initial_pkt_num; - } - - if (!config.pmtud_probes.empty()) { - settings.pmtud_probes = config.pmtud_probes.data(); - settings.pmtud_probeslen = config.pmtud_probes.size(); - - if (!config.max_udp_payload_size) { - settings.max_tx_udp_payload_size = - *std::ranges::max_element(config.pmtud_probes); - } - } - - ngtcp2_transport_params params; - ngtcp2_transport_params_default(¶ms); - params.initial_max_stream_data_bidi_local = config.max_stream_data_bidi_local; - params.initial_max_stream_data_bidi_remote = - config.max_stream_data_bidi_remote; - params.initial_max_stream_data_uni = config.max_stream_data_uni; - params.initial_max_data = config.max_data; - params.initial_max_streams_bidi = config.max_streams_bidi; - params.initial_max_streams_uni = config.max_streams_uni; - params.max_idle_timeout = config.timeout; - params.stateless_reset_token_present = 1; - params.active_connection_id_limit = 7; - params.grease_quic_bit = 1; - - if (ocid) { - params.original_dcid = *ocid; - params.retry_scid = *scid; - params.retry_scid_present = 1; - } else { - params.original_dcid = *scid; - } - - params.original_dcid_present = 1; - - if (ngtcp2_crypto_generate_stateless_reset_token( - params.stateless_reset_token, config.static_secret.data(), - config.static_secret.size(), &scid_) != 0) { - return -1; - } - - if (config.preferred_ipv4_addr.len || config.preferred_ipv6_addr.len) { - params.preferred_addr_present = 1; - - if (config.preferred_ipv4_addr.len) { - params.preferred_addr.ipv4 = config.preferred_ipv4_addr.su.in; - params.preferred_addr.ipv4_present = 1; - } - - if (config.preferred_ipv6_addr.len) { - params.preferred_addr.ipv6 = config.preferred_ipv6_addr.su.in6; - params.preferred_addr.ipv6_present = 1; - } - - if (util::generate_secure_random( - params.preferred_addr.stateless_reset_token) != 0) { - std::cerr << "Could not generate preferred address stateless reset token" - << std::endl; - return -1; - } - - params.preferred_addr.cid.datalen = NGTCP2_SV_SCIDLEN; - if (util::generate_secure_random({params.preferred_addr.cid.data, - params.preferred_addr.cid.datalen}) != - 0) { - std::cerr << "Could not generate preferred address connection ID" - << std::endl; - return -1; - } - } - - auto path = ngtcp2_path{ - .local = - { - .addr = const_cast(&local_addr.su.sa), - .addrlen = local_addr.len, - }, - .remote = - { - .addr = const_cast(sa), - .addrlen = salen, - }, - .user_data = const_cast(&ep), - }; - if (auto rv = - ngtcp2_conn_server_new(&conn_, dcid, &scid_, &path, version, &callbacks, - &settings, ¶ms, nullptr, this); - rv != 0) { - std::cerr << "ngtcp2_conn_server_new: " << ngtcp2_strerror(rv) << std::endl; - return -1; - } - - if (tls_session_.init(tls_ctx, this) != 0) { - return -1; - } - - tls_session_.enable_keylog(); - - ngtcp2_conn_set_tls_native_handle(conn_, tls_session_.get_native_handle()); - - ev_io_set(&wev_, ep.fd, EV_WRITE); - - return 0; -} - -int Handler::feed_data(const Endpoint &ep, const Address &local_addr, - const sockaddr *sa, socklen_t salen, - const ngtcp2_pkt_info *pi, - std::span data) { - auto path = ngtcp2_path{ - .local = - { - .addr = const_cast(&local_addr.su.sa), - .addrlen = local_addr.len, - }, - .remote = - { - .addr = const_cast(sa), - .addrlen = salen, - }, - .user_data = const_cast(&ep), - }; - - if (auto rv = ngtcp2_conn_read_pkt(conn_, &path, pi, data.data(), data.size(), - util::timestamp()); - rv != 0) { - std::cerr << "ngtcp2_conn_read_pkt: " << ngtcp2_strerror(rv) << std::endl; - switch (rv) { - case NGTCP2_ERR_DRAINING: - start_draining_period(); - return NETWORK_ERR_CLOSE_WAIT; - case NGTCP2_ERR_RETRY: - return NETWORK_ERR_RETRY; - case NGTCP2_ERR_DROP_CONN: - return NETWORK_ERR_DROP_CONN; - case NGTCP2_ERR_CRYPTO: - if (!last_error_.error_code) { - ngtcp2_ccerr_set_tls_alert( - &last_error_, ngtcp2_conn_get_tls_alert(conn_), nullptr, 0); - } - break; - default: - if (!last_error_.error_code) { - ngtcp2_ccerr_set_liberr(&last_error_, rv, nullptr, 0); - } - } - return handle_error(); - } - - return 0; -} - -int Handler::on_read(const Endpoint &ep, const Address &local_addr, - const sockaddr *sa, socklen_t salen, - const ngtcp2_pkt_info *pi, std::span data) { - if (auto rv = feed_data(ep, local_addr, sa, salen, pi, data); rv != 0) { - return rv; - } - - update_timer(); - - return 0; -} - -int Handler::handle_expiry() { - auto now = util::timestamp(); - if (auto rv = ngtcp2_conn_handle_expiry(conn_, now); rv != 0) { - std::cerr << "ngtcp2_conn_handle_expiry: " << ngtcp2_strerror(rv) - << std::endl; - ngtcp2_ccerr_set_liberr(&last_error_, rv, nullptr, 0); - return handle_error(); - } - - return 0; -} - -int Handler::on_write() { - if (ngtcp2_conn_in_closing_period(conn_) || - ngtcp2_conn_in_draining_period(conn_)) { - return 0; - } - - if (tx_.send_blocked) { - if (auto rv = send_blocked_packet(); rv != 0) { - return rv; - } - - if (tx_.send_blocked) { - return 0; - } - } - - ev_io_stop(loop_, &wev_); - - if (auto rv = write_streams(); rv != 0) { - return rv; - } - - update_timer(); - - return 0; -} - -namespace { -ngtcp2_ssize write_pkt(ngtcp2_conn *conn, ngtcp2_path *path, - ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, - ngtcp2_tstamp ts, void *user_data) { - auto h = static_cast(user_data); - - return h->write_pkt(path, pi, dest, destlen, ts); -} -} // namespace - -ngtcp2_ssize Handler::write_pkt(ngtcp2_path *path, ngtcp2_pkt_info *pi, - uint8_t *dest, size_t destlen, - ngtcp2_tstamp ts) { - std::array vec; - - for (;;) { - int64_t stream_id = -1; - int fin = 0; - nghttp3_ssize sveccnt = 0; - - if (httpconn_ && ngtcp2_conn_get_max_data_left(conn_)) { - sveccnt = nghttp3_conn_writev_stream(httpconn_, &stream_id, &fin, - vec.data(), vec.size()); - if (sveccnt < 0) { - std::cerr << "nghttp3_conn_writev_stream: " - << nghttp3_strerror(static_cast(sveccnt)) << std::endl; - ngtcp2_ccerr_set_application_error( - &last_error_, - nghttp3_err_infer_quic_app_error_code(static_cast(sveccnt)), - nullptr, 0); - return NGTCP2_ERR_CALLBACK_FAILURE; - } - } - - ngtcp2_ssize ndatalen; - auto v = vec.data(); - auto vcnt = static_cast(sveccnt); - - uint32_t flags = - NGTCP2_WRITE_STREAM_FLAG_MORE | NGTCP2_WRITE_STREAM_FLAG_PADDING; - if (fin) { - flags |= NGTCP2_WRITE_STREAM_FLAG_FIN; - } - - auto nwrite = ngtcp2_conn_writev_stream( - conn_, path, pi, dest, destlen, &ndatalen, flags, stream_id, - reinterpret_cast(v), vcnt, ts); - if (nwrite < 0) { - switch (nwrite) { - case NGTCP2_ERR_STREAM_DATA_BLOCKED: - assert(ndatalen == -1); - nghttp3_conn_block_stream(httpconn_, stream_id); - continue; - case NGTCP2_ERR_STREAM_SHUT_WR: - assert(ndatalen == -1); - nghttp3_conn_shutdown_stream_write(httpconn_, stream_id); - continue; - case NGTCP2_ERR_WRITE_MORE: - assert(ndatalen >= 0); - if (auto rv = nghttp3_conn_add_write_offset(httpconn_, stream_id, - as_unsigned(ndatalen)); - rv != 0) { - std::cerr << "nghttp3_conn_add_write_offset: " << nghttp3_strerror(rv) - << std::endl; - ngtcp2_ccerr_set_application_error( - &last_error_, nghttp3_err_infer_quic_app_error_code(rv), nullptr, - 0); - return NGTCP2_ERR_CALLBACK_FAILURE; - } - continue; - } - - assert(ndatalen == -1); - - std::cerr << "ngtcp2_conn_writev_stream: " - << ngtcp2_strerror(static_cast(nwrite)) << std::endl; - ngtcp2_ccerr_set_liberr(&last_error_, static_cast(nwrite), nullptr, - 0); - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - if (ndatalen >= 0) { - if (auto rv = nghttp3_conn_add_write_offset(httpconn_, stream_id, - as_unsigned(ndatalen)); - rv != 0) { - std::cerr << "nghttp3_conn_add_write_offset: " << nghttp3_strerror(rv) - << std::endl; - ngtcp2_ccerr_set_application_error( - &last_error_, nghttp3_err_infer_quic_app_error_code(rv), nullptr, 0); - return NGTCP2_ERR_CALLBACK_FAILURE; - } - } - - return nwrite; - } -} - -int Handler::write_streams() { - ngtcp2_path_storage ps; - ngtcp2_pkt_info pi; - size_t gso_size; - auto ts = util::timestamp(); - auto txbuf = std::span{tx_.data.get(), NGTCP2_TX_BUFLEN}; - auto buflen = util::clamp_buffer_size(conn_, txbuf.size(), config.gso_burst); - - ngtcp2_path_storage_zero(&ps); - - auto nwrite = ngtcp2_conn_write_aggregate_pkt2( - conn_, &ps.path, &pi, txbuf.data(), buflen, &gso_size, ::write_pkt, - config.gso_burst, ts); - if (nwrite < 0) { - return handle_error(); - } - - ngtcp2_conn_update_pkt_tx_time(conn_, ts); - - if (nwrite == 0) { - return 0; - } - - send_packet(ps.path, pi.ecn, txbuf.first(static_cast(nwrite)), - gso_size); - - return 0; -} - -int Handler::send_packet(const ngtcp2_path &path, unsigned int ecn, - std::span data, size_t gso_size) { - auto &ep = *static_cast(path.user_data); - auto [rest, rv] = server_->send_packet(ep, no_gso_, path.local, path.remote, - ecn, data, gso_size); - if (rv != 0) { - assert(NETWORK_ERR_SEND_BLOCKED == rv); - - on_send_blocked(path, ecn, rest, gso_size); - - start_wev_endpoint(ep); - - return rv; - } - - return 0; -} - -void Handler::on_send_blocked(const ngtcp2_path &path, unsigned int ecn, - std::span data, size_t gso_size) { - assert(!tx_.send_blocked); - assert(gso_size); - - tx_.send_blocked = true; - - auto &p = tx_.blocked; - - memcpy(&p.local_addr.su, path.local.addr, path.local.addrlen); - memcpy(&p.remote_addr.su, path.remote.addr, path.remote.addrlen); - - p.local_addr.len = path.local.addrlen; - p.remote_addr.len = path.remote.addrlen; - p.endpoint = static_cast(path.user_data); - p.ecn = ecn; - p.data = data; - p.gso_size = gso_size; -} - -void Handler::start_wev_endpoint(const Endpoint &ep) { - // We do not close ep.fd, so we can expect that each Endpoint has - // unique fd. - if (ep.fd != wev_.fd) { - if (ev_is_active(&wev_)) { - ev_io_stop(loop_, &wev_); - } - - ev_io_set(&wev_, ep.fd, EV_WRITE); - } - - ev_io_start(loop_, &wev_); -} - -int Handler::send_blocked_packet() { - assert(tx_.send_blocked); - - auto &p = tx_.blocked; - - ngtcp2_addr local_addr{ - .addr = &p.local_addr.su.sa, - .addrlen = p.local_addr.len, - }; - ngtcp2_addr remote_addr{ - .addr = &p.remote_addr.su.sa, - .addrlen = p.remote_addr.len, - }; - - auto [rest, rv] = server_->send_packet( - *p.endpoint, no_gso_, local_addr, remote_addr, p.ecn, p.data, p.gso_size); - if (rv != 0) { - assert(NETWORK_ERR_SEND_BLOCKED == rv); - - p.data = rest; - - start_wev_endpoint(*p.endpoint); - - return 0; - } - - tx_.send_blocked = false; - - return 0; -} - -void Handler::signal_write() { ev_io_start(loop_, &wev_); } - -void Handler::start_draining_period() { - ev_io_stop(loop_, &wev_); - - ev_set_cb(&timer_, close_waitcb); - timer_.repeat = - static_cast(ngtcp2_conn_get_pto(conn_)) / NGTCP2_SECONDS * 3; - ev_timer_again(loop_, &timer_); - - if (!config.quiet) { - std::cerr << "Draining period has started (" << timer_.repeat << " seconds)" - << std::endl; - } -} - -int Handler::start_closing_period() { - if (!conn_ || ngtcp2_conn_in_closing_period(conn_) || - ngtcp2_conn_in_draining_period(conn_)) { - return 0; - } - - ev_io_stop(loop_, &wev_); - - ev_set_cb(&timer_, close_waitcb); - timer_.repeat = - static_cast(ngtcp2_conn_get_pto(conn_)) / NGTCP2_SECONDS * 3; - ev_timer_again(loop_, &timer_); - - if (!config.quiet) { - std::cerr << "Closing period has started (" << timer_.repeat << " seconds)" - << std::endl; - } - - conn_closebuf_ = std::make_unique(NGTCP2_MAX_UDP_PAYLOAD_SIZE); - - ngtcp2_path_storage ps; - - ngtcp2_path_storage_zero(&ps); - - ngtcp2_pkt_info pi; - auto n = ngtcp2_conn_write_connection_close( - conn_, &ps.path, &pi, conn_closebuf_->wpos(), conn_closebuf_->left(), - &last_error_, util::timestamp()); - if (n < 0) { - std::cerr << "ngtcp2_conn_write_connection_close: " - << ngtcp2_strerror(static_cast(n)) << std::endl; - return -1; - } - - if (n == 0) { - return 0; - } - - conn_closebuf_->push(as_unsigned(n)); - - return 0; -} - -int Handler::handle_error() { - if (last_error_.type == NGTCP2_CCERR_TYPE_IDLE_CLOSE) { - return -1; - } - - if (start_closing_period() != 0) { - return -1; - } - - if (ngtcp2_conn_in_draining_period(conn_)) { - return NETWORK_ERR_CLOSE_WAIT; - } - - if (auto rv = send_conn_close(); rv != NETWORK_ERR_OK) { - return rv; - } - - return NETWORK_ERR_CLOSE_WAIT; -} - -int Handler::send_conn_close() { - if (!config.quiet) { - std::cerr << "Closing Period: TX CONNECTION_CLOSE" << std::endl; - } - - assert(conn_closebuf_ && conn_closebuf_->size()); - assert(conn_); - assert(!ngtcp2_conn_in_draining_period(conn_)); - - auto path = ngtcp2_conn_get_path(conn_); - - return server_->send_packet(*static_cast(path->user_data), - path->local, path->remote, - /* ecn = */ 0, conn_closebuf_->data()); -} - -int Handler::send_conn_close(const Endpoint &ep, const Address &local_addr, - const sockaddr *sa, socklen_t salen, - const ngtcp2_pkt_info *pi, - std::span data) { - assert(conn_closebuf_ && conn_closebuf_->size()); - - close_wait_.bytes_recv += data.size(); - ++close_wait_.num_pkts_recv; - - if (close_wait_.num_pkts_recv < close_wait_.next_pkts_recv || - close_wait_.bytes_recv * 3 < - close_wait_.bytes_sent + conn_closebuf_->size()) { - return 0; - } - - auto path = ngtcp2_path{ - .local = - { - .addr = const_cast(&local_addr.su.sa), - .addrlen = local_addr.len, - }, - .remote = - { - .addr = const_cast(sa), - .addrlen = salen, - }, - .user_data = const_cast(&ep), - }; - - auto rv = server_->send_packet(ep, path.local, path.remote, - /* ecn = */ 0, conn_closebuf_->data()); - if (rv != 0) { - return rv; - } - - close_wait_.bytes_sent += conn_closebuf_->size(); - close_wait_.next_pkts_recv *= 2; - - return 0; -} - -void Handler::update_timer() { - auto expiry = ngtcp2_conn_get_expiry(conn_); - auto now = util::timestamp(); - - if (expiry <= now) { - if (!config.quiet) { - auto t = static_cast(now - expiry) / NGTCP2_SECONDS; - std::cerr << "Timer has already expired: " << std::fixed << t << "s" - << std::defaultfloat << std::endl; - } - - ev_feed_event(loop_, &timer_, EV_TIMER); - - return; - } - - auto t = static_cast(expiry - now) / NGTCP2_SECONDS; - if (!config.quiet) { - std::cerr << "Set timer=" << std::fixed << t << "s" << std::defaultfloat - << std::endl; - } - timer_.repeat = t; - ev_timer_again(loop_, &timer_); -} - -int Handler::recv_stream_data(uint32_t flags, int64_t stream_id, - std::span data) { - if (!config.quiet && !config.no_quic_dump) { - debug::print_stream_data(stream_id, data); - } - - if (!httpconn_) { - return 0; - } - - auto nconsumed = nghttp3_conn_read_stream2( - httpconn_, stream_id, data.data(), data.size(), - flags & NGTCP2_STREAM_DATA_FLAG_FIN, ngtcp2_conn_get_timestamp(conn_)); - if (nconsumed < 0) { - std::cerr << "nghttp3_conn_read_stream2: " - << nghttp3_strerror(static_cast(nconsumed)) << std::endl; - ngtcp2_ccerr_set_application_error( - &last_error_, - nghttp3_err_infer_quic_app_error_code(static_cast(nconsumed)), - nullptr, 0); - return -1; - } - - ngtcp2_conn_extend_max_stream_offset(conn_, stream_id, - static_cast(nconsumed)); - ngtcp2_conn_extend_max_offset(conn_, static_cast(nconsumed)); - - return 0; -} - -int Handler::update_key(uint8_t *rx_secret, uint8_t *tx_secret, - ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv, - ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv, - const uint8_t *current_rx_secret, - const uint8_t *current_tx_secret, size_t secretlen) { - auto crypto_ctx = ngtcp2_conn_get_crypto_ctx(conn_); - auto aead = &crypto_ctx->aead; - auto keylen = ngtcp2_crypto_aead_keylen(aead); - auto ivlen = ngtcp2_crypto_packet_protection_ivlen(aead); - - ++nkey_update_; - - std::array rx_key, tx_key; - - if (ngtcp2_crypto_update_key(conn_, rx_secret, tx_secret, rx_aead_ctx, - rx_key.data(), rx_iv, tx_aead_ctx, tx_key.data(), - tx_iv, current_rx_secret, current_tx_secret, - secretlen) != 0) { - return -1; - } - - if (!config.quiet && config.show_secret) { - std::cerr << "application_traffic rx secret " << nkey_update_ << std::endl; - debug::print_secrets({rx_secret, secretlen}, {rx_key.data(), keylen}, - {rx_iv, ivlen}); - std::cerr << "application_traffic tx secret " << nkey_update_ << std::endl; - debug::print_secrets({tx_secret, secretlen}, {tx_key.data(), keylen}, - {tx_iv, ivlen}); - } - - return 0; -} - -Server *Handler::server() const { return server_; } - -int Handler::on_stream_close(int64_t stream_id, uint64_t app_error_code) { - if (!config.quiet) { - std::cerr << "QUIC stream " << stream_id << " closed" << std::endl; - } - - if (httpconn_) { - if (app_error_code == 0) { - app_error_code = NGHTTP3_H3_NO_ERROR; - } - auto rv = nghttp3_conn_close_stream(httpconn_, stream_id, app_error_code); - switch (rv) { - case 0: - http_stream_close(stream_id, app_error_code); - break; - case NGHTTP3_ERR_STREAM_NOT_FOUND: - if (ngtcp2_is_bidi_stream(stream_id)) { - assert(!ngtcp2_conn_is_local_stream(conn_, stream_id)); - ngtcp2_conn_extend_max_streams_bidi(conn_, 1); - } - break; - default: - std::cerr << "nghttp3_conn_close_stream: " << nghttp3_strerror(rv) - << std::endl; - ngtcp2_ccerr_set_application_error( - &last_error_, nghttp3_err_infer_quic_app_error_code(rv), nullptr, 0); - return -1; - } - } - - return 0; -} - -void Handler::shutdown_read(int64_t stream_id, uint64_t app_error_code) { - ngtcp2_conn_shutdown_stream_read(conn_, 0, stream_id, app_error_code); -} - -namespace { -void sreadcb(struct ev_loop *loop, ev_io *w, int revents) { - auto ep = static_cast(w->data); - - ep->server->on_read(*ep); -} -} // namespace - -namespace { -void siginthandler(struct ev_loop *loop, ev_signal *watcher, int revents) { - ev_break(loop, EVBREAK_ALL); -} -} // namespace - -Server::Server(struct ev_loop *loop, TLSServerContext &tls_ctx) - : loop_(loop), - tls_ctx_(tls_ctx), - stateless_reset_bucket_(NGTCP2_STATELESS_RESET_BURST) { - ev_signal_init(&sigintev_, siginthandler, SIGINT); - - ev_timer_init( - &stateless_reset_regen_timer_, - [](struct ev_loop *loop, ev_timer *w, int revents) { - auto server = static_cast(w->data); - - server->on_stateless_reset_regen(); - }, - 0., 1.); - stateless_reset_regen_timer_.data = this; -} - -Server::~Server() { - disconnect(); - close(); -} - -void Server::disconnect() { - config.tx_loss_prob = 0; - - for (auto &ep : endpoints_) { - ev_io_stop(loop_, &ep.rev); - } - - ev_timer_stop(loop_, &stateless_reset_regen_timer_); - ev_signal_stop(loop_, &sigintev_); - - while (!handlers_.empty()) { - auto it = std::ranges::begin(handlers_); - auto &h = (*it).second; - - h->handle_error(); - - remove(h); - } -} - -void Server::close() { - for (auto &ep : endpoints_) { - ::close(ep.fd); - } - - endpoints_.clear(); -} - -namespace { -int create_sock(Address &local_addr, const char *addr, const char *port, - int family) { - addrinfo hints{ - .ai_flags = AI_PASSIVE, - .ai_family = family, - .ai_socktype = SOCK_DGRAM, - }; - addrinfo *res, *rp; - int val = 1; - - if (strcmp(addr, "*") == 0) { - addr = nullptr; - } - - if (auto rv = getaddrinfo(addr, port, &hints, &res); rv != 0) { - std::cerr << "getaddrinfo: " << gai_strerror(rv) << std::endl; - return -1; - } - - auto res_d = defer(freeaddrinfo, res); - - int fd = -1; - - for (rp = res; rp; rp = rp->ai_next) { - fd = util::create_nonblock_socket(rp->ai_family, rp->ai_socktype, - rp->ai_protocol); - if (fd == -1) { - continue; - } - - if (rp->ai_family == AF_INET6) { - if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, - static_cast(sizeof(val))) == -1) { - close(fd); - continue; - } - - if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val, - static_cast(sizeof(val))) == -1) { - close(fd); - continue; - } - } else if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &val, - static_cast(sizeof(val))) == -1) { - close(fd); - continue; - } - - if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, - static_cast(sizeof(val))) == -1) { - close(fd); - continue; - } - - fd_set_recv_ecn(fd, rp->ai_family); - fd_set_ip_mtu_discover(fd, rp->ai_family); - fd_set_ip_dontfrag(fd, family); - fd_set_udp_gro(fd); - - if (bind(fd, rp->ai_addr, rp->ai_addrlen) != -1) { - break; - } - - close(fd); - } - - if (!rp) { - std::cerr << "Could not bind" << std::endl; - return -1; - } - - socklen_t len = sizeof(local_addr.su.storage); - if (getsockname(fd, &local_addr.su.sa, &len) == -1) { - std::cerr << "getsockname: " << strerror(errno) << std::endl; - close(fd); - return -1; - } - local_addr.len = len; - local_addr.ifindex = 0; - - return fd; -} - -} // namespace - -namespace { -int add_endpoint(std::vector &endpoints, const char *addr, - const char *port, int af) { - Address dest; - auto fd = create_sock(dest, addr, port, af); - if (fd == -1) { - return -1; - } - - endpoints.emplace_back(); - auto &ep = endpoints.back(); - ep.addr = dest; - ep.fd = fd; - ev_io_init(&ep.rev, sreadcb, 0, EV_READ); - ev_set_priority(&ep.rev, EV_MAXPRI); - - return 0; -} -} // namespace - -namespace { -int add_endpoint(std::vector &endpoints, const Address &addr) { - auto fd = util::create_nonblock_socket(addr.su.sa.sa_family, SOCK_DGRAM, 0); - if (fd == -1) { - std::cerr << "socket: " << strerror(errno) << std::endl; - return -1; - } - - int val = 1; - if (addr.su.sa.sa_family == AF_INET6) { - if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, - static_cast(sizeof(val))) == -1) { - std::cerr << "setsockopt: " << strerror(errno) << std::endl; - close(fd); - return -1; - } - - if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val, - static_cast(sizeof(val))) == -1) { - std::cerr << "setsockopt: " << strerror(errno) << std::endl; - close(fd); - return -1; - } - } else if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &val, - static_cast(sizeof(val))) == -1) { - std::cerr << "setsockopt: " << strerror(errno) << std::endl; - close(fd); - return -1; - } - - if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, - static_cast(sizeof(val))) == -1) { - close(fd); - return -1; - } - - fd_set_recv_ecn(fd, addr.su.sa.sa_family); - fd_set_ip_mtu_discover(fd, addr.su.sa.sa_family); - fd_set_ip_dontfrag(fd, addr.su.sa.sa_family); - fd_set_udp_gro(fd); - - if (bind(fd, &addr.su.sa, addr.len) == -1) { - std::cerr << "bind: " << strerror(errno) << std::endl; - close(fd); - return -1; - } - - endpoints.emplace_back(Endpoint{}); - auto &ep = endpoints.back(); - ep.addr = addr; - ep.fd = fd; - ev_io_init(&ep.rev, sreadcb, 0, EV_READ); - ev_set_priority(&ep.rev, EV_MAXPRI); - - return 0; -} -} // namespace - -int Server::init(const char *addr, const char *port) { - endpoints_.reserve(4); - - auto ready = false; - if (!util::numeric_host(addr, AF_INET6) && - add_endpoint(endpoints_, addr, port, AF_INET) == 0) { - ready = true; - } - if (!util::numeric_host(addr, AF_INET) && - add_endpoint(endpoints_, addr, port, AF_INET6) == 0) { - ready = true; - } - if (!ready) { - return -1; - } - - if (config.preferred_ipv4_addr.len && - add_endpoint(endpoints_, config.preferred_ipv4_addr) != 0) { - return -1; - } - if (config.preferred_ipv6_addr.len && - add_endpoint(endpoints_, config.preferred_ipv6_addr) != 0) { - return -1; - } - - for (auto &ep : endpoints_) { - ep.server = this; - ep.rev.data = &ep; - - ev_io_set(&ep.rev, ep.fd, EV_READ); - - ev_io_start(loop_, &ep.rev); - } - - ev_signal_start(loop_, &sigintev_); - - return 0; -} - -int Server::on_read(const Endpoint &ep) { - sockaddr_union su; - std::array buf; - size_t pktcnt = 0; - ngtcp2_pkt_info pi; - - iovec msg_iov{ - .iov_base = buf.data(), - .iov_len = buf.size(), - }; - - uint8_t msg_ctrl[CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(in6_pktinfo)) + - CMSG_SPACE(sizeof(int))]; - - msghdr msg{ - .msg_name = &su, - .msg_iov = &msg_iov, - .msg_iovlen = 1, - .msg_control = msg_ctrl, - }; - - auto start = util::timestamp(); - - for (; pktcnt < MAX_RECV_PKTS;) { - if (util::recv_pkt_time_threshold_exceeded( - config.cc_algo == NGTCP2_CC_ALGO_BBR, start, pktcnt)) { - return 0; - } - - msg.msg_namelen = sizeof(su); - msg.msg_controllen = sizeof(msg_ctrl); - - auto nread = recvmsg(ep.fd, &msg, 0); - if (nread == -1) { - if (!(errno == EAGAIN || errno == ENOTCONN)) { - std::cerr << "recvmsg: " << strerror(errno) << std::endl; - } - return 0; - } - - // Packets less than 21 bytes never be a valid QUIC packet. - if (nread < 21) { - ++pktcnt; - - continue; - } - - if (util::prohibited_port(util::port(&su))) { - ++pktcnt; - - continue; - } - - pi.ecn = msghdr_get_ecn(&msg, su.storage.ss_family); - auto local_addr = msghdr_get_local_addr(&msg, su.storage.ss_family); - if (!local_addr) { - ++pktcnt; - std::cerr << "Unable to obtain local address" << std::endl; - continue; - } - - auto gso_size = msghdr_get_udp_gro(&msg); - if (gso_size == 0) { - gso_size = static_cast(nread); - } - - set_port(*local_addr, ep.addr); - - auto data = std::span{buf.data(), static_cast(nread)}; - - for (; !data.empty();) { - auto datalen = std::min(data.size(), gso_size); - - ++pktcnt; - - if (!config.quiet) { - std::array ifname; - std::cerr << "Received packet: local=" - << util::straddr(&local_addr->su.sa, local_addr->len) - << " remote=" << util::straddr(&su.sa, msg.msg_namelen) - << " if=" - << if_indextoname(local_addr->ifindex, ifname.data()) - << " ecn=0x" << std::hex << static_cast(pi.ecn) - << std::dec << " " << datalen << " bytes" << std::endl; - } - - // Packets less than 21 bytes never be a valid QUIC packet. - if (datalen < 21) { - break; - } - - if (debug::packet_lost(config.rx_loss_prob)) { - if (!config.quiet) { - std::cerr << "** Simulated incoming packet loss **" << std::endl; - } - } else { - read_pkt(ep, *local_addr, &su.sa, msg.msg_namelen, &pi, - {data.data(), datalen}); - } - - data = data.subspan(datalen); - } - } - - return 0; -} - -void Server::read_pkt(const Endpoint &ep, const Address &local_addr, - const sockaddr *sa, socklen_t salen, - const ngtcp2_pkt_info *pi, - std::span data) { - ngtcp2_version_cid vc; - - switch (auto rv = ngtcp2_pkt_decode_version_cid(&vc, data.data(), data.size(), - NGTCP2_SV_SCIDLEN); - rv) { - case 0: - break; - case NGTCP2_ERR_VERSION_NEGOTIATION: - send_version_negotiation(vc.version, {vc.scid, vc.scidlen}, - {vc.dcid, vc.dcidlen}, ep, local_addr, sa, salen); - return; - default: - std::cerr << "Could not decode version and CID from QUIC packet header: " - << ngtcp2_strerror(rv) << std::endl; - return; - } - - auto dcid_key = util::make_cid_key({vc.dcid, vc.dcidlen}); - - auto handler_it = handlers_.find(dcid_key); - if (handler_it == std::ranges::end(handlers_)) { - ngtcp2_pkt_hd hd; - - if (auto rv = ngtcp2_accept(&hd, data.data(), data.size()); rv != 0) { - if (!config.quiet) { - std::cerr << "Unexpected packet received: length=" << data.size() - << std::endl; - } - - if (!(data[0] & 0x80) && data.size() >= NGTCP2_SV_SCIDLEN + 21) { - send_stateless_reset(data.size(), {vc.dcid, vc.dcidlen}, ep, local_addr, - sa, salen); - } - - return; - } - - ngtcp2_cid ocid; - ngtcp2_cid *pocid = nullptr; - ngtcp2_token_type token_type = NGTCP2_TOKEN_TYPE_UNKNOWN; - - assert(hd.type == NGTCP2_PKT_INITIAL); - - if (config.validate_addr || hd.tokenlen) { - std::cerr << "Perform stateless address validation" << std::endl; - if (hd.tokenlen == 0) { - send_retry(&hd, ep, local_addr, sa, salen, data.size() * 3); - return; - } - - if (hd.token[0] != NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY2 && - hd.dcid.datalen < NGTCP2_MIN_INITIAL_DCIDLEN) { - send_stateless_connection_close(&hd, ep, local_addr, sa, salen); - return; - } - - switch (hd.token[0]) { - case NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY2: - switch (verify_retry_token(&ocid, &hd, sa, salen)) { - case 0: - pocid = &ocid; - token_type = NGTCP2_TOKEN_TYPE_RETRY; - break; - case -1: - send_stateless_connection_close(&hd, ep, local_addr, sa, salen); - return; - case 1: - hd.token = nullptr; - hd.tokenlen = 0; - break; - } - - break; - case NGTCP2_CRYPTO_TOKEN_MAGIC_REGULAR: - if (verify_token(&hd, sa, salen) != 0) { - if (config.validate_addr) { - send_retry(&hd, ep, local_addr, sa, salen, data.size() * 3); - return; - } - - hd.token = nullptr; - hd.tokenlen = 0; - } else { - token_type = NGTCP2_TOKEN_TYPE_NEW_TOKEN; - } - break; - default: - if (!config.quiet) { - std::cerr << "Ignore unrecognized token" << std::endl; - } - if (config.validate_addr) { - send_retry(&hd, ep, local_addr, sa, salen, data.size() * 3); - return; - } - - hd.token = nullptr; - hd.tokenlen = 0; - break; - } - } - - auto h = std::make_unique(loop_, this); - if (h->init(ep, local_addr, sa, salen, &hd.scid, &hd.dcid, pocid, - {hd.token, hd.tokenlen}, token_type, hd.version, - tls_ctx_) != 0) { - return; - } - - switch (h->on_read(ep, local_addr, sa, salen, pi, data)) { - case 0: - break; - case NETWORK_ERR_RETRY: - send_retry(&hd, ep, local_addr, sa, salen, data.size() * 3); - return; - default: - return; - } - - if (h->on_write() != 0) { - return; - } - - std::array scids; - auto conn = h->conn(); - - auto num_scid = ngtcp2_conn_get_scid(conn, nullptr); - - assert(num_scid <= scids.size()); - - ngtcp2_conn_get_scid(conn, scids.data()); - - for (size_t i = 0; i < num_scid; ++i) { - associate_cid(&scids[i], h.get()); - } - - handlers_.emplace(dcid_key, h.release()); - - return; - } - - auto h = (*handler_it).second; - auto conn = h->conn(); - if (ngtcp2_conn_in_closing_period(conn)) { - if (h->send_conn_close(ep, local_addr, sa, salen, pi, data) != 0) { - remove(h); - } - return; - } - if (ngtcp2_conn_in_draining_period(conn)) { - return; - } - - if (auto rv = h->on_read(ep, local_addr, sa, salen, pi, data); rv != 0) { - if (rv != NETWORK_ERR_CLOSE_WAIT) { - remove(h); - } - return; - } - - h->signal_write(); -} - -namespace { -uint32_t generate_reserved_version(const sockaddr *sa, socklen_t salen, - uint32_t version) { - uint32_t h = 0x811C9DC5u; - const uint8_t *p = (const uint8_t *)sa; - const uint8_t *ep = p + salen; - for (; p != ep; ++p) { - h ^= *p; - h *= 0x01000193u; - } - version = htonl(version); - p = (const uint8_t *)&version; - ep = p + sizeof(version); - for (; p != ep; ++p) { - h ^= *p; - h *= 0x01000193u; - } - h &= 0xf0f0f0f0u; - h |= 0x0a0a0a0au; - return h; -} -} // namespace - -int Server::send_version_negotiation(uint32_t version, - std::span dcid, - std::span scid, - const Endpoint &ep, - const Address &local_addr, - const sockaddr *sa, socklen_t salen) { - Buffer buf{NGTCP2_MAX_UDP_PAYLOAD_SIZE}; - std::array sv; - - auto p = std::ranges::begin(sv); - - *p++ = generate_reserved_version(sa, salen, version); - - if (config.preferred_versions.empty()) { - *p++ = NGTCP2_PROTO_VER_V1; - } else { - for (auto v : config.preferred_versions) { - *p++ = v; - } - } - - auto nwrite = ngtcp2_pkt_write_version_negotiation( - buf.wpos(), buf.left(), std::uniform_int_distribution()(randgen), - dcid.data(), dcid.size(), scid.data(), scid.size(), sv.data(), - as_unsigned(p - std::ranges::begin(sv))); - if (nwrite < 0) { - std::cerr << "ngtcp2_pkt_write_version_negotiation: " - << ngtcp2_strerror(static_cast(nwrite)) << std::endl; - return -1; - } - - buf.push(as_unsigned(nwrite)); - - ngtcp2_addr laddr{ - .addr = const_cast(&local_addr.su.sa), - .addrlen = local_addr.len, - }; - ngtcp2_addr raddr{ - .addr = const_cast(sa), - .addrlen = salen, - }; - - if (send_packet(ep, laddr, raddr, /* ecn = */ 0, buf.data()) != - NETWORK_ERR_OK) { - return -1; - } - - return 0; -} - -int Server::send_retry(const ngtcp2_pkt_hd *chd, const Endpoint &ep, - const Address &local_addr, const sockaddr *sa, - socklen_t salen, size_t max_pktlen) { - std::array host; - std::array port; - - if (auto rv = getnameinfo(sa, salen, host.data(), host.size(), port.data(), - port.size(), NI_NUMERICHOST | NI_NUMERICSERV); - rv != 0) { - std::cerr << "getnameinfo: " << gai_strerror(rv) << std::endl; - return -1; - } - - if (!config.quiet) { - std::cerr << "Sending Retry packet to [" << host.data() - << "]:" << port.data() << std::endl; - } - - ngtcp2_cid scid; - - scid.datalen = NGTCP2_SV_SCIDLEN; - if (util::generate_secure_random({scid.data, scid.datalen}) != 0) { - return -1; - } - - std::array token; - - auto t = util::system_clock_now(); - - auto tokenlen = ngtcp2_crypto_generate_retry_token2( - token.data(), config.static_secret.data(), config.static_secret.size(), - chd->version, sa, salen, &scid, &chd->dcid, t); - if (tokenlen < 0) { - return -1; - } - - if (!config.quiet) { - std::cerr << "Generated address validation token:" << std::endl; - util::hexdump(stderr, token.data(), as_unsigned(tokenlen)); - } - - Buffer buf{ - std::min(static_cast(NGTCP2_MAX_UDP_PAYLOAD_SIZE), max_pktlen)}; - - auto nwrite = ngtcp2_crypto_write_retry(buf.wpos(), buf.left(), chd->version, - &chd->scid, &scid, &chd->dcid, - token.data(), as_unsigned(tokenlen)); - if (nwrite < 0) { - std::cerr << "ngtcp2_crypto_write_retry failed" << std::endl; - return -1; - } - - buf.push(as_unsigned(nwrite)); - - ngtcp2_addr laddr{ - .addr = const_cast(&local_addr.su.sa), - .addrlen = local_addr.len, - }; - ngtcp2_addr raddr{ - .addr = const_cast(sa), - .addrlen = salen, - }; - - if (send_packet(ep, laddr, raddr, /* ecn = */ 0, buf.data()) != - NETWORK_ERR_OK) { - return -1; - } - - return 0; -} - -int Server::send_stateless_connection_close(const ngtcp2_pkt_hd *chd, - const Endpoint &ep, - const Address &local_addr, - const sockaddr *sa, - socklen_t salen) { - Buffer buf{NGTCP2_MAX_UDP_PAYLOAD_SIZE}; - - auto nwrite = ngtcp2_crypto_write_connection_close( - buf.wpos(), buf.left(), chd->version, &chd->scid, &chd->dcid, - NGTCP2_INVALID_TOKEN, nullptr, 0); - if (nwrite < 0) { - std::cerr << "ngtcp2_crypto_write_connection_close failed" << std::endl; - return -1; - } - - buf.push(as_unsigned(nwrite)); - - ngtcp2_addr laddr{ - .addr = const_cast(&local_addr.su.sa), - .addrlen = local_addr.len, - }; - ngtcp2_addr raddr{ - .addr = const_cast(sa), - .addrlen = salen, - }; - - if (send_packet(ep, laddr, raddr, /* ecn = */ 0, buf.data()) != - NETWORK_ERR_OK) { - return -1; - } - - return 0; -} - -int Server::send_stateless_reset(size_t pktlen, std::span dcid, - const Endpoint &ep, const Address &local_addr, - const sockaddr *sa, socklen_t salen) { - if (stateless_reset_bucket_ == 0) { - return 0; - } - - --stateless_reset_bucket_; - - if (!ev_is_active(&stateless_reset_regen_timer_)) { - ev_timer_again(loop_, &stateless_reset_regen_timer_); - } - - ngtcp2_cid cid; - - ngtcp2_cid_init(&cid, dcid.data(), dcid.size()); - - std::array token; - - if (ngtcp2_crypto_generate_stateless_reset_token( - token.data(), config.static_secret.data(), config.static_secret.size(), - &cid) != 0) { - return -1; - } - - // SCID + minimum expansion - NGTCP2_STATELESS_RESET_TOKENLEN - constexpr size_t max_rand_byteslen = - NGTCP2_MAX_CIDLEN + 22 - NGTCP2_STATELESS_RESET_TOKENLEN; - - size_t rand_byteslen; - - if (pktlen <= 43) { - // As per - // https://datatracker.ietf.org/doc/html/rfc9000#section-10.3 - rand_byteslen = pktlen - NGTCP2_STATELESS_RESET_TOKENLEN - 1; - } else { - rand_byteslen = max_rand_byteslen; - } - - std::array rand_bytes; - - if (util::generate_secure_random({rand_bytes.data(), rand_byteslen}) != 0) { - return -1; - } - - Buffer buf{NGTCP2_MAX_UDP_PAYLOAD_SIZE}; - - auto nwrite = ngtcp2_pkt_write_stateless_reset( - buf.wpos(), buf.left(), token.data(), rand_bytes.data(), rand_byteslen); - if (nwrite < 0) { - std::cerr << "ngtcp2_pkt_write_stateless_reset: " - << ngtcp2_strerror(static_cast(nwrite)) << std::endl; - - return -1; - } - - buf.push(as_unsigned(nwrite)); - - ngtcp2_addr laddr{ - .addr = const_cast(&local_addr.su.sa), - .addrlen = local_addr.len, - }; - ngtcp2_addr raddr{ - .addr = const_cast(sa), - .addrlen = salen, - }; - - if (send_packet(ep, laddr, raddr, /* ecn = */ 0, buf.data()) != - NETWORK_ERR_OK) { - return -1; - } - - return 0; -} - -int Server::verify_retry_token(ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd, - const sockaddr *sa, socklen_t salen) { - int rv; - - if (!config.quiet) { - std::array host; - std::array port; - - if (auto rv = getnameinfo(sa, salen, host.data(), host.size(), port.data(), - port.size(), NI_NUMERICHOST | NI_NUMERICSERV); - rv != 0) { - std::cerr << "getnameinfo: " << gai_strerror(rv) << std::endl; - return -1; - } - - std::cerr << "Verifying Retry token from [" << host.data() - << "]:" << port.data() << std::endl; - util::hexdump(stderr, hd->token, hd->tokenlen); - } - - auto t = util::system_clock_now(); - - rv = ngtcp2_crypto_verify_retry_token2( - ocid, hd->token, hd->tokenlen, config.static_secret.data(), - config.static_secret.size(), hd->version, sa, salen, &hd->dcid, - 10 * NGTCP2_SECONDS, t); - switch (rv) { - case 0: - break; - case NGTCP2_CRYPTO_ERR_VERIFY_TOKEN: - std::cerr << "Could not verify Retry token" << std::endl; - - return -1; - default: - std::cerr << "Could not read Retry token. Continue without the token" - << std::endl; - - return 1; - } - - if (!config.quiet) { - std::cerr << "Token was successfully validated" << std::endl; - } - - return 0; -} - -int Server::verify_token(const ngtcp2_pkt_hd *hd, const sockaddr *sa, - socklen_t salen) { - std::array host; - std::array port; - - if (auto rv = getnameinfo(sa, salen, host.data(), host.size(), port.data(), - port.size(), NI_NUMERICHOST | NI_NUMERICSERV); - rv != 0) { - std::cerr << "getnameinfo: " << gai_strerror(rv) << std::endl; - return -1; - } - - if (!config.quiet) { - std::cerr << "Verifying token from [" << host.data() << "]:" << port.data() - << std::endl; - util::hexdump(stderr, hd->token, hd->tokenlen); - } - - auto t = util::system_clock_now(); - - if (ngtcp2_crypto_verify_regular_token(hd->token, hd->tokenlen, - config.static_secret.data(), - config.static_secret.size(), sa, salen, - 3600 * NGTCP2_SECONDS, t) != 0) { - if (!config.quiet) { - std::cerr << "Could not verify token" << std::endl; - } - return -1; - } - - if (!config.quiet) { - std::cerr << "Token was successfully validated" << std::endl; - } - - return 0; -} - -int Server::send_packet(const Endpoint &ep, const ngtcp2_addr &local_addr, - const ngtcp2_addr &remote_addr, unsigned int ecn, - std::span data) { - auto no_gso = false; - auto [_, rv] = - send_packet(ep, no_gso, local_addr, remote_addr, ecn, data, data.size()); - - return rv; -} - -std::pair, int> -Server::send_packet(const Endpoint &ep, bool &no_gso, - const ngtcp2_addr &local_addr, - const ngtcp2_addr &remote_addr, unsigned int ecn, - std::span data, size_t gso_size) { - assert(gso_size); - - if (debug::packet_lost(config.tx_loss_prob)) { - if (!config.quiet) { - std::cerr << "** Simulated outgoing packet loss **" << std::endl; - } - return {{}, NETWORK_ERR_OK}; - } - - if (no_gso && data.size() > gso_size) { - for (; !data.empty();) { - auto len = std::min(gso_size, data.size()); - - auto [_, rv] = send_packet(ep, no_gso, local_addr, remote_addr, ecn, - data.first(len), len); - if (rv != 0) { - return {data, rv}; - } - - data = data.subspan(len); - } - - return {{}, 0}; - } - - iovec msg_iov{ - .iov_base = const_cast(data.data()), - .iov_len = data.size(), - }; - - uint8_t msg_ctrl[CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(uint16_t)) + - CMSG_SPACE(sizeof(in6_pktinfo))]{}; - - msghdr msg{ - .msg_name = const_cast(remote_addr.addr), - .msg_namelen = remote_addr.addrlen, - .msg_iov = &msg_iov, - .msg_iovlen = 1, - .msg_control = msg_ctrl, - .msg_controllen = sizeof(msg_ctrl), - }; - - size_t controllen = 0; - - auto cm = CMSG_FIRSTHDR(&msg); - - switch (local_addr.addr->sa_family) { - case AF_INET: { - controllen += CMSG_SPACE(sizeof(in_pktinfo)); - cm->cmsg_level = IPPROTO_IP; - cm->cmsg_type = IP_PKTINFO; - cm->cmsg_len = CMSG_LEN(sizeof(in_pktinfo)); - auto addrin = reinterpret_cast(local_addr.addr); - in_pktinfo pktinfo{ - .ipi_spec_dst = addrin->sin_addr, - }; - memcpy(CMSG_DATA(cm), &pktinfo, sizeof(pktinfo)); - - break; - } - case AF_INET6: { - controllen += CMSG_SPACE(sizeof(in6_pktinfo)); - cm->cmsg_level = IPPROTO_IPV6; - cm->cmsg_type = IPV6_PKTINFO; - cm->cmsg_len = CMSG_LEN(sizeof(in6_pktinfo)); - auto addrin = reinterpret_cast(local_addr.addr); - in6_pktinfo pktinfo{ - .ipi6_addr = addrin->sin6_addr, - }; - memcpy(CMSG_DATA(cm), &pktinfo, sizeof(pktinfo)); - - break; - } - default: - assert(0); - } - -#ifdef UDP_SEGMENT - if (data.size() > gso_size) { - controllen += CMSG_SPACE(sizeof(uint16_t)); - cm = CMSG_NXTHDR(&msg, cm); - cm->cmsg_level = SOL_UDP; - cm->cmsg_type = UDP_SEGMENT; - cm->cmsg_len = CMSG_LEN(sizeof(uint16_t)); - auto n = static_cast(gso_size); - memcpy(CMSG_DATA(cm), &n, sizeof(n)); - } -#endif // defined(UDP_SEGMENT) - - controllen += CMSG_SPACE(sizeof(int)); - cm = CMSG_NXTHDR(&msg, cm); - cm->cmsg_len = CMSG_LEN(sizeof(int)); - memcpy(CMSG_DATA(cm), &ecn, sizeof(ecn)); - - switch (local_addr.addr->sa_family) { - case AF_INET: - cm->cmsg_level = IPPROTO_IP; - cm->cmsg_type = IP_TOS; - - break; - case AF_INET6: - cm->cmsg_level = IPPROTO_IPV6; - cm->cmsg_type = IPV6_TCLASS; - - break; - default: - assert(0); - } - - msg.msg_controllen = -#ifndef __APPLE__ - controllen -#else // defined(__APPLE__) - static_cast(controllen) -#endif // defined(__APPLE__) - ; - - ssize_t nwrite = 0; - - do { - nwrite = sendmsg(ep.fd, &msg, 0); - } while (nwrite == -1 && errno == EINTR); - - if (nwrite == -1) { - switch (errno) { - case EAGAIN: -#if EAGAIN != EWOULDBLOCK - case EWOULDBLOCK: -#endif // EAGAIN != EWOULDBLOCK - return {data, NETWORK_ERR_SEND_BLOCKED}; -#ifdef UDP_SEGMENT - case EIO: - if (data.size() > gso_size) { - // GSO failure; send each packet in a separate sendmsg call. - std::cerr << "sendmsg: disabling GSO due to " << strerror(errno) - << std::endl; - - no_gso = true; - - return send_packet(ep, no_gso, local_addr, remote_addr, ecn, data, - gso_size); - } - break; -#endif // defined(UDP_SEGMENT) - } - - std::cerr << "sendmsg: " << strerror(errno) << std::endl; - // TODO We have packet which is expected to fail to send (e.g., - // path validation to old path). - return {{}, NETWORK_ERR_OK}; - } - - if (!config.quiet) { - std::cerr << "Sent packet: local=" - << util::straddr(local_addr.addr, local_addr.addrlen) - << " remote=" - << util::straddr(remote_addr.addr, remote_addr.addrlen) - << " ecn=0x" << std::hex << ecn << std::dec << " " << nwrite - << " bytes" << std::endl; - } - - return {{}, NETWORK_ERR_OK}; -} - -void Server::associate_cid(const ngtcp2_cid *cid, Handler *h) { - handlers_.emplace(*cid, h); -} - -void Server::dissociate_cid(const ngtcp2_cid *cid) { handlers_.erase(*cid); } - -void Server::remove(const Handler *h) { - auto conn = h->conn(); - - dissociate_cid(ngtcp2_conn_get_client_initial_dcid(conn)); - - std::vector cids(ngtcp2_conn_get_scid(conn, nullptr)); - ngtcp2_conn_get_scid(conn, cids.data()); - - for (auto &cid : cids) { - dissociate_cid(&cid); - } - - delete h; -} - -void Server::on_stateless_reset_regen() { - assert(stateless_reset_bucket_ < NGTCP2_STATELESS_RESET_BURST); - - if (++stateless_reset_bucket_ == NGTCP2_STATELESS_RESET_BURST) { - ev_timer_stop(loop_, &stateless_reset_regen_timer_); - } -} - -namespace { -int parse_host_port(Address &dest, int af, const std::string_view &host_port) { - if (host_port.empty()) { - return -1; - } - - auto first = std::ranges::begin(host_port); - auto last = std::ranges::end(host_port); - - std::string_view hostv; - - if (*first == '[') { - ++first; - - auto it = std::ranges::find(first, last, ']'); - if (it == last) { - return -1; - } - - hostv = std::string_view{first, it}; - first = it + 1; - - if (first == last || *first != ':') { - return -1; - } - } else { - auto it = std::ranges::find(first, last, ':'); - if (it == last) { - return -1; - } - - hostv = std::string_view{first, it}; - first = it; - } - - if (++first == last) { - return -1; - } - - std::array host; - *std::ranges::copy(hostv, std::ranges::begin(host)).out = '\0'; - - addrinfo hints{ - .ai_family = af, - .ai_socktype = SOCK_DGRAM, - }; - addrinfo *res; - auto svc = first; - - if (auto rv = getaddrinfo(host.data(), svc, &hints, &res); rv != 0) { - std::cerr << "getaddrinfo: [" << host.data() << "]:" << svc << ": " - << gai_strerror(rv) << std::endl; - return -1; - } - - dest.len = res->ai_addrlen; - memcpy(&dest.su, res->ai_addr, res->ai_addrlen); - - freeaddrinfo(res); - - return 0; -} -} // namespace - -namespace { -const char *prog = "server"; -} // namespace - -namespace { -void print_usage() { - std::cerr << "Usage: " << prog - << " [OPTIONS] " - "" - << std::endl; -} -} // namespace - -namespace { -void config_set_default(Config &config) { - auto path = realpath(".", nullptr); - assert(path); - auto htdocs = std::string(path); - free(path); - - config = Config{ - .tx_loss_prob = 0., - .rx_loss_prob = 0., - .ciphers = util::crypto_default_ciphers(), - .groups = util::crypto_default_groups(), - .htdocs = std::move(htdocs), - .mime_types_file = "/etc/mime.types"sv, - .timeout = 30 * NGTCP2_SECONDS, - .max_data = 1_m, - .max_stream_data_bidi_remote = 256_k, - .max_stream_data_uni = 256_k, - .max_streams_bidi = 100, - .max_streams_uni = 3, - .max_window = 6_m, - .max_stream_window = 6_m, - .max_dyn_length = 20_m, - .cc_algo = NGTCP2_CC_ALGO_CUBIC, - .initial_rtt = NGTCP2_DEFAULT_INITIAL_RTT, - .handshake_timeout = UINT64_MAX, - .ack_thresh = 2, - .initial_pkt_num = UINT32_MAX, - }; -} -} // namespace - -namespace { -void print_help() { - print_usage(); - - config_set_default(config); - - std::cout << R"( - Address to listen to. '*' binds to any address. - Port - - Path to private key file - - Path to certificate file -Options: - -t, --tx-loss=

- The probability of losing outgoing packets.

must be - [0.0, 1.0], inclusive. 0.0 means no packet loss. 1.0 - means 100% packet loss. - -r, --rx-loss=

- The probability of losing incoming packets.

must be - [0.0, 1.0], inclusive. 0.0 means no packet loss. 1.0 - means 100% packet loss. - --ciphers= - Specify the cipher suite list to enable. - Default: )" - << config.ciphers << R"( - --groups= - Specify the supported groups. - Default: )" - << config.groups << R"( - -d, --htdocs= - Specify document root. If this option is not specified, - the document root is the current working directory. - -q, --quiet Suppress debug output. - -s, --show-secret - Print out secrets unless --quiet is used. - --timeout= - Specify idle timeout. - Default: )" - << util::format_duration(config.timeout) << R"( - -V, --validate-addr - Perform address validation. - --preferred-ipv4-addr=: - Specify preferred IPv4 address and port. - --preferred-ipv6-addr=: - Specify preferred IPv6 address and port. A numeric IPv6 - address must be enclosed by '[' and ']' (e.g., - [::1]:8443) - --mime-types-file= - Path to file that contains MIME media types and the - extensions. - Default: )" - << config.mime_types_file << R"( - --early-response - Start sending response when it receives HTTP header - fields without waiting for request body. If HTTP - response data is written before receiving request body, - STOP_SENDING is sent. - --verify-client - Request a client certificate. At the moment, we just - request a certificate and no verification is done. - --qlog-dir= - Path to the directory where qlog file is stored. The - file name of each qlog is the Source Connection ID of - server. - --no-quic-dump - Disables printing QUIC STREAM and CRYPTO frame data out. - --no-http-dump - Disables printing HTTP response body out. - --max-data= - The initial connection-level flow control window. - Default: )" - << util::format_uint_iec(config.max_data) << R"( - --max-stream-data-bidi-local= - The initial stream-level flow control window for a - bidirectional stream that the local endpoint initiates. - Default: )" - << util::format_uint_iec(config.max_stream_data_bidi_local) << R"( - --max-stream-data-bidi-remote= - The initial stream-level flow control window for a - bidirectional stream that the remote endpoint initiates. - Default: )" - << util::format_uint_iec(config.max_stream_data_bidi_remote) << R"( - --max-stream-data-uni= - The initial stream-level flow control window for a - unidirectional stream. - Default: )" - << util::format_uint_iec(config.max_stream_data_uni) << R"( - --max-streams-bidi= - The number of the concurrent bidirectional streams that - the remote endpoint initiates. - Default: )" - << config.max_streams_bidi << R"( - --max-streams-uni= - The number of the concurrent unidirectional streams that - the remote endpoint initiates. - Default: )" - << config.max_streams_uni << R"( - --max-dyn-length= - The maximum length of a dynamically generated content. - Default: )" - << util::format_uint_iec(config.max_dyn_length) << R"( - --cc=(cubic|reno|bbr) - The name of congestion controller algorithm. - Default: )" - << util::strccalgo(config.cc_algo) << R"( - --initial-rtt= - Set an initial RTT. - Default: )" - << util::format_duration(config.initial_rtt) << R"( - --max-udp-payload-size= - Override maximum UDP payload size that server transmits. - With this option, server assumes that a path supports - byte of UDP datagram payload, without performing - Path MTU Discovery. - --send-trailers - Send trailer fields. - --max-window= - Maximum connection-level flow control window size. The - window auto-tuning is enabled if nonzero value is given, - and window size is scaled up to this value. - Default: )" - << util::format_uint_iec(config.max_window) << R"( - --max-stream-window= - Maximum stream-level flow control window size. The - window auto-tuning is enabled if nonzero value is given, - and window size is scaled up to this value. - Default: )" - << util::format_uint_iec(config.max_stream_window) << R"( - --handshake-timeout= - Set the QUIC handshake timeout. It defaults to no - timeout. - --preferred-versions=[[,]...] - Specify QUIC versions in hex string in the order of - preference. Server negotiates one of those versions if - client initially selects a less preferred version. - These versions must be supported by libngtcp2. Instead - of specifying hex string, there are special aliases - available: "v1" indicates QUIC v1, and "v2" indicates - QUIC v2. - --available-versions=[[,]...] - Specify QUIC versions in hex string that are sent in - available_versions field of version_information - transport parameter. This list can include a version - which is not supported by libngtcp2. Instead of - specifying hex string, there are special aliases - available: "v1" indicates QUIC v1, and "v2" indicates - QUIC v2. - --no-pmtud Disables Path MTU Discovery. - --ack-thresh= - The minimum number of the received ACK eliciting packets - that triggers immediate acknowledgement. - Default: )" - << config.ack_thresh << R"( - --initial-pkt-num= - The initial packet number that is used for each packet - number space. It must be in range [0, (1 << 31) - 1], - inclusive. By default, the initial packet number is - chosen randomly. - --pmtud-probes=[[,]...] - Specify UDP datagram payload sizes to probe in Path MTU - Discovery. must be strictly larger than 1200. - --ech-config-file= - Read private key and ECHConfig from . The file - denoted by must contain private key and ECHConfig - as described in - https://datatracker.ietf.org/doc/html/draft-farrell-tls-pemesni. - ECH configuration is only applied if an underlying TLS - stack supports it. - --origin= - Specify the origin to send in ORIGIN frame. Repeat to - add multiple origins. - --no-gso Disables GSO. - --show-stat Print the connection statistics when the connection is - closed. - --gso-burst= - The maximum number of packets to aggregate for GSO. If - GSO is disabled, this is the maximum number of packets - to send per an event loop in a single connection. It - defaults to 0, which means it is not limited by the - configuration. - -h, --help Display this help and exit. - ---- - - The argument is an integer and an optional unit (e.g., 10K is - 10 * 1024). Units are K, M and G (powers of 1024). - - The argument is an integer and an optional unit (e.g., 1s - is 1 second and 500ms is 500 milliseconds). Units are h, m, s, ms, - us, or ns (hours, minutes, seconds, milliseconds, microseconds, and - nanoseconds respectively). If a unit is omitted, a second is used - as unit. - - The argument is an hex string which must start with "0x" - (e.g., 0x00000001).)" - << std::endl; -} -} // namespace - -std::ofstream keylog_file; - -int main(int argc, char **argv) { - config_set_default(config); - - if (argc) { - prog = basename(argv[0]); - } - - std::string_view ech_config_file; - - for (;;) { - static int flag = 0; - constexpr static option long_opts[] = { - {"help", no_argument, nullptr, 'h'}, - {"tx-loss", required_argument, nullptr, 't'}, - {"rx-loss", required_argument, nullptr, 'r'}, - {"htdocs", required_argument, nullptr, 'd'}, - {"quiet", no_argument, nullptr, 'q'}, - {"show-secret", no_argument, nullptr, 's'}, - {"validate-addr", no_argument, nullptr, 'V'}, - {"ciphers", required_argument, &flag, 1}, - {"groups", required_argument, &flag, 2}, - {"timeout", required_argument, &flag, 3}, - {"preferred-ipv4-addr", required_argument, &flag, 4}, - {"preferred-ipv6-addr", required_argument, &flag, 5}, - {"mime-types-file", required_argument, &flag, 6}, - {"early-response", no_argument, &flag, 7}, - {"verify-client", no_argument, &flag, 8}, - {"qlog-dir", required_argument, &flag, 9}, - {"no-quic-dump", no_argument, &flag, 10}, - {"no-http-dump", no_argument, &flag, 11}, - {"max-data", required_argument, &flag, 12}, - {"max-stream-data-bidi-local", required_argument, &flag, 13}, - {"max-stream-data-bidi-remote", required_argument, &flag, 14}, - {"max-stream-data-uni", required_argument, &flag, 15}, - {"max-streams-bidi", required_argument, &flag, 16}, - {"max-streams-uni", required_argument, &flag, 17}, - {"max-dyn-length", required_argument, &flag, 18}, - {"cc", required_argument, &flag, 19}, - {"initial-rtt", required_argument, &flag, 20}, - {"max-udp-payload-size", required_argument, &flag, 21}, - {"send-trailers", no_argument, &flag, 22}, - {"max-window", required_argument, &flag, 23}, - {"max-stream-window", required_argument, &flag, 24}, - {"handshake-timeout", required_argument, &flag, 26}, - {"preferred-versions", required_argument, &flag, 27}, - {"available-versions", required_argument, &flag, 28}, - {"no-pmtud", no_argument, &flag, 29}, - {"ack-thresh", required_argument, &flag, 30}, - {"initial-pkt-num", required_argument, &flag, 31}, - {"pmtud-probes", required_argument, &flag, 32}, - {"ech-config-file", required_argument, &flag, 33}, - {"origin", required_argument, &flag, 34}, - {"no-gso", no_argument, &flag, 35}, - {"show-stat", no_argument, &flag, 36}, - {"gso-burst", required_argument, &flag, 37}, - {}, - }; - - auto optidx = 0; - auto c = getopt_long(argc, argv, "d:hqr:st:V", long_opts, &optidx); - if (c == -1) { - break; - } - switch (c) { - case 'd': { - // --htdocs - auto path = realpath(optarg, nullptr); - if (path == nullptr) { - std::cerr << "path: invalid path " << std::quoted(optarg) << std::endl; - exit(EXIT_FAILURE); - } - config.htdocs = path; - free(path); - break; - } - case 'h': - // --help - print_help(); - exit(EXIT_SUCCESS); - case 'q': - // --quiet - config.quiet = true; - break; - case 'r': - // --rx-loss - config.rx_loss_prob = strtod(optarg, nullptr); - break; - case 's': - // --show-secret - config.show_secret = true; - break; - case 't': - // --tx-loss - config.tx_loss_prob = strtod(optarg, nullptr); - break; - case 'V': - // --validate-addr - config.validate_addr = true; - break; - case '?': - print_usage(); - exit(EXIT_FAILURE); - case 0: - switch (flag) { - case 1: - // --ciphers - if (util::crypto_default_ciphers()[0] == '\0') { - std::cerr << "ciphers: not supported" << std::endl; - exit(EXIT_FAILURE); - } - config.ciphers = optarg; - break; - case 2: - // --groups - config.groups = optarg; - break; - case 3: - // --timeout - if (auto t = util::parse_duration(optarg); !t) { - std::cerr << "timeout: invalid argument" << std::endl; - exit(EXIT_FAILURE); - } else { - config.timeout = *t; - } - break; - case 4: - // --preferred-ipv4-addr - if (parse_host_port(config.preferred_ipv4_addr, AF_INET, optarg) != 0) { - std::cerr << "preferred-ipv4-addr: could not use " - << std::quoted(optarg) << std::endl; - exit(EXIT_FAILURE); - } - break; - case 5: - // --preferred-ipv6-addr - if (parse_host_port(config.preferred_ipv6_addr, AF_INET6, optarg) != - 0) { - std::cerr << "preferred-ipv6-addr: could not use " - << std::quoted(optarg) << std::endl; - exit(EXIT_FAILURE); - } - break; - case 6: - // --mime-types-file - config.mime_types_file = optarg; - break; - case 7: - // --early-response - config.early_response = true; - break; - case 8: - // --verify-client - config.verify_client = true; - break; - case 9: - // --qlog-dir - config.qlog_dir = optarg; - break; - case 10: - // --no-quic-dump - config.no_quic_dump = true; - break; - case 11: - // --no-http-dump - config.no_http_dump = true; - break; - case 12: - // --max-data - if (auto n = util::parse_uint_iec(optarg); !n) { - std::cerr << "max-data: invalid argument" << std::endl; - exit(EXIT_FAILURE); - } else { - config.max_data = *n; - } - break; - case 13: - // --max-stream-data-bidi-local - if (auto n = util::parse_uint_iec(optarg); !n) { - std::cerr << "max-stream-data-bidi-local: invalid argument" - << std::endl; - exit(EXIT_FAILURE); - } else { - config.max_stream_data_bidi_local = *n; - } - break; - case 14: - // --max-stream-data-bidi-remote - if (auto n = util::parse_uint_iec(optarg); !n) { - std::cerr << "max-stream-data-bidi-remote: invalid argument" - << std::endl; - exit(EXIT_FAILURE); - } else { - config.max_stream_data_bidi_remote = *n; - } - break; - case 15: - // --max-stream-data-uni - if (auto n = util::parse_uint_iec(optarg); !n) { - std::cerr << "max-stream-data-uni: invalid argument" << std::endl; - exit(EXIT_FAILURE); - } else { - config.max_stream_data_uni = *n; - } - break; - case 16: - // --max-streams-bidi - if (auto n = util::parse_uint(optarg); !n) { - std::cerr << "max-streams-bidi: invalid argument" << std::endl; - exit(EXIT_FAILURE); - } else { - config.max_streams_bidi = *n; - } - break; - case 17: - // --max-streams-uni - if (auto n = util::parse_uint(optarg); !n) { - std::cerr << "max-streams-uni: invalid argument" << std::endl; - exit(EXIT_FAILURE); - } else { - config.max_streams_uni = *n; - } - break; - case 18: - // --max-dyn-length - if (auto n = util::parse_uint_iec(optarg); !n) { - std::cerr << "max-dyn-length: invalid argument" << std::endl; - exit(EXIT_FAILURE); - } else { - config.max_dyn_length = *n; - } - break; - case 19: - // --cc - if (strcmp("cubic", optarg) == 0) { - config.cc_algo = NGTCP2_CC_ALGO_CUBIC; - break; - } - if (strcmp("reno", optarg) == 0) { - config.cc_algo = NGTCP2_CC_ALGO_RENO; - break; - } - if (strcmp("bbr", optarg) == 0) { - config.cc_algo = NGTCP2_CC_ALGO_BBR; - break; - } - std::cerr << "cc: specify cubic, reno, or bbr" << std::endl; - exit(EXIT_FAILURE); - case 20: - // --initial-rtt - if (auto t = util::parse_duration(optarg); !t) { - std::cerr << "initial-rtt: invalid argument" << std::endl; - exit(EXIT_FAILURE); - } else { - config.initial_rtt = *t; - } - break; - case 21: - // --max-udp-payload-size - if (auto n = util::parse_uint_iec(optarg); !n) { - std::cerr << "max-udp-payload-size: invalid argument" << std::endl; - exit(EXIT_FAILURE); - } else if (*n > NGTCP2_MAX_TX_UDP_PAYLOAD_SIZE) { - std::cerr << "max-udp-payload-size: must not exceed " - << NGTCP2_MAX_TX_UDP_PAYLOAD_SIZE << std::endl; - exit(EXIT_FAILURE); - } else { - config.max_udp_payload_size = *n; - } - break; - case 22: - // --send-trailers - config.send_trailers = true; - break; - case 23: - // --max-window - if (auto n = util::parse_uint_iec(optarg); !n) { - std::cerr << "max-window: invalid argument" << std::endl; - exit(EXIT_FAILURE); - } else { - config.max_window = *n; - } - break; - case 24: - // --max-stream-window - if (auto n = util::parse_uint_iec(optarg); !n) { - std::cerr << "max-stream-window: invalid argument" << std::endl; - exit(EXIT_FAILURE); - } else { - config.max_stream_window = *n; - } - break; - case 26: - // --handshake-timeout - if (auto t = util::parse_duration(optarg); !t) { - std::cerr << "handshake-timeout: invalid argument" << std::endl; - exit(EXIT_FAILURE); - } else { - config.handshake_timeout = *t; - } - break; - case 27: { - // --preferred-versions - auto l = util::split_str(optarg); - if (l.size() > max_preferred_versionslen) { - std::cerr << "preferred-versions: too many versions > " - << max_preferred_versionslen << std::endl; - } - config.preferred_versions.resize(l.size()); - auto it = std::ranges::begin(config.preferred_versions); - for (const auto &k : l) { - if (k == "v1"sv) { - *it++ = NGTCP2_PROTO_VER_V1; - continue; - } - if (k == "v2"sv) { - *it++ = NGTCP2_PROTO_VER_V2; - continue; - } - auto rv = util::parse_version(k); - if (!rv) { - std::cerr << "preferred-versions: invalid version " - << std::quoted(k) << std::endl; - exit(EXIT_FAILURE); - } - if (!ngtcp2_is_supported_version(*rv)) { - std::cerr << "preferred-versions: unsupported version " - << std::quoted(k) << std::endl; - exit(EXIT_FAILURE); - } - *it++ = *rv; - } - break; - } - case 28: { - // --available-versions - auto l = util::split_str(optarg); - config.available_versions.resize(l.size()); - auto it = std::ranges::begin(config.available_versions); - for (const auto &k : l) { - if (k == "v1"sv) { - *it++ = NGTCP2_PROTO_VER_V1; - continue; - } - if (k == "v2"sv) { - *it++ = NGTCP2_PROTO_VER_V2; - continue; - } - auto rv = util::parse_version(k); - if (!rv) { - std::cerr << "available-versions: invalid version " - << std::quoted(k) << std::endl; - exit(EXIT_FAILURE); - } - *it++ = *rv; - } - break; - } - case 29: - // --no-pmtud - config.no_pmtud = true; - break; - case 30: - // --ack-thresh - if (auto n = util::parse_uint(optarg); !n) { - std::cerr << "ack-thresh: invalid argument" << std::endl; - exit(EXIT_FAILURE); - } else if (*n > 100) { - std::cerr << "ack-thresh: must not exceed 100" << std::endl; - exit(EXIT_FAILURE); - } else { - config.ack_thresh = *n; - } - break; - case 31: - // --initial-pkt-num - if (auto n = util::parse_uint(optarg); !n) { - std::cerr << "initial-pkt-num: invalid argument" << std::endl; - exit(EXIT_FAILURE); - } else if (*n > INT32_MAX) { - std::cerr << "initial-pkt-num: must not exceed (1 << 31) - 1" - << std::endl; - exit(EXIT_FAILURE); - } else { - config.initial_pkt_num = static_cast(*n); - } - break; - case 32: { - // --pmtud-probes - auto l = util::split_str(optarg); - for (auto &s : l) { - if (auto n = util::parse_uint_iec(s); !n) { - std::cerr << "pmtud-probes: invalid argument" << std::endl; - exit(EXIT_FAILURE); - } else if (*n <= NGTCP2_MAX_UDP_PAYLOAD_SIZE || - *n > NGTCP2_MAX_TX_UDP_PAYLOAD_SIZE) { - std::cerr << "pmtud-probes: must be in range [" - << NGTCP2_MAX_UDP_PAYLOAD_SIZE + 1 << ", " - << NGTCP2_MAX_TX_UDP_PAYLOAD_SIZE << "], inclusive." - << std::endl; - exit(EXIT_FAILURE); - } else { - config.pmtud_probes.push_back(static_cast(*n)); - } - } - break; - } - case 33: - // --ech-config-file - ech_config_file = optarg; - break; - case 34: { - // --origin - auto origin = std::string_view{optarg}; - - if (auto max = std::numeric_limits::max(); - max < origin.size()) { - std::cerr << "origin: must be less than or equal to " << max - << std::endl; - exit(EXIT_FAILURE); - } - - if (!config.origin_list) { - config.origin_list = std::vector(); - } - - config.origin_list->push_back(static_cast(origin.size() >> 8)); - config.origin_list->push_back(origin.size() & 0xff); - std::ranges::copy(origin, std::back_inserter(*config.origin_list)); - - break; - } - case 35: - // --no-gso - config.no_gso = true; - break; - case 36: - // --show-stat - config.show_stat = true; - break; - case 37: { - // --gso-burst - auto n = util::parse_uint(optarg); - if (!n) { - std::cerr << "gso-burst: invalid argument" << std::endl; - exit(EXIT_FAILURE); - } - - if (*n > 64) { - std::cerr << "gso-burst: must be in range [0, 64], inclusive." - << std::endl; - exit(EXIT_FAILURE); - } - - config.gso_burst = static_cast(*n); - - break; - } - } - break; - default: - break; - } - } - - if (argc - optind < 4) { - std::cerr << "Too few arguments" << std::endl; - print_usage(); - exit(EXIT_FAILURE); - } - - auto addr = argv[optind++]; - auto port = argv[optind++]; - auto private_key_file = argv[optind++]; - auto cert_file = argv[optind++]; - - if (auto n = util::parse_uint(port); !n) { - std::cerr << "port: invalid port number" << std::endl; - exit(EXIT_FAILURE); - } else if (*n > 65535) { - std::cerr << "port: must not exceed 65535" << std::endl; - exit(EXIT_FAILURE); - } else { - config.port = static_cast(*n); - } - - if (auto mt = util::read_mime_types(config.mime_types_file); !mt) { - std::cerr << "mime-types-file: Could not read MIME media types file " - << std::quoted(config.mime_types_file) << std::endl; - } else { - config.mime_types = std::move(*mt); - } - - if (!ech_config_file.empty()) { - auto ech_config = util::read_ech_server_config(ech_config_file); - if (!ech_config) { - std::cerr << "ech-config-file: Could not read private key and ECHConfig" - << std::endl; - exit(EXIT_FAILURE); - } - - config.ech_config = std::move(*ech_config); - } - - TLSServerContext tls_ctx; - - if (tls_ctx.init(private_key_file, cert_file, AppProtocol::H3) != 0) { - exit(EXIT_FAILURE); - } - - if (config.htdocs.back() != '/') { - config.htdocs += '/'; - } - - std::cerr << "Using document root " << config.htdocs << std::endl; - - auto ev_loop_d = defer(ev_loop_destroy, EV_DEFAULT); - - auto keylog_filename = getenv("SSLKEYLOGFILE"); - if (keylog_filename) { - keylog_file.open(keylog_filename, std::ios_base::app); - if (keylog_file) { - tls_ctx.enable_keylog(); - } - } - - if (util::generate_secret(config.static_secret) != 0) { - std::cerr << "Unable to generate static secret" << std::endl; - exit(EXIT_FAILURE); - } - - Server s(EV_DEFAULT, tls_ctx); - if (s.init(addr, port) != 0) { - exit(EXIT_FAILURE); - } - - ev_run(EV_DEFAULT, 0); - - s.disconnect(); - s.close(); - - return EXIT_SUCCESS; -} diff --git a/src/ngtcp2/server.h b/src/ngtcp2/server.h deleted file mode 100644 index 6f112f1..0000000 --- a/src/ngtcp2/server.h +++ /dev/null @@ -1,276 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2017 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#ifndef SERVER_H -#define SERVER_H - -#ifdef HAVE_CONFIG_H -# include -#endif // defined(HAVE_CONFIG_H) - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include - -#include "server_base.h" -#include "tls_server_context.h" -#include "network.h" -#include "shared.h" -#include "util.h" - -using namespace ngtcp2; - -struct HTTPHeader { - HTTPHeader(const std::string_view &name, const std::string_view &value) - : name(name), value(value) {} - - std::string_view name; - std::string_view value; -}; - -class Handler; -struct FileEntry; - -struct Stream { - Stream(int64_t stream_id, Handler *handler); - - int start_response(nghttp3_conn *conn); - std::pair open_file(const std::string &path); - void map_file(const FileEntry &fe); - int send_status_response(nghttp3_conn *conn, unsigned int status_code, - const std::vector &extra_headers = {}); - int send_redirect_response(nghttp3_conn *conn, unsigned int status_code, - const std::string_view &path); - int64_t find_dyn_length(const std::string_view &path); - void http_acked_stream_data(uint64_t datalen); - - int64_t stream_id; - Handler *handler; - // uri is request uri/path. - std::string uri; - std::string method; - std::string authority; - std::string status_resp_body; - // data is a pointer to the memory which maps file denoted by fd. - uint8_t *data; - // datalen is the length of mapped file by data. - uint64_t datalen; - // dynresp is true if dynamic data response is enabled. - bool dynresp; - // dyndataleft is the number of dynamic data left to send. - uint64_t dyndataleft; - // dynbuflen is the number of bytes in-flight. - uint64_t dynbuflen; -}; - -class Server; - -// Endpoint is a local endpoint. -struct Endpoint { - Address addr; - ev_io rev; - Server *server; - int fd; -}; - -class Handler : public HandlerBase { -public: - Handler(struct ev_loop *loop, Server *server); - ~Handler(); - - int init(const Endpoint &ep, const Address &local_addr, const sockaddr *sa, - socklen_t salen, const ngtcp2_cid *dcid, const ngtcp2_cid *scid, - const ngtcp2_cid *ocid, std::span token, - ngtcp2_token_type token_type, uint32_t version, - TLSServerContext &tls_ctx); - - int on_read(const Endpoint &ep, const Address &local_addr, const sockaddr *sa, - socklen_t salen, const ngtcp2_pkt_info *pi, - std::span data); - int on_write(); - int write_streams(); - int feed_data(const Endpoint &ep, const Address &local_addr, - const sockaddr *sa, socklen_t salen, const ngtcp2_pkt_info *pi, - std::span data); - void update_timer(); - int handle_expiry(); - void signal_write(); - int handshake_completed(); - - Server *server() const; - int recv_stream_data(uint32_t flags, int64_t stream_id, - std::span data); - int acked_stream_data_offset(int64_t stream_id, uint64_t datalen); - uint32_t version() const; - void on_stream_open(int64_t stream_id); - int on_stream_close(int64_t stream_id, uint64_t app_error_code); - void start_draining_period(); - int start_closing_period(); - int handle_error(); - int send_conn_close(); - int send_conn_close(const Endpoint &ep, const Address &local_addr, - const sockaddr *sa, socklen_t salen, - const ngtcp2_pkt_info *pi, std::span data); - - int update_key(uint8_t *rx_secret, uint8_t *tx_secret, - ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv, - ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv, - const uint8_t *current_rx_secret, - const uint8_t *current_tx_secret, size_t secretlen); - - int setup_httpconn(); - void http_consume(int64_t stream_id, size_t nconsumed); - void extend_max_remote_streams_bidi(uint64_t max_streams); - Stream *find_stream(int64_t stream_id); - void http_begin_request_headers(int64_t stream_id); - void http_recv_request_header(Stream *stream, int32_t token, - nghttp3_rcbuf *name, nghttp3_rcbuf *value); - int http_end_request_headers(Stream *stream); - int http_end_stream(Stream *stream); - int start_response(Stream *stream); - int on_stream_reset(int64_t stream_id); - int on_stream_stop_sending(int64_t stream_id); - int extend_max_stream_data(int64_t stream_id, uint64_t max_data); - void shutdown_read(int64_t stream_id, uint64_t app_error_code); - void http_acked_stream_data(Stream *stream, uint64_t datalen); - void http_stream_close(int64_t stream_id, uint64_t app_error_code); - int http_stop_sending(int64_t stream_id, uint64_t app_error_code); - int http_reset_stream(int64_t stream_id, uint64_t app_error_code); - - void write_qlog(const void *data, size_t datalen); - - void on_send_blocked(const ngtcp2_path &path, unsigned int ecn, - std::span data, size_t gso_size); - void start_wev_endpoint(const Endpoint &ep); - int send_packet(const ngtcp2_path &path, unsigned int ecn, - std::span data, size_t gso_size); - int send_blocked_packet(); - - ngtcp2_ssize write_pkt(ngtcp2_path *path, ngtcp2_pkt_info *pi, uint8_t *dest, - size_t destlen, ngtcp2_tstamp ts); - -private: - struct ev_loop *loop_; - Server *server_; - ev_io wev_; - ev_timer timer_; - FILE *qlog_; - ngtcp2_cid scid_; - nghttp3_conn *httpconn_; - std::unordered_map> streams_; - // conn_closebuf_ contains a packet which contains CONNECTION_CLOSE. - // This packet is repeatedly sent as a response to the incoming - // packet in draining period. - std::unique_ptr conn_closebuf_; - // nkey_update_ is the number of key update occurred. - size_t nkey_update_; - bool no_gso_; - struct { - size_t bytes_recv; - size_t bytes_sent; - size_t num_pkts_recv; - size_t next_pkts_recv; - } close_wait_; - - struct { - bool send_blocked; - // blocked field is effective only when send_blocked is true. - struct { - const Endpoint *endpoint; - Address local_addr; - Address remote_addr; - unsigned int ecn; - std::span data; - size_t gso_size; - } blocked; - std::unique_ptr data; - } tx_; -}; - -class Server { -public: - Server(struct ev_loop *loop, TLSServerContext &tls_ctx); - ~Server(); - - int init(const char *addr, const char *port); - void disconnect(); - void close(); - - int on_read(const Endpoint &ep); - void read_pkt(const Endpoint &ep, const Address &local_addr, - const sockaddr *sa, socklen_t salen, const ngtcp2_pkt_info *pi, - std::span data); - int send_version_negotiation(uint32_t version, std::span dcid, - std::span scid, - const Endpoint &ep, const Address &local_addr, - const sockaddr *sa, socklen_t salen); - int send_retry(const ngtcp2_pkt_hd *chd, const Endpoint &ep, - const Address &local_addr, const sockaddr *sa, socklen_t salen, - size_t max_pktlen); - int send_stateless_connection_close(const ngtcp2_pkt_hd *chd, - const Endpoint &ep, - const Address &local_addr, - const sockaddr *sa, socklen_t salen); - int send_stateless_reset(size_t pktlen, std::span dcid, - const Endpoint &ep, const Address &local_addr, - const sockaddr *sa, socklen_t salen); - int verify_retry_token(ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd, - const sockaddr *sa, socklen_t salen); - int verify_token(const ngtcp2_pkt_hd *hd, const sockaddr *sa, - socklen_t salen); - int send_packet(const Endpoint &ep, const ngtcp2_addr &local_addr, - const ngtcp2_addr &remote_addr, unsigned int ecn, - std::span data); - std::pair, int> - send_packet(const Endpoint &ep, bool &no_gso, const ngtcp2_addr &local_addr, - const ngtcp2_addr &remote_addr, unsigned int ecn, - std::span data, size_t gso_size); - void remove(const Handler *h); - - void associate_cid(const ngtcp2_cid *cid, Handler *h); - void dissociate_cid(const ngtcp2_cid *cid); - - void on_stateless_reset_regen(); - -private: - std::unordered_map handlers_; - struct ev_loop *loop_; - std::vector endpoints_; - TLSServerContext &tls_ctx_; - ev_signal sigintev_; - ev_timer stateless_reset_regen_timer_; - size_t stateless_reset_bucket_; -}; - -#endif // !defined(SERVER_H) diff --git a/src/ngtcp2/server_base.cc b/src/ngtcp2/server_base.cc deleted file mode 100644 index 1b7cb6d..0000000 --- a/src/ngtcp2/server_base.cc +++ /dev/null @@ -1,62 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2020 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#include "server_base.h" - -#include -#include -#include - -#include "debug.h" - -using namespace ngtcp2; - -extern Config config; - -Buffer::Buffer(const uint8_t *data, size_t datalen) - : buf{data, data + datalen}, begin(buf.data()), tail(begin + datalen) {} -Buffer::Buffer(size_t datalen) : buf(datalen), begin(buf.data()), tail(begin) {} - -static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) { - auto h = static_cast(conn_ref->user_data); - return h->conn(); -} - -HandlerBase::HandlerBase() : conn_ref_{get_conn, this}, conn_(nullptr) { - ngtcp2_ccerr_default(&last_error_); -} - -HandlerBase::~HandlerBase() { - if (conn_) { - if (config.show_stat) { - debug::print_conn_info(conn_); - } - - ngtcp2_conn_del(conn_); - } -} - -ngtcp2_conn *HandlerBase::conn() const { return conn_; } - -ngtcp2_crypto_conn_ref *HandlerBase::conn_ref() { return &conn_ref_; } diff --git a/src/ngtcp2/server_base.h b/src/ngtcp2/server_base.h deleted file mode 100644 index 6347ba3..0000000 --- a/src/ngtcp2/server_base.h +++ /dev/null @@ -1,212 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2020 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#ifndef SERVER_BASE_H -#define SERVER_BASE_H - -#ifdef HAVE_CONFIG_H -# include -#endif // defined(HAVE_CONFIG_H) - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "tls_server_session.h" -#include "network.h" -#include "shared.h" -#include "template.h" -#include "util.h" - -using namespace ngtcp2; - -struct Config { - Address preferred_ipv4_addr; - Address preferred_ipv6_addr; - // tx_loss_prob is probability of losing outgoing packet. - double tx_loss_prob; - // rx_loss_prob is probability of losing incoming packet. - double rx_loss_prob; - // ciphers is the list of enabled ciphers. - const char *ciphers; - // groups is the list of supported groups. - const char *groups; - // htdocs is a root directory to serve documents. - std::string htdocs; - // mime_types_file is a path to "MIME media types and the - // extensions" file. Ubuntu mime-support package includes it in - // /etc/mime/types. - std::string_view mime_types_file; - // mime_types maps file extension to MIME media type. - std::unordered_map mime_types; - // port is the port number which server listens on for incoming - // connections. - uint16_t port; - // quiet suppresses the output normally shown except for the error - // messages. - bool quiet; - // timeout is an idle timeout for QUIC connection. - ngtcp2_duration timeout; - // show_secret is true if transport secrets should be printed out. - bool show_secret; - // validate_addr is true if server requires address validation. - bool validate_addr; - // early_response is true if server starts sending response when it - // receives HTTP header fields without waiting for request body. If - // HTTP response data is written before receiving request body, - // STOP_SENDING is sent. - bool early_response; - // verify_client is true if server verifies client with X.509 - // certificate based authentication. - bool verify_client; - // qlog_dir is the path to directory where qlog is stored. - std::string_view qlog_dir; - // no_quic_dump is true if hexdump of QUIC STREAM and CRYPTO data - // should be disabled. - bool no_quic_dump; - // no_http_dump is true if hexdump of HTTP response body should be - // disabled. - bool no_http_dump; - // max_data is the initial connection-level flow control window. - uint64_t max_data; - // max_stream_data_bidi_local is the initial stream-level flow - // control window for a bidirectional stream that the local endpoint - // initiates. - uint64_t max_stream_data_bidi_local; - // max_stream_data_bidi_remote is the initial stream-level flow - // control window for a bidirectional stream that the remote - // endpoint initiates. - uint64_t max_stream_data_bidi_remote; - // max_stream_data_uni is the initial stream-level flow control - // window for a unidirectional stream. - uint64_t max_stream_data_uni; - // max_streams_bidi is the number of the concurrent bidirectional - // streams. - uint64_t max_streams_bidi; - // max_streams_uni is the number of the concurrent unidirectional - // streams. - uint64_t max_streams_uni; - // max_window is the maximum connection-level flow control window - // size if auto-tuning is enabled. - uint64_t max_window; - // max_stream_window is the maximum stream-level flow control window - // size if auto-tuning is enabled. - uint64_t max_stream_window; - // max_dyn_length is the maximum length of dynamically generated - // response. - uint64_t max_dyn_length; - // static_secret is used to derive keying materials for Retry and - // Stateless Retry token. - std::array static_secret; - // cc_algo is the congestion controller algorithm. - ngtcp2_cc_algo cc_algo; - // initial_rtt is an initial RTT. - ngtcp2_duration initial_rtt; - // max_udp_payload_size is the maximum UDP payload size that server - // transmits. - size_t max_udp_payload_size; - // send_trailers controls whether server sends trailer fields or - // not. - bool send_trailers; - // handshake_timeout is the period of time before giving up QUIC - // connection establishment. - ngtcp2_duration handshake_timeout; - // preferred_versions includes QUIC versions in the order of - // preference. Server negotiates one of those versions if a client - // initially selects a less preferred version. - std::vector preferred_versions; - // available_versions includes QUIC versions that are sent in - // available_versions field of version_information - // transport_parameter. - std::vector available_versions; - // no_pmtud disables Path MTU Discovery. - bool no_pmtud; - // ack_thresh is the minimum number of the received ACK eliciting - // packets that triggers immediate acknowledgement. - size_t ack_thresh; - // initial_pkt_num is the initial packet number for each packet - // number space. If it is set to UINT32_MAX, it is chosen randomly. - uint32_t initial_pkt_num; - // pmtud_probes is the array of UDP datagram payload size to probes. - std::vector pmtud_probes; - // ech_config contains server-side ECH configuration. - util::ECHServerConfig ech_config; - // origin_list contains a payload of ORIGIN frame. - std::optional> origin_list; - // no_gso disables GSO. - bool no_gso; - // show_stat, if true, displays the connection statistics when the - // connection is closed. - bool show_stat; - // gso_burst is the number of packets to aggregate in GSO. 0 means - // it is not limited by the configuration. - size_t gso_burst; -}; - -struct Buffer { - Buffer(const uint8_t *data, size_t datalen); - explicit Buffer(size_t datalen); - - size_t size() const { return as_unsigned(tail - begin); } - size_t left() const { return as_unsigned(buf.data() + buf.size() - tail); } - uint8_t *const wpos() { return tail; } - std::span data() const { return {begin, size()}; } - void push(size_t len) { tail += len; } - void reset() { tail = begin; } - - std::vector buf; - // begin points to the beginning of the buffer. This might point to - // buf.data() if a buffer space is allocated by this object. It is - // also allowed to point to the external shared buffer. - uint8_t *begin; - // tail points to the position of the buffer where write should - // occur. - uint8_t *tail; -}; - -class HandlerBase { -public: - HandlerBase(); - ~HandlerBase(); - - ngtcp2_conn *conn() const; - - TLSServerSession *get_session() { return &tls_session_; } - - ngtcp2_crypto_conn_ref *conn_ref(); - -protected: - ngtcp2_crypto_conn_ref conn_ref_; - TLSServerSession tls_session_; - ngtcp2_conn *conn_; - ngtcp2_ccerr last_error_; -}; - -#endif // !defined(SERVER_BASE_H) diff --git a/src/ngtcp2/shared.cc b/src/ngtcp2/shared.cc index 8aa09ec..a75286f 100644 --- a/src/ngtcp2/shared.cc +++ b/src/ngtcp2/shared.cc @@ -24,144 +24,11 @@ */ #include "shared.h" -#include - -#include #include -#include - -#include -#ifdef HAVE_NETINET_IN_H -# include -#endif // defined(HAVE_NETINET_IN_H) -#ifdef HAVE_NETINET_UDP_H -# include -#endif // defined(HAVE_NETINET_UDP_H) -#ifdef HAVE_NETINET_IP_H -# include -#endif // defined(HAVE_NETINET_IP_H) -#ifdef HAVE_ASM_TYPES_H -# include -#endif // defined(HAVE_ASM_TYPES_H) -#ifdef HAVE_LINUX_NETLINK_H -# include -#endif // defined(HAVE_LINUX_NETLINK_H) -#ifdef HAVE_LINUX_RTNETLINK_H -# include -#endif // defined(HAVE_LINUX_RTNETLINK_H) - -#include "template.h" +#include namespace ngtcp2 { -uint8_t msghdr_get_ecn(msghdr *msg, int family) { - switch (family) { - case AF_INET: - for (auto cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) { - if (cmsg->cmsg_level == IPPROTO_IP && -#ifdef __APPLE__ - cmsg->cmsg_type == IP_RECVTOS -#else // !defined(__APPLE__) - cmsg->cmsg_type == IP_TOS -#endif // !defined(__APPLE__) - && cmsg->cmsg_len) { - return *reinterpret_cast(CMSG_DATA(cmsg)) & IPTOS_ECN_MASK; - } - } - break; - case AF_INET6: - for (auto cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) { - if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_TCLASS && - cmsg->cmsg_len) { - unsigned int tos; - - memcpy(&tos, CMSG_DATA(cmsg), sizeof(int)); - - return tos & IPTOS_ECN_MASK; - } - } - break; - } - - return 0; -} - -void fd_set_recv_ecn(int fd, int family) { - unsigned int tos = 1; - switch (family) { - case AF_INET: - if (setsockopt(fd, IPPROTO_IP, IP_RECVTOS, &tos, - static_cast(sizeof(tos))) == -1) { - std::cerr << "setsockopt: " << strerror(errno) << std::endl; - } - break; - case AF_INET6: - if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVTCLASS, &tos, - static_cast(sizeof(tos))) == -1) { - std::cerr << "setsockopt: " << strerror(errno) << std::endl; - } - break; - } -} - -void fd_set_ip_mtu_discover(int fd, int family) { -#if defined(IP_MTU_DISCOVER) && defined(IPV6_MTU_DISCOVER) - int val; - - switch (family) { - case AF_INET: - val = IP_PMTUDISC_PROBE; - if (setsockopt(fd, IPPROTO_IP, IP_MTU_DISCOVER, &val, - static_cast(sizeof(val))) == -1) { - std::cerr << "setsockopt: IP_MTU_DISCOVER: " << strerror(errno) - << std::endl; - } - break; - case AF_INET6: - val = IPV6_PMTUDISC_PROBE; - if (setsockopt(fd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &val, - static_cast(sizeof(val))) == -1) { - std::cerr << "setsockopt: IPV6_MTU_DISCOVER: " << strerror(errno) - << std::endl; - } - break; - } -#endif // defined(IP_MTU_DISCOVER) && defined(IPV6_MTU_DISCOVER) -} - -void fd_set_ip_dontfrag(int fd, int family) { -#if defined(IP_DONTFRAG) && defined(IPV6_DONTFRAG) - int val = 1; - - switch (family) { - case AF_INET: - if (setsockopt(fd, IPPROTO_IP, IP_DONTFRAG, &val, - static_cast(sizeof(val))) == -1) { - std::cerr << "setsockopt: IP_DONTFRAG: " << strerror(errno) << std::endl; - } - break; - case AF_INET6: - if (setsockopt(fd, IPPROTO_IPV6, IPV6_DONTFRAG, &val, - static_cast(sizeof(val))) == -1) { - std::cerr << "setsockopt: IPV6_DONTFRAG: " << strerror(errno) - << std::endl; - } - break; - } -#endif // defined(IP_DONTFRAG) && defined(IPV6_DONTFRAG) -} - -void fd_set_udp_gro(int fd) { -#ifdef UDP_GRO - int val = 1; - - if (setsockopt(fd, IPPROTO_UDP, UDP_GRO, &val, - static_cast(sizeof(val))) == -1) { - std::cerr << "setsockopt: UDP_GRO: " << strerror(errno) << std::endl; - } -#endif // defined(UDP_GRO) -} - std::optional

msghdr_get_local_addr(msghdr *msg, int family) { switch (family) { case AF_INET: @@ -200,22 +67,6 @@ std::optional
msghdr_get_local_addr(msghdr *msg, int family) { return {}; } -size_t msghdr_get_udp_gro(msghdr *msg) { - int gso_size = 0; - -#ifdef UDP_GRO - for (auto cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) { - if (cmsg->cmsg_level == SOL_UDP && cmsg->cmsg_type == UDP_GRO) { - memcpy(&gso_size, CMSG_DATA(cmsg), sizeof(gso_size)); - - break; - } - } -#endif // defined(UDP_GRO) - - return static_cast(gso_size); -} - void set_port(Address &dst, const Address &src) { switch (dst.su.storage.ss_family) { case AF_INET: @@ -231,277 +82,4 @@ void set_port(Address &dst, const Address &src) { } } -#ifdef HAVE_LINUX_RTNETLINK_H - -struct nlmsg { - nlmsghdr hdr; - rtmsg msg; - rtattr dst; - in_addr_union dst_addr; -}; - -namespace { -int send_netlink_msg(int fd, const Address &remote_addr, uint32_t seq) { - nlmsg nlmsg{ - .hdr = - { - .nlmsg_type = RTM_GETROUTE, - .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK, - .nlmsg_seq = seq, - }, - .msg = - { - .rtm_family = static_cast(remote_addr.su.sa.sa_family), - .rtm_protocol = RTPROT_KERNEL, - }, - .dst = - { - .rta_type = RTA_DST, - }, - }; - - switch (remote_addr.su.sa.sa_family) { - case AF_INET: - nlmsg.dst.rta_len = RTA_LENGTH(sizeof(remote_addr.su.in.sin_addr)); - memcpy(RTA_DATA(&nlmsg.dst), &remote_addr.su.in.sin_addr, - sizeof(remote_addr.su.in.sin_addr)); - break; - case AF_INET6: - nlmsg.dst.rta_len = RTA_LENGTH(sizeof(remote_addr.su.in6.sin6_addr)); - memcpy(RTA_DATA(&nlmsg.dst), &remote_addr.su.in6.sin6_addr, - sizeof(remote_addr.su.in6.sin6_addr)); - break; - default: - assert(0); - } - - nlmsg.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(nlmsg.msg) + nlmsg.dst.rta_len); - - sockaddr_nl sa{ - .nl_family = AF_NETLINK, - }; - - iovec iov{ - .iov_base = &nlmsg, - .iov_len = nlmsg.hdr.nlmsg_len, - }; - msghdr msg{ - .msg_name = &sa, - .msg_namelen = sizeof(sa), - .msg_iov = &iov, - .msg_iovlen = 1, - }; - - ssize_t nwrite; - - do { - nwrite = sendmsg(fd, &msg, 0); - } while (nwrite == -1 && errno == EINTR); - - if (nwrite == -1) { - std::cerr << "sendmsg: Could not write netlink message: " << strerror(errno) - << std::endl; - return -1; - } - - return 0; -} -} // namespace - -namespace { -int recv_netlink_msg(in_addr_union &iau, int fd, uint32_t seq) { - std::array buf; - iovec iov = { - .iov_base = buf.data(), - .iov_len = buf.size(), - }; - sockaddr_nl sa{}; - msghdr msg{ - .msg_name = &sa, - .msg_namelen = sizeof(sa), - .msg_iov = &iov, - .msg_iovlen = 1, - }; - ssize_t nread; - - do { - nread = recvmsg(fd, &msg, 0); - } while (nread == -1 && errno == EINTR); - - if (nread == -1) { - std::cerr << "recvmsg: Could not receive netlink message: " - << strerror(errno) << std::endl; - return -1; - } - - size_t in_addrlen = 0; - - for (auto hdr = reinterpret_cast(buf.data()); - NLMSG_OK(hdr, nread); hdr = NLMSG_NEXT(hdr, nread)) { - if (seq != hdr->nlmsg_seq) { - std::cerr << "netlink: unexpected sequence number " << hdr->nlmsg_seq - << " while expecting " << seq << std::endl; - return -1; - } - - if (hdr->nlmsg_flags & NLM_F_MULTI) { - std::cerr << "netlink: unexpected NLM_F_MULTI flag set" << std::endl; - return -1; - } - - switch (hdr->nlmsg_type) { - case NLMSG_DONE: - std::cerr << "netlink: unexpected NLMSG_DONE" << std::endl; - return -1; - case NLMSG_NOOP: - continue; - case NLMSG_ERROR: - std::cerr << "netlink: " - << strerror(-static_cast(NLMSG_DATA(hdr))->error) - << std::endl; - return -1; - } - - auto attrlen = hdr->nlmsg_len - NLMSG_SPACE(sizeof(rtmsg)); - - for (auto rta = reinterpret_cast( - static_cast(NLMSG_DATA(hdr)) + sizeof(rtmsg)); - RTA_OK(rta, attrlen); rta = RTA_NEXT(rta, attrlen)) { - if (rta->rta_type != RTA_PREFSRC) { - continue; - } - - switch (static_cast(NLMSG_DATA(hdr))->rtm_family) { - case AF_INET: - in_addrlen = sizeof(in_addr); - break; - case AF_INET6: - in_addrlen = sizeof(in6_addr); - break; - default: - assert(0); - abort(); - } - - if (RTA_LENGTH(in_addrlen) != rta->rta_len) { - return -1; - } - - memcpy(&iau, RTA_DATA(rta), in_addrlen); - - break; - } - } - - if (in_addrlen == 0) { - return -1; - } - - // Read ACK - sa = {}; - msg = {}; - - msg.msg_name = &sa; - msg.msg_namelen = sizeof(sa); - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - int error = -1; - - do { - nread = recvmsg(fd, &msg, 0); - } while (nread == -1 && errno == EINTR); - - if (nread == -1) { - std::cerr << "recvmsg: Could not receive netlink message: " - << strerror(errno) << std::endl; - return -1; - } - - error = -1; - - for (auto hdr = reinterpret_cast(buf.data()); - NLMSG_OK(hdr, nread); hdr = NLMSG_NEXT(hdr, nread)) { - if (seq != hdr->nlmsg_seq) { - std::cerr << "netlink: unexpected sequence number " << hdr->nlmsg_seq - << " while expecting " << seq << std::endl; - return -1; - } - - if (hdr->nlmsg_flags & NLM_F_MULTI) { - std::cerr << "netlink: unexpected NLM_F_MULTI flag set" << std::endl; - return -1; - } - - switch (hdr->nlmsg_type) { - case NLMSG_DONE: - std::cerr << "netlink: unexpected NLMSG_DONE" << std::endl; - return -1; - case NLMSG_NOOP: - continue; - case NLMSG_ERROR: - error = -static_cast(NLMSG_DATA(hdr))->error; - if (error == 0) { - break; - } - - std::cerr << "netlink: " << strerror(error) << std::endl; - - return -1; - } - } - - if (error != 0) { - return -1; - } - - return 0; -} -} // namespace - -int get_local_addr(in_addr_union &iau, const Address &remote_addr) { - sockaddr_nl sa{ - .nl_family = AF_NETLINK, - }; - - auto fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); - if (fd == -1) { - std::cerr << "socket: Could not create netlink socket: " << strerror(errno) - << std::endl; - return -1; - } - - auto fd_d = defer(close, fd); - - if (bind(fd, reinterpret_cast(&sa), sizeof(sa)) == -1) { - std::cerr << "bind: Could not bind netlink socket: " << strerror(errno) - << std::endl; - return -1; - } - - uint32_t seq = 1; - - if (send_netlink_msg(fd, remote_addr, seq) != 0) { - return -1; - } - - return recv_netlink_msg(iau, fd, seq); -} - -#endif // defined(HAVE_LINUX_NETLINK_H) - -bool addreq(const sockaddr *sa, const in_addr_union &iau) { - switch (sa->sa_family) { - case AF_INET: - return memcmp(&reinterpret_cast(sa)->sin_addr, &iau.in, - sizeof(iau.in)) == 0; - case AF_INET6: - return memcmp(&reinterpret_cast(sa)->sin6_addr, - &iau.in6, sizeof(iau.in6)) == 0; - default: - assert(0); - abort(); - } -} - } // namespace ngtcp2 diff --git a/src/ngtcp2/shared.h b/src/ngtcp2/shared.h index 28ce66c..4d6e48a 100644 --- a/src/ngtcp2/shared.h +++ b/src/ngtcp2/shared.h @@ -25,77 +25,20 @@ #ifndef SHARED_H #define SHARED_H -#ifdef HAVE_CONFIG_H -# include -#endif // defined(HAVE_CONFIG_H) - #include -#include -#include - -#include #include "network.h" -using namespace std::literals; - namespace ngtcp2 { -enum class AppProtocol { - H3, - HQ, -}; - -template -consteval std::span as_uint8_span(const uint8_t (&s)[N]) { - return {s, N - 1}; -} - -inline constexpr uint8_t RAW_HQ_ALPN[] = "\xahq-interop"; -inline constexpr auto HQ_ALPN = as_uint8_span(RAW_HQ_ALPN); -inline constexpr auto HQ_ALPN_V1 = as_uint8_span(RAW_HQ_ALPN); - -inline constexpr uint8_t RAW_H3_ALPN[] = "\x2h3"; -inline constexpr auto H3_ALPN = as_uint8_span(RAW_H3_ALPN); -inline constexpr auto H3_ALPN_V1 = as_uint8_span(RAW_H3_ALPN); - -inline constexpr uint32_t TLS_ALERT_ECH_REQUIRED = 121; - -inline constexpr size_t MAX_RECV_PKTS = 64; - -// msghdr_get_ecn gets ECN bits from |msg|. |family| is the address -// family from which packet is received. -uint8_t msghdr_get_ecn(msghdr *msg, int family); - -// fd_set_recv_ecn sets socket option to |fd| so that it can receive -// ECN bits. -void fd_set_recv_ecn(int fd, int family); - -// fd_set_ip_mtu_discover sets IP(V6)_MTU_DISCOVER socket option to -// |fd|. -void fd_set_ip_mtu_discover(int fd, int family); - -// fd_set_ip_dontfrag sets IP(V6)_DONTFRAG socket option to |fd|. -void fd_set_ip_dontfrag(int fd, int family); - -// fd_set_udp_gro sets UDP_GRO socket option to |fd|. -void fd_set_udp_gro(int fd); - +// msghdr_get_local_addr returns the local (destination) address of the packet +// described by |msg|, as delivered by IP(V6)_PKTINFO. |family| is the address +// family from which the packet was received. std::optional
msghdr_get_local_addr(msghdr *msg, int family); -// msghdr_get_udp_gro returns UDP_GRO value from |msg|. If UDP_GRO is -// not found, or UDP_GRO is not supported, this function returns 0. -size_t msghdr_get_udp_gro(msghdr *msg); - +// set_port copies the port of |src| into |dst|. void set_port(Address &dst, const Address &src); -// get_local_addr stores preferred local address (interface address) -// in |iau| for a given destination address |remote_addr|. -int get_local_addr(in_addr_union &iau, const Address &remote_addr); - -// addreq returns true if |sa| and |iau| contain the same address. -bool addreq(const sockaddr *sa, const in_addr_union &iau); - } // namespace ngtcp2 #endif // !defined(SHARED_H) diff --git a/src/ngtcp2/siphash.cc b/src/ngtcp2/siphash.cc deleted file mode 100644 index f19bd71..0000000 --- a/src/ngtcp2/siphash.cc +++ /dev/null @@ -1,116 +0,0 @@ -/* Copyright 2019 The BoringSSL Authors - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY - * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION - * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN - * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ - -/* - * ngtcp2 - * - * Copyright (c) 2025 nghttp2 contributors - * Copyright (c) 2025 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#include -#include -#include - -#include "siphash.h" - -namespace { -auto CRYPTO_load_u64_le(std::span in) { - uint64_t v; - - memcpy(&v, in.data(), sizeof(v)); - - if constexpr (std::endian::native == std::endian::big) { - return byteswap(v); - } - - return v; -} -} // namespace - -namespace { -constexpr void siphash_round(uint64_t v[4]) { - v[0] += v[1]; - v[2] += v[3]; - v[1] = std::rotl(v[1], 13); - v[3] = std::rotl(v[3], 16); - v[1] ^= v[0]; - v[3] ^= v[2]; - v[0] = std::rotl(v[0], 32); - v[2] += v[1]; - v[0] += v[3]; - v[1] = std::rotl(v[1], 17); - v[3] = std::rotl(v[3], 21); - v[1] ^= v[2]; - v[3] ^= v[0]; - v[2] = std::rotl(v[2], 32); -} -} // namespace - -uint64_t siphash24(std::span key, - std::span input) { - const auto orig_input_len = input.size(); - uint64_t v[]{ - key[0] ^ UINT64_C(0x736f6d6570736575), - key[1] ^ UINT64_C(0x646f72616e646f6d), - key[0] ^ UINT64_C(0x6c7967656e657261), - key[1] ^ UINT64_C(0x7465646279746573), - }; - - while (input.size() >= sizeof(uint64_t)) { - auto m = CRYPTO_load_u64_le(input.first()); - v[3] ^= m; - siphash_round(v); - siphash_round(v); - v[0] ^= m; - - input = input.subspan(sizeof(uint64_t)); - } - - std::array last_block{}; - std::ranges::copy(input, std::ranges::begin(last_block)); - last_block.back() = orig_input_len & 0xff; - - auto last_block_word = CRYPTO_load_u64_le(last_block); - v[3] ^= last_block_word; - siphash_round(v); - siphash_round(v); - v[0] ^= last_block_word; - - v[2] ^= 0xff; - siphash_round(v); - siphash_round(v); - siphash_round(v); - siphash_round(v); - - return v[0] ^ v[1] ^ v[2] ^ v[3]; -} diff --git a/src/ngtcp2/siphash.h b/src/ngtcp2/siphash.h deleted file mode 100644 index 5d46db3..0000000 --- a/src/ngtcp2/siphash.h +++ /dev/null @@ -1,62 +0,0 @@ -/* Copyright 2019 The BoringSSL Authors - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY - * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION - * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN - * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ - -/* - * ngtcp2 - * - * Copyright (c) 2025 nghttp2 contributors - * Copyright (c) 2025 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#ifndef SIPHASH_H -#define SIPHASH_H - -#include -#include -#include -#include - -// SipHash is a fast, secure PRF that is often used for hash tables. - -// siphash24 implements SipHash-2-4. See -// https://131002.net/siphash/siphash.pdf -uint64_t siphash24(std::span key, - std::span input); - -// Define here to be usable in tests. -template T byteswap(T v) { - auto c = std::bit_cast>(v); - std::ranges::reverse(c); - return std::bit_cast(c); -} - -#endif // !defined(SIPHASH_H) diff --git a/src/ngtcp2/siphash_test.cc b/src/ngtcp2/siphash_test.cc deleted file mode 100644 index 9c64c69..0000000 --- a/src/ngtcp2/siphash_test.cc +++ /dev/null @@ -1,98 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2025 nghttp2 contributors - * Copyright (c) 2025 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#include "siphash_test.h" - -#include -#include -#include - -#include "siphash.h" -#include "siphash_vector.h" - -namespace ngtcp2 { - -namespace { -const MunitTest tests[]{ - munit_void_test(test_siphash), - munit_void_test(test_siphash_vector), - munit_test_end(), -}; -} // namespace - -const MunitSuite siphash_suite{ - .prefix = "/siphash", - .tests = tests, -}; - -void test_siphash(void) { - std::array key_bytes; - std::iota(std::ranges::begin(key_bytes), std::ranges::end(key_bytes), 0); - - std::array key; - memcpy(key.data(), key_bytes.data(), key_bytes.size()); - - if constexpr (std::endian::native == std::endian::big) { - key[0] = byteswap(key[0]); - key[1] = byteswap(key[1]); - } - - std::array input; - std::iota(std::ranges::begin(input), std::ranges::end(input), 0); - - assert_uint64(0xa129ca6149be45e5ull, ==, siphash24(key, input)); -} - -void test_siphash_vector(void) { - std::array key_bytes; - std::iota(std::ranges::begin(key_bytes), std::ranges::end(key_bytes), 0); - - std::array key; - memcpy(key.data(), key_bytes.data(), key_bytes.size()); - - if constexpr (std::endian::native == std::endian::big) { - key[0] = byteswap(key[0]); - key[1] = byteswap(key[1]); - } - - std::array in; - - for (size_t i = 0; i < 64; ++i) { - in[i] = static_cast(i); - auto h = siphash24(key, std::span{in}.first(i)); - - uint64_t expect; - - memcpy(&expect, &vectors_sip64[i], sizeof(expect)); - - if constexpr (std::endian::native == std::endian::big) { - expect = byteswap(expect); - } - - assert_uint64(expect, ==, h); - } -} - -} // namespace ngtcp2 diff --git a/src/ngtcp2/siphash_test.h b/src/ngtcp2/siphash_test.h deleted file mode 100644 index c356d2e..0000000 --- a/src/ngtcp2/siphash_test.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2025 nghttp2 contributors - * Copyright (c) 2025 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#ifndef SIPHASH_TEST_H -#define SIPHASH_TEST_H - -#ifdef HAVE_CONFIG_H -# include -#endif // defined(HAVE_CONFIG_H) - -#define MUNIT_ENABLE_ASSERT_ALIASES - -#include "munit.h" - -namespace ngtcp2 { - -extern const MunitSuite siphash_suite; - -munit_void_test_decl(test_siphash) -munit_void_test_decl(test_siphash_vector) - -} // namespace ngtcp2 - -#endif // !defined(SIPHASH_TEST_H) diff --git a/src/ngtcp2/siphash_vector.h b/src/ngtcp2/siphash_vector.h deleted file mode 100644 index 62cc0e2..0000000 --- a/src/ngtcp2/siphash_vector.h +++ /dev/null @@ -1,645 +0,0 @@ -// https://github.com/veorq/SipHash/blob/f26d35e964c6290ffe23d9043475ad3129f409e0/vectors.h -#include - -const uint8_t vectors_sip64[64][8] = { - { - 0x31, - 0x0e, - 0x0e, - 0xdd, - 0x47, - 0xdb, - 0x6f, - 0x72, - }, - { - 0xfd, - 0x67, - 0xdc, - 0x93, - 0xc5, - 0x39, - 0xf8, - 0x74, - }, - { - 0x5a, - 0x4f, - 0xa9, - 0xd9, - 0x09, - 0x80, - 0x6c, - 0x0d, - }, - { - 0x2d, - 0x7e, - 0xfb, - 0xd7, - 0x96, - 0x66, - 0x67, - 0x85, - }, - { - 0xb7, - 0x87, - 0x71, - 0x27, - 0xe0, - 0x94, - 0x27, - 0xcf, - }, - { - 0x8d, - 0xa6, - 0x99, - 0xcd, - 0x64, - 0x55, - 0x76, - 0x18, - }, - { - 0xce, - 0xe3, - 0xfe, - 0x58, - 0x6e, - 0x46, - 0xc9, - 0xcb, - }, - { - 0x37, - 0xd1, - 0x01, - 0x8b, - 0xf5, - 0x00, - 0x02, - 0xab, - }, - { - 0x62, - 0x24, - 0x93, - 0x9a, - 0x79, - 0xf5, - 0xf5, - 0x93, - }, - { - 0xb0, - 0xe4, - 0xa9, - 0x0b, - 0xdf, - 0x82, - 0x00, - 0x9e, - }, - { - 0xf3, - 0xb9, - 0xdd, - 0x94, - 0xc5, - 0xbb, - 0x5d, - 0x7a, - }, - { - 0xa7, - 0xad, - 0x6b, - 0x22, - 0x46, - 0x2f, - 0xb3, - 0xf4, - }, - { - 0xfb, - 0xe5, - 0x0e, - 0x86, - 0xbc, - 0x8f, - 0x1e, - 0x75, - }, - { - 0x90, - 0x3d, - 0x84, - 0xc0, - 0x27, - 0x56, - 0xea, - 0x14, - }, - { - 0xee, - 0xf2, - 0x7a, - 0x8e, - 0x90, - 0xca, - 0x23, - 0xf7, - }, - { - 0xe5, - 0x45, - 0xbe, - 0x49, - 0x61, - 0xca, - 0x29, - 0xa1, - }, - { - 0xdb, - 0x9b, - 0xc2, - 0x57, - 0x7f, - 0xcc, - 0x2a, - 0x3f, - }, - { - 0x94, - 0x47, - 0xbe, - 0x2c, - 0xf5, - 0xe9, - 0x9a, - 0x69, - }, - { - 0x9c, - 0xd3, - 0x8d, - 0x96, - 0xf0, - 0xb3, - 0xc1, - 0x4b, - }, - { - 0xbd, - 0x61, - 0x79, - 0xa7, - 0x1d, - 0xc9, - 0x6d, - 0xbb, - }, - { - 0x98, - 0xee, - 0xa2, - 0x1a, - 0xf2, - 0x5c, - 0xd6, - 0xbe, - }, - { - 0xc7, - 0x67, - 0x3b, - 0x2e, - 0xb0, - 0xcb, - 0xf2, - 0xd0, - }, - { - 0x88, - 0x3e, - 0xa3, - 0xe3, - 0x95, - 0x67, - 0x53, - 0x93, - }, - { - 0xc8, - 0xce, - 0x5c, - 0xcd, - 0x8c, - 0x03, - 0x0c, - 0xa8, - }, - { - 0x94, - 0xaf, - 0x49, - 0xf6, - 0xc6, - 0x50, - 0xad, - 0xb8, - }, - { - 0xea, - 0xb8, - 0x85, - 0x8a, - 0xde, - 0x92, - 0xe1, - 0xbc, - }, - { - 0xf3, - 0x15, - 0xbb, - 0x5b, - 0xb8, - 0x35, - 0xd8, - 0x17, - }, - { - 0xad, - 0xcf, - 0x6b, - 0x07, - 0x63, - 0x61, - 0x2e, - 0x2f, - }, - { - 0xa5, - 0xc9, - 0x1d, - 0xa7, - 0xac, - 0xaa, - 0x4d, - 0xde, - }, - { - 0x71, - 0x65, - 0x95, - 0x87, - 0x66, - 0x50, - 0xa2, - 0xa6, - }, - { - 0x28, - 0xef, - 0x49, - 0x5c, - 0x53, - 0xa3, - 0x87, - 0xad, - }, - { - 0x42, - 0xc3, - 0x41, - 0xd8, - 0xfa, - 0x92, - 0xd8, - 0x32, - }, - { - 0xce, - 0x7c, - 0xf2, - 0x72, - 0x2f, - 0x51, - 0x27, - 0x71, - }, - { - 0xe3, - 0x78, - 0x59, - 0xf9, - 0x46, - 0x23, - 0xf3, - 0xa7, - }, - { - 0x38, - 0x12, - 0x05, - 0xbb, - 0x1a, - 0xb0, - 0xe0, - 0x12, - }, - { - 0xae, - 0x97, - 0xa1, - 0x0f, - 0xd4, - 0x34, - 0xe0, - 0x15, - }, - { - 0xb4, - 0xa3, - 0x15, - 0x08, - 0xbe, - 0xff, - 0x4d, - 0x31, - }, - { - 0x81, - 0x39, - 0x62, - 0x29, - 0xf0, - 0x90, - 0x79, - 0x02, - }, - { - 0x4d, - 0x0c, - 0xf4, - 0x9e, - 0xe5, - 0xd4, - 0xdc, - 0xca, - }, - { - 0x5c, - 0x73, - 0x33, - 0x6a, - 0x76, - 0xd8, - 0xbf, - 0x9a, - }, - { - 0xd0, - 0xa7, - 0x04, - 0x53, - 0x6b, - 0xa9, - 0x3e, - 0x0e, - }, - { - 0x92, - 0x59, - 0x58, - 0xfc, - 0xd6, - 0x42, - 0x0c, - 0xad, - }, - { - 0xa9, - 0x15, - 0xc2, - 0x9b, - 0xc8, - 0x06, - 0x73, - 0x18, - }, - { - 0x95, - 0x2b, - 0x79, - 0xf3, - 0xbc, - 0x0a, - 0xa6, - 0xd4, - }, - { - 0xf2, - 0x1d, - 0xf2, - 0xe4, - 0x1d, - 0x45, - 0x35, - 0xf9, - }, - { - 0x87, - 0x57, - 0x75, - 0x19, - 0x04, - 0x8f, - 0x53, - 0xa9, - }, - { - 0x10, - 0xa5, - 0x6c, - 0xf5, - 0xdf, - 0xcd, - 0x9a, - 0xdb, - }, - { - 0xeb, - 0x75, - 0x09, - 0x5c, - 0xcd, - 0x98, - 0x6c, - 0xd0, - }, - { - 0x51, - 0xa9, - 0xcb, - 0x9e, - 0xcb, - 0xa3, - 0x12, - 0xe6, - }, - { - 0x96, - 0xaf, - 0xad, - 0xfc, - 0x2c, - 0xe6, - 0x66, - 0xc7, - }, - { - 0x72, - 0xfe, - 0x52, - 0x97, - 0x5a, - 0x43, - 0x64, - 0xee, - }, - { - 0x5a, - 0x16, - 0x45, - 0xb2, - 0x76, - 0xd5, - 0x92, - 0xa1, - }, - { - 0xb2, - 0x74, - 0xcb, - 0x8e, - 0xbf, - 0x87, - 0x87, - 0x0a, - }, - { - 0x6f, - 0x9b, - 0xb4, - 0x20, - 0x3d, - 0xe7, - 0xb3, - 0x81, - }, - { - 0xea, - 0xec, - 0xb2, - 0xa3, - 0x0b, - 0x22, - 0xa8, - 0x7f, - }, - { - 0x99, - 0x24, - 0xa4, - 0x3c, - 0xc1, - 0x31, - 0x57, - 0x24, - }, - { - 0xbd, - 0x83, - 0x8d, - 0x3a, - 0xaf, - 0xbf, - 0x8d, - 0xb7, - }, - { - 0x0b, - 0x1a, - 0x2a, - 0x32, - 0x65, - 0xd5, - 0x1a, - 0xea, - }, - { - 0x13, - 0x50, - 0x79, - 0xa3, - 0x23, - 0x1c, - 0xe6, - 0x60, - }, - { - 0x93, - 0x2b, - 0x28, - 0x46, - 0xe4, - 0xd7, - 0x06, - 0x66, - }, - { - 0xe1, - 0x91, - 0x5f, - 0x5c, - 0xb1, - 0xec, - 0xa4, - 0x6c, - }, - { - 0xf3, - 0x25, - 0x96, - 0x5c, - 0xa1, - 0x6d, - 0x62, - 0x9f, - }, - { - 0x57, - 0x5f, - 0xf2, - 0x8e, - 0x60, - 0x38, - 0x1b, - 0xe5, - }, - { - 0x72, - 0x45, - 0x06, - 0xeb, - 0x4c, - 0x32, - 0x8a, - 0x95, - }, -}; diff --git a/src/ngtcp2/template.h b/src/ngtcp2/template.h deleted file mode 100644 index 5807cb7..0000000 --- a/src/ngtcp2/template.h +++ /dev/null @@ -1,91 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2017 ngtcp2 contributors - * Copyright (c) 2015 ngttp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#ifndef TEMPLATE_H -#define TEMPLATE_H - -#include -#include -#include -#include - -template -[[nodiscard]] constexpr auto as_unsigned(T n) noexcept { - return static_cast>(n); -} - -template -[[nodiscard]] constexpr auto as_signed(T n) noexcept { - return static_cast>(n); -} - -// inspired by , but our -// template can take functions returning other than void. -template struct Defer { - Defer(F &&f, T &&...t) - : f(std::bind(std::forward(f), std::forward(t)...)) {} - Defer(Defer &&o) noexcept : f(std::move(o.f)) {} - ~Defer() { f(); } - - using ResultType = std::invoke_result_t; - std::function f; -}; - -template Defer defer(F &&f, T &&...t) { - return Defer(std::forward(f), std::forward(t)...); -} - -template constexpr size_t array_size(T (&)[N]) { - return N; -} - -template constexpr size_t str_size(T (&)[N]) { - return N - 1; -} - -// User-defined literals for K, M, and G (powers of 1024) - -constexpr unsigned long long operator""_k(unsigned long long k) { - return k * 1024; -} - -constexpr unsigned long long operator""_m(unsigned long long m) { - return m * 1024 * 1024; -} - -constexpr unsigned long long operator""_g(unsigned long long g) { - return g * 1024 * 1024 * 1024; -} - -template -[[nodiscard]] std::span -as_writable_uint8_span(std::span s) noexcept { - return std::span{ - reinterpret_cast(s.data()), s.size_bytes()}; -} - -#endif // !defined(TEMPLATE_H) diff --git a/src/ngtcp2/tls_client_context.h b/src/ngtcp2/tls_client_context.h deleted file mode 100644 index 065d37b..0000000 --- a/src/ngtcp2/tls_client_context.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2020 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#ifndef TLS_CLIENT_CONTEXT_H -#define TLS_CLIENT_CONTEXT_H - -#ifdef HAVE_CONFIG_H -# include -#endif // defined(HAVE_CONFIG_H) - -#ifdef WITH_EXAMPLE_QUICTLS -# include "tls_client_context_quictls.h" -#endif // defined(WITH_EXAMPLE_QUICTLS) - -#ifdef WITH_EXAMPLE_GNUTLS -# include "tls_client_context_gnutls.h" -#endif // defined(WITH_EXAMPLE_GNUTLS) - -#ifdef WITH_EXAMPLE_BORINGSSL -# include "tls_client_context_boringssl.h" -#endif // defined(WITH_EXAMPLE_BORINGSSL) - -#ifdef WITH_EXAMPLE_PICOTLS -# include "tls_client_context_picotls.h" -#endif // defined(WITH_EXAMPLE_PICOTLS) - -#ifdef WITH_EXAMPLE_WOLFSSL -# include "tls_client_context_wolfssl.h" -#endif // defined(WITH_EXAMPLE_WOLFSSL) - -#ifdef WITH_EXAMPLE_OSSL -# include "tls_client_context_ossl.h" -#endif // defined(WITH_EXAMPLE_OSSL) - -#endif // !defined(TLS_CLIENT_CONTEXT_H) diff --git a/src/ngtcp2/tls_client_context_boringssl.cc b/src/ngtcp2/tls_client_context_boringssl.cc deleted file mode 100644 index b0c0e7d..0000000 --- a/src/ngtcp2/tls_client_context_boringssl.cc +++ /dev/null @@ -1,142 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2021 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#include "tls_client_context_boringssl.h" - -#include -#include -#include - -#include - -#include - -#include "client_base.h" -#include "template.h" -#include "tls_shared_boringssl.h" - -extern Config config; - -TLSClientContext::TLSClientContext() : ssl_ctx_{nullptr} {} - -TLSClientContext::~TLSClientContext() { - if (ssl_ctx_) { - SSL_CTX_free(ssl_ctx_); - } -} - -SSL_CTX *TLSClientContext::get_native_handle() const { return ssl_ctx_; } - -namespace { -int new_session_cb(SSL *ssl, SSL_SESSION *session) { - auto conn_ref = static_cast(SSL_get_app_data(ssl)); - auto c = static_cast(conn_ref->user_data); - - c->ticket_received(); - - auto f = BIO_new_file(config.session_file, "w"); - if (f == nullptr) { - std::cerr << "Could not write TLS session in " << config.session_file - << std::endl; - return 0; - } - - if (!PEM_write_bio_SSL_SESSION(f, session)) { - std::cerr << "Unable to write TLS session to file" << std::endl; - } - - BIO_free(f); - - return 0; -} -} // namespace - -int TLSClientContext::init(const char *private_key_file, - const char *cert_file) { - ssl_ctx_ = SSL_CTX_new(TLS_client_method()); - if (!ssl_ctx_) { - std::cerr << "SSL_CTX_new: " << ERR_error_string(ERR_get_error(), nullptr) - << std::endl; - return -1; - } - - if (ngtcp2_crypto_boringssl_configure_client_context(ssl_ctx_) != 0) { - std::cerr << "ngtcp2_crypto_boringssl_configure_client_context failed" - << std::endl; - return -1; - } - - SSL_CTX_set_default_verify_paths(ssl_ctx_); - - if (SSL_CTX_set1_groups_list(ssl_ctx_, config.groups) != 1) { - std::cerr << "SSL_CTX_set1_groups_list failed" << std::endl; - return -1; - } - - if (private_key_file && cert_file) { - if (SSL_CTX_use_PrivateKey_file(ssl_ctx_, private_key_file, - SSL_FILETYPE_PEM) != 1) { - std::cerr << "SSL_CTX_use_PrivateKey_file: " - << ERR_error_string(ERR_get_error(), nullptr) << std::endl; - return -1; - } - - if (SSL_CTX_use_certificate_chain_file(ssl_ctx_, cert_file) != 1) { - std::cerr << "SSL_CTX_use_certificate_chain_file: " - << ERR_error_string(ERR_get_error(), nullptr) << std::endl; - return -1; - } - } - - if (config.session_file) { - SSL_CTX_set_session_cache_mode(ssl_ctx_, SSL_SESS_CACHE_CLIENT | - SSL_SESS_CACHE_NO_INTERNAL); - SSL_CTX_sess_set_new_cb(ssl_ctx_, new_session_cb); - } - -#ifdef HAVE_LIBBROTLI - if (!SSL_CTX_add_cert_compression_alg( - ssl_ctx_, ngtcp2::tls::CERTIFICATE_COMPRESSION_ALGO_BROTLI, - ngtcp2::tls::cert_compress, ngtcp2::tls::cert_decompress)) { - std::cerr << "SSL_CTX_add_cert_compression_alg failed" << std::endl; - return -1; - } -#endif // defined(HAVE_LIBBROTLI) - - return 0; -} - -extern std::ofstream keylog_file; - -namespace { -void keylog_callback(const SSL *ssl, const char *line) { - keylog_file.write(line, static_cast(strlen(line))); - keylog_file.put('\n'); - keylog_file.flush(); -} -} // namespace - -void TLSClientContext::enable_keylog() { - SSL_CTX_set_keylog_callback(ssl_ctx_, keylog_callback); -} diff --git a/src/ngtcp2/tls_client_context_boringssl.h b/src/ngtcp2/tls_client_context_boringssl.h deleted file mode 100644 index 0537025..0000000 --- a/src/ngtcp2/tls_client_context_boringssl.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2021 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#ifndef TLS_CLIENT_CONTEXT_BORINGSSL_H -#define TLS_CLIENT_CONTEXT_BORINGSSL_H - -#ifdef HAVE_CONFIG_H -# include -#endif // defined(HAVE_CONFIG_H) - -#include - -class TLSClientContext { -public: - TLSClientContext(); - ~TLSClientContext(); - - int init(const char *private_key_file, const char *cert_file); - - SSL_CTX *get_native_handle() const; - - void enable_keylog(); - -private: - SSL_CTX *ssl_ctx_; -}; - -#endif // !defined(TLS_CLIENT_CONTEXT_BORINGSSL_H) diff --git a/src/ngtcp2/tls_client_context_ossl.cc b/src/ngtcp2/tls_client_context_ossl.cc deleted file mode 100644 index 036b28a..0000000 --- a/src/ngtcp2/tls_client_context_ossl.cc +++ /dev/null @@ -1,149 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2025 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#include "tls_client_context_ossl.h" - -#include -#include -#include -#include -#include - -#include - -#include - -#include "client_base.h" -#include "template.h" - -namespace { -auto _ = []() { - if (ngtcp2_crypto_ossl_init() != 0) { - assert(0); - abort(); - } - - return 0; -}(); -} // namespace - -extern Config config; - -TLSClientContext::TLSClientContext() : ssl_ctx_{nullptr} {} - -TLSClientContext::~TLSClientContext() { - if (ssl_ctx_) { - SSL_CTX_free(ssl_ctx_); - } -} - -SSL_CTX *TLSClientContext::get_native_handle() const { return ssl_ctx_; } - -namespace { -int new_session_cb(SSL *ssl, SSL_SESSION *session) { - auto conn_ref = static_cast(SSL_get_app_data(ssl)); - auto c = static_cast(conn_ref->user_data); - - c->ticket_received(); - - if (SSL_SESSION_get_max_early_data(session) != - std::numeric_limits::max()) { - std::cerr << "max_early_data_size is not 0xffffffff" << std::endl; - } - auto f = BIO_new_file(config.session_file, "w"); - if (f == nullptr) { - std::cerr << "Could not write TLS session in " << config.session_file - << std::endl; - return 0; - } - - if (!PEM_write_bio_SSL_SESSION(f, session)) { - std::cerr << "Unable to write TLS session to file" << std::endl; - } - - BIO_free(f); - - return 0; -} -} // namespace - -int TLSClientContext::init(const char *private_key_file, - const char *cert_file) { - ssl_ctx_ = SSL_CTX_new(TLS_client_method()); - if (!ssl_ctx_) { - std::cerr << "SSL_CTX_new: " << ERR_error_string(ERR_get_error(), nullptr) - << std::endl; - return -1; - } - - SSL_CTX_set_default_verify_paths(ssl_ctx_); - - if (SSL_CTX_set_ciphersuites(ssl_ctx_, config.ciphers) != 1) { - std::cerr << "SSL_CTX_set_ciphersuites: " - << ERR_error_string(ERR_get_error(), nullptr) << std::endl; - return -1; - } - - if (SSL_CTX_set1_groups_list(ssl_ctx_, config.groups) != 1) { - std::cerr << "SSL_CTX_set1_groups_list failed" << std::endl; - return -1; - } - - if (private_key_file && cert_file) { - if (SSL_CTX_use_PrivateKey_file(ssl_ctx_, private_key_file, - SSL_FILETYPE_PEM) != 1) { - std::cerr << "SSL_CTX_use_PrivateKey_file: " - << ERR_error_string(ERR_get_error(), nullptr) << std::endl; - return -1; - } - - if (SSL_CTX_use_certificate_chain_file(ssl_ctx_, cert_file) != 1) { - std::cerr << "SSL_CTX_use_certificate_chain_file: " - << ERR_error_string(ERR_get_error(), nullptr) << std::endl; - return -1; - } - } - - if (config.session_file) { - SSL_CTX_set_session_cache_mode(ssl_ctx_, SSL_SESS_CACHE_CLIENT | - SSL_SESS_CACHE_NO_INTERNAL); - SSL_CTX_sess_set_new_cb(ssl_ctx_, new_session_cb); - } - - return 0; -} - -extern std::ofstream keylog_file; - -namespace { -void keylog_callback(const SSL *ssl, const char *line) { - keylog_file.write(line, static_cast(strlen(line))); - keylog_file.put('\n'); - keylog_file.flush(); -} -} // namespace - -void TLSClientContext::enable_keylog() { - SSL_CTX_set_keylog_callback(ssl_ctx_, keylog_callback); -} diff --git a/src/ngtcp2/tls_client_context_ossl.h b/src/ngtcp2/tls_client_context_ossl.h deleted file mode 100644 index db65662..0000000 --- a/src/ngtcp2/tls_client_context_ossl.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2025 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#ifndef TLS_CLIENT_CONTEXT_OSSL_H -#define TLS_CLIENT_CONTEXT_OSSL_H - -#ifdef HAVE_CONFIG_H -# include -#endif // defined(HAVE_CONFIG_H) - -#include - -class TLSClientContext { -public: - TLSClientContext(); - ~TLSClientContext(); - - int init(const char *private_key_file, const char *cert_file); - - SSL_CTX *get_native_handle() const; - - void enable_keylog(); - -private: - SSL_CTX *ssl_ctx_; -}; - -#endif // !defined(TLS_CLIENT_CONTEXT_OSSL_H) diff --git a/src/ngtcp2/tls_client_session.h b/src/ngtcp2/tls_client_session.h deleted file mode 100644 index 39ce2ca..0000000 --- a/src/ngtcp2/tls_client_session.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2020 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#ifndef TLS_CLIENT_SESSION_H -#define TLS_CLIENT_SESSION_H - -#ifdef HAVE_CONFIG_H -# include -#endif // defined(HAVE_CONFIG_H) - -#ifdef WITH_EXAMPLE_QUICTLS -# include "tls_client_session_quictls.h" -#endif // defined(WITH_EXAMPLE_QUICTLS) - -#ifdef WITH_EXAMPLE_GNUTLS -# include "tls_client_session_gnutls.h" -#endif // defined(WITH_EXAMPLE_GNUTLS) - -#ifdef WITH_EXAMPLE_BORINGSSL -# include "tls_client_session_boringssl.h" -#endif // defined(WITH_EXAMPLE_BORINGSSL) - -#ifdef WITH_EXAMPLE_PICOTLS -# include "tls_client_session_picotls.h" -#endif // defined(WITH_EXAMPLE_PICOTLS) - -#ifdef WITH_EXAMPLE_WOLFSSL -# include "tls_client_session_wolfssl.h" -#endif // defined(WITH_EXAMPLE_WOLFSSL) - -#ifdef WITH_EXAMPLE_OSSL -# include "tls_client_session_ossl.h" -#endif // defined(WITH_EXAMPLE_OSSL) - -#endif // !defined(TLS_CLIENT_SESSION_H) diff --git a/src/ngtcp2/tls_client_session_boringssl.cc b/src/ngtcp2/tls_client_session_boringssl.cc deleted file mode 100644 index 2b0a269..0000000 --- a/src/ngtcp2/tls_client_session_boringssl.cc +++ /dev/null @@ -1,147 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2021 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#include "tls_client_session_boringssl.h" - -#include -#include -#include - -#include "tls_client_context_boringssl.h" -#include "client_base.h" -#include "template.h" -#include "util.h" - -TLSClientSession::TLSClientSession() {} - -TLSClientSession::~TLSClientSession() {} - -extern Config config; - -int TLSClientSession::init(bool &early_data_enabled, - const TLSClientContext &tls_ctx, - const char *remote_addr, ClientBase *client, - uint32_t quic_version, AppProtocol app_proto) { - early_data_enabled = false; - - auto ssl_ctx = tls_ctx.get_native_handle(); - - ssl_ = SSL_new(ssl_ctx); - if (!ssl_) { - std::cerr << "SSL_new: " << ERR_error_string(ERR_get_error(), nullptr) - << std::endl; - return -1; - } - - SSL_set_app_data(ssl_, client->conn_ref()); - SSL_set_connect_state(ssl_); - - switch (app_proto) { - case AppProtocol::H3: - SSL_set_alpn_protos(ssl_, H3_ALPN.data(), H3_ALPN.size()); - break; - case AppProtocol::HQ: - SSL_set_alpn_protos(ssl_, HQ_ALPN.data(), HQ_ALPN.size()); - break; - } - - if (!config.sni.empty()) { - SSL_set_tlsext_host_name(ssl_, config.sni.data()); - } else if (util::numeric_host(remote_addr)) { - // If remote host is numeric address, just send "localhost" as SNI - // for now. - SSL_set_tlsext_host_name(ssl_, "localhost"); - } else { - SSL_set_tlsext_host_name(ssl_, remote_addr); - } - - if (config.session_file) { - auto f = BIO_new_file(config.session_file, "r"); - if (f == nullptr) { - std::cerr << "Could not read TLS session file " << config.session_file - << std::endl; - } else { - auto session = PEM_read_bio_SSL_SESSION(f, nullptr, 0, nullptr); - BIO_free(f); - if (session == nullptr) { - std::cerr << "Could not read TLS session file " << config.session_file - << std::endl; - } else { - if (!SSL_set_session(ssl_, session)) { - std::cerr << "Could not set session" << std::endl; - } else if (!config.disable_early_data && - SSL_SESSION_early_data_capable(session)) { - early_data_enabled = true; - SSL_set_early_data_enabled(ssl_, 1); - } - SSL_SESSION_free(session); - } - } - } - - if (!config.ech_config_list.empty() && - SSL_set1_ech_config_list(ssl_, config.ech_config_list.data(), - config.ech_config_list.size()) != 1) { - std::cerr << "Could not set ECHConfigList: " - << ERR_error_string(ERR_get_error(), nullptr) << std::endl; - return -1; - } - - return 0; -} - -bool TLSClientSession::get_early_data_accepted() const { - return SSL_early_data_accepted(ssl_); -} - -bool TLSClientSession::get_ech_accepted() const { - return SSL_ech_accepted(ssl_); -} - -int TLSClientSession::write_ech_config_list(const char *path) const { - const uint8_t *retry_configs; - size_t retry_configslen; - - SSL_get0_ech_retry_configs(ssl_, &retry_configs, &retry_configslen); - if (retry_configslen == 0) { - std::cerr << "No ECH retry configs found" << std::endl; - return -1; - } - - auto f = std::ofstream(path); - - if (!f) { - return -1; - } - - f.write(reinterpret_cast(retry_configs), - static_cast(retry_configslen)); - f.close(); - - if (!f) { - return -1; - } - - return 0; -} diff --git a/src/ngtcp2/tls_client_session_boringssl.h b/src/ngtcp2/tls_client_session_boringssl.h deleted file mode 100644 index 929488a..0000000 --- a/src/ngtcp2/tls_client_session_boringssl.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2021 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#ifndef TLS_CLIENT_SESSION_BORINGSSL_H -#define TLS_CLIENT_SESSION_BORINGSSL_H - -#ifdef HAVE_CONFIG_H -# include -#endif // defined(HAVE_CONFIG_H) - -#include "tls_session_base_quictls.h" -#include "shared.h" - -using namespace ngtcp2; - -class TLSClientContext; -class ClientBase; - -class TLSClientSession : public TLSSessionBase { -public: - TLSClientSession(); - ~TLSClientSession(); - - int init(bool &early_data_enabled, const TLSClientContext &tls_ctx, - const char *remote_addr, ClientBase *client, uint32_t quic_version, - AppProtocol app_proto); - - bool get_early_data_accepted() const; - bool get_ech_accepted() const; - int write_ech_config_list(const char *path) const; -}; - -#endif // !defined(TLS_CLIENT_SESSION_BORINGSSL_H) diff --git a/src/ngtcp2/tls_client_session_ossl.cc b/src/ngtcp2/tls_client_session_ossl.cc deleted file mode 100644 index f748d0f..0000000 --- a/src/ngtcp2/tls_client_session_ossl.cc +++ /dev/null @@ -1,121 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2025 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#include "tls_client_session_ossl.h" - -#include -#include - -#include - -#include "tls_client_context_ossl.h" -#include "client_base.h" -#include "template.h" -#include "util.h" - -TLSClientSession::TLSClientSession() {} - -TLSClientSession::~TLSClientSession() {} - -extern Config config; - -int TLSClientSession::init(bool &early_data_enabled, - const TLSClientContext &tls_ctx, - const char *remote_addr, ClientBase *client, - uint32_t quic_version, AppProtocol app_proto) { - early_data_enabled = false; - - auto ssl_ctx = tls_ctx.get_native_handle(); - - auto ssl = SSL_new(ssl_ctx); - if (!ssl) { - std::cerr << "SSL_new: " << ERR_error_string(ERR_get_error(), nullptr) - << std::endl; - return -1; - } - - ngtcp2_crypto_ossl_ctx_set_ssl(ossl_ctx_, ssl); - - if (ngtcp2_crypto_ossl_configure_client_session(ssl) != 0) { - std::cerr << "ngtcp2_crypto_ossl_configure_client_session failed" - << std::endl; - return -1; - } - - SSL_set_app_data(ssl, client->conn_ref()); - SSL_set_connect_state(ssl); - - switch (app_proto) { - case AppProtocol::H3: - SSL_set_alpn_protos(ssl, H3_ALPN.data(), H3_ALPN.size()); - break; - case AppProtocol::HQ: - SSL_set_alpn_protos(ssl, HQ_ALPN.data(), HQ_ALPN.size()); - break; - } - - if (!config.sni.empty()) { - SSL_set_tlsext_host_name(ssl, config.sni.data()); - } else if (util::numeric_host(remote_addr)) { - // If remote host is numeric address, just send "localhost" as SNI - // for now. - SSL_set_tlsext_host_name(ssl, "localhost"); - } else { - SSL_set_tlsext_host_name(ssl, remote_addr); - } - - if (config.session_file) { - auto f = BIO_new_file(config.session_file, "r"); - if (f == nullptr) { - std::cerr << "Could not read TLS session file " << config.session_file - << std::endl; - } else { - auto session = PEM_read_bio_SSL_SESSION(f, nullptr, 0, nullptr); - BIO_free(f); - if (session == nullptr) { - std::cerr << "Could not read TLS session file " << config.session_file - << std::endl; - } else { - if (!SSL_set_session(ssl, session)) { - std::cerr << "Could not set session" << std::endl; - } else if (!config.disable_early_data && - SSL_SESSION_get_max_early_data(session)) { - early_data_enabled = true; - SSL_set_quic_tls_early_data_enabled(ssl, 1); - } - - SSL_SESSION_free(session); - } - } - } - - return 0; -} - -bool TLSClientSession::get_early_data_accepted() const { - auto ssl = ngtcp2_crypto_ossl_ctx_get_ssl(ossl_ctx_); - - // SSL_get_early_data_status works after handshake completes. - return SSL_get_early_data_status(ssl) == SSL_EARLY_DATA_ACCEPTED; -} diff --git a/src/ngtcp2/tls_client_session_ossl.h b/src/ngtcp2/tls_client_session_ossl.h deleted file mode 100644 index a1ee051..0000000 --- a/src/ngtcp2/tls_client_session_ossl.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2025 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#ifndef TLS_CLIENT_SESSION_OSSL_H -#define TLS_CLIENT_SESSION_OSSL_H - -#ifdef HAVE_CONFIG_H -# include -#endif // defined(HAVE_CONFIG_H) - -#include "tls_session_base_ossl.h" -#include "shared.h" - -using namespace ngtcp2; - -class TLSClientContext; -class ClientBase; - -class TLSClientSession : public TLSSessionBase { -public: - TLSClientSession(); - ~TLSClientSession(); - - int init(bool &early_data_enabled, const TLSClientContext &tls_ctx, - const char *remote_addr, ClientBase *client, uint32_t quic_version, - AppProtocol app_proto); - - bool get_early_data_accepted() const; - bool get_ech_accepted() const { return false; } - int write_ech_config_list(const char *path) const { return 0; } -}; - -#endif // !defined(TLS_CLIENT_SESSION_OSSL_H) diff --git a/src/ngtcp2/tls_server_context.h b/src/ngtcp2/tls_server_context.h deleted file mode 100644 index 1aa1691..0000000 --- a/src/ngtcp2/tls_server_context.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2020 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#ifndef TLS_SERVER_CONTEXT_H -#define TLS_SERVER_CONTEXT_H - -#ifdef HAVE_CONFIG_H -# include -#endif // defined(HAVE_CONFIG_H) - -#ifdef WITH_EXAMPLE_QUICTLS -# include "tls_server_context_quictls.h" -#endif // defined(WITH_EXAMPLE_QUICTLS) - -#ifdef WITH_EXAMPLE_GNUTLS -# include "tls_server_context_gnutls.h" -#endif // defined(WITH_EXAMPLE_GNUTLS) - -#ifdef WITH_EXAMPLE_BORINGSSL -# include "tls_server_context_boringssl.h" -#endif // defined(WITH_EXAMPLE_BORINGSSL) - -#ifdef WITH_EXAMPLE_PICOTLS -# include "tls_server_context_picotls.h" -#endif // defined(WITH_EXAMPLE_PICOTLS) - -#ifdef WITH_EXAMPLE_WOLFSSL -# include "tls_server_context_wolfssl.h" -#endif // defined(WITH_EXAMPLE_WOLFSSL) - -#ifdef WITH_EXAMPLE_OSSL -# include "tls_server_context_ossl.h" -#endif // defined(WITH_EXAMPLE_OSSL) - -#endif // !defined(TLS_SERVER_CONTEXT_H) diff --git a/src/ngtcp2/tls_server_context_boringssl.cc b/src/ngtcp2/tls_server_context_boringssl.cc deleted file mode 100644 index cce8139..0000000 --- a/src/ngtcp2/tls_server_context_boringssl.cc +++ /dev/null @@ -1,265 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2021 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#include "tls_server_context_boringssl.h" - -#include -#include -#include -#include - -#include - -#include -#include - -#include "server_base.h" -#include "template.h" -#include "tls_shared_boringssl.h" - -extern Config config; - -TLSServerContext::TLSServerContext() : ssl_ctx_{nullptr} {} - -TLSServerContext::~TLSServerContext() { - if (ssl_ctx_) { - SSL_CTX_free(ssl_ctx_); - } -} - -SSL_CTX *TLSServerContext::get_native_handle() const { return ssl_ctx_; } - -namespace { -int alpn_select_proto_h3_cb(SSL *ssl, const unsigned char **out, - unsigned char *outlen, const unsigned char *in, - unsigned int inlen, void *arg) { - auto conn_ref = static_cast(SSL_get_app_data(ssl)); - auto h = static_cast(conn_ref->user_data); - // This should be the negotiated version, but we have not set the - // negotiated version when this callback is called. - auto version = ngtcp2_conn_get_client_chosen_version(h->conn()); - - switch (version) { - case NGTCP2_PROTO_VER_V1: - case NGTCP2_PROTO_VER_V2: - break; - default: - if (!config.quiet) { - std::cerr << "Unexpected quic protocol version: " << std::hex << "0x" - << version << std::dec << std::endl; - } - return SSL_TLSEXT_ERR_ALERT_FATAL; - } - - for (auto s = std::span{in, inlen}; s.size() >= H3_ALPN_V1.size(); - s = s.subspan(s[0] + 1)) { - if (std::ranges::equal(H3_ALPN_V1, s.first(H3_ALPN_V1.size()))) { - *out = &s[1]; - *outlen = s[0]; - return SSL_TLSEXT_ERR_OK; - } - } - - if (!config.quiet) { - std::cerr << "Client did not present ALPN " << &H3_ALPN_V1[1] << std::endl; - } - - return SSL_TLSEXT_ERR_ALERT_FATAL; -} -} // namespace - -namespace { -int alpn_select_proto_hq_cb(SSL *ssl, const unsigned char **out, - unsigned char *outlen, const unsigned char *in, - unsigned int inlen, void *arg) { - auto conn_ref = static_cast(SSL_get_app_data(ssl)); - auto h = static_cast(conn_ref->user_data); - // This should be the negotiated version, but we have not set the - // negotiated version when this callback is called. - auto version = ngtcp2_conn_get_client_chosen_version(h->conn()); - - switch (version) { - case NGTCP2_PROTO_VER_V1: - case NGTCP2_PROTO_VER_V2: - break; - default: - if (!config.quiet) { - std::cerr << "Unexpected quic protocol version: " << std::hex << "0x" - << version << std::dec << std::endl; - } - return SSL_TLSEXT_ERR_ALERT_FATAL; - } - - for (auto s = std::span{in, inlen}; s.size() >= HQ_ALPN_V1.size(); - s = s.subspan(s[0] + 1)) { - if (std::ranges::equal(HQ_ALPN_V1, s.first(HQ_ALPN_V1.size()))) { - *out = &s[1]; - *outlen = s[0]; - return SSL_TLSEXT_ERR_OK; - } - } - - if (!config.quiet) { - std::cerr << "Client did not present ALPN " << &HQ_ALPN_V1[1] << std::endl; - } - - return SSL_TLSEXT_ERR_ALERT_FATAL; -} -} // namespace - -namespace { -int verify_cb(int preverify_ok, X509_STORE_CTX *ctx) { - // We don't verify the client certificate. Just request it for the - // testing purpose. - return 1; -} -} // namespace - -int TLSServerContext::init(const char *private_key_file, const char *cert_file, - AppProtocol app_proto) { - constexpr static unsigned char sid_ctx[] = "ngtcp2 server"; - - ssl_ctx_ = SSL_CTX_new(TLS_server_method()); - if (!ssl_ctx_) { - std::cerr << "SSL_CTX_new: " << ERR_error_string(ERR_get_error(), nullptr) - << std::endl; - return -1; - } - - constexpr auto ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) | - SSL_OP_SINGLE_ECDH_USE | - SSL_OP_CIPHER_SERVER_PREFERENCE; - - SSL_CTX_set_options(ssl_ctx_, ssl_opts); - - if (SSL_CTX_set1_groups_list(ssl_ctx_, config.groups) != 1) { - std::cerr << "SSL_CTX_set1_groups_list failed" << std::endl; - return -1; - } - - SSL_CTX_set_mode(ssl_ctx_, SSL_MODE_RELEASE_BUFFERS); - - if (ngtcp2_crypto_boringssl_configure_server_context(ssl_ctx_) != 0) { - std::cerr << "ngtcp2_crypto_boringssl_configure_server_context failed" - << std::endl; - return -1; - } - - switch (app_proto) { - case AppProtocol::H3: - SSL_CTX_set_alpn_select_cb(ssl_ctx_, alpn_select_proto_h3_cb, nullptr); - break; - case AppProtocol::HQ: - SSL_CTX_set_alpn_select_cb(ssl_ctx_, alpn_select_proto_hq_cb, nullptr); - break; - } - - SSL_CTX_set_default_verify_paths(ssl_ctx_); - - if (SSL_CTX_use_PrivateKey_file(ssl_ctx_, private_key_file, - SSL_FILETYPE_PEM) != 1) { - std::cerr << "SSL_CTX_use_PrivateKey_file: " - << ERR_error_string(ERR_get_error(), nullptr) << std::endl; - return -1; - } - - if (SSL_CTX_use_certificate_chain_file(ssl_ctx_, cert_file) != 1) { - std::cerr << "SSL_CTX_use_certificate_chain_file: " - << ERR_error_string(ERR_get_error(), nullptr) << std::endl; - return -1; - } - - if (SSL_CTX_check_private_key(ssl_ctx_) != 1) { - std::cerr << "SSL_CTX_check_private_key: " - << ERR_error_string(ERR_get_error(), nullptr) << std::endl; - return -1; - } - - SSL_CTX_set_session_id_context(ssl_ctx_, sid_ctx, sizeof(sid_ctx) - 1); - - if (config.verify_client) { - SSL_CTX_set_verify(ssl_ctx_, - SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE | - SSL_VERIFY_FAIL_IF_NO_PEER_CERT, - verify_cb); - } - -#ifdef HAVE_LIBBROTLI - if (!SSL_CTX_add_cert_compression_alg( - ssl_ctx_, ngtcp2::tls::CERTIFICATE_COMPRESSION_ALGO_BROTLI, - ngtcp2::tls::cert_compress, ngtcp2::tls::cert_decompress)) { - std::cerr << "SSL_CTX_add_cert_compression_alg failed" << std::endl; - return -1; - } -#endif // defined(HAVE_LIBBROTLI) - - if (!config.ech_config.ech_config.empty()) { - const auto &echconf = config.ech_config; - - auto pkey = EVP_HPKE_KEY_new(); - - if (EVP_HPKE_KEY_init(pkey, EVP_hpke_x25519_hkdf_sha256(), - echconf.private_key.bytes.data(), - echconf.private_key.bytes.size()) != 1) { - std::cerr << "EVP_HPKE_KEY_init failed: " - << ERR_error_string(ERR_get_error(), nullptr) << std::endl; - - return -1; - } - - auto pkey_d = defer(EVP_HPKE_KEY_free, pkey); - - auto keys = SSL_ECH_KEYS_new(); - auto keys_d = defer(SSL_ECH_KEYS_free, keys); - - if (SSL_ECH_KEYS_add(keys, 1, echconf.ech_config.data(), - echconf.ech_config.size(), pkey) != 1) { - std::cerr << "SSL_ECH_KEYS_add failed: " - << ERR_error_string(ERR_get_error(), nullptr) << std::endl; - return -1; - } - - if (SSL_CTX_set1_ech_keys(ssl_ctx_, keys) != 1) { - std::cerr << "SSL_CTX_set1_ech_keys failed: " - << ERR_error_string(ERR_get_error(), nullptr) << std::endl; - return -1; - } - } - - return 0; -} - -extern std::ofstream keylog_file; - -namespace { -void keylog_callback(const SSL *ssl, const char *line) { - keylog_file.write(line, static_cast(strlen(line))); - keylog_file.put('\n'); - keylog_file.flush(); -} -} // namespace - -void TLSServerContext::enable_keylog() { - SSL_CTX_set_keylog_callback(ssl_ctx_, keylog_callback); -} diff --git a/src/ngtcp2/tls_server_context_boringssl.h b/src/ngtcp2/tls_server_context_boringssl.h deleted file mode 100644 index 48af9a6..0000000 --- a/src/ngtcp2/tls_server_context_boringssl.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2021 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#ifndef TLS_SERVER_CONTEXT_BORINGSSL_H -#define TLS_SERVER_CONTEXT_BORINGSSL_H - -#ifdef HAVE_CONFIG_H -# include -#endif // defined(HAVE_CONFIG_H) - -#include - -#include "shared.h" - -using namespace ngtcp2; - -class TLSServerContext { -public: - TLSServerContext(); - ~TLSServerContext(); - - int init(const char *private_key_file, const char *cert_file, - AppProtocol app_proto); - - SSL_CTX *get_native_handle() const; - - void enable_keylog(); - -private: - SSL_CTX *ssl_ctx_; -}; - -#endif // !defined(TLS_SERVER_CONTEXT_BORINGSSL_H) diff --git a/src/ngtcp2/tls_server_context_ossl.cc b/src/ngtcp2/tls_server_context_ossl.cc deleted file mode 100644 index 65aef5c..0000000 --- a/src/ngtcp2/tls_server_context_ossl.cc +++ /dev/null @@ -1,307 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2025 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#include "tls_server_context_ossl.h" - -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include "server_base.h" -#include "template.h" - -namespace { -auto _ = []() { - if (ngtcp2_crypto_ossl_init() != 0) { - assert(0); - abort(); - } - - return 0; -}(); -} // namespace - -extern Config config; - -TLSServerContext::TLSServerContext() : ssl_ctx_{nullptr} {} - -TLSServerContext::~TLSServerContext() { - if (ssl_ctx_) { - SSL_CTX_free(ssl_ctx_); - } -} - -SSL_CTX *TLSServerContext::get_native_handle() const { return ssl_ctx_; } - -namespace { -int alpn_select_proto_h3_cb(SSL *ssl, const unsigned char **out, - unsigned char *outlen, const unsigned char *in, - unsigned int inlen, void *arg) { - auto conn_ref = static_cast(SSL_get_app_data(ssl)); - auto h = static_cast(conn_ref->user_data); - // This should be the negotiated version, but we have not set the - // negotiated version when this callback is called. - auto version = ngtcp2_conn_get_client_chosen_version(h->conn()); - - switch (version) { - case NGTCP2_PROTO_VER_V1: - case NGTCP2_PROTO_VER_V2: - break; - default: - if (!config.quiet) { - std::cerr << "Unexpected quic protocol version: " << std::hex << "0x" - << version << std::dec << std::endl; - } - return SSL_TLSEXT_ERR_ALERT_FATAL; - } - - for (auto s = std::span{in, inlen}; s.size() >= H3_ALPN_V1.size(); - s = s.subspan(s[0] + 1)) { - if (std::ranges::equal(H3_ALPN_V1, s.first(H3_ALPN_V1.size()))) { - *out = &s[1]; - *outlen = s[0]; - return SSL_TLSEXT_ERR_OK; - } - } - - if (!config.quiet) { - std::cerr << "Client did not present ALPN " << &H3_ALPN_V1[1] << std::endl; - } - - return SSL_TLSEXT_ERR_ALERT_FATAL; -} -} // namespace - -namespace { -int alpn_select_proto_hq_cb(SSL *ssl, const unsigned char **out, - unsigned char *outlen, const unsigned char *in, - unsigned int inlen, void *arg) { - auto conn_ref = static_cast(SSL_get_app_data(ssl)); - auto h = static_cast(conn_ref->user_data); - // This should be the negotiated version, but we have not set the - // negotiated version when this callback is called. - auto version = ngtcp2_conn_get_client_chosen_version(h->conn()); - - switch (version) { - case NGTCP2_PROTO_VER_V1: - case NGTCP2_PROTO_VER_V2: - break; - default: - if (!config.quiet) { - std::cerr << "Unexpected quic protocol version: " << std::hex << "0x" - << version << std::dec << std::endl; - } - return SSL_TLSEXT_ERR_ALERT_FATAL; - } - - for (auto s = std::span{in, inlen}; s.size() >= HQ_ALPN_V1.size(); - s = s.subspan(s[0] + 1)) { - if (std::ranges::equal(HQ_ALPN_V1, s.first(HQ_ALPN_V1.size()))) { - *out = &s[1]; - *outlen = s[0]; - return SSL_TLSEXT_ERR_OK; - } - } - - if (!config.quiet) { - std::cerr << "Client did not present ALPN " << &HQ_ALPN_V1[1] << std::endl; - } - - return SSL_TLSEXT_ERR_ALERT_FATAL; -} -} // namespace - -namespace { -int verify_cb(int preverify_ok, X509_STORE_CTX *ctx) { - // We don't verify the client certificate. Just request it for the - // testing purpose. - return 1; -} -} // namespace - -namespace { -int gen_ticket_cb(SSL *ssl, void *arg) { - auto conn_ref = static_cast(SSL_get_app_data(ssl)); - auto h = static_cast(conn_ref->user_data); - auto ver = htonl(ngtcp2_conn_get_negotiated_version(h->conn())); - - if (!SSL_SESSION_set1_ticket_appdata(SSL_get0_session(ssl), &ver, - sizeof(ver))) { - return 0; - } - - return 1; -} -} // namespace - -namespace { -SSL_TICKET_RETURN decrypt_ticket_cb(SSL *ssl, SSL_SESSION *session, - const unsigned char *keyname, - size_t keynamelen, SSL_TICKET_STATUS status, - void *arg) { - switch (status) { - case SSL_TICKET_EMPTY: - case SSL_TICKET_NO_DECRYPT: - return SSL_TICKET_RETURN_IGNORE_RENEW; - } - - uint8_t *pver; - uint32_t ver; - size_t verlen; - - if (!SSL_SESSION_get0_ticket_appdata( - session, reinterpret_cast(&pver), &verlen) || - verlen != sizeof(ver)) { - switch (status) { - case SSL_TICKET_SUCCESS: - return SSL_TICKET_RETURN_IGNORE; - case SSL_TICKET_SUCCESS_RENEW: - default: - return SSL_TICKET_RETURN_IGNORE_RENEW; - } - } - - memcpy(&ver, pver, sizeof(ver)); - - auto conn_ref = static_cast(SSL_get_app_data(ssl)); - auto h = static_cast(conn_ref->user_data); - - if (ngtcp2_conn_get_client_chosen_version(h->conn()) != ntohl(ver)) { - switch (status) { - case SSL_TICKET_SUCCESS: - return SSL_TICKET_RETURN_IGNORE; - case SSL_TICKET_SUCCESS_RENEW: - default: - return SSL_TICKET_RETURN_IGNORE_RENEW; - } - } - - switch (status) { - case SSL_TICKET_SUCCESS: - return SSL_TICKET_RETURN_USE; - case SSL_TICKET_SUCCESS_RENEW: - default: - return SSL_TICKET_RETURN_USE_RENEW; - } -} -} // namespace - -int TLSServerContext::init(const char *private_key_file, const char *cert_file, - AppProtocol app_proto) { - constexpr static unsigned char sid_ctx[] = "ngtcp2 server"; - - ssl_ctx_ = SSL_CTX_new(TLS_server_method()); - if (!ssl_ctx_) { - std::cerr << "SSL_CTX_new: " << ERR_error_string(ERR_get_error(), nullptr) - << std::endl; - return -1; - } - - SSL_CTX_set_max_early_data(ssl_ctx_, UINT32_MAX); - - constexpr auto ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) | - SSL_OP_SINGLE_ECDH_USE | - SSL_OP_CIPHER_SERVER_PREFERENCE | - SSL_OP_NO_ANTI_REPLAY; - - SSL_CTX_set_options(ssl_ctx_, ssl_opts); - - if (SSL_CTX_set_ciphersuites(ssl_ctx_, config.ciphers) != 1) { - std::cerr << "SSL_CTX_set_ciphersuites: " - << ERR_error_string(ERR_get_error(), nullptr) << std::endl; - return -1; - } - - if (SSL_CTX_set1_groups_list(ssl_ctx_, config.groups) != 1) { - std::cerr << "SSL_CTX_set1_groups_list failed" << std::endl; - return -1; - } - - SSL_CTX_set_mode(ssl_ctx_, SSL_MODE_RELEASE_BUFFERS); - - switch (app_proto) { - case AppProtocol::H3: - SSL_CTX_set_alpn_select_cb(ssl_ctx_, alpn_select_proto_h3_cb, nullptr); - break; - case AppProtocol::HQ: - SSL_CTX_set_alpn_select_cb(ssl_ctx_, alpn_select_proto_hq_cb, nullptr); - break; - } - - SSL_CTX_set_default_verify_paths(ssl_ctx_); - - if (SSL_CTX_use_PrivateKey_file(ssl_ctx_, private_key_file, - SSL_FILETYPE_PEM) != 1) { - std::cerr << "SSL_CTX_use_PrivateKey_file: " - << ERR_error_string(ERR_get_error(), nullptr) << std::endl; - return -1; - } - - if (SSL_CTX_use_certificate_chain_file(ssl_ctx_, cert_file) != 1) { - std::cerr << "SSL_CTX_use_certificate_chain_file: " - << ERR_error_string(ERR_get_error(), nullptr) << std::endl; - return -1; - } - - if (SSL_CTX_check_private_key(ssl_ctx_) != 1) { - std::cerr << "SSL_CTX_check_private_key: " - << ERR_error_string(ERR_get_error(), nullptr) << std::endl; - return -1; - } - - SSL_CTX_set_session_id_context(ssl_ctx_, sid_ctx, sizeof(sid_ctx) - 1); - - if (config.verify_client) { - SSL_CTX_set_verify(ssl_ctx_, - SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE | - SSL_VERIFY_FAIL_IF_NO_PEER_CERT, - verify_cb); - } - - SSL_CTX_set_session_ticket_cb(ssl_ctx_, gen_ticket_cb, decrypt_ticket_cb, - nullptr); - - return 0; -} - -extern std::ofstream keylog_file; - -namespace { -void keylog_callback(const SSL *ssl, const char *line) { - keylog_file.write(line, static_cast(strlen(line))); - keylog_file.put('\n'); - keylog_file.flush(); -} -} // namespace - -void TLSServerContext::enable_keylog() { - SSL_CTX_set_keylog_callback(ssl_ctx_, keylog_callback); -} diff --git a/src/ngtcp2/tls_server_context_ossl.h b/src/ngtcp2/tls_server_context_ossl.h deleted file mode 100644 index 7f9227c..0000000 --- a/src/ngtcp2/tls_server_context_ossl.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2025 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#ifndef TLS_SERVER_CONTEXT_OSSL_H -#define TLS_SERVER_CONTEXT_OSSL_H - -#ifdef HAVE_CONFIG_H -# include -#endif // defined(HAVE_CONFIG_H) - -#include - -#include "shared.h" - -using namespace ngtcp2; - -class TLSServerContext { -public: - TLSServerContext(); - ~TLSServerContext(); - - int init(const char *private_key_file, const char *cert_file, - AppProtocol app_proto); - - SSL_CTX *get_native_handle() const; - - void enable_keylog(); - -private: - SSL_CTX *ssl_ctx_; -}; - -#endif // !defined(TLS_SERVER_CONTEXT_OSSL_H) diff --git a/src/ngtcp2/tls_server_session.h b/src/ngtcp2/tls_server_session.h deleted file mode 100644 index 652f76d..0000000 --- a/src/ngtcp2/tls_server_session.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2020 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#ifndef TLS_SERVER_SESSION_H -#define TLS_SERVER_SESSION_H - -#ifdef HAVE_CONFIG_H -# include -#endif // defined(HAVE_CONFIG_H) - -#ifdef WITH_EXAMPLE_QUICTLS -# include "tls_server_session_quictls.h" -#endif // defined(WITH_EXAMPLE_QUICTLS) - -#ifdef WITH_EXAMPLE_GNUTLS -# include "tls_server_session_gnutls.h" -#endif // defined(WITH_EXAMPLE_GNUTLS) - -#ifdef WITH_EXAMPLE_BORINGSSL -# include "tls_server_session_boringssl.h" -#endif // defined(WITH_EXAMPLE_BORINGSSL) - -#ifdef WITH_EXAMPLE_PICOTLS -# include "tls_server_session_picotls.h" -#endif // defined(WITH_EXAMPLE_PICOTLS) - -#ifdef WITH_EXAMPLE_WOLFSSL -# include "tls_server_session_wolfssl.h" -#endif // defined(WITH_EXAMPLE_WOLFSSL) - -#ifdef WITH_EXAMPLE_OSSL -# include "tls_server_session_ossl.h" -#endif // defined(WITH_EXAMPLE_OSSL) - -#endif // !defined(TLS_SERVER_SESSION_H) diff --git a/src/ngtcp2/tls_server_session_boringssl.cc b/src/ngtcp2/tls_server_session_boringssl.cc deleted file mode 100644 index 43234d9..0000000 --- a/src/ngtcp2/tls_server_session_boringssl.cc +++ /dev/null @@ -1,84 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2021 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#include "tls_server_session_boringssl.h" - -#include -#include - -#include - -#include "tls_server_context_boringssl.h" -#include "server_base.h" - -extern Config config; - -TLSServerSession::TLSServerSession() {} - -TLSServerSession::~TLSServerSession() {} - -int TLSServerSession::init(const TLSServerContext &tls_ctx, - HandlerBase *handler) { - auto ssl_ctx = tls_ctx.get_native_handle(); - - ssl_ = SSL_new(ssl_ctx); - if (!ssl_) { - std::cerr << "SSL_new: " << ERR_error_string(ERR_get_error(), nullptr) - << std::endl; - return -1; - } - - SSL_set_app_data(ssl_, handler->conn_ref()); - SSL_set_accept_state(ssl_); - SSL_set_early_data_enabled(ssl_, 1); - - std::array quic_early_data_ctx; - ngtcp2_transport_params params; - ngtcp2_transport_params_default(¶ms); - params.initial_max_streams_bidi = config.max_streams_bidi; - params.initial_max_streams_uni = config.max_streams_uni; - params.initial_max_stream_data_bidi_local = config.max_stream_data_bidi_local; - params.initial_max_stream_data_bidi_remote = - config.max_stream_data_bidi_remote; - params.initial_max_stream_data_uni = config.max_stream_data_uni; - params.initial_max_data = config.max_data; - - auto quic_early_data_ctxlen = ngtcp2_transport_params_encode( - quic_early_data_ctx.data(), quic_early_data_ctx.size(), ¶ms); - if (quic_early_data_ctxlen < 0) { - std::cerr << "ngtcp2_transport_params_encode: " - << ngtcp2_strerror(static_cast(quic_early_data_ctxlen)) - << std::endl; - return -1; - } - - if (SSL_set_quic_early_data_context(ssl_, quic_early_data_ctx.data(), - as_unsigned(quic_early_data_ctxlen)) != - 1) { - std::cerr << "SSL_set_quic_early_data_context failed" << std::endl; - return -1; - } - - return 0; -} diff --git a/src/ngtcp2/tls_server_session_boringssl.h b/src/ngtcp2/tls_server_session_boringssl.h deleted file mode 100644 index 2dc6442..0000000 --- a/src/ngtcp2/tls_server_session_boringssl.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2021 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#ifndef TLS_SERVER_SESSION_BORINGSSL_H -#define TLS_SERVER_SESSION_BORINGSSL_H - -#ifdef HAVE_CONFIG_H -# include -#endif // defined(HAVE_CONFIG_H) - -#include "tls_session_base_quictls.h" - -class TLSServerContext; -class HandlerBase; - -class TLSServerSession : public TLSSessionBase { -public: - TLSServerSession(); - ~TLSServerSession(); - - int init(const TLSServerContext &tls_ctx, HandlerBase *handler); - // ticket is sent automatically. - int send_session_ticket() { return 0; } -}; - -#endif // !defined(TLS_SERVER_SESSION_BORINGSSL_H) diff --git a/src/ngtcp2/tls_server_session_ossl.cc b/src/ngtcp2/tls_server_session_ossl.cc deleted file mode 100644 index 39e9408..0000000 --- a/src/ngtcp2/tls_server_session_ossl.cc +++ /dev/null @@ -1,62 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2025 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#include "tls_server_session_ossl.h" - -#include - -#include - -#include "tls_server_context_ossl.h" -#include "server_base.h" - -TLSServerSession::TLSServerSession() {} - -TLSServerSession::~TLSServerSession() {} - -int TLSServerSession::init(const TLSServerContext &tls_ctx, - HandlerBase *handler) { - auto ssl_ctx = tls_ctx.get_native_handle(); - - auto ssl = SSL_new(ssl_ctx); - if (!ssl) { - std::cerr << "SSL_new: " << ERR_error_string(ERR_get_error(), nullptr) - << std::endl; - return -1; - } - - ngtcp2_crypto_ossl_ctx_set_ssl(ossl_ctx_, ssl); - - if (ngtcp2_crypto_ossl_configure_server_session(ssl) != 0) { - std::cerr << "ngtcp2_crypto_ossl_configure_server_session failed" - << std::endl; - return -1; - } - - SSL_set_app_data(ssl, handler->conn_ref()); - SSL_set_accept_state(ssl); - SSL_set_quic_tls_early_data_enabled(ssl, 1); - - return 0; -} diff --git a/src/ngtcp2/tls_server_session_ossl.h b/src/ngtcp2/tls_server_session_ossl.h deleted file mode 100644 index 3d4e0d7..0000000 --- a/src/ngtcp2/tls_server_session_ossl.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2025 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#ifndef TLS_SERVER_SESSION_OSSL_H -#define TLS_SERVER_SESSION_OSSL_H - -#ifdef HAVE_CONFIG_H -# include -#endif // defined(HAVE_CONFIG_H) - -#include "tls_session_base_ossl.h" - -class TLSServerContext; -class HandlerBase; - -class TLSServerSession : public TLSSessionBase { -public: - TLSServerSession(); - ~TLSServerSession(); - - int init(const TLSServerContext &tls_ctx, HandlerBase *handler); - // ticket is sent automatically. - int send_session_ticket() { return 0; } -}; - -#endif // !defined(TLS_SERVER_SESSION_OSSL_H) diff --git a/src/ngtcp2/tls_session_base_ossl.cc b/src/ngtcp2/tls_session_base_ossl.cc deleted file mode 100644 index cc9edda..0000000 --- a/src/ngtcp2/tls_session_base_ossl.cc +++ /dev/null @@ -1,77 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2025 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#include "tls_session_base_ossl.h" - -#include - -#include "util.h" -#include "template.h" - -using namespace ngtcp2; -using namespace std::literals; - -TLSSessionBase::TLSSessionBase() { - ngtcp2_crypto_ossl_ctx_new(&ossl_ctx_, nullptr); -} - -TLSSessionBase::~TLSSessionBase() { - auto ssl = ngtcp2_crypto_ossl_ctx_get_ssl(ossl_ctx_); - - if (ssl) { - SSL_set_app_data(ssl, nullptr); - SSL_free(ssl); - } - - ngtcp2_crypto_ossl_ctx_del(ossl_ctx_); -} - -ngtcp2_crypto_ossl_ctx *TLSSessionBase::get_native_handle() const { - return ossl_ctx_; -} - -std::string TLSSessionBase::get_cipher_name() const { - return SSL_get_cipher_name(ngtcp2_crypto_ossl_ctx_get_ssl(ossl_ctx_)); -} - -std::string_view TLSSessionBase::get_negotiated_group() const { - auto ssl = ngtcp2_crypto_ossl_ctx_get_ssl(ossl_ctx_); - auto name = SSL_get0_group_name(ssl); - - if (!name) { - return ""sv; - } - - return name; -} - -std::string TLSSessionBase::get_selected_alpn() const { - auto ssl = ngtcp2_crypto_ossl_ctx_get_ssl(ossl_ctx_); - const unsigned char *alpn = nullptr; - unsigned int alpnlen; - - SSL_get0_alpn_selected(ssl, &alpn, &alpnlen); - - return std::string{alpn, alpn + alpnlen}; -} diff --git a/src/ngtcp2/tls_session_base_ossl.h b/src/ngtcp2/tls_session_base_ossl.h deleted file mode 100644 index d4de743..0000000 --- a/src/ngtcp2/tls_session_base_ossl.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2025 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#ifndef TLS_SESSION_BASE_OSSL_H -#define TLS_SESSION_BASE_OSSL_H - -#ifdef HAVE_CONFIG_H -# include -#endif // defined(HAVE_CONFIG_H) - -#include -#include - -#include - -#include - -class TLSSessionBase { -public: - TLSSessionBase(); - ~TLSSessionBase(); - - ngtcp2_crypto_ossl_ctx *get_native_handle() const; - - std::string get_cipher_name() const; - std::string_view get_negotiated_group() const; - std::string get_selected_alpn() const; - // Keylog is enabled per SSL_CTX. - void enable_keylog() {} - -protected: - ngtcp2_crypto_ossl_ctx *ossl_ctx_; -}; - -#endif // !defined(TLS_SESSION_BASE_OSSL_H) diff --git a/src/ngtcp2/tls_session_base_quictls.cc b/src/ngtcp2/tls_session_base_quictls.cc deleted file mode 100644 index 5471cf0..0000000 --- a/src/ngtcp2/tls_session_base_quictls.cc +++ /dev/null @@ -1,102 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2020 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#include "tls_session_base_quictls.h" - -#include - -#include "util.h" -#include "template.h" - -using namespace ngtcp2; -using namespace std::literals; - -TLSSessionBase::TLSSessionBase() : ssl_{nullptr} {} - -TLSSessionBase::~TLSSessionBase() { - if (ssl_) { - SSL_free(ssl_); - } -} - -SSL *TLSSessionBase::get_native_handle() const { return ssl_; } - -std::string TLSSessionBase::get_cipher_name() const { - return SSL_get_cipher_name(ssl_); -} - -std::string_view TLSSessionBase::get_negotiated_group() const { -#ifdef WITH_EXAMPLE_BORINGSSL - return SSL_get_group_name(SSL_get_group_id(ssl_)); -#elif OPENSSL_VERSION_NUMBER >= 0x30000000L - auto name = - SSL_group_to_name(ssl_, static_cast(SSL_get_negotiated_group(ssl_))); - if (!name) { - return ""sv; - } - - return name; -#elif defined(LIBRESSL_VERSION_NUMBER) - return ""sv; -#else // !(defined(WITH_EXAMPLE_BORINGSSL) || - // OPENSSL_VERSION_NUMBER >= 0x30000000L || - // defined(LIBRESSL_VERSION_NUMBER)) - EVP_PKEY *key; - - if (!SSL_get_tmp_key(ssl_, &key)) { - return ""sv; - } - - auto key_del = defer(EVP_PKEY_free, key); - - auto nid = EVP_PKEY_id(key); - if (nid == EVP_PKEY_EC) { - auto ec = EVP_PKEY_get1_EC_KEY(key); - auto ec_del = defer(EC_KEY_free, ec); - - nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec)); - } - - auto name = EC_curve_nid2nist(nid); - if (!name) { - name = OBJ_nid2sn(nid); - if (!name) { - return ""sv; - } - } - - return name; -#endif // !(defined(WITH_EXAMPLE_BORINGSSL) || - // OPENSSL_VERSION_NUMBER >= 0x30000000L || - // defined(LIBRESSL_VERSION_NUMBER)) -} - -std::string TLSSessionBase::get_selected_alpn() const { - const unsigned char *alpn = nullptr; - unsigned int alpnlen; - - SSL_get0_alpn_selected(ssl_, &alpn, &alpnlen); - - return std::string{alpn, alpn + alpnlen}; -} diff --git a/src/ngtcp2/tls_session_base_quictls.h b/src/ngtcp2/tls_session_base_quictls.h deleted file mode 100644 index aee0656..0000000 --- a/src/ngtcp2/tls_session_base_quictls.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2020 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#ifndef TLS_SESSION_BASE_QUICTLS_H -#define TLS_SESSION_BASE_QUICTLS_H - -#ifdef HAVE_CONFIG_H -# include -#endif // defined(HAVE_CONFIG_H) - -#include -#include - -#include - -class TLSSessionBase { -public: - TLSSessionBase(); - ~TLSSessionBase(); - - SSL *get_native_handle() const; - - std::string get_cipher_name() const; - std::string_view get_negotiated_group() const; - std::string get_selected_alpn() const; - // Keylog is enabled per SSL_CTX. - void enable_keylog() {} - -protected: - SSL *ssl_; -}; - -#endif // !defined(TLS_SESSION_BASE_QUICTLS_H) diff --git a/src/ngtcp2/tls_shared_boringssl.cc b/src/ngtcp2/tls_shared_boringssl.cc deleted file mode 100644 index eaf673e..0000000 --- a/src/ngtcp2/tls_shared_boringssl.cc +++ /dev/null @@ -1,89 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2024 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#include "tls_shared_boringssl.h" - -#ifdef HAVE_LIBBROTLI -# include -# include -#endif // defined(HAVE_LIBBROTLI) - -namespace ngtcp2 { - -namespace tls { - -#ifdef HAVE_LIBBROTLI -int cert_compress(SSL *ssl, CBB *out, const uint8_t *in, size_t in_len) { - uint8_t *dest; - - auto compressed_size = BrotliEncoderMaxCompressedSize(in_len); - if (compressed_size == 0) { - return 0; - } - - if (!CBB_reserve(out, &dest, compressed_size)) { - return 0; - } - - if (BrotliEncoderCompress(BROTLI_MAX_QUALITY, BROTLI_DEFAULT_WINDOW, - BROTLI_MODE_GENERIC, in_len, in, &compressed_size, - dest) != BROTLI_TRUE) { - return 0; - } - - if (!CBB_did_write(out, compressed_size)) { - return 0; - } - - return 1; -} - -int cert_decompress(SSL *ssl, CRYPTO_BUFFER **out, size_t uncompressed_len, - const uint8_t *in, size_t in_len) { - uint8_t *dest; - auto buf = CRYPTO_BUFFER_alloc(&dest, uncompressed_len); - auto len = uncompressed_len; - - if (BrotliDecoderDecompress(in_len, in, &len, dest) != - BROTLI_DECODER_RESULT_SUCCESS) { - CRYPTO_BUFFER_free(buf); - - return 0; - } - - if (uncompressed_len != len) { - CRYPTO_BUFFER_free(buf); - - return 0; - } - - *out = buf; - - return 1; -} -#endif // defined(HAVE_LIBBROTLI) - -} // namespace tls - -} // namespace ngtcp2 diff --git a/src/ngtcp2/tls_shared_boringssl.h b/src/ngtcp2/tls_shared_boringssl.h deleted file mode 100644 index c08712d..0000000 --- a/src/ngtcp2/tls_shared_boringssl.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2024 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#ifndef TLS_SHARED_BORINGSSL_H -#define TLS_SHARED_BORINGSSL_H - -#ifdef HAVE_CONFIG_H -# include -#endif // defined(HAVE_CONFIG_H) - -#include - -namespace ngtcp2 { - -namespace tls { - -inline constexpr uint16_t CERTIFICATE_COMPRESSION_ALGO_BROTLI = 2; - -#ifdef HAVE_LIBBROTLI -int cert_compress(SSL *ssl, CBB *out, const uint8_t *in, size_t in_len); - -int cert_decompress(SSL *ssl, CRYPTO_BUFFER **out, size_t uncompressed_len, - const uint8_t *in, size_t in_len); -#endif // defined(HAVE_LIBBROTLI) - -} // namespace tls - -} // namespace ngtcp2 - -#endif // !defined(TLS_SHARED_BORINGSSL_H) diff --git a/src/ngtcp2/util.cc b/src/ngtcp2/util.cc index b459b4b..ed7379e 100644 --- a/src/ngtcp2/util.cc +++ b/src/ngtcp2/util.cc @@ -24,125 +24,17 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "util.h" -#include -#ifdef HAVE_ARPA_INET_H -# include -#endif // defined(HAVE_ARPA_INET_H) -#ifdef HAVE_NETINET_IN_H -# include -#endif // defined(HAVE_NETINET_IN_H) -#include -#include -#include -#include #include -#include -#include -#include #include -#include +#include #include -#include -#include -#include -#include - -#include "template.h" - -using namespace std::literals; namespace ngtcp2 { namespace util { -std::optional -read_hpke_private_key_pem(const std::string_view &filename); - -std::optional> read_pem(const std::string_view &filename, - const std::string_view &name, - const std::string_view &type); - -int write_pem(const std::string_view &filename, const std::string_view &name, - const std::string_view &type, std::span data); - -std::string decode_hex(const std::string_view &s) { - assert(s.size() % 2 == 0); - std::string res(s.size() / 2, '0'); - auto p = std::ranges::begin(res); - for (auto it = std::ranges::begin(s); it != std::ranges::end(s); it += 2) { - *p++ = static_cast((hex_to_uint(*it) << 4) | hex_to_uint(*(it + 1))); - } - return res; -} - -namespace { -// format_fraction2 formats |n| as fraction part of integer. |n| is -// considered as fraction, and its precision is 3 digits. The last -// digit is ignored. The precision of the resulting fraction is 2 -// digits. -std::string format_fraction2(uint32_t n) { - n /= 10; - - if (n < 10) { - return {'.', '0', static_cast('0' + n)}; - } - return {'.', static_cast('0' + n / 10), - static_cast('0' + (n % 10))}; -} -} // namespace - -namespace { -// round2even rounds the last digit of |n| so that the n / 10 becomes -// even. -uint64_t round2even(uint64_t n) { - if (n % 10 == 5) { - if ((n / 10) & 1) { - n += 10; - } - } else { - n += 5; - } - return n; -} -} // namespace - -std::string format_durationf(uint64_t ns) { - static constexpr std::string_view units[] = {"us"sv, "ms"sv, "s"sv}; - if (ns < 1000) { - return format_uint(ns) + "ns"; - } - auto unit = 0; - if (ns < 1000000) { - // do nothing - } else if (ns < 1000000000) { - ns /= 1000; - unit = 1; - } else { - ns /= 1000000; - unit = 2; - } - - ns = round2even(ns); - - if (ns / 1000 >= 1000 && unit < 2) { - ns /= 1000; - ++unit; - } - - auto res = format_uint(ns / 1000); - res += format_fraction2(static_cast(ns % 1000)); - res += units[unit]; - - return res; -} - -std::mt19937 make_mt19937() { - std::random_device rd; - return std::mt19937(rd()); -} - ngtcp2_tstamp timestamp() { return static_cast( std::chrono::duration_cast( @@ -150,191 +42,6 @@ ngtcp2_tstamp timestamp() { .count()); } -ngtcp2_tstamp system_clock_now() { - return static_cast( - std::chrono::floor( - std::chrono::system_clock::now().time_since_epoch()) - .count()); -} - -bool numeric_host(const char *hostname) { - return numeric_host(hostname, AF_INET) || numeric_host(hostname, AF_INET6); -} - -bool numeric_host(const char *hostname, int family) { - int rv; - std::array dst; - - rv = inet_pton(family, hostname, dst.data()); - - return rv == 1; -} - -namespace { -uint8_t *hexdump_addr(uint8_t *dest, size_t addr) { - // Lower 32 bits are displayed. - return format_hex(static_cast(addr), dest); -} -} // namespace - -namespace { -uint8_t *hexdump_ascii(uint8_t *dest, std::span data) { - *dest++ = '|'; - - for (auto c : data) { - if (0x20 <= c && c <= 0x7e) { - *dest++ = c; - } else { - *dest++ = '.'; - } - } - - *dest++ = '|'; - - return dest; -} -} // namespace - -namespace { -uint8_t *hexdump8(uint8_t *dest, std::span data) { - for (auto c : data) { - dest = format_hex(c, dest); - *dest++ = ' '; - } - - for (auto i = data.size(); i < 8; ++i) { - *dest++ = ' '; - *dest++ = ' '; - *dest++ = ' '; - } - - return dest; -} -} // namespace - -namespace { -uint8_t *hexdump16(uint8_t *dest, std::span data) { - if (data.size() > 8) { - dest = hexdump8(dest, {data.data(), 8}); - *dest++ = ' '; - dest = hexdump8(dest, data.subspan(8)); - *dest++ = ' '; - } else { - dest = hexdump8(dest, data); - *dest++ = ' '; - dest = hexdump8(dest, {}); - *dest++ = ' '; - } - - return dest; -} -} // namespace - -namespace { -uint8_t *hexdump_line(uint8_t *dest, std::span data, - size_t addr) { - dest = hexdump_addr(dest, addr); - *dest++ = ' '; - *dest++ = ' '; - - dest = hexdump16(dest, data); - - return hexdump_ascii(dest, data); -} -} // namespace - -namespace { -int hexdump_write(int fd, std::span data) { - ssize_t nwrite; - - for (; - (nwrite = write(fd, data.data(), data.size())) == -1 && errno == EINTR;) - ; - if (nwrite == -1) { - return -1; - } - - return 0; -} -} // namespace - -int hexdump(FILE *out, const void *data, size_t datalen) { - if (datalen == 0) { - return 0; - } - - // min_space is the additional minimum space that the buffer must - // accept, which is the size of a single full line output + one - // repeat line marker ("*\n"). If the remaining buffer size is less - // than that, flush the buffer and reset. - constexpr size_t min_space = 79 + 2; - - auto fd = fileno(out); - std::array buf; - auto input = std::span{reinterpret_cast(data), datalen}; - auto last = buf.data(); - auto repeated = false; - std::span s, last_s{}; - - for (; !input.empty(); input = input.subspan(s.size())) { - s = input; - - if (s.size() >= 16) { - s = s.first(16); - - if (std::ranges::equal(last_s, s)) { - if (repeated) { - continue; - } - - repeated = true; - - *last++ = '*'; - *last++ = '\n'; - - continue; - } - - repeated = false; - } - - last = hexdump_line( - last, s, as_unsigned(s.data() - reinterpret_cast(data))); - *last++ = '\n'; - last_s = s; - - auto len = static_cast(last - buf.data()); - if (len + min_space > buf.size()) { - if (hexdump_write(fd, {buf.data(), len}) != 0) { - return -1; - } - - last = buf.data(); - } - } - - last = hexdump_addr(last, datalen); - *last++ = '\n'; - - auto len = static_cast(last - buf.data()); - if (len) { - return hexdump_write(fd, {buf.data(), len}); - } - - return 0; -} - -ngtcp2_cid make_cid_key(std::span cid) { - assert(cid.size() <= NGTCP2_MAX_CIDLEN); - - ngtcp2_cid res; - - std::ranges::copy(cid, std::ranges::begin(res.data)); - res.datalen = cid.size(); - - return res; -} - std::string straddr(const sockaddr *sa, socklen_t salen) { std::array host; std::array port; @@ -352,525 +59,6 @@ std::string straddr(const sockaddr *sa, socklen_t salen) { return res; } -uint16_t port(const sockaddr_union *su) { - switch (su->sa.sa_family) { - case AF_INET: - return ntohs(su->in.sin_port); - case AF_INET6: - return ntohs(su->in6.sin6_port); - default: - return 0; - } -} - -bool prohibited_port(uint16_t port) { - switch (port) { - case 1900: - case 5353: - case 11211: - case 20800: - case 27015: - return true; - default: - return port < 1024; - } -} - -std::string_view strccalgo(ngtcp2_cc_algo cc_algo) { - switch (cc_algo) { - case NGTCP2_CC_ALGO_RENO: - return "reno"sv; - case NGTCP2_CC_ALGO_CUBIC: - return "cubic"sv; - case NGTCP2_CC_ALGO_BBR: - return "bbr"sv; - default: - assert(0); - abort(); - } -} - -namespace { -constexpr bool rws(char c) { return c == '\t' || c == ' '; } -} // namespace - -std::optional> -read_mime_types(const std::string_view &filename) { - std::ifstream f(filename.data()); - if (!f) { - return {}; - } - - std::unordered_map dest; - - std::string line; - while (std::getline(f, line)) { - if (line.empty() || line[0] == '#') { - continue; - } - - auto p = std::ranges::find_if(line, rws); - if (p == std::ranges::begin(line) || p == std::ranges::end(line)) { - continue; - } - - auto media_type = std::string{std::ranges::begin(line), p}; - for (;;) { - auto ext = std::ranges::find_if_not(p, std::ranges::end(line), rws); - if (ext == std::ranges::end(line)) { - break; - } - - p = std::ranges::find_if(ext, std::ranges::end(line), rws); - dest.emplace(std::string{ext, p}, media_type); - } - } - - return dest; -} - -std::string format_duration(ngtcp2_duration n) { - if (n >= 3600 * NGTCP2_SECONDS && (n % (3600 * NGTCP2_SECONDS)) == 0) { - return format_uint(n / (3600 * NGTCP2_SECONDS)) + 'h'; - } - if (n >= 60 * NGTCP2_SECONDS && (n % (60 * NGTCP2_SECONDS)) == 0) { - return format_uint(n / (60 * NGTCP2_SECONDS)) + 'm'; - } - if (n >= NGTCP2_SECONDS && (n % NGTCP2_SECONDS) == 0) { - return format_uint(n / NGTCP2_SECONDS) + 's'; - } - if (n >= NGTCP2_MILLISECONDS && (n % NGTCP2_MILLISECONDS) == 0) { - return format_uint(n / NGTCP2_MILLISECONDS) + "ms"; - } - if (n >= NGTCP2_MICROSECONDS && (n % NGTCP2_MICROSECONDS) == 0) { - return format_uint(n / NGTCP2_MICROSECONDS) + "us"; - } - return format_uint(n) + "ns"; -} - -namespace { -std::optional> -parse_uint_internal(const std::string_view &s) { - uint64_t res = 0; - - if (s.empty()) { - return {}; - } - - for (size_t i = 0; i < s.size(); ++i) { - auto c = s[i]; - if (!is_digit(c)) { - return {{res, i}}; - } - - auto d = static_cast(c - '0'); - if (res > (std::numeric_limits::max() - d) / 10) { - return {}; - } - - res *= 10; - res += d; - } - - return {{res, s.size()}}; -} -} // namespace - -std::optional parse_uint(const std::string_view &s) { - auto o = parse_uint_internal(s); - if (!o) { - return {}; - } - auto [res, idx] = *o; - if (idx != s.size()) { - return {}; - } - return res; -} - -std::optional parse_uint_iec(const std::string_view &s) { - auto o = parse_uint_internal(s); - if (!o) { - return {}; - } - auto [res, idx] = *o; - if (idx == s.size()) { - return res; - } - if (idx + 1 != s.size()) { - return {}; - } - - uint64_t m; - switch (s[idx]) { - case 'G': - case 'g': - m = 1 << 30; - break; - case 'M': - case 'm': - m = 1 << 20; - break; - case 'K': - case 'k': - m = 1 << 10; - break; - default: - return {}; - } - - if (res > std::numeric_limits::max() / m) { - return {}; - } - - return res * m; -} - -std::optional parse_duration(const std::string_view &s) { - auto o = parse_uint_internal(s); - if (!o) { - return {}; - } - auto [res, idx] = *o; - if (idx == s.size()) { - return res * NGTCP2_SECONDS; - } - - uint64_t m; - if (idx + 1 == s.size()) { - switch (s[idx]) { - case 'H': - case 'h': - m = 3600 * NGTCP2_SECONDS; - break; - case 'M': - case 'm': - m = 60 * NGTCP2_SECONDS; - break; - case 'S': - case 's': - m = NGTCP2_SECONDS; - break; - default: - return {}; - } - } else if (idx + 2 == s.size() && (s[idx + 1] == 's' || s[idx + 1] == 'S')) { - switch (s[idx]) { - case 'M': - case 'm': - m = NGTCP2_MILLISECONDS; - break; - case 'U': - case 'u': - m = NGTCP2_MICROSECONDS; - break; - case 'N': - case 'n': - return res; - default: - return {}; - } - } else { - return {}; - } - - if (res > std::numeric_limits::max() / m) { - return {}; - } - - return res * m; -} - -namespace { -template InputIt eat_file(InputIt first, InputIt last) { - if (first == last) { - *first++ = '/'; - return first; - } - - if (*(last - 1) == '/') { - return last; - } - - auto p = last; - for (; p != first && *(p - 1) != '/'; --p) - ; - if (p == first) { - // this should not happened in normal case, where we expect path - // starts with '/' - *first++ = '/'; - return first; - } - - return p; -} -} // namespace - -namespace { -template InputIt eat_dir(InputIt first, InputIt last) { - auto p = eat_file(first, last); - - --p; - - assert(*p == '/'); - - return eat_file(first, p); -} -} // namespace - -std::string normalize_path(const std::string_view &path) { - assert(path.size() <= 1024); - assert(path.size() > 0); - assert(path[0] == '/'); - - std::array res; - auto p = res.data(); - - auto first = std::ranges::begin(path); - auto last = std::ranges::end(path); - - *p++ = '/'; - ++first; - for (; first != last && *first == '/'; ++first) - ; - - for (; first != last;) { - if (*first == '.') { - if (first + 1 == last) { - break; - } - if (*(first + 1) == '/') { - first += 2; - continue; - } - if (*(first + 1) == '.') { - if (first + 2 == last) { - p = eat_dir(res.data(), p); - break; - } - if (*(first + 2) == '/') { - p = eat_dir(res.data(), p); - first += 3; - continue; - } - } - } - if (*(p - 1) != '/') { - p = eat_file(res.data(), p); - } - auto slash = std::ranges::find(first, last, '/'); - if (slash == last) { - p = std::ranges::copy(first, last, p).out; - break; - } - p = std::ranges::copy(first, slash + 1, p).out; - first = slash + 1; - for (; first != last && *first == '/'; ++first) - ; - } - return std::string{res.data(), p}; -} - -int make_socket_nonblocking(int fd) { - int rv; - int flags; - - while ((flags = fcntl(fd, F_GETFL, 0)) == -1 && errno == EINTR) - ; - if (flags == -1) { - return -1; - } - - while ((rv = fcntl(fd, F_SETFL, flags | O_NONBLOCK)) == -1 && errno == EINTR) - ; - - return rv; -} - -int create_nonblock_socket(int domain, int type, int protocol) { -#ifdef SOCK_NONBLOCK - auto fd = socket(domain, type | SOCK_NONBLOCK, protocol); - if (fd == -1) { - return -1; - } -#else // !defined(SOCK_NONBLOCK) - auto fd = socket(domain, type, protocol); - if (fd == -1) { - return -1; - } - - make_socket_nonblocking(fd); -#endif // !defined(SOCK_NONBLOCK) - - return fd; -} - -std::vector split_str(const std::string_view &s, char delim) { - size_t len = 1; - auto last = std::ranges::end(s); - std::string_view::const_iterator d; - for (auto first = std::ranges::begin(s); - (d = std::ranges::find(first, last, delim)) != last; - ++len, first = d + 1) - ; - - auto list = std::vector(len); - - len = 0; - for (auto first = std::ranges::begin(s);; ++len) { - auto stop = std::ranges::find(first, last, delim); - // xcode clang does not understand std::string_view{first, stop}. - list[len] = std::string_view{first, static_cast(stop - first)}; - if (stop == last) { - break; - } - first = stop + 1; - } - return list; -} - -std::optional parse_version(const std::string_view &s) { - if (!util::istarts_with(s, "0x"sv)) { - return {}; - } - auto k = s.substr(2); - auto k_last = k.data() + k.size(); - uint32_t v; - auto rv = std::from_chars(k.data(), k_last, v, 16); - if (rv.ptr != k_last || rv.ec != std::errc{}) { - return {}; - } - - return v; -} - -std::optional> -read_token(const std::string_view &filename) { - return read_pem(filename, "token"sv, "QUIC TOKEN"sv); -} - -int write_token(const std::string_view &filename, - std::span token) { - return write_pem(filename, "token"sv, "QUIC TOKEN"sv, token); -} - -std::optional> -read_transport_params(const std::string_view &filename) { - return read_pem(filename, "transport parameters"sv, - "QUIC TRANSPORT PARAMETERS"sv); -} - -int write_transport_params(const std::string_view &filename, - std::span data) { - return write_pem(filename, "transport parameters"sv, - "QUIC TRANSPORT PARAMETERS"sv, data); -} - -std::string percent_decode(const std::string_view &s) { - std::string result; - result.resize(s.size()); - auto p = std::ranges::begin(result); - for (auto first = std::ranges::begin(s), last = std::ranges::end(s); - first != last; ++first) { - if (*first != '%') { - *p++ = *first; - continue; - } - - if (first + 1 != last && first + 2 != last && is_hex_digit(*(first + 1)) && - is_hex_digit(*(first + 2))) { - *p++ = static_cast((hex_to_uint(*(first + 1)) << 4) + - hex_to_uint(*(first + 2))); - first += 2; - continue; - } - - *p++ = *first; - } - result.resize(as_unsigned(p - std::ranges::begin(result))); - return result; -} - -std::optional> read_file(const std::string_view &path) { - auto fd = open(path.data(), O_RDONLY); - if (fd == -1) { - return {}; - } - - auto fd_d = defer(close, fd); - - auto size = lseek(fd, 0, SEEK_END); - if (size == static_cast(-1)) { - return {}; - } - - auto addr = - mmap(nullptr, static_cast(size), PROT_READ, MAP_SHARED, fd, 0); - if (addr == MAP_FAILED) { - return {}; - } - - auto addr_d = defer(munmap, addr, static_cast(size)); - - auto p = static_cast(addr); - - return {{p, p + size}}; -} - -size_t clamp_buffer_size(ngtcp2_conn *conn, size_t buflen, size_t gso_burst) { - return std::min(gso_burst == 0 - ? ngtcp2_conn_get_send_quantum(conn) - : ngtcp2_conn_get_path_max_tx_udp_payload_size(conn) * - gso_burst, - buflen); -} - -bool recv_pkt_time_threshold_exceeded(bool time_sensitive, ngtcp2_tstamp start, - size_t pktcnt) { - return time_sensitive && pktcnt && - util::timestamp() - start >= NGTCP2_MILLISECONDS; -} - -std::optional -read_ech_server_config(const std::string_view &path) { - auto pkey = read_hpke_private_key_pem(path); - if (!pkey) { - return {}; - } - - auto ech_config = read_pem(path, "ECH config"sv, "ECHCONFIG"sv); - if (!ech_config) { - return {}; - } - - return ECHServerConfig{ - .private_key = std::move(*pkey), - .ech_config = std::move(*ech_config), - }; -} - -std::span generate_siphash_key() { - static auto key = []() { - std::array key; - - auto rv = generate_secure_random(as_writable_uint8_span(std::span{key})); - if (rv != 0) { - assert(0); - abort(); - } - - return key; - }(); - - ++key[0]; - - return key; -} - } // namespace util -std::ostream &operator<<(std::ostream &os, const ngtcp2_cid &cid) { - return os << "0x" << util::format_hex(cid.data, as_signed(cid.datalen)); -} - } // namespace ngtcp2 diff --git a/src/ngtcp2/util.h b/src/ngtcp2/util.h index f3b1e9f..ec05e28 100644 --- a/src/ngtcp2/util.h +++ b/src/ngtcp2/util.h @@ -26,60 +26,19 @@ #ifndef UTIL_H #define UTIL_H -#ifdef HAVE_CONFIG_H -# include -#endif // defined(HAVE_CONFIG_H) - #include -#include -#include +#include +#include +#include #include -#include -#include -#include -#include #include -#include - -#include - -#include "network.h" -#include "siphash.h" -#include "template.h" namespace ngtcp2 { namespace util { -inline nghttp3_nv make_nv(const std::string_view &name, - const std::string_view &value, uint8_t flags) { - return nghttp3_nv{ - reinterpret_cast(const_cast(std::ranges::data(name))), - reinterpret_cast(const_cast(std::ranges::data(value))), - name.size(), - value.size(), - flags, - }; -} - -inline nghttp3_nv make_nv_cc(const std::string_view &name, - const std::string_view &value) { - return make_nv(name, value, NGHTTP3_NV_FLAG_NONE); -} - -inline nghttp3_nv make_nv_nc(const std::string_view &name, - const std::string_view &value) { - return make_nv(name, value, NGHTTP3_NV_FLAG_NO_COPY_NAME); -} - -inline nghttp3_nv make_nv_nn(const std::string_view &name, - const std::string_view &value) { - return make_nv(name, value, - NGHTTP3_NV_FLAG_NO_COPY_NAME | NGHTTP3_NV_FLAG_NO_COPY_VALUE); -} - inline constexpr auto hexdigits = []() { constexpr char LOWER_XDIGITS[] = "0123456789abcdef"; @@ -132,480 +91,21 @@ constexpr std::string format_hex(I first, std::iter_difference_t n) { std::string res; - res.resize(as_unsigned(n * 2)); + res.resize(static_cast(n * 2)); format_hex(std::move(first), std::move(n), std::ranges::begin(res)); return res; } -// format_hex converts |R| in hex format, and stores the result in -// another range, beginning at |result|. It returns an output -// iterator to the element past the last element stored. -template -requires(std::indirectly_writable && - !std::is_array_v> && - sizeof(std::ranges::range_value_t) == sizeof(uint8_t)) -constexpr O format_hex(R &&r, O result) { - return format_hex(std::ranges::begin(r), std::ranges::end(r), - std::move(result)); -} - -// format_hex converts |R| in hex format, and returns the result. -template -requires(!std::is_array_v> && - sizeof(std::ranges::range_value_t) == sizeof(uint8_t)) -constexpr std::string format_hex(R &&r) { - std::string res; - - res.resize(as_unsigned(std::ranges::distance(r) * 2)); - - format_hex(std::forward(r), std::ranges::begin(res)); - - return res; -} - -// format_hex converts |n| in hex format, and stores the result in -// another range, beginning at |result|. It returns an output -// iterator to the element past the last element stored. -template -requires(std::indirectly_writable) -constexpr O format_hex(T n, O result) { - if constexpr (sizeof(n) == 1) { - return std::ranges::copy_n(hexdigits.data() + n * 2, 2, result).out; - } - - if constexpr (std::endian::native == std::endian::little) { - auto end = reinterpret_cast(&n); - auto p = end + sizeof(n); - - for (; p != end; --p) { - result = - std::ranges::copy_n(hexdigits.data() + *(p - 1) * 2, 2, result).out; - } - } else { - auto p = reinterpret_cast(&n); - auto end = p + sizeof(n); - - for (; p != end; ++p) { - result = std::ranges::copy_n(hexdigits.data() + *p * 2, 2, result).out; - } - } - - return result; -} - -// format_hex converts |n| in hex format, and returns it. -template constexpr std::string format_hex(T n) { - std::string res; - - res.resize(sizeof(n) * 2); - - format_hex(std::move(n), std::ranges::begin(res)); - - return res; -} - -std::string decode_hex(const std::string_view &s); - -// format_durationf formats |ns| in human readable manner. |ns| must -// be nanoseconds resolution. This function uses the largest unit so -// that the integral part is strictly more than zero, and the -// precision is at most 2 digits. For example, 1234 is formatted as -// "1.23us". The largest unit is seconds. -std::string format_durationf(uint64_t ns); - -std::mt19937 make_mt19937(); - +// timestamp returns the current timestamp of steady clock, in nanoseconds. ngtcp2_tstamp timestamp(); -// system_clock_now returns the current timestamp of system clock. -ngtcp2_tstamp system_clock_now(); - -bool numeric_host(const char *hostname); - -bool numeric_host(const char *hostname, int family); - -// hexdump dumps |data| of length |datalen| in the format similar to -// hexdump(1) with -C option. This function returns 0 if it succeeds, -// or -1. -int hexdump(FILE *out, const void *data, size_t datalen); - -inline constexpr uint8_t lowcase_tbl[] = { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, - 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, - 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, - 60, 61, 62, 63, 64, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', - 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', - 'z', 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, - 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, - 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, - 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, - 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, - 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, - 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, - 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, - 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, - 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, - 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, - 255, -}; - -constexpr char lowcase(char c) noexcept { - return as_signed(lowcase_tbl[static_cast(c)]); -} - -struct CaseCmp { - constexpr bool operator()(char lhs, char rhs) const noexcept { - return lowcase(lhs) == lowcase(rhs); - } -}; - -// istarts_with returns true if |s| starts with |prefix|. Comparison -// is performed in case-insensitive manner. -constexpr bool istarts_with(const std::string_view &s, - const std::string_view &prefix) { - return s.size() >= prefix.size() && - std::ranges::equal(s.substr(0, prefix.size()), prefix, CaseCmp()); -} - -// make_cid_key returns the key for |cid|. -std::string_view make_cid_key(const ngtcp2_cid *cid); -ngtcp2_cid make_cid_key(std::span cid); - // straddr stringifies |sa| of length |salen| in a format "[IP]:PORT". std::string straddr(const sockaddr *sa, socklen_t salen); -// port returns port from |su|. -uint16_t port(const sockaddr_union *su); - -// prohibited_port returns true if |port| is prohibited as a client -// port. -bool prohibited_port(uint16_t port); - -// strccalgo stringifies |cc_algo|. -std::string_view strccalgo(ngtcp2_cc_algo cc_algo); - -// read_mime_types reads "MIME media types and the extensions" file -// denoted by |filename| and returns the mapping of extension to MIME -// media type. -std::optional> -read_mime_types(const std::string_view &filename); - -inline constexpr auto count_digit_tbl = []() { - std::array::digits10> tbl; - - uint64_t x = 1; - - for (size_t i = 0; i < tbl.size(); ++i) { - x *= 10; - tbl[i] = x - 1; - } - - return tbl; -}(); - -// count_digit returns the minimum number of digits to represent |x| -// in base 10. -// -// credit: -// https://lemire.me/blog/2025/01/07/counting-the-digits-of-64-bit-integers/ -template constexpr size_t count_digit(T x) { - auto y = static_cast(19 * (std::numeric_limits::digits - 1 - - std::countl_zero(static_cast(x | 1))) >> - 6); - - y += x > count_digit_tbl[y]; - - return y + 1; -} - -inline constexpr auto utos_digits = []() { - std::array a; - - for (size_t i = 0; i < 100; ++i) { - a[i * 2] = '0' + static_cast(i / 10); - a[i * 2 + 1] = '0' + static_cast(i % 10); - } - - return a; -}(); - -struct UIntFormatter { - template - requires(std::indirectly_writable) - constexpr O operator()(T n, O result) { - using result_type = std::iter_value_t; - - if (n < 10) { - *result++ = static_cast('0' + static_cast(n)); - return result; - } - - if (n < 100) { - return std::ranges::copy_n(utos_digits.data() + n * 2, 2, result).out; - } - - std::ranges::advance(result, as_signed(count_digit(n))); - - auto p = result; - - for (; n >= 100; n /= 100) { - std::ranges::advance(p, -2); - std::ranges::copy_n(utos_digits.data() + (n % 100) * 2, 2, p); - } - - if (n < 10) { - *--p = static_cast('0' + static_cast(n)); - return result; - } - - std::ranges::advance(p, -2); - std::ranges::copy_n(utos_digits.data() + n * 2, 2, p); - - return result; - } -}; - -template -requires(std::indirectly_writable) -constexpr O utos(T n, O result) { - return UIntFormatter{}(std::move(n), std::move(result)); -} - -// format_uint converts |n| into string. -template constexpr std::string format_uint(T n) { - using namespace std::literals; - - if (n == 0) { - return "0"s; - } - - std::string res; - - res.resize(count_digit(n)); - - utos(n, std::ranges::begin(res)); - - return res; -} - -// format_uint_iec converts |n| into string with the IEC unit (either -// "G", "M", or "K"). It chooses the largest unit which does not drop -// precision. -template std::string format_uint_iec(T n) { - if (n >= (1 << 30) && (n & ((1 << 30) - 1)) == 0) { - return format_uint(n / (1 << 30)) + 'G'; - } - if (n >= (1 << 20) && (n & ((1 << 20) - 1)) == 0) { - return format_uint(n / (1 << 20)) + 'M'; - } - if (n >= (1 << 10) && (n & ((1 << 10) - 1)) == 0) { - return format_uint(n / (1 << 10)) + 'K'; - } - return format_uint(n); -} - -// format_duration converts |n| into string with the unit in either -// "h" (hours), "m" (minutes), "s" (seconds), "ms" (milliseconds), -// "us" (microseconds) or "ns" (nanoseconds). It chooses the largest -// unit which does not drop precision. |n| is in nanosecond -// resolution. -std::string format_duration(ngtcp2_duration n); - -// parse_uint parses |s| as 64-bit unsigned integer. If it cannot -// parse |s|, the return value does not contain a value. -std::optional parse_uint(const std::string_view &s); - -// parse_uint_iec parses |s| as 64-bit unsigned integer. It accepts -// IEC unit letter (either "G", "M", or "K") in |s|. If it cannot -// parse |s|, the return value does not contain a value. -std::optional parse_uint_iec(const std::string_view &s); - -// parse_duration parses |s| as 64-bit unsigned integer. It accepts a -// unit (either "h", "m", "s", "ms", "us", or "ns") in |s|. If no -// unit is present, the unit "s" is assumed. If it cannot parse |s|, -// the return value does not contain a value. -std::optional parse_duration(const std::string_view &s); - -// generate_secure_random generates a cryptographically secure pseudo -// random data of |data|. -int generate_secure_random(std::span data); - -// generate_secret generates secret and writes it to |secret|. -// Currently, |secret| must be 32 bytes long. -int generate_secret(std::span secret); - -// normalize_path removes ".." by consuming a previous path component. -// It also removes ".". It assumes that |path| starts with "/". If -// it cannot consume a previous path component, it just removes "..". -std::string normalize_path(const std::string_view &path); - -template Pred> -consteval auto pred_tbl_gen256(Pred pred) { - std::array tbl; - - for (size_t i = 0; i < tbl.size(); ++i) { - tbl[i] = pred(i); - } - - return tbl; -} - -consteval auto digit_pred(size_t i) noexcept { return '0' <= i && i <= '9'; } - -inline constexpr auto is_digit_tbl = pred_tbl_gen256(digit_pred); - -constexpr bool is_digit(char c) noexcept { - return is_digit_tbl[static_cast(c)]; -} - -consteval auto hex_digit_pred(size_t i) noexcept { - return digit_pred(i) || ('A' <= i && i <= 'F') || ('a' <= i && i <= 'f'); -} - -inline constexpr auto is_hex_digit_tbl = pred_tbl_gen256(hex_digit_pred); - -constexpr bool is_hex_digit(char c) noexcept { - return is_hex_digit_tbl[static_cast(c)]; -} - -// is_hex_string returns true if the length of |s| is even, and |s| -// does not contain a character other than [0-9A-Fa-f]. It returns -// false otherwise. -template -requires(!std::is_array_v>) -constexpr bool is_hex_string(R &&r) { - return !(std::ranges::size(r) & 1) && std::ranges::all_of(r, is_hex_digit); -} - -inline constexpr auto hex_to_uint_tbl = []() { - std::array tbl; - - std::ranges::fill(tbl, 256); - - for (char i = '0'; i <= '9'; ++i) { - tbl[static_cast(i)] = static_cast(i - '0'); - } - - for (char i = 'A'; i <= 'F'; ++i) { - tbl[static_cast(i)] = static_cast(i - 'A' + 10); - } - - for (char i = 'a'; i <= 'f'; ++i) { - tbl[static_cast(i)] = static_cast(i - 'a' + 10); - } - - return tbl; -}(); - -// hex_to_uint returns integer corresponding to hex notation |c|. If -// is_hex_digit(c) is false, it returns 256. -constexpr uint32_t hex_to_uint(char c) noexcept { - return hex_to_uint_tbl[static_cast(c)]; -} - -std::string percent_decode(const std::string_view &s); - -int make_socket_nonblocking(int fd); - -int create_nonblock_socket(int domain, int type, int protocol); - -std::optional> -read_token(const std::string_view &filename); -int write_token(const std::string_view &filename, - std::span token); - -std::optional> -read_transport_params(const std::string_view &filename); -int write_transport_params(const std::string_view &filename, - std::span data); - -const char *crypto_default_ciphers(); - -const char *crypto_default_groups(); - -// split_str parses delimited strings in |s| and returns substrings -// delimited by |delim|. The any white spaces around substring are -// treated as a part of substring. -std::vector split_str(const std::string_view &s, - char delim = ','); - -// parse_version parses |s| to get 4 byte QUIC version. |s| must be a -// hex string and must start with "0x" (e.g., 0x00000001). -std::optional parse_version(const std::string_view &s); - -// read_file reads a file denoted by |path| and returns its content. -std::optional> read_file(const std::string_view &path); - -size_t clamp_buffer_size(ngtcp2_conn *conn, size_t buflen, size_t gso_burst); - -bool recv_pkt_time_threshold_exceeded(bool time_sensitive, ngtcp2_tstamp start, - size_t pktcnt); - -enum HPKEPrivateKeyType : uint16_t { - HPKE_DHKEM_X25519_HKDF_SHA256 = 0x0020, -}; - -// HPKEPrivateKey contains HPKE private key. -struct HPKEPrivateKey { - // type is HPKE private key type. - HPKEPrivateKeyType type; - // bytes contains raw private key. - std::vector bytes; -}; - -// ECHServerConfig is a server-side ECH configuration. -struct ECHServerConfig { - // private_key contains a private key used for decrypting encrypted - // Client Hello. - HPKEPrivateKey private_key; - // ech_config contains a serialized ECHConfig. - std::vector ech_config; -}; - -// read_ech_server_config reads server-side ECH configuration from a -// file denoted by |path|. -std::optional -read_ech_server_config(const std::string_view &path); - -std::span generate_siphash_key(); - -// get_string returns a URL component specified by |f| of |uri|. This -// function assumes that u.field_set & (1 << f) is nonzero. -constexpr std::string_view get_string(const std::string_view &uri, - const urlparse_url &u, - urlparse_url_fields f) { - assert(u.field_set & (1 << f)); - - auto p = &u.field_data[f]; - return {uri.data() + p->off, p->len}; -} - } // namespace util -std::ostream &operator<<(std::ostream &os, const ngtcp2_cid &cid); - } // namespace ngtcp2 -namespace std { -template <> struct hash { - hash() { - std::ranges::copy(ngtcp2::util::generate_siphash_key(), - std::ranges::begin(key)); - } - - std::size_t operator()(const ngtcp2_cid &cid) const noexcept { - return static_cast(siphash24(key, {cid.data, cid.datalen})); - } - - std::array key; -}; -} // namespace std - -inline bool operator==(const ngtcp2_cid &lhs, const ngtcp2_cid &rhs) { - return ngtcp2_cid_eq(&lhs, &rhs); -} - #endif // !defined(UTIL_H) diff --git a/src/ngtcp2/util_openssl.cc b/src/ngtcp2/util_openssl.cc deleted file mode 100644 index 711ef16..0000000 --- a/src/ngtcp2/util_openssl.cc +++ /dev/null @@ -1,200 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2020 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#include "util.h" - -#include -#include -#include -#include - -#include - -#include -#include -#include -#include - -#include "template.h" - -namespace ngtcp2 { - -namespace util { - -int generate_secure_random(std::span data) { -#ifdef WITH_EXAMPLE_BORINGSSL - using size_type = size_t; -#else // !defined(WITH_EXAMPLE_BORINGSSL) - using size_type = int; -#endif // !defined(WITH_EXAMPLE_BORINGSSL) - - if (RAND_bytes(data.data(), static_cast(data.size())) != 1) { - return -1; - } - - return 0; -} - -int generate_secret(std::span secret) { - std::array rand; - - if (generate_secure_random(rand) != 0) { - return -1; - } - - auto ctx = EVP_MD_CTX_new(); - if (ctx == nullptr) { - return -1; - } - - auto ctx_deleter = defer(EVP_MD_CTX_free, ctx); - - auto mdlen = static_cast(secret.size()); - if (!EVP_DigestInit_ex(ctx, EVP_sha256(), nullptr) || - !EVP_DigestUpdate(ctx, rand.data(), rand.size()) || - !EVP_DigestFinal_ex(ctx, secret.data(), &mdlen)) { - return -1; - } - - return 0; -} - -namespace { -void openssl_free_wrap(void *ptr) { OPENSSL_free(ptr); } -} // namespace - -std::optional -read_hpke_private_key_pem(const std::string_view &filename) { - auto f = BIO_new_file(filename.data(), "r"); - if (f == nullptr) { - std::cerr << "Could not open file " << filename << std::endl; - return {}; - } - - auto f_d = defer(BIO_free, f); - - EVP_PKEY *pkey; - - if (PEM_read_bio_PrivateKey(f, &pkey, nullptr, nullptr) == nullptr) { - return {}; - } - - auto pkey_d = defer(EVP_PKEY_free, pkey); - - HPKEPrivateKey res; - - switch (EVP_PKEY_id(pkey)) { - case EVP_PKEY_X25519: { - res.type = HPKE_DHKEM_X25519_HKDF_SHA256; - - size_t len; - - EVP_PKEY_get_raw_private_key(pkey, nullptr, &len); - - res.bytes.resize(len); - - EVP_PKEY_get_raw_private_key(pkey, &res.bytes[0], &len); - - break; - } - default: - return {}; - } - - return res; -} - -std::optional> read_pem(const std::string_view &filename, - const std::string_view &name, - const std::string_view &type) { - auto f = BIO_new_file(filename.data(), "r"); - if (f == nullptr) { - std::cerr << "Could not open " << name << " file " << filename << std::endl; - return {}; - } - - auto f_d = defer(BIO_free, f); - - for (;;) { - char *pem_type, *header; - unsigned char *data; - long datalen; - - if (PEM_read_bio(f, &pem_type, &header, &data, &datalen) != 1) { - std::cerr << "Could not read " << name << " file " << filename - << std::endl; - return {}; - } - - auto pem_type_d = defer(openssl_free_wrap, pem_type); - auto pem_header = defer(openssl_free_wrap, header); - auto data_d = defer(openssl_free_wrap, data); - - if (type != pem_type) { - continue; - } - - return {{data, data + datalen}}; - } -} - -int write_pem(const std::string_view &filename, const std::string_view &name, - const std::string_view &type, std::span data) { - auto f = BIO_new_file(filename.data(), "w"); - if (f == nullptr) { - std::cerr << "Could not write " << name << " in " << filename << std::endl; - return -1; - } - - PEM_write_bio(f, type.data(), "", data.data(), - static_cast(data.size())); - BIO_free(f); - - return 0; -} - -const char *crypto_default_ciphers() { -#if defined(WITH_EXAMPLE_QUICTLS) || defined(WITH_EXAMPLE_OSSL) - return "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_" - "SHA256" -# ifndef LIBRESSL_VERSION_NUMBER - ":TLS_AES_128_CCM_SHA256" -# endif // !defined(LIBRESSL_VERSION_NUMBER) - ; -#else // !(defined(WITH_EXAMPLE_QUICTLS) && defined(WITH_EXAMPLE_OSSL)) - return ""; -#endif // !(defined(WITH_EXAMPLE_QUICTLS) && defined(WITH_EXAMPLE_OSSL)) -} - -const char *crypto_default_groups() { - return "X25519:P-256:P-384:P-521" -#if defined(WITH_EXAMPLE_BORINGSSL) || defined(WITH_EXAMPLE_OSSL) - ":X25519MLKEM768" -#endif // defined(WITH_EXAMPLE_BORINGSSL) || defined(WITH_EXAMPLE_OSSL) - ; -} - -} // namespace util - -} // namespace ngtcp2 diff --git a/src/ngtcp2/util_test.cc b/src/ngtcp2/util_test.cc deleted file mode 100644 index 85e68d7..0000000 --- a/src/ngtcp2/util_test.cc +++ /dev/null @@ -1,442 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2018 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#include "util_test.h" - -#include -#include - -#include "util.h" - -using namespace std::literals; - -namespace ngtcp2 { - -namespace { -const MunitTest tests[]{ - munit_void_test(test_util_format_durationf), - munit_void_test(test_util_format_uint), - munit_void_test(test_util_format_uint_iec), - munit_void_test(test_util_format_duration), - munit_void_test(test_util_parse_uint), - munit_void_test(test_util_parse_uint_iec), - munit_void_test(test_util_parse_duration), - munit_void_test(test_util_normalize_path), - munit_void_test(test_util_hexdump), - munit_void_test(test_util_format_hex), - munit_void_test(test_util_decode_hex), - munit_void_test(test_util_is_hex_string), - munit_test_end(), -}; -} // namespace - -const MunitSuite util_suite{ - .prefix = "/util", - .tests = tests, -}; - -namespace util { -std::optional -read_hpke_private_key_pem(const std::string_view &filename) { - return {}; -} -} // namespace util - -namespace util { -std::optional> read_pem(const std::string_view &filename, - const std::string_view &name, - const std::string_view &type) { - return {}; -} -} // namespace util - -namespace util { -int write_pem(const std::string_view &filename, const std::string_view &name, - const std::string_view &type, std::span data) { - return -1; -} -} // namespace util - -namespace util { -int generate_secure_random(std::span data) { return -1; } -} // namespace util - -void test_util_format_durationf() { - assert_stdstring_equal("0ns", util::format_durationf(0)); - assert_stdstring_equal("999ns", util::format_durationf(999)); - assert_stdstring_equal("1.00us", util::format_durationf(1000)); - assert_stdstring_equal("1.00us", util::format_durationf(1004)); - assert_stdstring_equal("1.00us", util::format_durationf(1005)); - assert_stdstring_equal("1.02us", util::format_durationf(1015)); - assert_stdstring_equal("2.00us", util::format_durationf(1999)); - assert_stdstring_equal("1.00ms", util::format_durationf(999999)); - assert_stdstring_equal("3.50ms", util::format_durationf(3500111)); - assert_stdstring_equal("9999.99s", util::format_durationf(9999990000000llu)); -} - -void test_util_format_uint() { - assert_stdstring_equal("0"s, util::format_uint(0u)); - assert_stdstring_equal("18446744073709551615"s, - util::format_uint(18446744073709551615ull)); -} - -void test_util_format_uint_iec() { - assert_stdstring_equal("0"s, util::format_uint_iec(0u)); - assert_stdstring_equal("1023"s, util::format_uint_iec((1u << 10) - 1)); - assert_stdstring_equal("1K"s, util::format_uint_iec(1u << 10)); - assert_stdstring_equal("1M"s, util::format_uint_iec(1u << 20)); - assert_stdstring_equal("1G"s, util::format_uint_iec(1u << 30)); - assert_stdstring_equal( - "18446744073709551615"s, - util::format_uint_iec(std::numeric_limits::max())); - assert_stdstring_equal("1025K"s, - util::format_uint_iec((1u << 20) + (1u << 10))); -} - -void test_util_format_duration() { - assert_stdstring_equal("0ns", util::format_duration(0)); - assert_stdstring_equal("999ns", util::format_duration(999)); - assert_stdstring_equal("1us", util::format_duration(1000)); - assert_stdstring_equal("1ms", util::format_duration(1000000)); - assert_stdstring_equal("1s", util::format_duration(1000000000)); - assert_stdstring_equal("1m", util::format_duration(60000000000ull)); - assert_stdstring_equal("1h", util::format_duration(3600000000000ull)); - assert_stdstring_equal( - "18446744073709551615ns", - util::format_duration(std::numeric_limits::max())); - assert_stdstring_equal("61s", util::format_duration(61000000000ull)); -} - -void test_util_parse_uint() { - { - auto res = util::parse_uint("0"); - assert_true(res.has_value()); - assert_uint64(0, ==, *res); - } - { - auto res = util::parse_uint("1"); - assert_true(res.has_value()); - assert_uint64(1, ==, *res); - } - { - auto res = util::parse_uint("18446744073709551615"); - assert_true(res.has_value()); - assert_uint64(18446744073709551615ull, ==, *res); - } - { - auto res = util::parse_uint("18446744073709551616"); - assert_false(res.has_value()); - } - { - auto res = util::parse_uint("a"); - assert_false(res.has_value()); - } - { - auto res = util::parse_uint("1a"); - assert_false(res.has_value()); - } -} - -void test_util_parse_uint_iec() { - { - auto res = util::parse_uint_iec("0"); - assert_true(res.has_value()); - assert_uint64(0, ==, *res); - } - { - auto res = util::parse_uint_iec("1023"); - assert_true(res.has_value()); - assert_uint64(1023, ==, *res); - } - { - auto res = util::parse_uint_iec("1K"); - assert_true(res.has_value()); - assert_uint64(1 << 10, ==, *res); - } - { - auto res = util::parse_uint_iec("1M"); - assert_true(res.has_value()); - assert_uint64(1 << 20, ==, *res); - } - { - auto res = util::parse_uint_iec("1G"); - assert_true(res.has_value()); - assert_uint64(1 << 30, ==, *res); - } - { - auto res = util::parse_uint_iec("11G"); - assert_true(res.has_value()); - assert_uint64((1ull << 30) * 11, ==, *res); - } - { - auto res = util::parse_uint_iec("18446744073709551616"); - assert_false(res.has_value()); - } - { - auto res = util::parse_uint_iec("1x"); - assert_false(res.has_value()); - } - { - auto res = util::parse_uint_iec("1Gx"); - assert_false(res.has_value()); - } -} - -void test_util_parse_duration() { - { - auto res = util::parse_duration("0"); - assert_true(res.has_value()); - assert_uint64(0, ==, *res); - } - { - auto res = util::parse_duration("1"); - assert_true(res.has_value()); - assert_uint64(NGTCP2_SECONDS, ==, *res); - } - { - auto res = util::parse_duration("0ns"); - assert_true(res.has_value()); - assert_uint64(0, ==, *res); - } - { - auto res = util::parse_duration("1ns"); - assert_true(res.has_value()); - assert_uint64(1, ==, *res); - } - { - auto res = util::parse_duration("1us"); - assert_true(res.has_value()); - assert_uint64(NGTCP2_MICROSECONDS, ==, *res); - } - { - auto res = util::parse_duration("1ms"); - assert_true(res.has_value()); - assert_uint64(NGTCP2_MILLISECONDS, ==, *res); - } - { - auto res = util::parse_duration("1s"); - assert_true(res.has_value()); - assert_uint64(NGTCP2_SECONDS, ==, *res); - } - { - auto res = util::parse_duration("1m"); - assert_true(res.has_value()); - assert_uint64(60 * NGTCP2_SECONDS, ==, *res); - } - { - auto res = util::parse_duration("1h"); - assert_true(res.has_value()); - assert_uint64(3600 * NGTCP2_SECONDS, ==, *res); - } - { - auto res = util::parse_duration("2h"); - assert_true(res.has_value()); - assert_uint64(2 * 3600 * NGTCP2_SECONDS, ==, *res); - } - { - auto res = util::parse_duration("18446744073709551616"); - assert_false(res.has_value()); - } - { - auto res = util::parse_duration("1x"); - assert_false(res.has_value()); - } - { - auto res = util::parse_duration("1mx"); - assert_false(res.has_value()); - } - { - auto res = util::parse_duration("1mxy"); - assert_false(res.has_value()); - } -} - -void test_util_normalize_path() { - assert_stdstring_equal("/", util::normalize_path("/")); - assert_stdstring_equal("/", util::normalize_path("//")); - assert_stdstring_equal("/foo", util::normalize_path("/foo")); - assert_stdstring_equal("/foo/bar/", util::normalize_path("/foo/bar/")); - assert_stdstring_equal("/foo/bar/", util::normalize_path("/foo/abc/../bar/")); - assert_stdstring_equal("/foo/bar/", - util::normalize_path("/../foo/abc/../bar/")); - assert_stdstring_equal("/foo/bar/", - util::normalize_path("/./foo/././abc///.././bar/./")); - assert_stdstring_equal("/foo/", util::normalize_path("/foo/.")); - assert_stdstring_equal("/foo/bar", util::normalize_path("/foo/./bar")); - assert_stdstring_equal("/bar", util::normalize_path("/foo/./../bar")); - assert_stdstring_equal("/bar", util::normalize_path("/../../bar")); -} - -void test_util_hexdump() { - char buf[4096]; - - struct hexdump_testdata { - const char *title; - std::string_view data; - std::string_view dump; - }; - - auto tests = std::to_array({ - { - .title = "Empty data", - .data = ""sv, - .dump = ""sv, - }, - { - .title = "1 byte", - .data = "0"sv, - .dump = "00000000 30 " - "|0|\n" - "00000001\n"sv, - }, - { - .title = "8 bytes", - .data = "01234567"sv, - .dump = "00000000 30 31 32 33 34 35 36 37 " - "|01234567|\n" - "00000008\n"sv, - }, - { - .title = "9 bytes", - .data = "012345678"sv, - .dump = "00000000 30 31 32 33 34 35 36 37 38 " - "|012345678|\n" - "00000009\n"sv, - }, - { - .title = "15 bytes", - .data = "0123456789abcde"sv, - .dump = "00000000 30 31 32 33 34 35 36 37 38 39 61 62 63 64 65 " - "|0123456789abcde|\n" - "0000000f\n"sv, - }, - { - .title = "16 bytes", - .data = "0123456789abcdef"sv, - .dump = "00000000 30 31 32 33 34 35 36 37 38 39 61 62 63 64 65 66 " - "|0123456789abcdef|\n" - "00000010\n"sv, - }, - { - .title = "17 bytes", - .data = "0123456789abcdefg"sv, - .dump = "00000000 30 31 32 33 34 35 36 37 38 39 61 62 63 64 65 66 " - "|0123456789abcdef|\n" - "00000010 67 " - "|g|\n" - "00000011\n"sv, - }, - { - .title = "Non-printables", - .data = "\0\a\b\t\n\v\f\r\x7f"sv, - .dump = "00000000 00 07 08 09 0a 0b 0c 0d 7f " - "|.........|\n" - "00000009\n"sv, - }, - { - .title = "Multiple lines", - .data = "alpha bravo charlie delta echo foxtrot golf"sv, - .dump = "00000000 61 6c 70 68 61 20 62 72 61 76 6f 20 63 68 61 72 " - "|alpha bravo char|\n" - "00000010 6c 69 65 20 64 65 6c 74 61 20 65 63 68 6f 20 66 " - "|lie delta echo f|\n" - "00000020 6f 78 74 72 6f 74 20 67 6f 6c 66 " - "|oxtrot golf|\n" - "0000002b\n"sv, - }, - { - .title = "Repeated lines", - .data = "000000000000000100000000000000010000000000000001000000000000" - "00020000" - "0000000000020000000000000003"sv, - .dump = "00000000 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 31 " - "|0000000000000001|\n" - "*\n" - "00000030 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 32 " - "|0000000000000002|\n" - "*\n" - "00000050 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 33 " - "|0000000000000003|\n" - "00000060\n"sv, - }, - { - .title = "Ends with the repeated line", - .data = "" - "000000000000000100000000000000010000000000000001000000000000" - "00020000000000000002"sv, - .dump = "00000000 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 31 " - "|0000000000000001|\n" - "*\n" - "00000030 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 32 " - "|0000000000000002|\n" - "*\n" - "00000050\n"sv, - }, - }); - - for (auto &t : tests) { - munit_log(MUNIT_LOG_INFO, t.title); - - auto f = tmpfile(); - auto rv = util::hexdump(f, t.data.data(), t.data.size()); - - assert_int(0, ==, rv); - - fseek(f, 0, SEEK_SET); - auto nread = fread(buf, 1, sizeof(buf), f); - buf[nread] = '\0'; - - assert_stdsv_equal(t.dump, buf); - - fclose(f); - } -} - -void test_util_format_hex() { - auto a = std::to_array({0xde, 0xad, 0xbe, 0xef}); - - assert_stdstring_equal("deadbeef"s, util::format_hex(a)); - assert_stdstring_equal("deadbeef"s, util::format_hex(0xdeadbeef)); - assert_stdstring_equal("beef"s, util::format_hex(a.data() + 2, 2)); -} - -void test_util_decode_hex() { - assert_stdstring_equal("\xde\xad\xbe\xef"s, util::decode_hex("deadbeef"sv)); - assert_stdstring_equal(""s, util::decode_hex(""sv)); -} - -void test_util_is_hex_string() { - assert_true(util::is_hex_string(""sv)); - assert_true(util::is_hex_string("0123456789abcdef"sv)); - assert_true(util::is_hex_string("0123456789ABCDEF"sv)); - assert_false(util::is_hex_string("0123456789ABCDEF9"sv)); - assert_false(util::is_hex_string("aaa"sv)); - assert_true(util::is_hex_string("aa"sv)); - assert_false(util::is_hex_string("a"sv)); - assert_false(util::is_hex_string("zzz"sv)); - assert_false(util::is_hex_string("zz"sv)); - assert_false(util::is_hex_string("z"sv)); -} - -} // namespace ngtcp2 diff --git a/src/ngtcp2/util_test.h b/src/ngtcp2/util_test.h deleted file mode 100644 index f5a597d..0000000 --- a/src/ngtcp2/util_test.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * ngtcp2 - * - * Copyright (c) 2018 ngtcp2 contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#ifndef UTIL_TEST_H -#define UTIL_TEST_H - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif // defined(HAVE_CONFIG_H) - -#define MUNIT_ENABLE_ASSERT_ALIASES - -#include "munitxx.h" - -namespace ngtcp2 { - -extern const MunitSuite util_suite; - -munit_void_test_decl(test_util_format_durationf) -munit_void_test_decl(test_util_format_uint) -munit_void_test_decl(test_util_format_uint_iec) -munit_void_test_decl(test_util_format_duration) -munit_void_test_decl(test_util_parse_uint) -munit_void_test_decl(test_util_parse_uint_iec) -munit_void_test_decl(test_util_parse_duration) -munit_void_test_decl(test_util_normalize_path) -munit_void_test_decl(test_util_hexdump) -munit_void_test_decl(test_util_format_hex) -munit_void_test_decl(test_util_decode_hex) -munit_void_test_decl(test_util_is_hex_string) - -} // namespace ngtcp2 - -#endif // !defined(UTIL_TEST_H) diff --git a/src/server_impl.cpp b/src/server_impl.cpp index ccbfe5d..1e1172e 100644 --- a/src/server_impl.cpp +++ b/src/server_impl.cpp @@ -8,6 +8,7 @@ #include "anyhttp/detect_ssl.hpp" #include "anyhttp/formatter.hpp" // IWYU pragma: keep #include "anyhttp/nghttp2_session.hpp" +#include "anyhttp/tls.hpp" #include #include @@ -73,7 +74,7 @@ Server::Impl::Impl(boost::asio::any_io_executor executor, Config config) */ void Server::Impl::start() { - co_spawn(m_executor, listen_loop(), [self = shared_from_this()](const std::exception_ptr& ex) + co_spawn(m_executor, tcp_listen_loop(), [self = shared_from_this()](const std::exception_ptr& ex) { if (ex) logw("TCP accept loop: {}", what(ex)); @@ -83,7 +84,8 @@ void Server::Impl::start() if (m_udp_socket) { - co_spawn(m_executor, udp_receive_loop(), + // On the socket's strand -- see listen_udp(). + co_spawn(m_udp_socket->get_executor(), udp_receive_loop(), [self = shared_from_this()](const std::exception_ptr& ex) { if (ex) @@ -106,19 +108,28 @@ void Server::Impl::destroy() // // Destroy all active sessions (TCP and QUIC) so their timers and async operations are // cancelled, allowing the io_context to drain. QUIC sessions send a final CONNECTION_CLOSE - // through the (still shared) UDP socket as part of destroy(), so this has to happen before - // that socket is closed below -- otherwise the peer only finds out via idle timeout. + // as part of destroy() -- through their own dup()ed fd, so closing the shared UDP socket + // below doesn't race with it. Setting m_destroyed under the same lock is what keeps + // process_quic_batch(), running on some session strand, from registering a new session + // after this loop has run: it re-checks the flag under the lock before inserting. // { auto lock = std::lock_guard(m_sessionMutex); + m_destroyed = true; for (auto& session : m_sessions) session->destroy(); } + // + // The socket lives on its own strand (see listen_udp()) and udp_receive_loop() keeps + // re-arming async_wait() on it there -- asio sockets are not thread-safe, so the close + // has to go through the same strand instead of racing that from here. + // if (m_udp_socket) - m_udp_socket->close(); // breaks udp_receive_loop() - - m_stopped = true; + { + asio::dispatch(m_udp_socket->get_executor(), [self = shared_from_this()] + { self->m_udp_socket->close(); }); // breaks udp_receive_loop() + } } // ------------------------------------------------------------------------------------------------- @@ -126,7 +137,7 @@ void Server::Impl::destroy() Server::Impl::~Impl() { logi("Server: dtor"); - assert(m_stopped); + assert(m_destroyed); } // ================================================================================================= @@ -151,7 +162,7 @@ void Server::Impl::listen_tcp() acceptor.listen(); endpoint = acceptor.local_endpoint(); - logi("Server: listening on {}", endpoint); + logi("Server: TCP listening on {}", endpoint); } // ------------------------------------------------------------------------------------------------- @@ -168,7 +179,12 @@ void Server::Impl::listen_udp() auto tcp_ep = m_acceptor->local_endpoint(); const bool is_v6 = tcp_ep.protocol() == ip::tcp::v6(); - m_udp_socket.emplace(m_executor); + // + // The socket gets its own strand: udp_receive_loop() runs on it (see start()), and destroy() + // dispatches the shutdown close() through it, so the two never touch the socket concurrently. + // + m_udp_socket.emplace(config().use_strand ? asio::any_io_executor{asio::make_strand(m_executor)} + : m_executor); m_udp_socket->open(is_v6 ? ip::udp::v6() : ip::udp::v4()); if (is_v6) @@ -309,6 +325,9 @@ awaitable Server::Impl::handleConnection(ip::tcp::socket socket) alpn = std::string_view(reinterpret_cast(data), len); } + logi("[{}] TLS handshake completed: {}", prefix, + tls_handshake_info(ssl_stream->native_handle())); + if (alpn == "h2") session = std::make_shared>> // @@ -378,7 +397,7 @@ awaitable Server::Impl::handleConnection(ip::tcp::socket socket) * https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3149r5.html#listener-loop-in-an-http-server * */ -awaitable Server::Impl::listen_loop() +awaitable Server::Impl::tcp_listen_loop() { assert(m_acceptor); auto& acceptor = *m_acceptor; @@ -399,9 +418,9 @@ awaitable Server::Impl::listen_loop() if (ec) { if (ec == boost::system::errc::operation_canceled) - logi("accept: {}", ec.message()); + logi("TCP accept: {}", ec.message()); else - logw("accept: {}", ec.message()); + logw("TCP accept: {}", ec.message()); break; } diff --git a/src/server_impl_udp.cpp b/src/server_impl_udp.cpp index 5bb6573..5df5a26 100644 --- a/src/server_impl_udp.cpp +++ b/src/server_impl_udp.cpp @@ -6,6 +6,15 @@ // (server::Response) into the same `RequestHandler` used by the HTTP/1.1 and HTTP/2 // backends. // +// Threading: with Config::use_strand, each Http3Session lives on its own strand -- the unit of +// serialization is the QUIC *connection* (one ngtcp2_conn/nghttp3_conn pair), not the CID: many +// CIDs alias one connection. udp_receive_loop() is a single coroutine that only demultiplexes: +// it copies each datagram, groups them by session and posts one batch per session to that +// session's strand (process_quic_batch()), where all ngtcp2/nghttp3 work, the timers and the +// request handlers run. The CID demux table is the only cross-connection state and is guarded +// by Server::Impl::m_quicMutex. Sends go straight out via a per-session dup() of the UDP fd -- +// sendto()/sendmsg() are atomic per datagram, so they need no serialization. +// // Not yet implemented: retry tokens, version negotiation, stateless reset, connection // migration, ECN, client-side (async_submit is a no-op). // @@ -16,6 +25,7 @@ #include "anyhttp/request_handlers.hpp" // IWYU pragma: keep #include "anyhttp/server_impl.hpp" #include "anyhttp/session_impl.hpp" +#include "anyhttp/tls.hpp" #include #include @@ -24,6 +34,10 @@ #include #include +#include +#include +#include + #include #include @@ -116,15 +130,6 @@ struct TlsServerContext SSL_OP_NO_ANTI_REPLAY); SSL_CTX_set_mode(ctx, SSL_MODE_RELEASE_BUFFERS); - // - // Prefer AES-128 over AES-256 GCM, like ngtcp2's example server does. Combined with - // SSL_OP_CIPHER_SERVER_PREFERENCE above, this is what actually picks the bulk cipher. - // - if (SSL_CTX_set_ciphersuites(ctx, "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:" - "TLS_CHACHA20_POLY1305_SHA256") != 1) - throw std::runtime_error(std::string{"SSL_CTX_set_ciphersuites: "} + - ERR_error_string(ERR_get_error(), nullptr)); - SSL_CTX_set_alpn_select_cb(ctx, &TlsServerContext::alpn_select_cb, nullptr); if (SSL_CTX_use_PrivateKey_file(ctx, "pki/out/server-key.pem", SSL_FILETYPE_PEM) != 1) @@ -466,7 +471,13 @@ class Http3Session : public Session::Impl // // Session::Impl // - asio::any_io_executor get_executor() const noexcept override { return server_.get_executor(); } + // The executor is this connection's strand (when Config::use_strand is set): every touch of + // ngtcp2/nghttp3 state -- datagram batches from the UDP demux, the expiry timer, wake_write() + // flushes, and the request-handler coroutines spawned in h3_cb_end_headers() -- runs through + // it, giving one QUIC connection the same single-threaded world a TCP connection gets from + // the strand its socket lives on. + // + asio::any_io_executor get_executor() const noexcept override { return executor_; } void async_submit(SubmitHandler&& handler, boost::urls::url, const Fields&) override; awaitable do_session(Buffer&& data) override; void destroy() noexcept override; @@ -617,11 +628,14 @@ class Http3Session : public Session::Impl void arm_timer_from_ngtcp2(); void signal_done(); void schedule_close_timer(); + void do_destroy() noexcept; // the body of destroy(), always run on executor_ private: Server::Impl& server_; Endpoint ep_; + bool owns_fd_ = false; // ep_.fd was dup()ed in the ctor, close it in the dtor ngtcp2::Address remote_; + asio::any_io_executor executor_; ngtcp2_cid scid_{}; ngtcp2_conn* conn_ = nullptr; @@ -1304,14 +1318,33 @@ void Http3Stream::maybe_close() // ================================================================================================= Http3Session::Http3Session(Server::Impl& server, Endpoint ep, ngtcp2::Address remote) - : server_(server), ep_(ep), remote_(remote), timer_(server.get_executor()), - done_signal_(server.get_executor()) + : server_(server), ep_(ep), remote_(remote), + executor_(server.config().use_strand + ? asio::any_io_executor{asio::make_strand(server.get_executor())} + : server.get_executor()), + timer_(executor_), done_signal_(executor_) { ngtcp2_ccerr_default(&last_error_); log_prefix_ = std::format("h3:{}", ngtcp2::util::straddr(&remote_.su.sa, remote_.len)); + + // + // Own a dup() of the shared UDP fd rather than borrowing the server's. Sends happen from this + // session's strand, concurrently with everything else -- sendto()/sendmsg() on a shared + // datagram fd is fine, each call is atomic -- but at shutdown the server closes its socket + // right after posting destroy() to every session, and the final CONNECTION_CLOSE would + // otherwise race that close (and, worse, a recycled fd number). + // + if (int fd = ::dup(ep_.fd); fd >= 0) + { + ep_.fd = fd; + owns_fd_ = true; + } + else + loge("[{}] dup: {}", log_prefix_, strerror(errno)); + // done_signal_ is armed at "never" until signal_done() moves it to the past. done_signal_.expires_at(asio::steady_timer::time_point::max()); - mlogi("session created"); + mlogd("session created"); } Http3Session::~Http3Session() @@ -1332,6 +1365,8 @@ Http3Session::~Http3Session() } ngtcp2_crypto_ossl_ctx_del(ossl_ctx_); } + if (owns_fd_) + ::close(ep_.fd); mlogi("session destroyed"); } @@ -1355,6 +1390,31 @@ awaitable Http3Session::do_session(Buffer&&) void Http3Session::destroy() noexcept { + // + // Called from wherever the Server is being torn down -- under multithreading that is some + // other thread's strand (e.g. the signal handler in server_main), while this session's own + // strand may be mid-flush. Everything below touches ngtcp2 state and the timers, so hop onto + // this session's executor first; with use_strand off and the caller already inside the + // io_context, dispatch() degenerates to an inline call. + // + asio::dispatch(executor_, [self = shared_from_this()] { + static_cast(*self).do_destroy(); + }); +} + +void Http3Session::do_destroy() noexcept +{ + // + // Tear the streams down here, on the session's executor, rather than leaving it to the + // destructor. The destructor runs wherever the last shared_ptr happens to drop -- at server + // shutdown that can be Server::Impl::~Impl() on a foreign thread -- and destroying streams + // detaches readers/writers and fires pending handlers, state that request-handler coroutines + // still running on this session's strand look at. Done here, that teardown is serialized + // with them; a handler that resumes afterwards finds its Reader/Writer detached and fails + // cleanly, exactly as in the single-threaded case. + // + streams_.clear(); + if (std::exchange(closed_, true)) { timer_.cancel(); @@ -1881,7 +1941,7 @@ void Http3Session::schedule_close_timer() auto session = std::static_pointer_cast(self.lock()); if (!session) return; - logi("[{}] closing/draining period over", session->log_prefix_); + logd("[{}] closing/draining period over", session->log_prefix_); session->server_.erase_quic_session(session.get()); session->signal_done(); }); @@ -1906,7 +1966,8 @@ void Http3Session::resend_conn_close() int Http3Session::cb_handshake_completed(ngtcp2_conn*, void* user) { auto self = static_cast(user); - logi("[{}] TLS handshake complete", self->log_prefix_); + logi("[{}] TLS handshake completed: {}", self->log_prefix_, + tls_handshake_info(ngtcp2_crypto_ossl_ctx_get_ssl(self->ossl_ctx_))); if (self->setup_http3() != 0) return NGTCP2_ERR_CALLBACK_FAILURE; return 0; @@ -2346,19 +2407,42 @@ std::optional to_ngtcp2_address(const sockaddr_storage& src, so } } // namespace +// +// What one pass of udp_on_read() hands a session: every datagram of the receive batch that was +// addressed to it, copied out of the receive buffer because the session consumes them on its own +// strand, after udp_on_read() has moved on. `is_new` marks a batch whose first datagram is the +// client Initial that created the session -- process_quic_batch() runs init() with it. +// +struct QuicBatch +{ + struct Datagram + { + ngtcp2_pkt_info pi; + ngtcp2::Address remote; + std::vector data; + }; + + bool is_new = false; + ngtcp2_pkt_hd hd{}; // decoded Initial packet header, only valid when is_new + boost::container::small_vector datagrams; +}; + void Server::Impl::associate_quic_cid(const ngtcp2_cid& cid, Http3Session* h) { + auto lock = std::lock_guard(m_quicMutex); m_quic_handlers.emplace(cid_key(cid), std::static_pointer_cast(h->shared_from_this())); } void Server::Impl::dissociate_quic_cid(const ngtcp2_cid& cid) { + auto lock = std::lock_guard(m_quicMutex); m_quic_handlers.erase(cid_key(cid)); } void Server::Impl::erase_quic_session(Http3Session* h) { + auto lock = std::lock_guard(m_quicMutex); std::erase_if(m_quic_handlers, [h](const auto& kv) { return kv.second.get() == h; }); } @@ -2381,17 +2465,12 @@ int Server::Impl::udp_on_read(Endpoint& ep) msg.msg_control = msg_ctrl; // - // Sessions that received something in this batch. They are written once, below, after every - // datagram the socket had queued has been handed to ngtcp2 -- so one aggregate pass can pack - // a whole response into a single GSO sendmsg() instead of dribbling it out per datagram. + // Datagrams collected per session over the whole batch. Each session gets its accumulated + // batch posted to its strand once, below, after every datagram the socket had queued has been + // demultiplexed -- so one aggregate pass on the strand can pack a whole response into a + // single GSO sendmsg() instead of dribbling it out per datagram. // - std::vector> pending; - auto mark_pending = [&pending](const std::shared_ptr& session) - { - if (std::ranges::find(pending, session) == pending.end()) - pending.push_back(session); - }; - + boost::container::small_flat_map, QuicBatch, 32> batches; for (size_t pktcnt = 0; pktcnt < 32; ++pktcnt) { if (pktcnt) @@ -2468,108 +2547,169 @@ int Server::Impl::udp_on_read(Endpoint& ep) } auto key = cid_key(vc.dcid, vc.dcidlen); - auto it = m_quic_handlers.find(key); + std::shared_ptr session; + { + auto lock = std::lock_guard(m_quicMutex); + if (auto it = m_quic_handlers.find(key); it != m_quic_handlers.end()) + session = it->second; + } - if (it == m_quic_handlers.end()) + if (!session) { ngtcp2_pkt_hd hd; if (ngtcp2_accept(&hd, data.data(), data.size()) != 0) continue; - auto session = std::make_shared(*this, ep, *remote); - if (session->init(hd.dcid, hd.scid, hd.version, pi, data) != 0) - continue; - - m_quic_handlers.emplace(std::move(key), session); - std::array scids; - auto num_scid = ngtcp2_conn_get_scid(session->conn(), nullptr); - if (num_scid <= scids.size()) - { - ngtcp2_conn_get_scid(session->conn(), scids.data()); - for (size_t i = 0; i < num_scid; ++i) - m_quic_handlers.emplace(cid_key(scids[i]), session); - } + session = std::make_shared(*this, ep, *remote); // - // Register with the shared session set + spawn the do_session() task so - // the session participates in server-wide shutdown, exactly like the - // TCP-based sessions. + // Publish the client-chosen DCID right away, so retransmitted Initials and + // follow-up packets -- in this batch or a later one -- find the session and queue + // up behind init() on its strand instead of spawning a duplicate session. init() + // itself, like everything that touches the connection, runs on the strand in + // process_quic_batch(). // - { - auto lock = std::lock_guard(m_sessionMutex); - m_sessions.emplace(session); - } - co_spawn(get_executor(), session->do_session({}), - [self = shared_from_this(), session](const std::exception_ptr& ex) - { - if (ex) - logw("[{}] {}", session->logPrefix(), what(ex)); - auto lock = std::lock_guard(self->m_sessionMutex); - self->m_sessions.erase(session); - }); - - mark_pending(session); + auto lock = std::lock_guard(m_quicMutex); + m_quic_handlers.emplace(std::move(key), session); + auto& batch = batches[session]; + batch.is_new = true; + batch.hd = hd; } - else - { - auto session = it->second; - - // - // Handle closing / draining periods. During closing we resend the - // buffered CONNECTION_CLOSE so the peer can tear down cleanly. - // During draining (peer sent CONNECTION_CLOSE) we just drop the packet. - // In both cases the session stays in m_quic_handlers until the 3-PTO - // close timer fires and calls erase_quic_session(). - // - if (auto* conn = session->conn()) - { - if (ngtcp2_conn_in_closing_period(conn)) - { - session->resend_conn_close(); - continue; - } - if (ngtcp2_conn_in_draining_period(conn)) - continue; - } - if (session->on_read(pi, data, *remote) == 0) - { - mark_pending(session); - } - else if (session->closed()) - { - // - // Only erase immediately when not in closing/draining period. - // If we are, the 3-PTO close timer in handle_error() will call - // erase_quic_session() once the period expires. - // - auto* conn = session->conn(); - if (!conn || - (!ngtcp2_conn_in_closing_period(conn) && !ngtcp2_conn_in_draining_period(conn))) - { - std::erase_if(m_quic_handlers, - [&](const auto& kv) { return kv.second.get() == session.get(); }); - } - } - } + batches[session].datagrams.push_back({pi, *remote, {data.begin(), data.end()}}); } } // - // One write pass per session, after the whole batch has been read. + // One job per session: its whole share of the receive batch, processed -- and answered with + // a single write pass -- on its own strand. // - for (const auto& session : pending) + for (auto& [session, batch] : batches) { - if (session->flush_write() == 0 || !session->closed()) + asio::post(session->get_executor(), + [self = shared_from_this(), session, batch = std::move(batch)]() mutable + { self->process_quic_batch(session, std::move(batch)); }); + } + + return 0; +} + +// ------------------------------------------------------------------------------------------------- + +// +// Runs on the session's strand: consumes the datagrams udp_on_read() collected for this session, +// serialized against the session's timers, wake_write() flushes and request handlers. This is +// what the demux loop used to do inline back when everything shared one implicit thread. +// +void Server::Impl::process_quic_batch(const std::shared_ptr& session, + QuicBatch&& batch) +{ + size_t next = 0; + bool read_ok = false; + + if (batch.is_new) + { + const auto& first = batch.datagrams[next++]; + if (session->init(batch.hd.dcid, batch.hd.scid, batch.hd.version, first.pi, first.data) != 0) + { + // Matches the old inline behavior: a connection that failed at its Initial is + // forgotten; a retransmitted Initial starts over from scratch. + erase_quic_session(session.get()); + return; + } + read_ok = true; + + std::array scids; + auto num_scid = ngtcp2_conn_get_scid(session->conn(), nullptr); + if (num_scid <= scids.size()) + { + ngtcp2_conn_get_scid(session->conn(), scids.data()); + for (size_t i = 0; i < num_scid; ++i) + associate_quic_cid(scids[i], session.get()); + } + + // + // Register with the shared session set + spawn the do_session() task so the session + // participates in server-wide shutdown, exactly like the TCP-based sessions. Re-check + // m_destroyed under the lock: destroy() may have swept m_sessions between udp_on_read() + // accepting this connection and this job running, and a session registered after that + // sweep would never be destroyed. + // + { + auto lock = std::lock_guard(m_sessionMutex); + if (m_destroyed) + { + erase_quic_session(session.get()); + session->destroy(); + return; + } + m_sessions.emplace(session); + } + co_spawn(session->get_executor(), session->do_session({}), + [self = shared_from_this(), session](const std::exception_ptr& ex) + { + if (ex) + logw("[{}] {}", session->logPrefix(), what(ex)); + auto lock = std::lock_guard(self->m_sessionMutex); + self->m_sessions.erase(session); + }); + } + + for (; next < batch.datagrams.size(); ++next) + { + const auto& d = batch.datagrams[next]; + + // + // Handle closing / draining periods. During closing we resend the + // buffered CONNECTION_CLOSE so the peer can tear down cleanly. + // During draining (peer sent CONNECTION_CLOSE) we just drop the packet. + // In both cases the session stays in m_quic_handlers until the 3-PTO + // close timer fires and calls erase_quic_session(). + // + if (auto* conn = session->conn()) + { + if (ngtcp2_conn_in_closing_period(conn)) + { + session->resend_conn_close(); + continue; + } + if (ngtcp2_conn_in_draining_period(conn)) + continue; + } + + // A session that died earlier in this very batch has nothing left to feed. + if (session->closed()) continue; - auto* conn = session->conn(); - if (!conn || (!ngtcp2_conn_in_closing_period(conn) && !ngtcp2_conn_in_draining_period(conn))) - std::erase_if(m_quic_handlers, - [&](const auto& kv) { return kv.second.get() == session.get(); }); + if (session->on_read(d.pi, d.data, d.remote) == 0) + { + read_ok = true; + } + else if (session->closed()) + { + // + // Only erase immediately when not in closing/draining period. + // If we are, the 3-PTO close timer in handle_error() will call + // erase_quic_session() once the period expires. + // + auto* conn = session->conn(); + if (!conn || + (!ngtcp2_conn_in_closing_period(conn) && !ngtcp2_conn_in_draining_period(conn))) + erase_quic_session(session.get()); + } } - return 0; + // + // One write pass for the whole batch, mirroring the old per-receive-pass flush. + // + if (!read_ok) + return; + if (session->flush_write() == 0 || !session->closed()) + return; + + auto* conn = session->conn(); + if (!conn || (!ngtcp2_conn_in_closing_period(conn) && !ngtcp2_conn_in_draining_period(conn))) + erase_quic_session(session.get()); } // ------------------------------------------------------------------------------------------------- diff --git a/src/tls.cpp b/src/tls.cpp new file mode 100644 index 0000000..fb42530 --- /dev/null +++ b/src/tls.cpp @@ -0,0 +1,83 @@ +#include + +#include +#include +#include + +#include + +// ================================================================================================= + +namespace anyhttp +{ + +namespace +{ + +/** + * Key exchange group of the handshake, like the "Server Temp Key" line of h2load, e.g. + * "X25519 (253 bits)" or "prime256v1 (256 bits)". + */ +std::string key_exchange(SSL* ssl) +{ + // + // The negotiated group is known even for groups OpenSSL has no EVP_PKEY name for, like the + // post-quantum hybrids ("X25519MLKEM768") that are the default in OpenSSL 3.5. + // + std::string name; + if (const char* group = SSL_get0_group_name(ssl)) + name = group; + + // + // On the client this is the server's key share, on the server the client's one. Either way, + // both peers agree on the group, which is what we are after. + // + EVP_PKEY* key = nullptr; + if (SSL_get_peer_tmp_key(ssl, &key) != 1 || !key) + return name.empty() ? "unknown" : name; + + if (name.empty()) + { + char group[80]; + size_t len = 0; + if (EVP_PKEY_get_group_name(key, group, sizeof(group), &len) == 1 && len) + name.assign(group, len); + else if (const char* sn = OBJ_nid2sn(EVP_PKEY_get_id(key))) + name = sn; + else + name = "unknown"; + } + + auto bits = EVP_PKEY_get_bits(key); + EVP_PKEY_free(key); + + return std::format("{} ({} bits)", name, bits); +} + +std::string_view alpn(SSL* ssl) +{ + const unsigned char* data = nullptr; + unsigned int len = 0; + SSL_get0_alpn_selected(ssl, &data, &len); + if (!data) + return "none"; + return {reinterpret_cast(data), len}; +} + +} // namespace + +// ------------------------------------------------------------------------------------------------- + +std::string tls_handshake_info(ssl_st* ssl) +{ + if (!ssl) + return "no TLS session"; + + return std::format("{}, cipher={}, group={}, alpn={}{}", SSL_get_version(ssl), + SSL_get_cipher_name(ssl), key_exchange(ssl), alpn(ssl), + SSL_session_reused(ssl) ? ", resumed" : ""); +} + +} // namespace anyhttp + +// ================================================================================================= diff --git a/test/test_server.cpp b/test/test_server.cpp index 60bb8fc..857e920 100644 --- a/test/test_server.cpp +++ b/test/test_server.cpp @@ -10,23 +10,23 @@ #include #include #include +#include #include #include #include +#include +#include #include #include #include #include #include -#include #include #include #include -#include - #include #include @@ -52,6 +52,7 @@ #include #include +#include #include #include @@ -59,6 +60,7 @@ #include #include #include +#include using namespace std::string_view_literals; using namespace std::chrono_literals; @@ -173,14 +175,26 @@ TEST_F(ClientConnect, WHEN_connect_to_broadcast_ip_THEN_completes_with_network_u class Server : public testing::TestWithParam { protected: + // + // Number of threads run() will run the io_context on. More than one makes the server put + // every connection on its own strand, see below. + // + virtual size_t threads() const + { +#if defined(MULTITHREADED) + return std::max(2u, std::thread::hardware_concurrency()); +#else + return 1; +#endif + } + void SetUp() override { setupLogging(); auto config = server::Config{.listen_address = "127.0.0.2", .port = 0}; -#if defined(MULTITHREADED) - config.use_strand = true; -#endif + config.use_strand = threads() > 1; + // // The main server acceptor loop does not need to run on a strand. Instead, a per-connection // strand is created after accepting a new connection. @@ -230,16 +244,22 @@ class Server : public testing::TestWithParam void run() { -#if defined(MULTITHREADED) - auto threads = - rv::iota(0) | rv::take(std::max(1u, std::thread::hardware_concurrency())) | - rv::transform([this](int) { return std::jthread([this] { ::run(context); }); }) | - std::ranges::to(); + const size_t n = threads(); + if (n <= 1) + { + ::run(context); + return; + } - ::run(context); -#else - ::run(context); -#endif + // + // The extra threads use context.run() directly: the per-operation logging of ::run() is + // meant for single-threaded debugging and would just interleave into noise here. + // + auto pool = rv::iota(size_t{1}, n) | rv::transform([this](size_t) { + return std::jthread([this] { context.run(); }); + }) | std::ranges::to(); + + context.run(); } protected: @@ -339,7 +359,7 @@ class External : public Server co_return result; } - awaitable spawn_process(bp::filesystem::path path, std::vector args) + awaitable spawn_process(std::filesystem::path path, std::vector args) { logi("spawn: {} {}", path.generic_string(), boost::algorithm::join(args, " ")); @@ -374,7 +394,7 @@ class External : public Server co_return result; } - std::future spawn(bp::filesystem::path path, std::vector args) + std::future spawn(std::filesystem::path path, std::vector args) { ++numSpawned; std::promise promise; @@ -408,8 +428,8 @@ class External : public Server } any_io_executor strand{make_strand(context.get_executor())}; - bp::filesystem::path testFile{"CMakeLists.txt"}; - size_t testFileSize = file_size(testFile); + std::filesystem::path testFile{"CMakeLists.txt"}; + std::filesystem::path dataFile{"test/data/64kminus1"}; // posted by h2load, one file per request std::atomic numSpawned = 0; }; @@ -417,6 +437,7 @@ using Args = std::vector; // ================================================================================================= +// plain-text only, so no HTTP/3 INSTANTIATE_TEST_SUITE_P(External, External, ::testing::Values(anyhttp::Protocol::http11, // HTTP/1.1 anyhttp::Protocol::h2), // HTTP/2 @@ -435,7 +456,7 @@ TEST_P(External, curl) auto future = spawn(CURL_PATH, std::move(args)); run(); - EXPECT_EQ(future.get().size(), testFileSize); + EXPECT_EQ(future.get().size(), file_size(testFile)); } TEST_P(External, curl_multiple) @@ -449,7 +470,7 @@ TEST_P(External, curl_multiple) auto future = spawn(CURL_PATH, std::move(args)); run(); - EXPECT_EQ(future.get().size(), testFileSize * 2); + EXPECT_EQ(future.get().size(), file_size(testFile) * 2); } // ================================================================================================= @@ -469,6 +490,44 @@ class ExternalTLS : public External return "--http3-only"; } } + + // + // Run h2load against /echo, posting the contents of 'dataFile' with every request, and check + // that all of it came back. h2load speaks the protocol of the fixture parameter. + // + void h2load(size_t n, size_t clients, size_t streams) + { + auto url = std::format("http://127.0.0.2:{}/echo", server->local_endpoint().port()); + Args args = {"-d", dataFile.string(), "-n", std::to_string(n), // + "-c", std::to_string(clients), "-m", std::to_string(streams), url}; + + switch (GetParam()) + { + case anyhttp::Protocol::http11: + args.insert(args.begin(), "--h1"); + break; + case anyhttp::Protocol::h3: + args.insert(args.begin(), "--h3"); // h2load negotiates h3 itself, http:// URL is fine + break; + default: + break; // h2load defaults to HTTP/2 + } + + auto future = spawn(H2LOAD_PATH, std::move(args)); + run(); + + const std::string output = future.get(); + std::smatch match; + std::regex regex( + R"((\d+) total, \d+ started, (\d+) done, (\d+) succeeded, (\d+) failed, \d+ errored)"); + ASSERT_TRUE(std::regex_search(output.begin(), output.end(), match, regex)) << output; + EXPECT_EQ(std::stoul(match[3].str()), n) << match[1]; + EXPECT_EQ(std::stoul(match[4].str()), 0) << match[1]; + + regex = std::regex(R"(\((\d+)\) data)"); + ASSERT_TRUE(std::regex_search(output.begin(), output.end(), match, regex)) << output; + EXPECT_EQ(std::stoul(match[1].str()), n * file_size(dataFile)) << match[1]; + } }; INSTANTIATE_TEST_SUITE_P(ExternalTLS, ExternalTLS, @@ -492,7 +551,7 @@ TEST_P(ExternalTLS, curl) auto future = spawn_curl(std::move(args)); run(); - EXPECT_EQ(future.get().size(), testFileSize); + EXPECT_EQ(future.get().size(), file_size(testFile)); } TEST_P(ExternalTLS, curl_many) @@ -516,7 +575,7 @@ TEST_P(ExternalTLS, curl_many) run(); for (auto& future : futures) - EXPECT_EQ(future.get().size(), testFileSize); + EXPECT_EQ(future.get().size(), file_size(testFile)); } TEST_P(ExternalTLS, curl_multiple) @@ -532,45 +591,33 @@ TEST_P(ExternalTLS, curl_multiple) auto future = spawn_curl(std::move(args)); run(); - EXPECT_EQ(future.get().size(), testFileSize * 4); + EXPECT_EQ(future.get().size(), file_size(testFile) * 4); } // ------------------------------------------------------------------------------------------------- -TEST_P(ExternalTLS, h2load) -{ - const size_t n = 100; // number of requests, echoing 65535 bytes each - const size_t data_size = 65535; - auto url = std::format("http://127.0.0.2:{}/echo", server->local_endpoint().port()); - Args args = {"-d", "test/data/64kminus1", "-n", std::to_string(n), "-c", "4", "-m", "3", url}; - - switch (GetParam()) - { - case anyhttp::Protocol::http11: - args.insert(args.begin(), "--h1"); - break; - case anyhttp::Protocol::h3: - args.insert(args.begin(), "--h3"); // h2load negotiates h3 itself, http:// URL is fine - break; - default: - break; // h2load defaults to HTTP/2 - } +TEST_P(ExternalTLS, h2load) { h2load(100, 4, 3); } - auto future = spawn(H2LOAD_PATH, std::move(args)); - run(); +// ================================================================================================= - const std::string output = future.get(); - std::smatch match; - std::regex regex( - R"((\d+) total, \d+ started, (\d+) done, (\d+) succeeded, (\d+) failed, \d+ errored)"); - ASSERT_TRUE(std::regex_search(output.begin(), output.end(), match, regex)) << output; - EXPECT_EQ(std::stoul(match[3].str()), n) << match[1]; - EXPECT_EQ(std::stoul(match[4].str()), 0) << match[1]; +// +// Same as ExternalTLS, but with the io_context run on multiple threads, so every connection gets +// its own strand. For HTTP/3 this is the regression test for concurrent access to a single +// ngtcp2_conn, which used to crash right away. +// +class ExternalTLSThreaded : public ExternalTLS +{ +protected: + size_t threads() const override { return 8; } +}; - regex = std::regex(R"(\((\d+)\) data)"); - ASSERT_TRUE(std::regex_search(output.begin(), output.end(), match, regex)) << output; - EXPECT_EQ(std::stoul(match[1].str()), n * data_size) << match[1]; -} +INSTANTIATE_TEST_SUITE_P(ExternalTLSThreaded, ExternalTLSThreaded, + ::testing::Values(anyhttp::Protocol::http11, // HTTP/1.1 + anyhttp::Protocol::h2, // HTTP/2 + anyhttp::Protocol::h3), // HTTP/3 (QUIC) + NameGenerator); + +TEST_P(ExternalTLSThreaded, h2load) { h2load(1000, 8, 5); } // ================================================================================================= @@ -601,7 +648,7 @@ TEST_F(ExternalCustom, nghttp2) auto future = spawn(NGHTTP_PATH, {"-d", testFile.string(), url}); run(); - EXPECT_EQ(future.get().size(), testFileSize); + EXPECT_EQ(future.get().size(), file_size(testFile)); } TEST_F(ExternalCustom, h2spec) @@ -642,7 +689,6 @@ class Client : public Server Server::SetUp(); url.set_port_number(server->local_endpoint().port()); client::Config config{.url = url, .protocol = GetParam()}; - config.url.set_port_number(server->local_endpoint().port()); #if defined(MULTITHREADED) client.emplace(make_strand(context.get_executor()), config); #else @@ -919,6 +965,8 @@ TEST_P(ClientAsync, YieldFuzz) co_await yield(dist(gen)); co_await response.async_write({}); co_await yield(dist(gen)); + std::array data; + co_await request.async_read_some(asio::buffer(data)); }; test = [this](Session session) -> awaitable { @@ -1701,7 +1749,15 @@ TEST_P(ClientAsync, WHEN_send_more_than_content_length_THEN_connection_is_reset) auto ex = co_await this_coro::executor; auto [ep] = co_await co_spawn(ex, send(request, rv::iota(uint8_t(0))), as_tuple); - EXPECT_EQ(code(ep), boost::system::errc::connection_reset); + + // + // Which of the two the write reports is a matter of how far the kernel has gotten with the + // peer's RST by the time we get to write again -- the first write after it fails with + // ECONNRESET, any later one with EPIPE. Single-threaded we reliably hit the former, with + // more than one thread the latter; both mean the same thing here. + // + EXPECT_THAT(code(ep), testing::AnyOf(boost::system::errc::connection_reset, + boost::system::errc::broken_pipe)); }; } @@ -1725,8 +1781,14 @@ TEST_P(ClientAsync, ResetServerDuringRequest) auto request = co_await session.async_submit(url.set_path("echo"), {}); auto response = co_await request.async_get_response(); - auto future = co_spawn(request.get_executor(), send(request, rv::iota(uint8_t(0))), - use_future(as_tuple)); + // + // Deliberately NOT use_future(): with more than one thread the client lives on a strand, + // and blocking that strand in future.get() below would keep the very handlers that + // complete this send from ever running. asio::experimental::promise starts the coroutine + // right away, just like use_future, but is awaited instead of waited on. + // + auto promise = co_spawn(request.get_executor(), send(request, rv::iota(uint8_t(0))), + asio::experimental::use_promise); std::println("============================================================================="); for (size_t i = 0; i < 10; ++i) @@ -1744,12 +1806,11 @@ TEST_P(ClientAsync, ResetServerDuringRequest) co_await yield(); } - auto exception_ptr = future.get(); + auto exception_ptr = co_await std::move(promise)(as_tuple(use_awaitable)); boost::system::error_code ec; auto received = co_await try_receive(response, ec); loge("received: {} ({} bytes)", ec.message(), received); - // future.wait_for(2s); }; } @@ -1834,7 +1895,7 @@ TEST_F(Http3IdleTimeout, WHEN_client_vanishes_in_flight_THEN_idle_timer_drops_th // // The server has to keep running while the client is frozen, so it gets a thread of its own. // - std::jthread server_thread([this] { context.run(); }); + std::jthread server_thread([this] { run(context); }); boost::scope::scope_exit stop_server([this] { context.stop(); }); //