diff --git a/.clang-format b/.clang-format index 0f2e9914..997febc0 100644 --- a/.clang-format +++ b/.clang-format @@ -1,20 +1,35 @@ BasedOnStyle: Google + +AccessModifierOffset: -2 + +# alignment AlignAfterOpenBracket: AlwaysBreak AlignConsecutiveAssignments: 'false' AlignConsecutiveDeclarations: 'false' AlignEscapedNewlines: Left AlignOperands: AlignAfterOperator AlignTrailingComments: 'true' + +# inlining AllowAllArgumentsOnNextLine: 'true' AllowShortBlocksOnASingleLine: 'false' AllowShortCaseLabelsOnASingleLine: 'false' -AllowShortFunctionsOnASingleLine: Inline +AllowShortFunctionsOnASingleLine: 'Inline' AllowShortIfStatementsOnASingleLine: 'false' AllowShortLoopsOnASingleLine: 'false' +RequiresClausePosition: 'OwnLine' + +# breaking AlwaysBreakAfterReturnType: None AlwaysBreakTemplateDeclarations: Yes BreakBeforeBinaryOperators: None BreakBeforeBraces: Custom +BreakBeforeConceptDeclarations: 'Always' +BreakBeforeTernaryOperators: 'true' +BreakConstructorInitializers: AfterColon +PenaltyBreakString: '3' + +# bracing BraceWrapping: AfterCaseLabel: true AfterClass: true @@ -33,29 +48,29 @@ BraceWrapping: SplitEmptyFunction: false SplitEmptyRecord: false SplitEmptyNamespace: false -BreakBeforeTernaryOperators: 'true' -BreakConstructorInitializers: AfterColon + +ColumnLimit: 125 +CompactNamespaces: 'true' +ConstructorInitializerIndentWidth: 8 +ContinuationIndentWidth: 8 Cpp11BracedListStyle: 'true' +IndentWidth: 4 KeepEmptyLinesAtTheStartOfBlocks: 'true' NamespaceIndentation: All -CompactNamespaces: 'true' -PenaltyBreakString: '3' +QualifierAlignment: Left +RemoveSemicolon: true +SortIncludes: true + +# spacing SpaceBeforeParens: ControlStatements SpacesInAngles: 'false' SpacesInContainerLiterals: 'false' SpacesInParentheses: 'false' SpacesInSquareBrackets: 'false' + Standard: c++20 -UseTab: Never -SortIncludes: true -ColumnLimit: 125 -IndentWidth: 4 -AccessModifierOffset: -2 -ConstructorInitializerIndentWidth: 8 -ContinuationIndentWidth: 8 -QualifierAlignment: Left -RemoveSemicolon: true +UseTab: Never # treat pointers and reference declarations as if part of the type DerivePointerAlignment: false diff --git a/.drone.jsonnet b/.drone.jsonnet index 7848afc2..4066357c 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -226,7 +226,7 @@ local clang(version) = debian_pipeline( cmake_extra='-DCMAKE_C_COMPILER=clang-' + version + ' -DCMAKE_CXX_COMPILER=clang++-' + version + ' ' ); -local full_llvm(version) = debian_pipeline( +local full_llvm(version, _allow_fail=false) = debian_pipeline( 'Debian sid/llvm-' + version, docker_base + 'debian-sid-clang', deps=['clang-' + version, ' lld-' + version, ' libc++-' + version + '-dev', 'libc++abi-' + version + '-dev'] @@ -238,7 +238,8 @@ local full_llvm(version) = debian_pipeline( '-DCMAKE_' + type + '_LINKER_FLAGS=-fuse-ld=lld-' + version for type in ['EXE', 'MODULE', 'SHARED'] ]) + - ' -DOXEN_LOGGING_FORCE_SUBMODULES=ON' + ' -DOXEN_LOGGING_FORCE_SUBMODULES=ON', + allow_fail=_allow_fail ); // Macos build @@ -288,7 +289,7 @@ local mac_builder(name, 'echo "Building on ${DRONE_STAGE_MACHINE}"', apt_get_quiet + ' update', apt_get_quiet + ' install -y eatmydata', - 'eatmydata ' + apt_get_quiet + ' install --no-install-recommends -y git clang-format-15 jsonnet', + 'eatmydata ' + apt_get_quiet + ' install --no-install-recommends -y git clang-format-16 jsonnet', './utils/ci/lint-check.sh', ], }], @@ -297,8 +298,10 @@ local mac_builder(name, // Various debian builds debian_pipeline('Debian sid', docker_base + 'debian-sid'), debian_pipeline('Debian sid/Debug', docker_base + 'debian-sid', build_type='Debug'), - clang(16), - full_llvm(16), + clang(17), + full_llvm(17), + clang(18), + full_llvm(18, true), // will fail on warning RE: libc++-19 char_traits debian_pipeline('Debian sid -GSO', docker_base + 'debian-sid', cmake_extra='-DLIBQUIC_SEND=sendmmsg'), debian_pipeline('Debian sid -mmsg', docker_base + 'debian-sid', cmake_extra='-DLIBQUIC_SEND=sendmsg -DLIBQUIC_RECVMMSG=OFF'), debian_pipeline('Debian sid -GSO/Debug', docker_base + 'debian-sid', build_type='Debug', cmake_extra='-DLIBQUIC_SEND=sendmmsg'), diff --git a/include/oxen/quic/address.hpp b/include/oxen/quic/address.hpp index 7e367487..08b6af03 100644 --- a/include/oxen/quic/address.hpp +++ b/include/oxen/quic/address.hpp @@ -6,7 +6,7 @@ #include "formattable.hpp" #include "ip.hpp" -#include "utils.hpp" +#include "types.hpp" #if defined(__OpenBSD__) || defined(__DragonFly__) // These systems are known to disallow dual stack binding, and so on such systems when @@ -25,8 +25,17 @@ namespace oxen::quic { inline constexpr std::array _ipv6_any_addr = {0}; - template - concept RawSockAddr = std::same_as || std::same_as || std::same_as; + struct Address; + + inline namespace concepts + { + template + concept raw_sockaddr_type = + std::same_as || std::same_as || std::same_as; + + template + concept quic_address_type = std::derived_from; + } // namespace concepts // Holds an address, with a ngtcp2_addr held for easier passing into ngtcp2 functions struct Address @@ -71,7 +80,7 @@ namespace oxen::quic explicit Address(const ipv6& v6, uint16_t port = 0); // Assignment from a sockaddr pointer; we copy the sockaddr's contents - template + template Address& operator=(const T* s) { _addr.addrlen = std::is_same_v @@ -215,12 +224,12 @@ namespace oxen::quic // pointer to other things (like bool) won't occur. // // If the given pointer is mutated you *must* call update_socklen() afterwards. - template + template operator T*() { return reinterpret_cast(&_sock_addr); } - template + template operator const T*() const { return reinterpret_cast(&_sock_addr); @@ -366,12 +375,10 @@ namespace oxen::quic namespace std { - inline constexpr size_t inverse_golden_ratio = sizeof(size_t) >= 8 ? 0x9e37'79b9'7f4a'7c15 : 0x9e37'79b9; - template <> struct hash { - size_t operator()(const oxen::quic::Address& addr) const + size_t operator()(const oxen::quic::Address& addr) const noexcept { std::string_view addr_data; uint16_t port; @@ -390,7 +397,7 @@ namespace std } auto h = hash{}(addr_data); - h ^= hash{}(port) + inverse_golden_ratio + (h << 6) + (h >> 2); + h ^= hash{}(port) + oxen::quic::inverse_golden_ratio + (h << 6) + (h >> 2); return h; } }; @@ -398,10 +405,10 @@ namespace std template <> struct hash { - size_t operator()(const oxen::quic::Path& addr) const + size_t operator()(const oxen::quic::Path& addr) const noexcept { auto h = hash{}(addr.local); - h ^= hash{}(addr.remote) + inverse_golden_ratio + (h << 6) + (h >> 2); + h ^= hash{}(addr.remote) + oxen::quic::inverse_golden_ratio + (h << 6) + (h >> 2); return h; } }; diff --git a/include/oxen/quic/btstream.hpp b/include/oxen/quic/btstream.hpp index 66564eef..44eeaf67 100644 --- a/include/oxen/quic/btstream.hpp +++ b/include/oxen/quic/btstream.hpp @@ -6,8 +6,6 @@ namespace oxen::quic { - using time_point = std::chrono::steady_clock::time_point; - // timeout is used for sent requests awaiting responses inline constexpr std::chrono::seconds DEFAULT_TIMEOUT{10s}; diff --git a/include/oxen/quic/connection.hpp b/include/oxen/quic/connection.hpp index 60128cdd..8e502ce7 100644 --- a/include/oxen/quic/connection.hpp +++ b/include/oxen/quic/connection.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include @@ -9,13 +8,13 @@ #include #include #include +#include #include #include "connection_ids.hpp" #include "context.hpp" #include "format.hpp" #include "types.hpp" -#include "utils.hpp" namespace oxen::quic { @@ -25,9 +24,6 @@ namespace oxen::quic inline constexpr uint64_t MAX_ACTIVE_CIDS{8}; inline constexpr size_t NGTCP2_RETRY_SCIDLEN{18}; - template - concept StreamDerived = std::derived_from; - class connection_interface : public std::enable_shared_from_this { protected: @@ -45,7 +41,7 @@ namespace oxen::quic /// ID; it will be made ready once the associated stream id is seen from the remote /// connection. Note that this constructor bypasses the stream constructor callback for the /// applicable stream id. - template + template std::shared_ptr queue_incoming_stream(Args&&... args) { // We defer resolution of `Endpoint` here via `EndpointDeferred` because the header only @@ -70,7 +66,7 @@ namespace oxen::quic /// such as from an increase in available stream ids resulting from the closure of an /// existing stream. Note that this constructor bypasses the stream constructor callback /// for the applicable stream id. - template + template requires std::derived_from std::shared_ptr open_stream(Args&&... args) { @@ -90,7 +86,7 @@ namespace oxen::quic /// StreamT is specified, is of the given Stream subclass). Returns nullptr if the id is /// not currently an open stream; throws std::invalid_argument if the stream exists but is /// not an instance of the given StreamT type. - template + template std::shared_ptr maybe_stream(int64_t id) { auto s = get_stream_impl(id); @@ -111,7 +107,7 @@ namespace oxen::quic /// StreamT is specified, is of the given Stream subclass). Otherwise throws /// std::out_of_range if the stream was not found, and std::invalid_argument if the stream /// was found, but is not an instance of StreamT. - template + template std::shared_ptr get_stream(int64_t id) { if (auto s = maybe_stream(id)) @@ -241,6 +237,7 @@ namespace oxen::quic { friend class TestHelper; friend struct rotating_buffer; + friend struct connection_callbacks; public: // Non-movable/non-copyable; you must always hold a Connection in a shared_ptr @@ -317,6 +314,11 @@ namespace oxen::quic bool datagrams_enabled() const override { return _datagrams_enabled; } bool packet_splitting_enabled() const override { return _packet_splitting; } + bool zero_rtt_enabled() const { return _0rtt_enabled; } + unsigned int zero_rtt_window() const { return _0rtt_window; } + + bool stateless_reset_enabled() const { return _stateless_reset_enabled; } + std::optional max_datagram_size_changed() override; // public debug functions; to be removed with friend test fixture class @@ -336,19 +338,23 @@ namespace oxen::quic connection_established_callback conn_established_cb; connection_closed_callback conn_closed_cb; - void early_data_rejected(); - void set_remote_addr(const ngtcp2_addr& new_remote); void store_associated_cid(const quic_cid& cid); + void delete_associated_cid(const quic_cid& cid); + std::unordered_set& associated_cids() { return _associated_cids; } int client_handshake_completed(); int server_handshake_completed(); - int server_path_validation(const ngtcp2_path* path); + int recv_stateless_reset(std::shared_ptr tok); + + int client_path_validation(const ngtcp2_path* path, bool res, uint32_t flags); + + int server_path_validation(const ngtcp2_path* path, bool res, uint32_t flags); void set_new_path(Path new_path); @@ -396,6 +402,11 @@ namespace oxen::quic Path _path; + bool _0rtt_enabled{false}; + const unsigned int _0rtt_window{}; + + bool _stateless_reset_enabled{false}; + const uint64_t _max_streams{DEFAULT_MAX_BIDI_STREAMS}; const bool _datagrams_enabled{false}; const bool _packet_splitting{false}; @@ -405,7 +416,13 @@ namespace oxen::quic std::atomic _close_quietly{false}; std::atomic _is_validated{false}; - ustring remote_pubkey; + ustring remote_pubkey{}; + + std::vector _early_streams; + + void make_early_streams(ngtcp2_conn* connptr); + + void revert_early_streams(); struct connection_deleter { @@ -466,7 +483,7 @@ namespace oxen::quic // streams are added to the back and popped from the front (FIFO) std::deque> pending_streams; - int init( + void init( ngtcp2_settings& settings, ngtcp2_transport_params& params, ngtcp2_callbacks& callbacks, @@ -492,7 +509,7 @@ namespace oxen::quic void stream_execute_close(Stream& s, uint64_t app_code); void stream_closed(int64_t id, uint64_t app_code); void close_all_streams(); - void check_pending_streams(uint64_t available); + void check_pending_streams(uint64_t available, bool is_early_stream = false); int recv_datagram(bstring_view data, bool fin); int ack_datagram(uint64_t dgram_id); int recv_token(const uint8_t* token, size_t tokenlen); diff --git a/include/oxen/quic/connection_ids.hpp b/include/oxen/quic/connection_ids.hpp index ca0772cc..39e3445d 100644 --- a/include/oxen/quic/connection_ids.hpp +++ b/include/oxen/quic/connection_ids.hpp @@ -77,7 +77,7 @@ namespace std template <> struct hash { - size_t operator()(const oxen::quic::quic_cid& cid) const + size_t operator()(const oxen::quic::quic_cid& cid) const noexcept { static_assert( alignof(oxen::quic::quic_cid) >= alignof(size_t) && @@ -89,6 +89,9 @@ namespace std template <> struct hash { - size_t operator()(const oxen::quic::ConnectionID& rid) const { return std::hash{}(rid.id); } + size_t operator()(const oxen::quic::ConnectionID& rid) const noexcept + { + return std::hash{}(rid.id); + } }; } // namespace std diff --git a/include/oxen/quic/context.hpp b/include/oxen/quic/context.hpp index b84a05ea..954bc0f1 100644 --- a/include/oxen/quic/context.hpp +++ b/include/oxen/quic/context.hpp @@ -46,6 +46,7 @@ namespace oxen::quic connection_established_callback conn_established_cb; connection_closed_callback conn_closed_cb; user_config config{}; + bool disable_key_verification = false; template IOContext(Direction d, Opt&&... opts) : dir{d} @@ -73,6 +74,7 @@ namespace oxen::quic void handle_ioctx_opt(dgram_data_callback func); void handle_ioctx_opt(connection_established_callback func); void handle_ioctx_opt(connection_closed_callback func); + void handle_ioctx_opt(opt::disable_key_verification db); /// Unwraps an optional option: does nothing if nullopt, otherwise applies the option. This /// is here to make runtime-dependent options (i.e. options whose presence depends on a diff --git a/include/oxen/quic/crypto.hpp b/include/oxen/quic/crypto.hpp index d7561fc5..b7e8b0f1 100644 --- a/include/oxen/quic/crypto.hpp +++ b/include/oxen/quic/crypto.hpp @@ -14,15 +14,18 @@ extern "C" namespace oxen::quic { - constexpr auto default_alpn_str = "default"sv; + inline constexpr auto default_alpn_str = "default"_usv; + inline constexpr std::chrono::milliseconds DEFAULT_ANTI_REPLAY_WINDOW{10min}; class TLSSession; class Connection; + struct IOContext; class TLSCreds { public: - virtual std::unique_ptr make_session(Connection& c, const std::vector& alpns) = 0; + virtual std::unique_ptr make_session( + Connection& c, const std::shared_ptr& ctx, const std::vector& alpns) = 0; virtual ~TLSCreds() = default; }; @@ -32,11 +35,10 @@ namespace oxen::quic ngtcp2_crypto_conn_ref conn_ref; virtual void* get_session() = 0; virtual void* get_anti_replay() const = 0; - virtual const void* get_session_ticket_key() const = 0; virtual bool get_early_data_accepted() const = 0; - virtual ustring_view selected_alpn() = 0; + virtual ustring_view selected_alpn() const = 0; virtual ustring_view remote_key() const = 0; - virtual void set_expected_remote_key(ustring key) = 0; + virtual void set_expected_remote_key(ustring_view key) = 0; virtual ~TLSSession() = default; virtual int send_session_ticket() = 0; }; diff --git a/include/oxen/quic/endpoint.hpp b/include/oxen/quic/endpoint.hpp index bf3659f4..bf77c87b 100644 --- a/include/oxen/quic/endpoint.hpp +++ b/include/oxen/quic/endpoint.hpp @@ -7,15 +7,8 @@ extern "C" #else #include #endif -#include -#include -#include -#include -#include } -#include - #include #include #include @@ -28,20 +21,13 @@ extern "C" #include "connection.hpp" #include "context.hpp" +#include "gnutls_crypto.hpp" #include "network.hpp" #include "udp.hpp" #include "utils.hpp" namespace oxen::quic { - template - static constexpr void check_for_tls_creds() - { - static_assert( - (0 + ... + std::is_convertible_v, std::shared_ptr>) == 1, - "Endpoint listen/connect require exactly one std::shared_ptr argument"); - } - class Endpoint : public std::enable_shared_from_this { public: @@ -67,6 +53,7 @@ namespace oxen::quic void listen(Opt&&... opts) { check_for_tls_creds(); + check_verification_scheme(this); net.call_get([&opts..., this]() mutable { if (inbound_ctx) @@ -79,11 +66,11 @@ namespace oxen::quic }); } - // creates new outbound connection to remote; emplaces conn/interface pair in outbound map - template - std::shared_ptr connect(RemoteAddress remote, Opt&&... opts) + template + std::shared_ptr connect(T remote, Opt&&... opts) { check_for_tls_creds(); + check_address_scheme(); std::promise> p; auto f = p.get_future(); @@ -94,10 +81,8 @@ namespace oxen::quic if (_local.is_ipv6() && !remote.is_ipv6()) remote.map_ipv4_as_ipv6(); - Path _path = Path{_local, remote}; - - net.call([&opts..., &p, path = _path, this, remote_pk = std::move(remote).get_remote_key()]() mutable { - quic_cid qcid; + net.call([this, &opts..., &p, remote = std::move(remote)]() mutable { + quic_cid qcid{}; auto next_rid = next_reference_id(); try @@ -105,32 +90,7 @@ namespace oxen::quic // initialize client context and client tls context simultaneously outbound_ctx = std::make_shared(Direction::OUTBOUND, std::forward(opts)...); _set_context_globals(outbound_ctx); - - for (;;) - { - // emplace random CID into lookup keyed to unique reference ID - if (auto [it_a, res_a] = conn_lookup.emplace(quic_cid::random(), next_rid); res_a) - { - qcid = it_a->first; - - if (auto [it_b, res_b] = conns.emplace(next_rid, nullptr); res_b) - { - it_b->second = Connection::make_conn( - *this, - next_rid, - it_a->first, - quic_cid::random(), - std::move(path), - outbound_ctx, - outbound_alpns, - handshake_timeout, - remote_pk); - - p.set_value(it_b->second); - return; - } - } - } + p.set_value(_connect(std::move(remote), qcid, next_rid)); } catch (...) { @@ -200,11 +160,20 @@ namespace oxen::quic void manually_receive_packet(Packet&& pkt); + bool zero_rtt_enabled() const { return _0rtt_enabled; } + unsigned int zero_rtt_window() const { return _0rtt_window; } + + bool stateless_reset_enabled() const { return _stateless_reset_enabled; } + + int validate_anti_replay(gtls_ticket_ptr ticket, time_t exp); + void store_session_ticket(gtls_ticket_ptr ticket); + gtls_ticket_ptr get_session_ticket(const ustring_view& remote_pk); + private: friend class Network; friend class Loop; friend class Connection; - friend struct Callbacks; + friend struct connection_callbacks; friend class TestHelper; Network& net; @@ -218,6 +187,14 @@ namespace oxen::quic int _rbufsize{4096}; opt::manual_routing _manual_routing; + bool _0rtt_enabled{false}; + unsigned int _0rtt_window{}; + + bool _stateless_reset_enabled{true}; + + gtls_db_validate_cb _validate_0rtt_ticket; + gtls_db_get_cb _get_session_ticket; + gtls_db_put_cb _put_session_ticket; uint64_t _next_rid{0}; @@ -230,9 +207,11 @@ namespace oxen::quic std::vector inbound_alpns; std::chrono::nanoseconds handshake_timeout{DEFAULT_HANDSHAKE_TIMEOUT}; - std::map anti_replay_db; - std::map encoded_transport_params; - std::map path_validation_tokens; + std::unordered_map session_tickets; + + std::unordered_map encoded_transport_params; + + std::unordered_map path_validation_tokens; const std::shared_ptr& get_loop() { return net._loop->loop(); } @@ -241,6 +220,11 @@ namespace oxen::quic // Does the non-templated bit of `listen()` void _listen(); + std::shared_ptr _connect(RemoteAddress remote, quic_cid qcid, ConnectionID rid); + + std::shared_ptr _connect( + Address remote, quic_cid qcid, ConnectionID rid, std::optional pk = std::nullopt); + void handle_ep_opt(opt::enable_datagrams dc); void handle_ep_opt(opt::outbound_alpns alpns); void handle_ep_opt(opt::inbound_alpns alpns); @@ -251,6 +235,8 @@ namespace oxen::quic void handle_ep_opt(connection_closed_callback conn_closed_cb); void handle_ep_opt(opt::static_secret ssecret); void handle_ep_opt(opt::manual_routing mrouting); + void handle_ep_opt(opt::enable_0rtt_ticketing rtt); + void handle_ep_opt(opt::disable_stateless_reset rst); // Takes a std::optional-wrapped option that does nothing if the optional is empty, // otherwise passes it through to the above. This is here to allow runtime-dependent @@ -291,22 +277,28 @@ namespace oxen::quic void connection_established(connection_interface& conn); - int validate_anti_replay(ustring key, ustring data, time_t exp); - - void store_0rtt_transport_params(ustring remote_pk, ustring encoded_params); + void store_0rtt_transport_params(Address remote, ustring encoded_params); - std::optional get_0rtt_transport_params(ustring remote_pk); + std::optional get_0rtt_transport_params(const Address& remote); - void store_path_validation_token(ustring remote_pk, ustring token); + void store_path_validation_token(Address remote, ustring token); - std::optional get_path_validation_token(ustring remote_pk); + std::optional get_path_validation_token(const Address& remote); void initial_association(Connection& conn); + void activate_cid(const ngtcp2_cid* cid, const uint8_t* token, Connection& conn); + + void deactivate_cid(const ngtcp2_cid* cid, Connection& conn); + + void associate_cid(quic_cid qcid, Connection& conn); + void associate_cid(const ngtcp2_cid* cid, Connection& conn); void dissociate_cid(const ngtcp2_cid* cid, Connection& conn); + void dissociate_cid(quic_cid qcid, Connection& conn); + const ustring& static_secret() const { return _static_secret; } Connection* fetch_associated_conn(quic_cid& cid); @@ -367,6 +359,12 @@ namespace oxen::quic std::unordered_map conn_lookup; + void expire_reset_tokens(time_point now = get_time()); + + // only used if stateless reset enabled + std::unordered_map> reset_token_lookup; + std::unordered_map, quic_cid> reset_token_map; + std::map draining_closing; std::optional handle_packet_connid(const Packet& pkt); @@ -381,11 +379,42 @@ namespace oxen::quic void send_or_queue_packet( const Path& p, std::vector buf, uint8_t ecn, std::function callback = nullptr); + void send_stateless_reset(const Packet& pkt, quic_cid& cid); + + Connection* check_stateless_reset(const Packet& pkt, quic_cid& cid); + void send_version_negotiation(const ngtcp2_version_cid& vid, Path p); void check_timeouts(); - Connection* accept_initial_connection(const Packet& pkt); + Connection* accept_initial_connection(const Packet& pkt, quic_cid& cid); + + template + static constexpr void check_for_tls_creds() + { + static_assert( + (0 + ... + std::is_convertible_v, std::shared_ptr>) == 1, + "Endpoint listen/connect require exactly one std::shared_ptr argument"); + } + + template + static void check_verification_scheme(Endpoint* e) + { + if constexpr ((std::is_same_v> || ...)) + { + if (e->zero_rtt_enabled()) + throw std::invalid_argument{"Disabling key verification is incompatible with 0rtt ticketing!"}; + } + } + + template + static constexpr void check_address_scheme() + { + if constexpr ((std::is_same_v> || ...)) + static_assert(std::is_same_v, "Disabling key verification requires keyless address!"); + else + static_assert(std::is_same_v, "Key verification requires keyed address!"); + } }; } // namespace oxen::quic diff --git a/include/oxen/quic/error.hpp b/include/oxen/quic/error.hpp index 90134ddc..80249db3 100644 --- a/include/oxen/quic/error.hpp +++ b/include/oxen/quic/error.hpp @@ -50,6 +50,10 @@ namespace oxen::quic inline constexpr uint64_t CONN_SEND_FAIL = ERROR_BASE + 1002; // Connection closing because it reached idle timeout inline constexpr uint64_t CONN_IDLE_CLOSED = ERROR_BASE + 1003; + // Early data rejected: + inline constexpr uint64_t CONN_EARLY_DATA_REJECTED = ERROR_BASE + 1004; + // Stateless reset received + inline constexpr uint64_t CONN_STATELESS_RESET = ERROR_BASE + 1005; inline std::string quic_strerror(uint64_t e) { diff --git a/include/oxen/quic/format.hpp b/include/oxen/quic/format.hpp index ffd732e1..cc4d569c 100644 --- a/include/oxen/quic/format.hpp +++ b/include/oxen/quic/format.hpp @@ -48,7 +48,7 @@ namespace oxen::quic namespace fmt { - template + template struct formatter : formatter { template diff --git a/include/oxen/quic/formattable.hpp b/include/oxen/quic/formattable.hpp index 2901ebf8..261fe7d8 100644 --- a/include/oxen/quic/formattable.hpp +++ b/include/oxen/quic/formattable.hpp @@ -2,22 +2,17 @@ #include -// GCC before 10 requires a "bool" keyword in concept; this CONCEPT_COMPAT is empty by default, but -// expands to bool if under such a GCC. -#if (!(defined(__clang__)) && defined(__GNUC__) && __GNUC__ < 10) -#define CONCEPT_COMPAT bool -#else -#define CONCEPT_COMPAT -#endif - namespace oxen::quic { - // Types can opt-in to being fmt-formattable by ensuring they have a ::to_string() method defined - template - concept CONCEPT_COMPAT ToStringFormattable = T::to_string_formattable && requires(T a) { - { - a.to_string() - } -> std::convertible_to; - }; + inline namespace concepts + { + // Types can opt-in to being fmt-formattable by ensuring they have a ::to_string() method defined + template + concept ToStringFormattable = T::to_string_formattable && requires(T a) { + { + a.to_string() + } -> std::convertible_to; + }; + } // namespace concepts } // namespace oxen::quic diff --git a/include/oxen/quic/gnutls_crypto.hpp b/include/oxen/quic/gnutls_crypto.hpp index fb34c349..e8c6943e 100644 --- a/include/oxen/quic/gnutls_crypto.hpp +++ b/include/oxen/quic/gnutls_crypto.hpp @@ -3,11 +3,13 @@ #include #include +#include #include #include +#include "connection_ids.hpp" #include "crypto.hpp" -#include "utils.hpp" +#include "types.hpp" namespace oxen::quic { @@ -55,17 +57,55 @@ namespace oxen::quic // These bytes mean "this is a raw Ed25519 public key" in ASN.1 (or something like that) inline constexpr auto ASN_ED25519_PUBKEY_PREFIX = "302a300506032b6570032100"_hex; - struct gnutls_key + struct gtls_datum + { + private: + gnutls_datum_t d{NULL, 0}; + + public: + gtls_datum() = default; + gtls_datum(unsigned char* data, size_t datalen) : d{data, static_cast(datalen)} {} + + ~gtls_datum() { reset(); } + + void reset() + { + if (d.data != NULL) + gnutls_free(d.data); + d.size = 0; + } + + ustring_view view() const { return {d.data, d.size}; } + + const unsigned char* data() const { return d.data; } + unsigned char* data() { return d.data; } + + size_t size() const { return d.size; } + + template T> + operator const T*() const + { + return &d; + } + + template T> + operator T*() + { + return &d; + } + }; + + struct gtls_key { private: std::array buf{}; - gnutls_key(const unsigned char* data, size_t size) { write(data, size); } + gtls_key(const unsigned char* data, size_t size) { write(data, size); } public: - gnutls_key() = default; - gnutls_key(std::string_view data) : gnutls_key{convert_sv(data)} {} - gnutls_key(ustring_view data) : gnutls_key{data.data(), data.size()} {} + gtls_key() = default; + gtls_key(std::string_view data) : gtls_key{convert_sv(data)} {} + gtls_key(ustring_view data) : gtls_key{data.data(), data.size()} {} // Writes to the internal buffer holding the gnutls key void write(const unsigned char* data, size_t size) @@ -78,9 +118,9 @@ namespace oxen::quic ustring_view view() const { return {buf.data(), buf.size()}; } - gnutls_key(const gnutls_key& other) { *this = other; } + gtls_key(const gtls_key& other) { *this = other; } - gnutls_key& operator=(const gnutls_key& other) + gtls_key& operator=(const gtls_key& other) { buf = other.buf; return *this; @@ -90,23 +130,22 @@ namespace oxen::quic explicit operator bool() const { return not buf.empty(); } - bool operator==(const gnutls_key& other) const { return buf == other.buf; } - bool operator!=(const gnutls_key& other) const { return !(*this == other); } + bool operator==(const gtls_key& other) const { return buf == other.buf; } + bool operator!=(const gtls_key& other) const { return !(*this == other); } }; // key: remote key to verify, alpn: negotiated alpn's using key_verify_callback = std::function; inline const gnutls_datum_t GNUTLS_DEFAULT_ALPN{ - const_cast(reinterpret_cast(default_alpn_str.data())), - static_cast(default_alpn_str.size())}; + const_cast(default_alpn_str.data()), default_alpn_str.size()}; struct gnutls_callback_wrapper { - gnutls_callback cb = nullptr; - unsigned int htype = 20; - unsigned int when = 1; - unsigned int incoming = 0; + gnutls_callback cb{nullptr}; + unsigned int htype{20}; + unsigned int when{1}; + unsigned int incoming{0}; bool applies(unsigned int h, unsigned int w, unsigned int i) const { @@ -131,10 +170,10 @@ namespace oxen::quic struct x509_loader { std::variant source; - gnutls_datum_t mem{nullptr, 0}; // Will point at the string content when in_mem() is true + gnutls_datum_t mem; // Will point at the string content when in_mem() is true gnutls_x509_crt_fmt_t format{}; - x509_loader() = default; + // x509_loader() = default; x509_loader(std::string input) { if (auto path = fs::path( @@ -173,8 +212,8 @@ namespace oxen::quic { if (auto* s = std::get_if(&source)) { - mem.data = reinterpret_cast(s->data()); - mem.size = s->size(); + mem.data = reinterpret_cast(s->data()); + mem.size = static_cast(s->size()); } else { @@ -219,8 +258,7 @@ namespace oxen::quic // // Hidden behind a template so that implicit conversion to pointer doesn't cause trouble via // other unwanted implicit conversions. - template - requires std::same_as + template T> operator const T*() const { return &mem; @@ -259,20 +297,110 @@ namespace oxen::quic } }; + struct gtls_session_ticket; + using gtls_ticket_ptr = std::unique_ptr; + + struct gtls_session_ticket + { + private: + const std::vector _key; + std::vector _ticket; + // do not double free; points to vector data that will be freed already + gnutls_datum_t _data; + + explicit gtls_session_ticket( + const unsigned char* key, unsigned int keysize, const unsigned char* ticket, unsigned int ticketsize) : + _key{key, key + keysize}, _ticket{ticket, ticket + ticketsize}, _data{_ticket.data(), ticketsize} + {} + + explicit gtls_session_ticket(ustring_view key, ustring_view ticket) : + gtls_session_ticket{ + key.data(), + static_cast(key.size()), + ticket.data(), + static_cast(ticket.size())} + {} + + public: + gtls_session_ticket() = delete; + gtls_session_ticket(gtls_session_ticket&& t) = delete; + gtls_session_ticket(const gtls_session_ticket& t) = delete; + gtls_session_ticket& operator=(gtls_session_ticket&&) = delete; + gtls_session_ticket& operator=(const gtls_session_ticket&) = delete; + + static gtls_ticket_ptr make(const gnutls_datum_t* key, const gnutls_datum_t* ticket) + { + return gtls_ticket_ptr(new gtls_session_ticket{key->data, key->size, ticket->data, ticket->size}); + } + + static gtls_ticket_ptr make(ustring_view key, const gnutls_datum_t* ticket) + { + return gtls_ticket_ptr( + new gtls_session_ticket{key.data(), static_cast(key.size()), ticket->data, ticket->size}); + } + + // Returns a view of the key for this ticket. The view is valid as long as this + // gtls_session_ticket object remains alive, and so can be used (for example) as the key of + // a map containing the object in the value. + ustring_view key() const { return {_key.data(), _key.size()}; } + + // Returns a view of the ticket data. + ustring_view ticket() const { return {_ticket.data(), _ticket.size()}; } + + // Accesses the ticket data pointer as needed by gnutls API + const gnutls_datum_t* datum() const { return &_data; } + gnutls_datum_t* datum() { return &_data; } + }; + + struct Packet; + + struct gtls_reset_token + { + static constexpr size_t TOKENSIZE{NGTCP2_STATELESS_RESET_TOKENLEN}; + static constexpr size_t RANDSIZE{NGTCP2_MIN_STATELESS_RESET_RANDLEN}; + + static constexpr std::chrono::milliseconds LIFETIME{10min}; + + private: + gtls_reset_token(const uint8_t* _tok, const uint8_t* _rand = nullptr); + gtls_reset_token(uint8_t* _static_secret, size_t _secret_len, const quic_cid& cid); + + public: + std::chrono::steady_clock::time_point expiry{get_time() + LIFETIME}; + + std::array _tok{}; + std::array _rand{}; + + const uint8_t* token() { return _tok.data(); } + const uint8_t* rand() { return _rand.data(); } + + bool is_expired(time_point now) const { return expiry < now; } + + static void generate_token(uint8_t* buffer, uint8_t* _static_secret, size_t _secret_len, const quic_cid& cid); + static void generate_rand(uint8_t* buffer); + static std::shared_ptr generate(uint8_t* _static_secret, size_t _secret_len, const quic_cid& cid); + static std::shared_ptr make_copy(const uint8_t* tok_buf, const uint8_t* rand_buf = nullptr); + static std::shared_ptr parse_packet(const Packet& pkt); + + auto operator<=>(const gtls_reset_token& t) const { return std::tie(_tok, _rand) <=> std::tie(t._tok, t._rand); } + bool operator==(const gtls_reset_token& t) const { return (*this <=> t) == 0; } + }; + class GNUTLSCreds : public TLSCreds { friend class GNUTLSSession; - private: - // Construct from raw Ed25519 keys GNUTLSCreds(std::string_view ed_seed, std::string_view ed_pubkey); public: - gnutls_pcert_st pcrt; - gnutls_privkey_t pkey; + static std::shared_ptr make_from_ed_keys(std::string_view seed, std::string_view pubkey); + static std::shared_ptr make_from_ed_seckey(std::string_view sk); ~GNUTLSCreds(); + private: + gnutls_pcert_st pcrt; + gnutls_privkey_t pkey; const bool using_raw_pk{false}; gnutls_certificate_credentials_t cred; @@ -281,15 +409,13 @@ namespace oxen::quic gnutls_priority_t priority_cache; + public: + std::unique_ptr make_session( + Connection& c, const std::shared_ptr&, const std::vector& alpns) override; + void load_keys(x509_loader& seed, x509_loader& pk); void set_key_verify_callback(key_verify_callback cb) { key_verify = std::move(cb); } - - static std::shared_ptr make_from_ed_keys(std::string_view seed, std::string_view pubkey); - - static std::shared_ptr make_from_ed_seckey(std::string_view sk); - - std::unique_ptr make_session(Connection& c, const std::vector& alpns) override; }; class GNUTLSSession : public TLSSession @@ -299,21 +425,25 @@ namespace oxen::quic private: gnutls_session_t session; - gnutls_datum_t session_ticket_key; + gtls_datum session_ticket_key{}; gnutls_anti_replay_t anti_replay; - bool is_client; + const bool _is_client; + const bool _0rtt_enabled; - gnutls_key _expected_remote_key{}; + ustring _selected_alpn{}; + gtls_key _expected_remote_key{}; + gtls_key _remote_key{}; - gnutls_key _remote_key{}; + void set_selected_alpn(); public: GNUTLSSession( GNUTLSCreds& creds, + const std::shared_ptr& ctx, Connection& c, const std::vector& alpns, - std::optional expected_key = std::nullopt); + std::optional expected_key = std::nullopt); ~GNUTLSSession(); @@ -321,8 +451,6 @@ namespace oxen::quic void* get_anti_replay() const override { return anti_replay; } - const void* get_session_ticket_key() const override { return &session_ticket_key; } - bool get_early_data_accepted() const override { return gnutls_session_get_flags(session) & GNUTLS_SFLAGS_EARLY_DATA; @@ -330,13 +458,13 @@ namespace oxen::quic ustring_view remote_key() const override { return _remote_key.view(); } - ustring_view selected_alpn() override; + ustring_view selected_alpn() const override { return _selected_alpn; } bool validate_remote_key(); int send_session_ticket() override; - void set_expected_remote_key(ustring key) override { _expected_remote_key(key); } + void set_expected_remote_key(ustring_view key) override { _expected_remote_key(key); } }; GNUTLSSession* get_session_from_gnutls(gnutls_session_t g_session); diff --git a/include/oxen/quic/iochannel.hpp b/include/oxen/quic/iochannel.hpp index 029b9cc1..89e4da11 100644 --- a/include/oxen/quic/iochannel.hpp +++ b/include/oxen/quic/iochannel.hpp @@ -1,4 +1,5 @@ #pragma once + #include #include "connection_ids.hpp" diff --git a/include/oxen/quic/loop.hpp b/include/oxen/quic/loop.hpp index 6b46dfed..6e0e46b5 100644 --- a/include/oxen/quic/loop.hpp +++ b/include/oxen/quic/loop.hpp @@ -206,17 +206,17 @@ namespace oxen::quic Configurable parameters: - start_immediately : will call ::event_add() before returning the ticker - - fixed_interval : - - if FALSE (default behavior), will attempt to execute every `interval`, regardless of how long the - event itself takes - - if TRUE, will wait the entire `interval` after finishing execution of the event before attempting - execution again + - wait : + - if FALSE (default behavior), the interval will not wait for the event to complete. will attempt to + execute every `interval`, regardless of how long the event itself takes. + - if TRUE, the interval will wait for the event to complete before beginning. It will wait the entire + `interval` after finishing execution of the event before attempting execution again. */ template [[nodiscard]] std::shared_ptr call_every( - std::chrono::microseconds interval, Callable&& f, bool start_immediately = true, bool fixed_interval = false) + std::chrono::microseconds interval, Callable&& f, bool start_immediately = true, bool wait = false) { - return _call_every(interval, std::forward(f), Loop::loop_id, start_immediately, fixed_interval); + return _call_every(interval, std::forward(f), Loop::loop_id, start_immediately, wait); } template diff --git a/include/oxen/quic/network.hpp b/include/oxen/quic/network.hpp index 4a6ad663..30301014 100644 --- a/include/oxen/quic/network.hpp +++ b/include/oxen/quic/network.hpp @@ -88,17 +88,17 @@ namespace oxen::quic Configurable parameters: - start_immediately : will call ::event_add() before returning the ticker - - fixed_interval : - - if FALSE (default behavior), will attempt to execute every `interval`, regardless of how long the - event itself takes - - if TRUE, will wait the entire `interval` after finishing execution of the event before attempting - execution again + - wait : + - if FALSE (default behavior), the interval will not wait for the event to complete. will attempt to + execute every `interval`, regardless of how long the event itself takes. + - if TRUE, the interval will wait for the event to complete before beginning. It will wait the entire + `interval` after finishing execution of the event before attempting execution again. */ template [[nodiscard]] std::shared_ptr call_every( - std::chrono::microseconds interval, Callable&& f, bool start_immediately = true, bool fixed_interval = false) + std::chrono::microseconds interval, Callable&& f, bool start_immediately = true, bool wait = false) { - return _loop->_call_every(interval, std::forward(f), net_id, start_immediately, fixed_interval); + return _loop->_call_every(interval, std::forward(f), net_id, start_immediately, wait); } template diff --git a/include/oxen/quic/opt.hpp b/include/oxen/quic/opt.hpp index fd80cc1a..322423d0 100644 --- a/include/oxen/quic/opt.hpp +++ b/include/oxen/quic/opt.hpp @@ -3,7 +3,7 @@ #include #include "address.hpp" -#include "crypto.hpp" +#include "gnutls_crypto.hpp" #include "types.hpp" namespace oxen::quic @@ -131,7 +131,7 @@ namespace oxen::quic // data. struct static_secret { - inline static constexpr size_t SECRET_MIN_SIZE = 16; + inline static constexpr size_t SECRET_MIN_SIZE{16}; ustring secret; explicit static_secret(ustring s) : secret{std::move(s)} @@ -154,7 +154,7 @@ namespace oxen::quic manual_routing() = default; - send_handler_t send_hook = nullptr; + send_handler_t send_hook{nullptr}; public: explicit manual_routing(send_handler_t cb) : send_hook{std::move(cb)} @@ -181,8 +181,8 @@ namespace oxen::quic using buffer_hook_t = std::function; private: - buffer_hook_t _hook = nullptr; - bool _persist = true; + buffer_hook_t _hook{nullptr}; + bool _persist{true}; public: watermark() = default; @@ -202,5 +202,90 @@ namespace oxen::quic _hook = nullptr; } }; + } // namespace opt + + using gtls_db_validate_cb = std::function; + using gtls_db_get_cb = std::function; + using gtls_db_put_cb = std::function; + + namespace opt + { + /** 0-RTT ticketing: + The application has two choices in managing 0-RTT ticket storage and the server-side anti-replay db. Passing + either the default-constructed or expiry window-constructed struct will signal to the endpoints that all storage + will happen internally. + The application can also pass ALL callbacks and take full responsiblity for management. In doing so, the user + must still pass an expiry window value, as that is given directly to gnutls. The following details enumerate the + specifics on the parameters and resulting capacities needed from the application. + + - `expiry` : In order to prevent the boundless recording of ClientHello messages, a certain window can be set to + only record messages within this time period. Any ClientHello messages received outside of this window are + considered to be replays, while those received within the period are referenced against the database. The + specific calculation of the 'obfuscated_ticket_age' is enumerated in RFC 8446 - Section 8.2 + see: + https://datatracker.ietf.org/doc/html/rfc8446#section-8.2 + + - `gtls_db_validate_cb` : The invocation of this cb provides the session ticket and the current ticket time given + by ngtcp2. All tickets should be held through the application chosen expiry window. The server must return + true/false in the following circumstances: + - Ticket not found -> store ticket, return 0 + - Ticket found... + - ...and is expired -> store ticket, return non-zero + - ...and is NOT expired -> KEEP TICKET, return 0 + see: + https://www.gnutls.org/manual/html_node/Core-TLS-API.html#gnutls_005fanti_005freplay_005fset_005fadd_005ffunction + + - `gtls_db_get_cb` : The invocation is provided one ustring_view storing the ticket key. The application will + return the session ticket in a unique ptr, or nullptr if not found. This can be constructed using the static + gtls_session_ticket::make(...) overrides provded. If the endpoint successfully fetches the ticket, it must + ERASE THE ENTRY. Servers will reject already used tokens in their cb, so the client must not store them. + + - `gtls_db_put_cb` : The invocation is provided one gtls_session_ticket held in a unique pointer and the expiry + time for the entry. This can be queried independently at any time using the gnutls method + `gnutls_db_check_entry_expire_time`, but why be redundant? + + Note: If one callback is provided, all the other must be as well. Endpoints are bi-directional in libquic, so + the server-only validate hooks and client-only get/put hooks must be provided together. Moreover, ALL hooks + must access the database being used; the verify hook must be consistent with the others. + */ + struct enable_0rtt_ticketing + { + std::chrono::milliseconds window{DEFAULT_ANTI_REPLAY_WINDOW}; + + gtls_db_validate_cb check{nullptr}; + gtls_db_get_cb fetch{nullptr}; + gtls_db_put_cb put{nullptr}; + + enable_0rtt_ticketing() = default; + + enable_0rtt_ticketing(std::chrono::milliseconds w) : window{w} {} + + explicit enable_0rtt_ticketing( + std::chrono::milliseconds w, gtls_db_validate_cb v, gtls_db_get_cb g, gtls_db_put_cb p) : + window{w}, check{std::move(v)}, fetch{std::move(g)}, put{std::move(p)} + { + if (not(check and fetch and put)) + throw std::invalid_argument{"All callbacks must be set!"}; + } + }; + + /** Handshake Key Verification: + This can be passed on endpoint creation to turn OFF key verification in the handshake process. This can be passed + to either endpoint::listen(...) or endpoint::connect(...) to disable it for that tls session + */ + struct disable_key_verification + {}; + + /** Stateless Reset Tokens: + This can be passed on endpoint creation to turn OFF stateless reset tokens. This will result in few main + functional differences: + - When processing incoming packets, if both the dcid cannot be matched to an active connection and calls to + `ngtcp2_accept` are unsuccessful, then a stateless reset packet will NOT be sent + - When connection id's are generated, a corresponding stateless reset token will NOT be created + + Note: DO NOT ENABLE STATELESS RESET AMONGST ENDPOINTS SHARING THE SAME STATIC KEY + */ + struct disable_stateless_reset + {}; } // namespace opt } // namespace oxen::quic diff --git a/include/oxen/quic/stream.hpp b/include/oxen/quic/stream.hpp index 4519ef4f..65946482 100644 --- a/include/oxen/quic/stream.hpp +++ b/include/oxen/quic/stream.hpp @@ -36,6 +36,12 @@ namespace oxen::quic void _chunk_sender_trace(const char* file, int lineno, std::string_view message); void _chunk_sender_trace(const char* file, int lineno, std::string_view message, size_t val); + inline namespace concepts + { + template + concept stream_derived_type = std::derived_from; + } + class Stream : public IOChannel, public std::enable_shared_from_this { friend class TestHelper; @@ -137,6 +143,9 @@ namespace oxen::quic size_t unsent_impl() const override; private: + // Called if 0-RTT early data was rejected; marks all sent data as unsent + void revert_stream(); + std::vector pending() override; size_t _unacked_size{0}; diff --git a/include/oxen/quic/udp.hpp b/include/oxen/quic/udp.hpp index 7b3e050a..60b8f41b 100644 --- a/include/oxen/quic/udp.hpp +++ b/include/oxen/quic/udp.hpp @@ -35,23 +35,26 @@ namespace oxen::quic { Path path; ngtcp2_pkt_info pkt_info{}; - std::variant pkt_data; + std::vector pkt_data; + std::span data_sp; + size_t size() const { return pkt_data.size(); } + + // Return a string_view type, starting from index `pos` template - std::basic_string_view data() const + std::basic_string_view data(size_t pos = 0) const { - return std::visit( - [](const auto& d) { - return std::basic_string_view{reinterpret_cast(d.data()), d.size()}; - }, - pkt_data); + return std::basic_string_view{reinterpret_cast(data_sp.data() + pos), data_sp.size() - pos}; } /// Constructs a packet from a path and data view: - Packet(Path p, bstring_view d) : path{std::move(p)}, pkt_data{std::move(d)} {} + Packet(Path p, bstring_view d) : path{std::move(p)}, data_sp{d.begin(), d.end()} {} /// Constructs a packet from a path and transferred data: - Packet(Path p, bstring&& d) : path{std::move(p)}, pkt_data{std::move(d)} {} + Packet(Path p, bstring&& d) : path{std::move(p)}, pkt_data(d.size()), data_sp{pkt_data.data(), d.size()} + { + std::memmove(pkt_data.data(), d.data(), d.size()); + } /// Constructs a packet from a local address, data, and the IP header; remote addr and ECN /// data are extracted from the header. diff --git a/include/oxen/quic/utils.hpp b/include/oxen/quic/utils.hpp index 56762752..a5453319 100644 --- a/include/oxen/quic/utils.hpp +++ b/include/oxen/quic/utils.hpp @@ -33,6 +33,7 @@ extern "C" #include #include #include +#include #include #include #include @@ -42,6 +43,8 @@ namespace oxen::quic { class connection_interface; + using time_point = std::chrono::steady_clock::time_point; + // called when a connection's handshake completes // the server will call this when it sends the final handshake packet // the client will call this when it receives that final handshake packet @@ -112,6 +115,8 @@ namespace oxen::quic inline constexpr std::chrono::seconds DEFAULT_HANDSHAKE_TIMEOUT = 10s; inline constexpr std::chrono::seconds DEFAULT_IDLE_TIMEOUT = 30s; + inline constexpr size_t inverse_golden_ratio = sizeof(size_t) >= 8 ? 0x9e37'79b9'7f4a'7c15 : 0x9e37'79b9; + // NGTCP2 sets the path_pmtud_payload to 1200 on connection creation, then discovers upwards // to a theoretical max of 1452. In 'lazy' mode, we take in split packets under the current max // pmtud size. In 'greedy' mode, we take in up to double the current pmtud size to split amongst @@ -145,6 +150,14 @@ namespace oxen::quic namespace detail { + struct ustring_hasher + { + size_t operator()(const ustring_view& sv) const noexcept + { + return std::hash{}({reinterpret_cast(sv.data()), sv.size()}); + } + }; + template struct bsv_literal { @@ -197,7 +210,7 @@ namespace oxen::quic return {reinterpret_cast(x.data()), x.size()}; } - std::chrono::steady_clock::time_point get_time(); + time_point get_time(); std::chrono::nanoseconds get_timestamp(); template diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1c5a79b1..31dec104 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -12,6 +12,7 @@ add_library(quic endpoint.cpp error.cpp format.cpp + gnutls_crypto.cpp gnutls_creds.cpp gnutls_session.cpp iochannel.cpp diff --git a/src/address.cpp b/src/address.cpp index c9e05e8b..f1f737a9 100644 --- a/src/address.cpp +++ b/src/address.cpp @@ -19,7 +19,7 @@ namespace oxen::quic sin6.sin6_port = oxenc::host_to_big(port); _addr.addrlen = sizeof(sockaddr_in6); if (!addr.empty()) - parse_addr(sin6.sin6_addr, addr); + detail::parse_addr(sin6.sin6_addr, addr); else // Otherwise default to all-0 IPv6 address with the dual stack flag enabled dual_stack = true; @@ -33,7 +33,7 @@ namespace oxen::quic #ifdef OXEN_LIBQUIC_ADDRESS_NO_DUAL_STACK if (!addr.empty()) #endif - parse_addr(sin4.sin_addr, addr); + detail::parse_addr(sin4.sin_addr, addr); } } diff --git a/src/btstream.cpp b/src/btstream.cpp index 1500314e..2a29d917 100644 --- a/src/btstream.cpp +++ b/src/btstream.cpp @@ -6,8 +6,6 @@ namespace oxen::quic { - inline auto bp_cat = oxen::log::Cat("bparser"); - static std::pair get_location(bstring& data, std::string_view substr) { auto* bsubstr = reinterpret_cast(substr.data()); @@ -37,40 +35,40 @@ namespace oxen::quic void message::respond(bstring_view body, bool error) const { - log::trace(bp_cat, "{} called", __PRETTY_FUNCTION__); + log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); if (auto ptr = return_sender.lock()) ptr->respond(req_id, body, error); else - log::warning(bp_cat, "BTRequestStream unable to send response: stream has gone away"); + log::warning(log_cat, "BTRequestStream unable to send response: stream has gone away"); } void BTRequestStream::handle_bp_opt(std::function close_cb) { - log::debug(bp_cat, "Bparser set user-provided close callback!"); + log::debug(log_cat, "Bparser set user-provided close callback!"); close_callback = std::move(close_cb); } void BTRequestStream::handle_bp_opt(std::function request_handler) { - log::debug(bp_cat, "Bparser set generic request handler"); + log::debug(log_cat, "Bparser set generic request handler"); generic_handler = std::move(request_handler); } void BTRequestStream::respond(int64_t rid, bstring_view body, bool error) { - log::trace(bp_cat, "{} called", __PRETTY_FUNCTION__); + log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); send(sent_request{*this, encode_response(rid, body, error), rid}.data); } void BTRequestStream::check_timeouts() { - log::trace(bp_cat, "{} called", __PRETTY_FUNCTION__); + log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); return check_timeouts(get_time()); } void BTRequestStream::check_timeouts(std::optional now) { - log::trace(bp_cat, "{} called", __PRETTY_FUNCTION__); + log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); while (!sent_reqs.empty()) { @@ -86,14 +84,14 @@ namespace oxen::quic } catch (const std::exception& e) { - log::error(bp_cat, "Uncaught exception from timeout response handler: {}", e.what()); + log::error(log_cat, "Uncaught exception from timeout response handler: {}", e.what()); } } } void BTRequestStream::receive(bstring_view data) { - log::trace(bp_cat, "bparser recv data callback called!"); + log::trace(log_cat, "bparser recv data callback called!"); if (is_closing()) return; @@ -104,14 +102,14 @@ namespace oxen::quic } catch (const std::exception& e) { - log::error(bp_cat, "Exception caught: {}", e.what()); + log::error(log_cat, "Exception caught: {}", e.what()); close(BPARSER_ERROR_EXCEPTION); } } void BTRequestStream::closed(uint64_t app_code) { - log::debug(bp_cat, "bparser closed with {}", quic_strerror(app_code)); + log::debug(log_cat, "bparser closed with {}", quic_strerror(app_code)); // First time out any pending requests, even if they haven't hit the timer, because we're // being closed and so they can never be answered. @@ -128,17 +126,17 @@ namespace oxen::quic void BTRequestStream::register_generic_handler(std::function request_handler) { - log::debug(bp_cat, "Bparser set generic request handler"); + log::debug(log_cat, "Bparser set generic request handler"); endpoint.call([this, func = std::move(request_handler)]() mutable { generic_handler = std::move(func); }); } void BTRequestStream::handle_input(message msg) { - log::trace(bp_cat, "{} called to handle {} input", __PRETTY_FUNCTION__, msg.type()); + log::trace(log_cat, "{} called to handle {} input", __PRETTY_FUNCTION__, msg.type()); if (auto type = msg.type(); type == message::TYPE_REPLY || type == message::TYPE_ERROR) { - log::trace(log_cat, "Looking for request with req_id={}", msg.req_id); + log::debug(log_cat, "Looking for request with req_id={}", msg.req_id); // Iterate using forward iterators, s.t. we go highest (newest) rids to lowest (oldest) rids. // As a result, our comparator checks if the sent request ID is greater thanthan the target rid auto itr = std::lower_bound( @@ -149,7 +147,7 @@ namespace oxen::quic if (itr != sent_reqs.end()) { - log::debug(bp_cat, "Successfully matched response to sent request!"); + log::debug(log_cat, "Successfully matched response (req_id={}) to sent request!", msg.req_id); auto req = std::move(*itr); sent_reqs.erase(itr); try @@ -158,7 +156,7 @@ namespace oxen::quic } catch (const std::exception& e) { - log::error(bp_cat, "Uncaught exception from response handler: {}", e.what()); + log::error(log_cat, "Uncaught exception from response handler: {}", e.what()); } } return; @@ -174,26 +172,26 @@ namespace oxen::quic { if (auto itr = func_map.find(ep); itr != func_map.end()) { - log::debug(bp_cat, "Executing request endpoint {}", msg.endpoint()); + log::debug(log_cat, "Executing request endpoint {}", msg.endpoint()); return itr->second(std::move(msg)); } } if (generic_handler) { - log::debug(bp_cat, "Executing generic request handler for endpoint {}", msg.endpoint()); + log::debug(log_cat, "Executing generic request handler for endpoint {}", msg.endpoint()); return generic_handler(std::move(msg)); } throw no_such_endpoint{}; } catch (const no_such_endpoint&) { - log::warning(bp_cat, "No handler found for endpoint {}, returning error response", ep); + log::warning(log_cat, "No handler found for endpoint {}, returning error response", ep); respond(req_id, convert_sv("Invalid endpoint '{}'"_format(ep)), true); } catch (const std::exception& e) { log::error( - bp_cat, + log_cat, "Handler for {} threw an uncaught exception ({}); returning a generic error message", ep, e.what()); @@ -203,7 +201,7 @@ namespace oxen::quic void BTRequestStream::process_incoming(std::string_view req) { - log::trace(bp_cat, "{} called", __PRETTY_FUNCTION__); + log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); while (not req.empty()) { @@ -308,7 +306,7 @@ namespace oxen::quic } catch (const std::exception& e) { - log::error(bp_cat, "Uncaught exception from closed-stream sent request response handler: {}", e.what()); + log::error(log_cat, "Uncaught exception from closed-stream sent request response handler: {}", e.what()); } } return nullptr; diff --git a/src/connection.cpp b/src/connection.cpp index bffe2498..3a307068 100644 --- a/src/connection.cpp +++ b/src/connection.cpp @@ -41,295 +41,310 @@ namespace oxen::quic } } - // ngtcp2 internal callback functions (that are deliberately source only, not a published - // header); we group them all in this struct to make them slightly easier to manage, but more - // importantly, because `Callbacks` is a friend-with-benefits of Endpoint that can touch its - // privates. - struct Callbacks + int connection_callbacks::on_ack_datagram(ngtcp2_conn* /* conn */, uint64_t dgram_id, void* user_data) { + log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); + return static_cast(user_data)->ack_datagram(dgram_id); + } - static int on_ack_datagram(ngtcp2_conn* /* conn */, uint64_t dgram_id, void* user_data) - { - log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); - return static_cast(user_data)->ack_datagram(dgram_id); - } - - static int on_recv_datagram( - ngtcp2_conn* /* conn */, uint32_t flags, const uint8_t* data, size_t datalen, void* user_data) - { - log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); - return static_cast(user_data)->recv_datagram( - {reinterpret_cast(data), datalen}, flags & NGTCP2_STREAM_DATA_FLAG_FIN); - } - - static int on_recv_token(ngtcp2_conn* /* conn */, const uint8_t* token, size_t tokenlen, void* user_data) - { - log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); - return static_cast(user_data)->recv_token(token, tokenlen); - } - - static int on_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*/) - { - log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); - return static_cast(user_data)->stream_receive( - stream_id, {reinterpret_cast(data), datalen}, flags & NGTCP2_STREAM_DATA_FLAG_FIN); - } - - static int on_acked_stream_data_offset( - ngtcp2_conn* /*conn_*/, - int64_t stream_id, - uint64_t offset, - uint64_t datalen, - void* user_data, - void* /*stream_user_data*/) - { - log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); - log::trace(log_cat, "Ack [{},{}]", offset, offset + datalen); - return static_cast(user_data)->stream_ack(stream_id, datalen); - } + int connection_callbacks::on_recv_datagram( + ngtcp2_conn* /* conn */, uint32_t flags, const uint8_t* data, size_t datalen, void* user_data) + { + log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); + return static_cast(user_data)->recv_datagram( + {reinterpret_cast(data), datalen}, flags & NGTCP2_STREAM_DATA_FLAG_FIN); + } - static int on_stream_open(ngtcp2_conn* /*conn*/, int64_t stream_id, void* user_data) - { - log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); - return static_cast(user_data)->stream_opened(stream_id); - } + int connection_callbacks::on_recv_token(ngtcp2_conn* /* conn */, const uint8_t* token, size_t tokenlen, void* user_data) + { + log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); + return static_cast(user_data)->recv_token(token, tokenlen); + } - static int on_stream_close( - ngtcp2_conn* /*conn*/, - uint32_t /*flags*/, - int64_t stream_id, - uint64_t app_error_code, - void* user_data, - void* /*stream_user_data*/) - { - log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); - static_cast(user_data)->stream_closed(stream_id, app_error_code); - return 0; - } + int connection_callbacks::on_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*/) + { + log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); + return static_cast(user_data)->stream_receive( + stream_id, {reinterpret_cast(data), datalen}, flags & NGTCP2_STREAM_DATA_FLAG_FIN); + } - static int on_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*/) - { - log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); - static_cast(user_data)->stream_closed(stream_id, app_error_code); - return 0; - } + int connection_callbacks::on_acked_stream_data_offset( + ngtcp2_conn* /*conn_*/, + int64_t stream_id, + uint64_t offset, + uint64_t datalen, + void* user_data, + void* /*stream_user_data*/) + { + log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); + log::trace(log_cat, "Ack [{},{}]", offset, offset + datalen); + return static_cast(user_data)->stream_ack(stream_id, datalen); + } - static int on_handshake_completed(ngtcp2_conn*, void* user_data) - { - auto* conn = static_cast(user_data); - auto dir_str = conn->is_inbound() ? "SERVER"s : "CLIENT"s; + int connection_callbacks::on_stream_open(ngtcp2_conn* /*conn*/, int64_t stream_id, void* user_data) + { + log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); + return static_cast(user_data)->stream_opened(stream_id); + } - log::trace(log_cat, "HANDSHAKE COMPLETED on {} connection", dir_str); + int connection_callbacks::on_stream_close( + ngtcp2_conn* /*conn*/, + uint32_t /*flags*/, + int64_t stream_id, + uint64_t app_error_code, + void* user_data, + void* /*stream_user_data*/) + { + log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); + static_cast(user_data)->stream_closed(stream_id, app_error_code); + return 0; + } - int rv = 0; + int connection_callbacks::on_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*/) + { + log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); + static_cast(user_data)->stream_closed(stream_id, app_error_code); + return 0; + } - if (conn->is_inbound()) - { - rv = conn->server_handshake_completed(); + int connection_callbacks::on_handshake_completed(ngtcp2_conn*, void* user_data) + { + auto* conn = static_cast(user_data); + auto dir_str = conn->is_inbound() ? "SERVER"s : "CLIENT"s; - if (conn->conn_established_cb) - conn->conn_established_cb(*conn); - else - conn->endpoint().connection_established(*conn); - } - else - rv = conn->client_handshake_completed(); + log::trace(log_cat, "HANDSHAKE COMPLETED on {} connection", dir_str); - return rv; - } + int rv = 0; - static int on_handshake_confirmed(ngtcp2_conn*, void* user_data) + if (conn->is_inbound()) { - auto* conn = static_cast(user_data); - - // server should never call this, as it "confirms" on handshake completed - assert(conn->is_outbound()); - log::trace(log_cat, "HANDSHAKE CONFIRMED on CLIENT connection"); + rv = conn->server_handshake_completed(); if (conn->conn_established_cb) conn->conn_established_cb(*conn); else conn->endpoint().connection_established(*conn); - - return 0; } + else + rv = conn->client_handshake_completed(); - static void rand_cb(uint8_t* dest, size_t destlen, const ngtcp2_rand_ctx* rand_ctx) - { - (void)rand_ctx; - (void)gnutls_rnd(GNUTLS_RND_RANDOM, dest, destlen); - } + return rv; + } - static int on_connection_id_status( - ngtcp2_conn* /* _conn */, - ngtcp2_connection_id_status_type type, - uint64_t /* seq */, - const ngtcp2_cid* cid, - const uint8_t* /* token */, - void* user_data) - { - auto* conn = static_cast(user_data); + int connection_callbacks::on_handshake_confirmed(ngtcp2_conn*, void* user_data) + { + auto* conn = static_cast(user_data); - auto dir_str = conn->is_inbound() ? "SERVER"s : "CLIENT"s; - auto action = type == NGTCP2_CONNECTION_ID_STATUS_TYPE_ACTIVATE ? "ACTIVATING"s : "DEACTIVATING"s; - log::trace(log_cat, "{} {} DCID:{}", dir_str, action, oxenc::to_hex(cid->data, cid->data + cid->datalen)); + // server should never call this, as it "confirms" on handshake completed + assert(conn->is_outbound()); + log::trace(log_cat, "HANDSHAKE CONFIRMED on CLIENT connection"); - // auto& ep = conn->endpoint(); + if (conn->conn_established_cb) + conn->conn_established_cb(*conn); + else + conn->endpoint().connection_established(*conn); - switch (type) - { - case NGTCP2_CONNECTION_ID_STATUS_TYPE_ACTIVATE: - // ep.associate_cid(cid, *conn); - break; - case NGTCP2_CONNECTION_ID_STATUS_TYPE_DEACTIVATE: - // ep.dissociate_cid(cid, *conn); - break; - default: - break; - } + return 0; + } - return 0; - } + void connection_callbacks::rand_cb(uint8_t* dest, size_t destlen, const ngtcp2_rand_ctx* rand_ctx) + { + (void)rand_ctx; + (void)gnutls_rnd(GNUTLS_RND_RANDOM, dest, destlen); + } + + int connection_callbacks::on_connection_id_status( + ngtcp2_conn* /* _conn */, + ngtcp2_connection_id_status_type type, + uint64_t /* seq */, + const ngtcp2_cid* cid, + const uint8_t* token, + void* user_data) + { + auto* conn = static_cast(user_data); + assert(conn->stateless_reset_enabled()); + + auto& ep = conn->endpoint(); - static int get_new_connection_id( - ngtcp2_conn* /* _conn */, ngtcp2_cid* cid, uint8_t* token, size_t cidlen, void* user_data) + // if token is not null, map to cid + if (token and type == NGTCP2_CONNECTION_ID_STATUS_TYPE_ACTIVATE) { - log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); + log::debug(log_cat, "Activating stateless reset token for new CID from remote: {}", conn->remote()); + ep.activate_cid(cid, token, *conn); + } + else if (type == NGTCP2_CONNECTION_ID_STATUS_TYPE_DEACTIVATE) + { + log::debug(log_cat, "Deactivating stateless reset token for CID from remote: {}", conn->remote()); + ep.deactivate_cid(cid, *conn); + } - if (gnutls_rnd(GNUTLS_RND_RANDOM, cid->data, cidlen) != 0) - return NGTCP2_ERR_CALLBACK_FAILURE; + return 0; + } - cid->datalen = cidlen; - auto* conn = static_cast(user_data); - auto& ep = conn->endpoint(); + int connection_callbacks::get_new_connection_id( + ngtcp2_conn* /* _conn */, ngtcp2_cid* cid, uint8_t* token, size_t cidlen, void* user_data) + { + log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); - if (ngtcp2_crypto_generate_stateless_reset_token( - token, ep._static_secret.data(), ep._static_secret.size(), cid) != 0) - return NGTCP2_ERR_CALLBACK_FAILURE; + if (gnutls_rnd(GNUTLS_RND_RANDOM, cid->data, cidlen) != 0) + return NGTCP2_ERR_CALLBACK_FAILURE; - auto dir_str = conn->is_outbound() ? "CLIENT"s : "SERVER"s; - log::trace(log_cat, "{} generated new CID for {}", dir_str, conn->reference_id()); - ep.associate_cid(cid, *conn); + cid->datalen = cidlen; + auto* conn = static_cast(user_data); + auto& ep = conn->endpoint(); - // TODO: send new stateless reset token - // write packet using ngtcp2_pkt_write_stateless_reset - // define recv_stateless_reset for client - // set stateless_reset_present in transport params + if (ngtcp2_crypto_generate_stateless_reset_token(token, ep._static_secret.data(), ep._static_secret.size(), cid) != + 0) + return NGTCP2_ERR_CALLBACK_FAILURE; - return 0; - } + log::trace(log_cat, "{} generated new CID for {}", conn->is_outbound() ? "CLIENT" : "SERVER", conn->reference_id()); + ep.associate_cid(cid, *conn); - static int remove_connection_id(ngtcp2_conn* /* _conn */, const ngtcp2_cid* cid, void* user_data) - { - log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); + return 0; + } - auto* conn = static_cast(user_data); - auto dir_str = conn->is_outbound() ? "CLIENT"s : "SERVER"s; - log::trace(log_cat, "{} dissociating CID for {}", dir_str, conn->reference_id()); - conn->endpoint().dissociate_cid(cid, *conn); + int connection_callbacks::remove_connection_id(ngtcp2_conn* /* _conn */, const ngtcp2_cid* cid, void* user_data) + { + log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); - return 0; - } + auto* conn = static_cast(user_data); + auto dir_str = conn->is_outbound() ? "CLIENT"s : "SERVER"s; + log::trace(log_cat, "{} dissociating CID for {}", dir_str, conn->reference_id()); + conn->endpoint().dissociate_cid(cid, *conn); - static int extend_max_local_streams_bidi( - [[maybe_unused]] ngtcp2_conn* _conn, uint64_t /*max_streams*/, void* user_data) - { - log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); + return 0; + } - auto& conn = *static_cast(user_data); - assert(_conn == conn); + int connection_callbacks::extend_max_local_streams_bidi( + [[maybe_unused]] ngtcp2_conn* _conn, uint64_t /*max_streams*/, void* user_data) + { + log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); + // removed assert to call this hook in 0rtt connection config + auto& conn = *static_cast(user_data); - if (auto remaining = ngtcp2_conn_get_streams_bidi_left(conn); remaining > 0) - conn.check_pending_streams(remaining); + if (auto remaining = ngtcp2_conn_get_streams_bidi_left(conn); remaining > 0) + conn.check_pending_streams(remaining); - return 0; - } + return 0; + } - static int on_path_validation( - ngtcp2_conn* _conn [[maybe_unused]], - uint32_t flags, - const ngtcp2_path* path, - const ngtcp2_path* /* old_path */, - ngtcp2_path_validation_result res, - void* user_data) - { - log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); + int connection_callbacks::on_path_validation( + ngtcp2_conn* _conn [[maybe_unused]], + uint32_t flags, + const ngtcp2_path* path, + const ngtcp2_path* /* old_path */, + ngtcp2_path_validation_result res, + void* user_data) + { + log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); - auto& conn = *static_cast(user_data); - assert(_conn == conn); + auto& conn = *static_cast(user_data); + assert(_conn == conn); - if (conn.is_outbound()) - { - log::trace(log_cat, "Client updating remote addr..."); - conn.set_remote_addr(path->remote); + auto b = res == NGTCP2_PATH_VALIDATION_RESULT_SUCCESS; - return 0; - } - else if (res != NGTCP2_PATH_VALIDATION_RESULT_SUCCESS) - { - log::debug(log_cat, "Path validation unsuccessful!"); - return 0; - } - else if (not(flags & NGTCP2_PATH_VALIDATION_FLAG_NEW_TOKEN)) - { - log::debug(log_cat, "Path validation successful!"); - return 0; - } - else - return conn.server_path_validation(path); - } + if (conn.is_outbound()) + return conn.client_path_validation(path, b, flags); + else + return conn.server_path_validation(path, b, flags); + } - static int on_early_data_rejected(ngtcp2_conn* _conn, void* user_data) - { - log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); + int connection_callbacks::on_early_data_rejected(ngtcp2_conn* _conn [[maybe_unused]], void* user_data) + { + log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); - auto& conn = *static_cast(user_data); - assert(_conn == conn); + auto& conn = *static_cast(user_data); + assert(_conn == conn); - (void)conn; - (void)_conn; + log::info(log_cat, "Client resetting early streams; 0-rtt rejected by server!"); + conn.revert_early_streams(); - return 0; - } - }; + return 0; + } - void Connection::set_close_quietly() + int connection_callbacks::recv_stateless_reset( + ngtcp2_conn* _conn [[maybe_unused]], const ngtcp2_pkt_stateless_reset* sr, void* user_data) { log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); - _close_quietly = true; + auto& conn = *static_cast(user_data); + assert(_conn == conn); + + return conn.recv_stateless_reset(gtls_reset_token::make_copy(sr->stateless_reset_token, sr->rand)); } - void Connection::set_new_path(Path new_path) + int Connection::recv_stateless_reset(std::shared_ptr tok) { - _endpoint.call([this, new_path]() { _path = new_path; }); + log::trace(log_cat, "Client recv_stateless_reset cb called..."); + + if (auto it = _endpoint.reset_token_map.find(tok); it != _endpoint.reset_token_map.end()) + { + log::warning( + log_cat, + "Received stateless reset for connection ({}) on path: {}; closing immediately!", + _ref_id, + _path); + + // delete reset token after use + _endpoint.reset_token_lookup.erase(it->second); + _endpoint.reset_token_map.erase(it); + + _endpoint.drop_connection(*this, io_error{CONN_STATELESS_RESET}); + } + else + log::debug(log_cat, "Could not match received stateless reset to any connection!"); + return 0; } - int Connection::recv_token(const uint8_t* token, size_t tokenlen) + int Connection::client_path_validation(const ngtcp2_path* path, bool success, uint32_t flags) { - // This should only be called by the client, and therefore this will always have a value - assert(not remote_pubkey.empty()); - _endpoint.store_path_validation_token(remote_pubkey, {token, tokenlen}); + log::trace(log_cat, "Client path_validation cb called..."); + assert(is_outbound()); + + if (flags & NGTCP2_PATH_VALIDATION_FLAG_PREFERRED_ADDR) + { + set_remote_addr(path->remote); + log::debug(log_cat, "Client set new remote ({}) on successful path validation...", _path.remote); + } + else if (success) + log::debug(log_cat, "Client path validation succeeded as no address was provided by server..."); + else + log::warning(log_cat, "Client path validation failed; no address was provided by server..."); + return 0; } - int Connection::server_path_validation(const ngtcp2_path* path) + int Connection::server_path_validation(const ngtcp2_path* path, bool success, uint32_t flags) { + log::trace(log_cat, "Server path_validation cb called..."); assert(is_inbound()); + + if (not success) + { + log::warning(log_cat, "Server path validation failed!"); + return 0; + } + + if (not(flags & NGTCP2_PATH_VALIDATION_FLAG_NEW_TOKEN)) + { + log::debug(log_cat, "Server path validation cb did not request a new token..."); + return 0; + } + std::array token; auto len = ngtcp2_crypto_generate_regular_token( @@ -355,49 +370,60 @@ namespace oxen::quic log::debug(log_cat, "Server completed path validation!"); return 0; } + void Connection::set_close_quietly() + { + log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); + + _close_quietly = true; + } + + void Connection::set_new_path(Path new_path) + { + _endpoint.call([this, new_path]() { _path = new_path; }); + } + + int Connection::recv_token(const uint8_t* token, size_t tokenlen) + { + // This should only be called by the client, and therefore this will always have a value + _endpoint.store_path_validation_token(_path.remote, {token, tokenlen}); + return 0; + } int Connection::client_handshake_completed() { - /** TODO: - This section will be uncommented and finished upon completion of 0RTT and session resumption capabilities. - - If early data is NOT ACCEPTED, then the call to ngtcp2_conn_tls_early_data_rejected must be invoked - to reset aspects of connection state prior to early data rejection. - - If early data is ACCEPTED, then we can open streams and start doing things immediately. At that point, - we should encode and store 0RTT transport parameters. - Moreover, decoding and setting 0RTT transport parameters must be handled in connection creation. Both that - location and the required callbacks are comment-blocked in the relevant location. - */ - // if (not tls_session->get_early_data_accepted()) - //{ - // log::info(log_cat, "Early data was rejected by server!"); - - // if (auto rv = ngtcp2_conn_tls_early_data_rejected(conn.get()); rv != 0) - // { - // log::error(log_cat, "ngtcp2_conn_tls_early_data_rejected: {}", ngtcp2_strerror(rv)); - // return -1; - // } - //} - - // ustring data; - // data.resize(256); - - // if (auto len = ngtcp2_conn_encode_0rtt_transport_params(conn.get(), data.data(), data.size()); len > 0) - // { - // _endpoint.store_0rtt_transport_params(remote_pubkey, data); - // log::info(log_cat, "Client encoded and stored 0rtt transport params"); - // } - // else - // { - // log::warning(log_cat, "Client could not encode 0-RTT transport parameters: {}", ngtcp2_strerror(len)); - // } + if (_0rtt_enabled) + { + if (not tls_session->get_early_data_accepted()) + { + log::debug(log_cat, "Early data was rejected by server!"); + + if (auto rv = ngtcp2_conn_tls_early_data_rejected(conn.get()); rv != 0) + { + log::error(log_cat, "ngtcp2_conn_tls_early_data_rejected failed: {}", ngtcp2_strerror(rv)); + return -1; + } + } + + ustring data; + data.resize(256); + + if (auto len = ngtcp2_conn_encode_0rtt_transport_params(conn.get(), data.data(), data.size()); len > 0) + { + _endpoint.store_0rtt_transport_params(_path.remote, std::move(data)); + log::debug(log_cat, "Client successfully encoded and stored 0rtt transport params"); + } + else + log::warning(log_cat, "Client could not encode 0-RTT transport parameters: {}", ngtcp2_strerror(len)); + } + + log::debug(log_cat, "Client handshake completed!"); return 0; } int Connection::server_handshake_completed() { - // TODO: uncomment this when 0rtt is implemented - // tls_session->send_session_ticket(); + tls_session->send_session_ticket(); auto path = ngtcp2_conn_get_path(conn.get()); auto now = get_timestamp().count(); @@ -424,6 +450,8 @@ namespace oxen::quic return -1; } + log::debug(log_cat, "Server successfully submitted regular token on handshake completion..."); + return 0; } @@ -431,7 +459,7 @@ namespace oxen::quic { _is_validated = true; - if (is_inbound()) + if (is_inbound() and not context->disable_key_verification) remote_pubkey = dynamic_cast(get_session())->remote_key(); } @@ -440,11 +468,6 @@ namespace oxen::quic return datagrams->recv_buffer.last_cleared; } - void Connection::early_data_rejected() - { - close_connection(); - } - void Connection::set_remote_addr(const ngtcp2_addr& new_remote) { _endpoint.call([this, new_remote]() { _path.set_new_remote(new_remote); }); @@ -464,6 +487,12 @@ namespace oxen::quic _associated_cids.insert(cid); } + void Connection::delete_associated_cid(const quic_cid& cid) + { + log::debug(log_cat, "Connection (RID:{}) deleting associated cid:{}", _ref_id, cid); + _associated_cids.erase(cid); + } + ustring_view Connection::remote_key() const { return remote_pubkey; @@ -496,6 +525,38 @@ namespace oxen::quic _endpoint.close_connection(*this, io_error{error_code}); } + void Connection::make_early_streams(ngtcp2_conn* connptr) + { + log::trace(log_cat, "Client making streams to attempt 0-rtt early data!"); + + if (auto remaining = ngtcp2_conn_get_streams_bidi_left(connptr); remaining > 0) + { + log::debug(log_cat, "Client has room to promote {} streams for early data!", remaining); + check_pending_streams(remaining, true); + } + else + log::debug(log_cat, "Client has NO room to promote streams for early data!"); + } + + void Connection::revert_early_streams() + { + _endpoint.call([&]() { + log::debug(log_cat, "Client reverting {} 0-rtt streams", _early_streams.size()); + + for (auto& _id : _early_streams) + { + if (auto it = _streams.find(_id); it != _streams.end()) + { + log::trace(log_cat, "Reverting stream (ID:{})...", _id); + it->second->revert_stream(); + } + else + log::warning(log_cat, "Could not find early stream (ID:{}) to revert!", _id); + } + _early_streams.clear(); + }); + } + void Connection::handle_conn_packet(const Packet& pkt) { if (auto rv = ngtcp2_conn_in_closing_period(*this); rv != 0) @@ -594,7 +655,7 @@ namespace oxen::quic // so, we move them to the streams map, where they will get picked up by flush_streams and dump // their buffers. If none are ready, we keep chugging along and make another stream as usual. Though // if none of the pending streams are ready, the new stream really shouldn't be ready, but here we are - void Connection::check_pending_streams(uint64_t available) + void Connection::check_pending_streams(uint64_t available, bool is_early_stream) { log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); uint64_t popped = 0; @@ -605,11 +666,15 @@ namespace oxen::quic if (int rv = ngtcp2_conn_open_bidi_stream(conn.get(), &str->_stream_id, str.get()); rv == 0) { - log::debug(log_cat, "Stream [ID:{}] ready for broadcast, moving out of pending streams", str->_stream_id); + auto _id = str->_stream_id; + log::debug(log_cat, "Stream [ID:{}] ready for broadcast, moving out of pending streams", _id); str->set_ready(); popped += 1; - _streams[str->_stream_id] = std::move(str); + _streams[_id] = std::move(str); pending_streams.pop_front(); + + if (_0rtt_enabled and is_early_stream) + _early_streams.emplace_back(_id); } else return; @@ -996,7 +1061,10 @@ namespace oxen::quic { if (ngtcp2_err_is_fatal(nwrite)) { - log::critical(log_cat, "Fatal ngtcp2 error: could not write frame - \"{}\"", ngtcp2_strerror(nwrite)); + log::warning( + log_cat, + "Fatal ngtcp2 error: could not write frame - \"{}\" - closing connection...", + ngtcp2_strerror(nwrite)); _endpoint.close_connection(*this, io_error{(int)nwrite}); return; } @@ -1093,7 +1161,7 @@ namespace oxen::quic if (exp_ns == std::numeric_limits::max()) { - log::info(log_cat, "No retransmit needed right now"); + log::debug(log_cat, "No retransmit needed right now"); event_del(packet_retransmit_timer.get()); return; } @@ -1120,8 +1188,7 @@ namespace oxen::quic int Connection::stream_opened(int64_t id) { - log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); - log::info(log_cat, "New stream ID:{}", id); + log::trace(log_cat, "New stream ID:{}", id); if (auto itr = _stream_queue.find(id); itr != _stream_queue.end()) { @@ -1176,7 +1243,7 @@ namespace oxen::quic { log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); assert(ngtcp2_is_bidi_stream(id)); - log::info(log_cat, "Stream {} closed with code {}", id, app_code); + log::info(log_cat, "Stream (ID:{}) closed with code {}", id, app_code); auto it = _streams.find(id); if (it == _streams.end()) @@ -1185,8 +1252,8 @@ namespace oxen::quic auto& stream = *it->second; stream_execute_close(stream, app_code); - log::info(log_cat, "Erasing stream {}", id); _streams.erase(it); + log::trace(log_cat, "Stream (ID:{}) erased", id); if (!ngtcp2_conn_is_local_stream(conn.get(), id)) ngtcp2_conn_extend_max_streams_bidi(conn.get(), 1); @@ -1357,7 +1424,7 @@ namespace oxen::quic } if (!datagrams->dgram_data_cb) - log::debug(log_cat, "Connection (CID: {}) has no endpoint-supplied datagram data callback", _source_cid); + log::trace(log_cat, "Connection (CID: {}) has no endpoint-supplied datagram data callback", _source_cid); else { bool good = false; @@ -1394,7 +1461,7 @@ namespace oxen::quic if (fin) { - log::info(log_cat, "Connection (CID: {}) received fin from remote", _source_cid); + log::debug(log_cat, "Connection (CID: {}) received FIN from remote", _source_cid); // TODO: no clean up, as close cb is called after? Or just for streams } @@ -1454,33 +1521,39 @@ namespace oxen::quic }); } - int Connection::init( + void Connection::init( ngtcp2_settings& settings, ngtcp2_transport_params& params, ngtcp2_callbacks& callbacks, std::chrono::nanoseconds handshake_timeout) { callbacks.recv_crypto_data = ngtcp2_crypto_recv_crypto_data_cb; - callbacks.path_validation = Callbacks::on_path_validation; + callbacks.path_validation = connection_callbacks::on_path_validation; callbacks.encrypt = ngtcp2_crypto_encrypt_cb; callbacks.decrypt = ngtcp2_crypto_decrypt_cb; callbacks.hp_mask = ngtcp2_crypto_hp_mask_cb; - callbacks.recv_stream_data = Callbacks::on_recv_stream_data; - callbacks.acked_stream_data_offset = Callbacks::on_acked_stream_data_offset; - callbacks.stream_close = Callbacks::on_stream_close; - callbacks.extend_max_local_streams_bidi = Callbacks::extend_max_local_streams_bidi; - callbacks.rand = Callbacks::rand_cb; - callbacks.get_new_connection_id = Callbacks::get_new_connection_id; - callbacks.remove_connection_id = Callbacks::remove_connection_id; - callbacks.dcid_status = Callbacks::on_connection_id_status; + callbacks.recv_stream_data = connection_callbacks::on_recv_stream_data; + callbacks.acked_stream_data_offset = connection_callbacks::on_acked_stream_data_offset; + callbacks.stream_close = connection_callbacks::on_stream_close; + callbacks.extend_max_local_streams_bidi = connection_callbacks::extend_max_local_streams_bidi; + callbacks.rand = connection_callbacks::rand_cb; + callbacks.get_new_connection_id = connection_callbacks::get_new_connection_id; + callbacks.remove_connection_id = connection_callbacks::remove_connection_id; callbacks.update_key = ngtcp2_crypto_update_key_cb; - callbacks.stream_reset = Callbacks::on_stream_reset; + callbacks.stream_reset = connection_callbacks::on_stream_reset; callbacks.delete_crypto_aead_ctx = ngtcp2_crypto_delete_crypto_aead_ctx_cb; callbacks.delete_crypto_cipher_ctx = ngtcp2_crypto_delete_crypto_cipher_ctx_cb; callbacks.get_path_challenge_data = ngtcp2_crypto_get_path_challenge_data_cb; callbacks.version_negotiation = ngtcp2_crypto_version_negotiation_cb; - callbacks.stream_open = Callbacks::on_stream_open; - callbacks.handshake_completed = Callbacks::on_handshake_completed; + callbacks.stream_open = connection_callbacks::on_stream_open; + callbacks.handshake_completed = connection_callbacks::on_handshake_completed; + + if (_stateless_reset_enabled) + { + callbacks.recv_stateless_reset = connection_callbacks::recv_stateless_reset; + callbacks.dcid_status = connection_callbacks::on_connection_id_status; + log::debug(log_cat, "Connection configured to monitor active dcids and stateless reset packets"); + } ngtcp2_settings_default(&settings); @@ -1521,9 +1594,9 @@ namespace oxen::quic params.max_udp_payload_size = NGTCP2_DEFAULT_MAX_RECV_UDP_PAYLOAD_SIZE; // 65527 settings.max_tx_udp_payload_size = MAX_PMTUD_UDP_PAYLOAD; // 1500 - 48 (approximate overhead) // settings.no_tx_udp_payload_size_shaping = 1; - callbacks.recv_datagram = Callbacks::on_recv_datagram; + callbacks.recv_datagram = connection_callbacks::on_recv_datagram; #ifndef NDEBUG - callbacks.ack_datagram = Callbacks::on_ack_datagram; + callbacks.ack_datagram = connection_callbacks::on_ack_datagram; #endif di = _endpoint.make_shared(*this); @@ -1534,8 +1607,6 @@ namespace oxen::quic params.max_datagram_frame_size = 0; callbacks.recv_datagram = nullptr; } - - return 0; } Connection::Connection( @@ -1559,6 +1630,9 @@ namespace oxen::quic _source_cid{scid}, _dest_cid{dcid}, _path{path}, + _0rtt_enabled{_endpoint._0rtt_enabled}, + _0rtt_window{_endpoint._0rtt_window}, + _stateless_reset_enabled{_endpoint._stateless_reset_enabled}, _max_streams{context->config.max_streams ? context->config.max_streams : DEFAULT_MAX_BIDI_STREAMS}, _datagrams_enabled{context->config.datagram_support}, _packet_splitting{context->config.split_packet}, @@ -1581,7 +1655,7 @@ namespace oxen::quic pseudo_stream = _endpoint.make_shared(*this, _endpoint); pseudo_stream->_stream_id = -1; - const auto d_str = is_outbound() ? "outbound"s : "inbound"s; + const auto d_str = is_outbound() ? "outbound" : "inbound"; log::trace(log_cat, "Creating new {} connection object", d_str); ngtcp2_settings settings; @@ -1591,42 +1665,39 @@ namespace oxen::quic int rv = 0; auto handshake_timeout = context->config.handshake_timeout.value_or(default_handshake_timeout); - if (rv = init(settings, params, callbacks, handshake_timeout); rv != 0) - log::critical(log_cat, "Error: {} connection not created", d_str); - tls_session = tls_creds->make_session(*this, alpns); + init(settings, params, callbacks, handshake_timeout); + + tls_session = tls_creds->make_session(*this, context, alpns); if (is_outbound()) { callbacks.client_initial = ngtcp2_crypto_client_initial_cb; - callbacks.handshake_confirmed = Callbacks::on_handshake_confirmed; + callbacks.handshake_confirmed = connection_callbacks::on_handshake_confirmed; callbacks.recv_retry = ngtcp2_crypto_recv_retry_cb; - callbacks.recv_new_token = Callbacks::on_recv_token; + callbacks.recv_new_token = connection_callbacks::on_recv_token; + + if (_0rtt_enabled) + callbacks.tls_early_data_rejected = connection_callbacks::on_early_data_rejected; // Clients should be the ones providing a remote pubkey here. This way we can emplace it into // the gnutlssession object to be verified. Servers should be verifying via callback - assert(remote_pk.has_value()); - remote_pubkey = *remote_pk; - tls_session->set_expected_remote_key(remote_pubkey); + if (not context->disable_key_verification) + { + log::debug(log_cat, "Outbound connection configured for key verification"); + assert(remote_pk.has_value()); + remote_pubkey = *remote_pk; + tls_session->set_expected_remote_key(remote_pubkey); + } + + auto maybe_token = _endpoint.get_path_validation_token(_path.remote); - auto maybe_token = _endpoint.get_path_validation_token(remote_pubkey); if (maybe_token) { settings.token = maybe_token->data(); settings.tokenlen = maybe_token->size(); } - // TODO: uncomment this with 0RTT resumption - // if (auto maybe_params = _endpoint.get_0rtt_transport_params(remote_pubkey)) - // { - // if (auto rv = ngtcp2_conn_decode_and_set_0rtt_transport_params( - // conn.get(), maybe_params->data(), maybe_params->size()); - // rv != 0) - // log::warning(log_cat, "Client failed to decode and set 0rtt transport params!"); - // else - // log::info(log_cat, "Client decoded and set 0rtt transport params!"); - // } - rv = ngtcp2_conn_client_new( &connptr, &_dest_cid, @@ -1638,6 +1709,34 @@ namespace oxen::quic ¶ms, nullptr, this); + + if (_0rtt_enabled) + { + auto maybe_params = _endpoint.get_0rtt_transport_params(_path.remote); + if (not maybe_params) + { + log::warning( + log_cat, + "Client failed to fetch 0rtt transport params for outbound; disabling 0rtt and proceeding..."); + _0rtt_enabled = false; + } + else + { + if (ngtcp2_conn_decode_and_set_0rtt_transport_params( + connptr, maybe_params->data(), maybe_params->size()) != 0) + { + log::warning( + log_cat, + "Client failed to decode and set 0rtt transport params; disabling 0rtt and proceeding..."); + _0rtt_enabled = false; + } + else + { + make_early_streams(connptr); + log::debug(log_cat, "Client encoded and set 0rtt params, ready to attempt early data!"); + } + } + } } else { @@ -1655,11 +1754,19 @@ namespace oxen::quic } params.original_dcid_present = 1; - // params.stateless_reset_token_present = 1; settings.token = hdr->token; settings.tokenlen = hdr->tokenlen; - // gnutls_rnd(GNUTLS_RND_RANDOM, params.stateless_reset_token, NGTCP2_STATELESS_RESET_TOKENLEN); + if (_stateless_reset_enabled) + { + params.stateless_reset_token_present = 1; + // generate stateless reset token using scid chosen by server + gtls_reset_token::generate_token( + params.stateless_reset_token, + _endpoint._static_secret.data(), + _endpoint._static_secret.size(), + _source_cid); + } if (token_type) settings.token_type = *token_type; diff --git a/src/context.cpp b/src/context.cpp index d1e9ae74..fd20384b 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -83,4 +83,13 @@ namespace oxen::quic log::trace(log_cat, "IO context stored connection closed callback"); conn_closed_cb = std::move(func); } + + void IOContext::handle_ioctx_opt(opt::disable_key_verification) + { + log::info( + log_cat, + "IOContext disabling key verification for {}bound connections!", + dir == Direction::INBOUND ? "in" : "out"); + disable_key_verification = true; + } } // namespace oxen::quic diff --git a/src/endpoint.cpp b/src/endpoint.cpp index e030271b..1618f6bb 100644 --- a/src/endpoint.cpp +++ b/src/endpoint.cpp @@ -16,6 +16,7 @@ extern "C" #include #include "connection.hpp" +#include "gnutls_crypto.hpp" #include "internal.hpp" #include "types.hpp" #include "utils.hpp" @@ -85,6 +86,58 @@ namespace oxen::quic _manual_routing = std::move(mrouting); } + void Endpoint::handle_ep_opt(opt::enable_0rtt_ticketing rtt) + { + _0rtt_enabled = true; + _0rtt_window = rtt.window.count(); + + _validate_0rtt_ticket = rtt.check ? std::move(rtt.check) : [this](gtls_ticket_ptr ticket, time_t current) -> bool { + auto key = ticket->key(); + + if (auto it = session_tickets.find(key); it != session_tickets.end()) + { + if (auto exp = gnutls_db_check_entry_expire_time(it->second->datum()); current < exp) + { + log::debug(log_cat, "Found existing anti-replay ticket for incoming connection; rejecting..."); + return GNUTLS_E_DB_ENTRY_EXISTS; + } + + log::debug(log_cat, "Found expired anti-replay ticket for incoming connection"); + } + + session_tickets[key] = std::move(ticket); + return 0; + }; + + _get_session_ticket = rtt.fetch ? std::move(rtt.fetch) : [this](ustring_view key) -> gtls_ticket_ptr { + gtls_ticket_ptr ret = nullptr; + if (auto it = session_tickets.find(key); it != session_tickets.end()) + { + ret = std::move(it->second); + session_tickets.erase(it); + log::debug(log_cat, "Found session ticket for remote; entry extracted and returned..."); + } + else + log::debug(log_cat, "Could not find session ticket for remote!"); + + return ret; + }; + + _put_session_ticket = rtt.put ? std::move(rtt.put) : [this](gtls_ticket_ptr ticket, time_t /* exp */) { + auto key = ticket->key(); + auto [_, b] = session_tickets.insert_or_assign(std::move(key), std::move(ticket)); + + log::debug( + log_cat, "Stored anti-replay ticket for connection to remote{}!", b ? "" : "; old ticket overwritten"); + }; + } + + void Endpoint::handle_ep_opt(opt::disable_stateless_reset /* rst */) + { + log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); + _stateless_reset_enabled = false; + } + ConnectionID Endpoint::next_reference_id() { log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); @@ -138,6 +191,43 @@ namespace oxen::quic log::debug(log_cat, "Inbound context ready for incoming connections"); } + std::shared_ptr Endpoint::_connect(RemoteAddress remote, quic_cid qcid, ConnectionID rid) + { + Address addr{remote}; + return _connect(std::move(addr), std::move(qcid), std::move(rid), std::move(remote).get_remote_key()); + } + + std::shared_ptr Endpoint::_connect( + Address remote, quic_cid qcid, ConnectionID rid, std::optional pk) + { + Path path = Path{_local, std::move(remote)}; + + for (;;) + { + // emplace random CID into lookup keyed to unique reference ID + if (auto [it_a, res_a] = conn_lookup.emplace(quic_cid::random(), rid); res_a) + { + qcid = it_a->first; + + if (auto [it_b, res_b] = conns.emplace(rid, nullptr); res_b) + { + it_b->second = Connection::make_conn( + *this, + rid, + it_a->first, + quic_cid::random(), + std::move(path), + outbound_ctx, + outbound_alpns, + handshake_timeout, + pk); + + return it_b->second; + } + } + } + } + void Endpoint::_set_context_globals(std::shared_ptr& ctx) { ctx->config.datagram_support = _datagrams; @@ -227,11 +317,20 @@ namespace oxen::quic { if (_accepting_inbound) { - cptr = accept_initial_connection(pkt); + cptr = accept_initial_connection(pkt, dcid); if (!cptr) { - log::warning(log_cat, "Error: connection could not be created"); + if (_stateless_reset_enabled) // must be done within the check for _accepting_inbound + { + send_stateless_reset(pkt, dcid); + log::debug( + log_cat, + "Server failed to decode pkt: dispatched reset token to remote ({})", + pkt.path.remote); + } + else + log::warning(log_cat, "Error: connection could not be created"); return; } @@ -239,7 +338,7 @@ namespace oxen::quic } else { - log::info(log_cat, "Dropping packet; unknown connection ID to endpoint not accepting inbound conns"); + log::info(log_cat, "Received packet with unknown connection ID; local endpoint not accepting inbounds!"); return; } } @@ -388,21 +487,25 @@ namespace oxen::quic conn.halt_events(); - log::debug(log_cat, "Deleting associated CIDs for connection ({})", rid); + log::debug(log_cat, "Deleting associated CIDs for connection {}", rid); if (conn.is_inbound()) { dissociate_cid(ngtcp2_conn_get_client_initial_dcid(conn), conn); } - log::debug(log_cat, "Deleting {} associated CIDs for connection ({})", conn.associated_cids().size(), rid); - auto& cids = conn.associated_cids(); - for (auto itr = cids.begin(); itr != cids.end();) + log::debug(log_cat, "Deleting {} associated CIDs for connection {}", cids.size(), rid); + + while (not cids.empty()) { - dissociate_cid(&*itr, conn); - itr = cids.erase(itr); + auto itr = cids.begin(); + // call to dissociate_cid deletes from cids in Connection object + if (_stateless_reset_enabled) + deactivate_cid(&*itr, conn); + else + dissociate_cid(*itr, conn); } conn.drop_streams(); @@ -421,28 +524,33 @@ namespace oxen::quic } } - int Endpoint::validate_anti_replay(ustring key, ustring data, time_t /* exp */) + int Endpoint::validate_anti_replay(gtls_ticket_ptr ticket, time_t current) { - if (auto itr = anti_replay_db.find(key); itr != anti_replay_db.end()) - { - auto& entry = itr->second; + return _validate_0rtt_ticket(std::move(ticket), current) == 0 ? 0 : GNUTLS_E_DB_ENTRY_EXISTS; + } - if (entry == data) - return GNUTLS_E_DB_ENTRY_EXISTS; - } + void Endpoint::store_session_ticket(gtls_ticket_ptr ticket) + { + log::trace(log_cat, "Storing session ticket..."); + return _put_session_ticket(std::move(ticket), 0); + } - anti_replay_db[std::move(key)] = std::move(data); - return 0; + gtls_ticket_ptr Endpoint::get_session_ticket(const ustring_view& remote_pk) + { + log::trace(log_cat, "Fetching session ticket (remote key: {})...", buffer_printer{remote_pk}); + return _get_session_ticket(remote_pk); } - void Endpoint::store_0rtt_transport_params(ustring remote_pk, ustring encoded_params) + void Endpoint::store_0rtt_transport_params(Address remote, ustring encoded_params) { - encoded_transport_params.insert_or_assign(remote_pk, std::move(encoded_params)); + log::trace(log_cat, "Storing 0rtt tranpsport params..."); + encoded_transport_params.insert_or_assign(std::move(remote), std::move(encoded_params)); } - std::optional Endpoint::get_0rtt_transport_params(ustring remote_pk) + std::optional Endpoint::get_0rtt_transport_params(const Address& remote) { - if (auto itr = encoded_transport_params.find(remote_pk); itr != encoded_transport_params.end()) + log::trace(log_cat, "Fetching 0rtt transport params..."); + if (auto itr = encoded_transport_params.find(remote); itr != encoded_transport_params.end()) return itr->second; return std::nullopt; @@ -468,38 +576,104 @@ namespace oxen::quic log::debug(log_cat, "Connection (RID:{}) completed initial CID association", conn.reference_id()); } - void Endpoint::associate_cid(const ngtcp2_cid* cid, Connection& conn) + void Endpoint::expire_reset_tokens(time_point now) { - auto dir_str = conn.is_inbound() ? "SERVER"s : "CLIENT"s; - log::trace( + assert(in_event_loop()); + size_t count{}; + + for (auto it = reset_token_map.begin(); it != reset_token_map.end();) + { + if (it->first->is_expired(now)) + { + count += 1; + log::trace(log_cat, "Pruning expired reset token (quic_cid:{})", it->second); + reset_token_lookup.erase(it->second); + it = reset_token_map.erase(it); + } + else + ++it; + } + + if (count) + log::debug(log_cat, "{} expired reset tokens pruned!", count); + } + + void Endpoint::activate_cid(const ngtcp2_cid* cid, const uint8_t* token, Connection& conn) + { + assert(in_event_loop()); + auto qcid = quic_cid{*cid}; + log::debug( log_cat, - "{} associating CID:{} to {}", - dir_str, - oxenc::to_hex(cid->data, cid->data + cid->datalen), + "{} activating new CID:{} with reset token for {}", + conn.is_inbound() ? "SERVER" : "CLIENT", + qcid, conn.reference_id()); + associate_cid(qcid, conn); + + // We only hold one reset token per connection at a time! + auto [it, _] = reset_token_map.emplace(gtls_reset_token::make_copy(token), qcid); + reset_token_lookup.emplace(qcid, it->first); + expire_reset_tokens(); + } + + void Endpoint::deactivate_cid(const ngtcp2_cid* cid, Connection& conn) + { assert(in_event_loop()); - auto ccid = quic_cid{*cid}; - conn_lookup.emplace(ccid, conn.reference_id()); - conn.store_associated_cid(ccid); + auto qcid = quic_cid{*cid}; + log::debug( + log_cat, + "{} deactivating CID:{} with reset token for {}", + conn.is_inbound() ? "SERVER" : "CLIENT", + qcid, + conn.reference_id()); + dissociate_cid(qcid, conn); + + if (auto it = reset_token_lookup.find(qcid); it != reset_token_lookup.end()) + { + reset_token_map.erase(it->second); + reset_token_lookup.erase(it); + + log::debug(log_cat, "Successfully deleted token for deactivated CID:{}", qcid); + } + else + log::debug(log_cat, "Could not find token corresponding to deactivated CID:{}", qcid); + + expire_reset_tokens(); } - void Endpoint::dissociate_cid(const ngtcp2_cid* cid, Connection& conn) + void Endpoint::associate_cid(quic_cid qcid, Connection& conn) { - if (not cid->datalen) - return; + assert(in_event_loop()); + log::trace( + log_cat, "{} associating CID:{} to {}", conn.is_inbound() ? "SERVER" : "CLIENT", qcid, conn.reference_id()); + + conn_lookup.emplace(qcid, conn.reference_id()); + conn.store_associated_cid(qcid); + } - auto dir_str = conn.is_inbound() ? "SERVER"s : "CLIENT"s; + void Endpoint::associate_cid(const ngtcp2_cid* cid, Connection& conn) + { + assert(in_event_loop()); + if (cid->datalen) + return associate_cid(quic_cid{*cid}, conn); + } + + void Endpoint::dissociate_cid(quic_cid qcid, Connection& conn) + { + assert(in_event_loop()); log::trace( - log_cat, - "{} dissociating CID:{} to {}", - dir_str, - oxenc::to_hex(cid->data, cid->data + cid->datalen), - conn.reference_id()); + log_cat, "{} dissociating CID:{} to {}", conn.is_inbound() ? "SERVER" : "CLIENT", qcid, conn.reference_id()); + conn_lookup.erase(qcid); + conn.delete_associated_cid(qcid); + } + + void Endpoint::dissociate_cid(const ngtcp2_cid* cid, Connection& conn) + { assert(in_event_loop()); - auto ccid = quic_cid{*cid}; - conn_lookup.erase(ccid); + if (cid->datalen) + return dissociate_cid(quic_cid{*cid}, conn); } Connection* Endpoint::fetch_associated_conn(quic_cid& ccid) @@ -532,11 +706,11 @@ namespace oxen::quic now); rv != 0) { - log::warning(log_cat, "Server could not verify regular token!"); + log::debug(log_cat, "Server (local={}) could not verify regular token! path: {}", _local, pkt.path); return false; } - log::debug(log_cat, "Server successfully verified regular token!"); + log::debug(log_cat, "Server successfully verified regular token! path: {}", pkt.path); return true; } @@ -566,6 +740,29 @@ namespace oxen::quic return true; } + void Endpoint::send_stateless_reset(const Packet& pkt, quic_cid& cid) + { + auto token = gtls_reset_token::generate(_static_secret.data(), _static_secret.size(), cid); + + std::vector buf; + buf.resize(MAX_PMTUD_UDP_PAYLOAD); + + auto nwrite = + ngtcp2_pkt_write_stateless_reset(u8data(buf), buf.size(), token->token(), token->rand(), token->RANDSIZE); + + if (nwrite < 0) + { + log::warning(log_cat, "Server failed to write stateless reset packet!"); + return; + } + + // ensure we had enough write space + assert(static_cast(nwrite) <= buf.size()); + buf.resize(nwrite); + + send_or_queue_packet(pkt.path, std::move(buf), /* ecn */ 0); + } + void Endpoint::send_retry(const Packet& pkt, ngtcp2_pkt_hd* hdr) { ngtcp2_cid scid; @@ -636,14 +833,14 @@ namespace oxen::quic send_or_queue_packet(pkt.path, std::move(buf), /* ecn */ 0); } - void Endpoint::store_path_validation_token(ustring remote_pk, ustring token) + void Endpoint::store_path_validation_token(Address remote, ustring token) { - path_validation_tokens.insert_or_assign(std::move(remote_pk), std::move(token)); + path_validation_tokens.insert_or_assign(std::move(remote), std::move(token)); } - std::optional Endpoint::get_path_validation_token(ustring remote_pk) + std::optional Endpoint::get_path_validation_token(const Address& remote) { - if (auto itr = path_validation_tokens.find(remote_pk); itr != path_validation_tokens.end()) + if (auto itr = path_validation_tokens.find(remote); itr != path_validation_tokens.end()) return itr->second; return std::nullopt; @@ -687,7 +884,32 @@ namespace oxen::quic return std::make_optional(vid.dcid, vid.dcidlen); } - Connection* Endpoint::accept_initial_connection(const Packet& pkt) + Connection* Endpoint::check_stateless_reset(const Packet& pkt, quic_cid& /* cid */) + { + log::trace(log_cat, "Checking last 16B of pkt for stateless reset token..."); + Connection* cptr = nullptr; + + auto gtls_token = gtls_reset_token::parse_packet(pkt); + + if (auto rit = reset_token_map.find(gtls_token); rit != reset_token_map.end()) + { + if (cptr = get_conn(rit->second); cptr) + { + log::info(log_cat, "Matched stateless reset token in unknown packet to connection {}", cptr->reference_id()); + } + else + log::debug(log_cat, "Received good stateless reset token but no connection exists for it; deleting entry"); + + reset_token_lookup.erase(rit->second); + reset_token_map.erase(rit); + } + else + log::trace(log_cat, "No stateless reset token match for pkt from remote: {}", pkt.path.remote); + + return cptr; + } + + Connection* Endpoint::accept_initial_connection(const Packet& pkt, quic_cid& dcid) { log::trace(log_cat, "Accepting new connection..."); @@ -698,17 +920,24 @@ namespace oxen::quic if (rv < 0) // catches all other possible ngtcp2 errors { - log::warning( - log_cat, - "Unknown packet received from {}, length={}, code={}; ignoring it.", - pkt.path.remote, - data.size(), - ngtcp2_strerror(rv)); + if (_stateless_reset_enabled) + { + // TODO: may not need this if the ngtcp2 cb works as it should...? + return check_stateless_reset(pkt, dcid); + } + else + log::warning( + log_cat, + "Unknown packet received from {}, length={}, code={}; ignoring it.", + pkt.path.remote, + data.size(), + ngtcp2_strerror(rv)); return nullptr; } - if (hdr.type == NGTCP2_PKT_0RTT) + + if (not _0rtt_enabled and hdr.type == NGTCP2_PKT_0RTT) { - log::error(log_cat, "0RTT is under development in this implementation; dropping packet"); + log::error(log_cat, "0-RTT is disabled for this endpoint; dropping 0-RTT packet"); return nullptr; } diff --git a/src/gnutls_creds.cpp b/src/gnutls_creds.cpp index 2e2d01eb..b4dba679 100644 --- a/src/gnutls_creds.cpp +++ b/src/gnutls_creds.cpp @@ -43,34 +43,6 @@ namespace oxen::quic { log::debug(log_cat, "GNUTLS Log (level {}): {}", level, str); } - - // Return value: 0 is pass, negative is fail - int cert_verify_callback_gnutls(gnutls_session_t session) - { - log::debug(log_cat, "{} called", __PRETTY_FUNCTION__); - auto* conn = get_connection_from_gnutls(session); - - GNUTLSSession* tls_session = dynamic_cast(conn->get_session()); - assert(tls_session); - - bool success = false; - auto local_name = (conn->is_outbound()) ? "CLIENT" : "SERVER"; - - // true: Peer provided a valid cert; connection is accepted and marked validated - // false: Peer either provided an invalid cert or no cert; connection is rejected - if (success = tls_session->validate_remote_key(); success) - conn->set_validated(); - - auto err = "Quic {} was {}able to validate peer certificate; {} connection!"_format( - local_name, success ? "" : "un", success ? "accepting" : "rejecting"); - - if (success) - log::debug(log_cat, "{}", err); - else - log::error(log_cat, "{}", err); - - return !success; - } } void GNUTLSCreds::load_keys(x509_loader& s, x509_loader& pk) @@ -139,8 +111,6 @@ namespace oxen::quic throw std::runtime_error("gnutls key exchange algorithm priority setup failed"); } - - gnutls_certificate_set_verify_function(cred, cert_verify_callback_gnutls); } GNUTLSCreds::~GNUTLSCreds() @@ -168,8 +138,9 @@ namespace oxen::quic return p; } - std::unique_ptr GNUTLSCreds::make_session(Connection& c, const std::vector& alpns) + std::unique_ptr GNUTLSCreds::make_session( + Connection& c, const std::shared_ptr& ctx, const std::vector& alpns) { - return std::make_unique(*this, c, alpns); + return std::make_unique(*this, ctx, c, alpns); } } // namespace oxen::quic diff --git a/src/gnutls_crypto.cpp b/src/gnutls_crypto.cpp new file mode 100644 index 00000000..a9447a5f --- /dev/null +++ b/src/gnutls_crypto.cpp @@ -0,0 +1,60 @@ +#include "gnutls_crypto.hpp" + +#include "internal.hpp" +#include "udp.hpp" + +namespace oxen::quic +{ + gtls_reset_token::gtls_reset_token(const uint8_t* t, const uint8_t* r) + { + std::memcpy(_tok.data(), t, gtls_reset_token::TOKENSIZE); + if (r) + std::memcpy(_rand.data(), r, gtls_reset_token::RANDSIZE); + else + generate_rand(_rand.data()); + } + + gtls_reset_token::gtls_reset_token(uint8_t* _static_secret, size_t _secret_len, const quic_cid& cid) + { + generate_token(_tok.data(), _static_secret, _secret_len, cid); + generate_rand(_rand.data()); + } + + void gtls_reset_token::generate_token(uint8_t* buffer, uint8_t* _static_secret, size_t _secret_len, const quic_cid& cid) + { + if (ngtcp2_crypto_generate_stateless_reset_token(buffer, _static_secret, _secret_len, &cid) != 0) + throw std::runtime_error{"Failed to generate stateless reset token!"}; + } + + void gtls_reset_token::generate_rand(uint8_t* buffer) + { + if (gnutls_rnd(GNUTLS_RND_RANDOM, buffer, RANDSIZE) != 0) + throw std::runtime_error{"Failed to generate stateless reset random"}; + } + + std::shared_ptr gtls_reset_token::generate( + uint8_t* _static_secret, size_t _secret_len, const quic_cid& cid) + { + std::shared_ptr ret = nullptr; + try + { + ret = std::shared_ptr{new gtls_reset_token{_static_secret, _secret_len, cid}}; + } + catch (const std::exception& e) + { + log::error(log_cat, "gtls_reset_token exception: {}", e.what()); + } + + return ret; + } + + std::shared_ptr gtls_reset_token::make_copy(const uint8_t* t, const uint8_t* r) + { + return std::shared_ptr{new gtls_reset_token{t, r}}; + } + + std::shared_ptr gtls_reset_token::parse_packet(const Packet& pkt) + { + return gtls_reset_token::make_copy(pkt.data(pkt.size() - gtls_reset_token::TOKENSIZE).data()); + } +} // namespace oxen::quic diff --git a/src/gnutls_session.cpp b/src/gnutls_session.cpp index 4335ea88..954fa9bf 100644 --- a/src/gnutls_session.cpp +++ b/src/gnutls_session.cpp @@ -11,40 +11,80 @@ namespace oxen::quic gnutls_session_get_data2 to be called in hook function after handshake completion */ - extern "C" - { - int anti_replay_db_add_func(void* dbf, time_t exp_time, const gnutls_datum_t* key, const gnutls_datum_t* data) - { - auto* ep = static_cast(dbf); - assert(ep); + static constexpr auto SESSION_TICKET_HEADER = "GNUTLS SESSION PARAMETERS"; - log::warning(log_cat, "0RTT session resumption is not available; callback is no-op"); - return 0; + int gtls_session_callbacks::server_anti_replay_cb( + void* dbf, time_t exp_time, const gnutls_datum_t* key, const gnutls_datum_t* data) + { + log::debug(log_cat, "{} called", __PRETTY_FUNCTION__); + auto* ep = static_cast(dbf); + assert(ep); - (void)exp_time; - (void)key; - (void)data; - (void)ep; + if (not ep->zero_rtt_enabled()) + throw std::runtime_error{"Anti-replay DB hook should not be called on 0rtt-disabled endpoint!"}; - // return ep->validate_anti_replay({key->data, key->size}, {data->data, data->size}, exp_time); - } + return ep->validate_anti_replay(gtls_session_ticket::make(key, data), exp_time); + } - int client_hook_func( - gnutls_session_t session, - unsigned int htype, - unsigned /* when */, - unsigned int /* incoming */, - const gnutls_datum_t* /* msg */) + int gtls_session_callbacks::client_session_cb( + gnutls_session_t session, + unsigned int htype, + unsigned /* when */, + unsigned int /* incoming */, + const gnutls_datum_t* /* msg */) + { + if (htype == GNUTLS_HANDSHAKE_NEW_SESSION_TICKET) { - if (htype == GNUTLS_HANDSHAKE_NEW_SESSION_TICKET) + log::debug(log_cat, "Client received new session ticket from server!"); + auto* conn = get_connection_from_gnutls(session); + auto remote_key = conn->remote_key(); + auto& ep = conn->endpoint(); + gtls_datum data{}, encoded{}; + + if (auto rv = gnutls_session_get_data2(session, data); rv != 0) + { + log::warning(log_cat, "Failed to query session data: {}", gnutls_strerror(rv)); + return rv; + } + + if (auto rv = gnutls_pem_base64_encode2(SESSION_TICKET_HEADER, data, encoded); rv != 0) { - auto* conn = get_connection_from_gnutls(session); - auto& ep = conn->endpoint(); - (void)ep; + log::warning(log_cat, "Failed to encode session data: {}", gnutls_strerror(rv)); + return rv; } - return 0; + ep.store_session_ticket(gtls_session_ticket::make(remote_key, encoded)); } + + return 0; + } + + // Return value: 0 is pass, negative is fail + int gtls_session_callbacks::cert_verify_callback_gnutls(gnutls_session_t session) + { + log::debug(log_cat, "{} called", __PRETTY_FUNCTION__); + auto* conn = get_connection_from_gnutls(session); + + GNUTLSSession* tls_session = dynamic_cast(conn->get_session()); + assert(tls_session); + + bool success = false; + auto local_name = (conn->is_outbound()) ? "CLIENT" : "SERVER"; + + // true: Peer provided a valid cert; connection is accepted and marked validated + // false: Peer either provided an invalid cert or no cert; connection is rejected + if (success = tls_session->validate_remote_key(); success) + conn->set_validated(); + + auto err = "Quic {} was {}able to validate peer certificate; {} connection!"_format( + local_name, success ? "" : "un", success ? "accepting" : "rejecting"); + + if (success) + log::debug(log_cat, "{}", err); + else + log::error(log_cat, "{}", err); + + return !success; } Connection* get_connection_from_gnutls(gnutls_session_t g_session) @@ -68,26 +108,30 @@ namespace oxen::quic { log::trace(log_cat, "Entered {}", __PRETTY_FUNCTION__); - if (not is_client) + if (not _is_client and _0rtt_enabled) gnutls_anti_replay_deinit(anti_replay); gnutls_deinit(session); - gnutls_free(session_ticket_key.data); } GNUTLSSession::GNUTLSSession( - GNUTLSCreds& creds, Connection& c, const std::vector& alpns, std::optional expected_key) : - creds{creds}, session_ticket_key{}, is_client{c.is_outbound()} + GNUTLSCreds& creds, + const std::shared_ptr& ctx, + Connection& c, + const std::vector& alpns, + std::optional expected_key) : + creds{creds}, _is_client{c.is_outbound()}, _0rtt_enabled{c.zero_rtt_enabled()} { log::trace(log_cat, "Entered {}", __PRETTY_FUNCTION__); - if (not is_client) + if (not _is_client and _0rtt_enabled) { gnutls_anti_replay_init(&anti_replay); - gnutls_anti_replay_set_add_function(anti_replay, anti_replay_db_add_func); + gnutls_anti_replay_set_add_function(anti_replay, gtls_session_callbacks::server_anti_replay_cb); gnutls_anti_replay_set_ptr(anti_replay, &c.endpoint()); + gnutls_anti_replay_set_window(anti_replay, c.zero_rtt_window()); - if (auto rv = gnutls_session_ticket_key_generate(&session_ticket_key); rv != 0) + if (auto rv = gnutls_session_ticket_key_generate(session_ticket_key); rv != 0) { auto err = "Server failed to generate session ticket key: {}"_format(gnutls_strerror(rv)); log::error(log_cat, "{}", err); @@ -98,12 +142,15 @@ namespace oxen::quic if (expected_key) _expected_remote_key = *expected_key; - auto direction_string = (is_client) ? "Client"s : "Server"s; - log::trace(log_cat, "Creating {} GNUTLSSession", direction_string); + auto direction_string = (_is_client) ? "Client"s : "Server"s; + log::trace(log_cat, "Creating {} GNUTLSSession with 0-rtt {}abled", direction_string, _0rtt_enabled ? "en" : "dis"); - uint32_t init_flags = is_client ? GNUTLS_CLIENT : GNUTLS_SERVER | GNUTLS_NO_AUTO_SEND_TICKET; + uint32_t init_flags = _is_client ? GNUTLS_CLIENT : GNUTLS_SERVER; - init_flags |= GNUTLS_ENABLE_EARLY_DATA | GNUTLS_NO_END_OF_EARLY_DATA; + if (_0rtt_enabled) + init_flags |= GNUTLS_ENABLE_EARLY_DATA | GNUTLS_NO_END_OF_EARLY_DATA; + else + init_flags |= GNUTLS_NO_AUTO_SEND_TICKET; // DISCUSS: we actually don't want to do this if the requested certificate is expecting // x509 (see gnutls_creds.cpp::cert_retrieve_callback_gnutls function body) @@ -136,19 +183,25 @@ namespace oxen::quic log::debug( log_cat, "[GNUTLS SESSION] Local ({}) cert type:{} \t Peer expecting cert type:{}", - is_client ? "CLIENT" : "SERVER", + _is_client ? "CLIENT" : "SERVER", get_cert_type(session, GNUTLS_CTYPE_OURS), get_cert_type(session, GNUTLS_CTYPE_PEERS)); - if (not is_client) + if (not _is_client) { log::trace(log_cat, "gnutls configuring server session..."); - if (auto rv = gnutls_session_ticket_enable_server(session, &session_ticket_key); rv != 0) + if (_0rtt_enabled) { - auto err = "gnutls_session_ticket_enable_server failed: {}"_format(gnutls_strerror(rv)); - log::error(log_cat, "{}", err); - throw std::runtime_error{err}; + if (auto rv = gnutls_session_ticket_enable_server(session, session_ticket_key); rv != 0) + { + auto err = "gnutls_session_ticket_enable_server failed: {}"_format(gnutls_strerror(rv)); + log::error(log_cat, "{}", err); + throw std::runtime_error{err}; + } + + gnutls_anti_replay_enable(session, anti_replay); + gnutls_record_set_max_early_data_size(session, 0xffffffffu); } if (auto rv = ngtcp2_crypto_gnutls_configure_server_session(session); rv < 0) @@ -157,20 +210,55 @@ namespace oxen::quic throw std::runtime_error("ngtcp2_crypto_gnutls_configure_client_session failed"); } - gnutls_anti_replay_enable(session, anti_replay); - gnutls_record_set_max_early_data_size(session, 0xffffffffu); - // server always requests cert from client gnutls_certificate_server_set_request(session, GNUTLS_CERT_REQUIRE); } else { log::trace(log_cat, "gnutls configuring client session..."); + + if (_0rtt_enabled) + { + if (auto rv = gnutls_session_ticket_enable_client(session); rv != 0) + { + auto err = "gnutls_session_ticket_enable_client failed: {}"_format(gnutls_strerror(rv)); + log::error(log_cat, "{}", err); + throw std::runtime_error{err}; + } + + log::trace(log_cat, "Setting client session ticket db hook..."); + gnutls_handshake_set_hook_function( + session, + GNUTLS_HANDSHAKE_NEW_SESSION_TICKET, + GNUTLS_HOOK_POST, + gtls_session_callbacks::client_session_cb); + } + if (auto rv = ngtcp2_crypto_gnutls_configure_client_session(session); rv < 0) { log::warning(log_cat, "ngtcp2_crypto_gnutls_configure_client_session failed: {}", ngtcp2_strerror(rv)); throw std::runtime_error("ngtcp2_crypto_gnutls_configure_client_session failed"); } + + if (_0rtt_enabled and _expected_remote_key) + { + if (auto maybe_ticket = c.endpoint().get_session_ticket(_expected_remote_key.view())) + { + gtls_datum d{}; + + if (auto rv = gnutls_pem_base64_decode2(SESSION_TICKET_HEADER, maybe_ticket->datum(), d); rv != 0) + { + log::warning(log_cat, "Failed to decode session ticket: {}", ngtcp2_strerror(rv)); + throw std::runtime_error("gnutls_pem_base64_decode2 failed"); + } + + if (auto rv = gnutls_session_set_data(session, d.data(), d.size()); rv != 0) + { + log::warning(log_cat, "Failed to set decoded session data: {}", ngtcp2_strerror(rv)); + throw std::runtime_error("gnutls_session_set_data failed"); + } + } + } } gnutls_session_set_ptr(session, &conn_ref); @@ -182,7 +270,7 @@ namespace oxen::quic } // NOTE: IPv4 or IPv6 addresses not allowed (cannot be "127.0.0.1") - if (is_client) + if (_is_client) { if (auto rv = gnutls_server_name_set(session, GNUTLS_NAME_DNS, "localhost", strlen("localhost")); rv < 0) { @@ -202,11 +290,10 @@ namespace oxen::quic to_sv(ustring_view{s.data(), s.size()}), direction_string); allowed_alpns.emplace_back( - gnutls_datum_t{const_cast(s.data()), static_cast(s.size())}); + gnutls_datum_t{const_cast(s.data()), static_cast(s.size())}); } - if (auto rv = - gnutls_alpn_set_protocols(session, &(allowed_alpns[0]), allowed_alpns.size(), GNUTLS_ALPN_MANDATORY); + if (auto rv = gnutls_alpn_set_protocols(session, &allowed_alpns[0], allowed_alpns.size(), GNUTLS_ALPN_MANDATORY); rv < 0) { log::error(log_cat, "gnutls_alpn_set_protocols failed: {}", gnutls_strerror(rv)); @@ -221,6 +308,14 @@ namespace oxen::quic throw std::runtime_error("gnutls_alpn_set_protocols failed"); } } + + if (not ctx->disable_key_verification) + { + log::debug(log_cat, "Setting key verify hook for {}bound session...", _is_client ? "out" : "in"); + gnutls_session_set_verify_function(session, gtls_session_callbacks::cert_verify_callback_gnutls); + } + else + log::info(log_cat, "Key verification has been turned OFF for this session!"); } int GNUTLSSession::send_session_ticket() @@ -236,18 +331,19 @@ namespace oxen::quic return 0; } - ustring_view GNUTLSSession::selected_alpn() + void GNUTLSSession::set_selected_alpn() { - gnutls_datum_t proto; + gnutls_datum_t _alpn{}; - if (auto rv = gnutls_alpn_get_selected_protocol(session, &proto); rv < 0) + if (auto rv = gnutls_alpn_get_selected_protocol(session, &_alpn); rv < 0) { auto err = fmt::format("{} called, but ALPN negotiation incomplete.", __PRETTY_FUNCTION__); log::error(log_cat, "{}", err); throw std::logic_error(err); } - return proto.size ? ustring_view{proto.data, proto.size} : ""_usv; + _selected_alpn.resize(_alpn.size); + std::memmove(_selected_alpn.data(), _alpn.data, _alpn.size); } // In our new cert verification scheme, the logic proceeds as follows. @@ -274,7 +370,7 @@ namespace oxen::quic log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); assert(creds.using_raw_pk); - const auto local_name = is_client ? "CLIENT" : "SERVER"; + const auto local_name = _is_client ? "CLIENT" : "SERVER"; bool success = false; log::debug( @@ -328,8 +424,11 @@ namespace oxen::quic // pubkey comes as 12 bytes header + 32 bytes key _remote_key.write(cert_data, cert_size); - if (is_client) - { // Client does validation through a remote pubkey provided when calling endpoint::connect + set_selected_alpn(); + + if (_is_client) + { + // Client does validation through a remote pubkey provided when calling endpoint::connect success = _remote_key == _expected_remote_key; log::debug( @@ -342,9 +441,8 @@ namespace oxen::quic return success; } else - { // Server does validation through callback - auto alpn = selected_alpn(); - + { + // Server does validation through callback log::debug( log_cat, "Quic {}: {} key verify callback{}", @@ -356,7 +454,7 @@ namespace oxen::quic // provided a certificate and is only called by the server, we can assume the following returns: // true: the certificate was verified, and the connection is marked as validated // false: the certificate was not verified, and the connection is rejected - success = (creds.key_verify) ? creds.key_verify(_remote_key.view(), alpn) : true; + success = (creds.key_verify) ? creds.key_verify(_remote_key.view(), selected_alpn()) : true; return success; } diff --git a/src/internal.hpp b/src/internal.hpp index ea723e9c..f0220c48 100644 --- a/src/internal.hpp +++ b/src/internal.hpp @@ -24,26 +24,122 @@ namespace oxen::quic 1; #endif - // Wrapper around inet_pton that throws an exception on error - inline void parse_addr(int af, void* dest, const std::string& from) + namespace detail { - auto rv = inet_pton(af, from.c_str(), dest); + // Wrapper around inet_pton that throws an exception on error + inline void parse_addr(int af, void* dest, const std::string& from) + { + auto rv = inet_pton(af, from.c_str(), dest); - if (rv == 0) // inet_pton returns this on invalid input - throw std::invalid_argument{"Unable to parse IP address!"}; - if (rv < 0) - throw std::system_error{errno, std::system_category()}; - } + if (rv == 0) // inet_pton returns this on invalid input + throw std::invalid_argument{"Unable to parse IP address!"}; + if (rv < 0) + throw std::system_error{errno, std::system_category()}; + } - // Parses an IPv4 address from string - inline void parse_addr(in_addr& into, const std::string& from) + // Parses an IPv4 address from string + inline void parse_addr(in_addr& into, const std::string& from) + { + parse_addr(AF_INET, &into.s_addr, from); + } + + // Parses an IPv6 address from string + inline void parse_addr(in6_addr& into, const std::string& from) + { + parse_addr(AF_INET6, &into, from); + } + } // namespace detail + + struct connection_callbacks { - parse_addr(AF_INET, &into.s_addr, from); - } + static int on_ack_datagram(ngtcp2_conn* conn, uint64_t dgram_id, void* user_data); + + static int on_recv_datagram(ngtcp2_conn* conn, uint32_t flags, const uint8_t* data, size_t datalen, void* user_data); + + static int on_recv_token(ngtcp2_conn* conn, const uint8_t* token, size_t tokenlen, void* user_data); + + static int on_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); + + static int on_acked_stream_data_offset( + ngtcp2_conn* conn_, + int64_t stream_id, + uint64_t offset, + uint64_t datalen, + void* user_data, + void* stream_user_data); + + static int on_stream_open(ngtcp2_conn* conn, int64_t stream_id, void* user_data); + + static int on_stream_close( + ngtcp2_conn* conn, + uint32_t flags, + int64_t stream_id, + uint64_t app_error_code, + void* user_data, + void* stream_user_data); + + static int on_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); + + static int on_handshake_completed(ngtcp2_conn*, void* user_data); + + static int on_handshake_confirmed(ngtcp2_conn*, void* user_data); - // Parses an IPv6 address from string - inline void parse_addr(in6_addr& into, const std::string& from) + static void rand_cb(uint8_t* dest, size_t destlen, const ngtcp2_rand_ctx* rand_ctx); + + static int on_connection_id_status( + ngtcp2_conn* _conn, + ngtcp2_connection_id_status_type type, + uint64_t seq, + const ngtcp2_cid* cid, + const uint8_t* token, + void* user_data); + + static int get_new_connection_id( + ngtcp2_conn* _conn, ngtcp2_cid* cid, uint8_t* token, size_t cidlen, void* user_data); + + static int remove_connection_id(ngtcp2_conn* _conn, const ngtcp2_cid* cid, void* user_data); + + static int extend_max_local_streams_bidi(ngtcp2_conn* _conn, uint64_t max_streams, void* user_data); + + static int on_path_validation( + ngtcp2_conn* _conn [[maybe_unused]], + uint32_t flags, + const ngtcp2_path* path, + const ngtcp2_path* old_path, + ngtcp2_path_validation_result res, + void* user_data); + + static int on_early_data_rejected(ngtcp2_conn* _conn, void* user_data); + + static int recv_stateless_reset(ngtcp2_conn* conn, const ngtcp2_pkt_stateless_reset* sr, void* user_data); + }; + + struct gtls_session_callbacks { - parse_addr(AF_INET6, &into, from); - } + static int server_anti_replay_cb(void* dbf, time_t exp_time, const gnutls_datum_t* key, const gnutls_datum_t* data); + + static int client_session_cb( + gnutls_session_t session, + unsigned int htype, + unsigned when, + unsigned int incoming, + const gnutls_datum_t* msg); + + static int cert_verify_callback_gnutls(gnutls_session_t session); + }; + } // namespace oxen::quic diff --git a/src/ip.cpp b/src/ip.cpp index 544dd1f3..e1798880 100644 --- a/src/ip.cpp +++ b/src/ip.cpp @@ -7,7 +7,7 @@ namespace oxen::quic ipv4::ipv4(const std::string& str) { in_addr sin; - parse_addr(sin, str); + detail::parse_addr(sin, str); addr = oxenc::big_to_host(sin.s_addr); } @@ -43,7 +43,7 @@ namespace oxen::quic ipv6::ipv6(const std::string& str) { in6_addr sin6; - parse_addr(sin6, str); + detail::parse_addr(sin6, str); hi = oxenc::load_big_to_host(&sin6.s6_addr[0]); lo = oxenc::load_big_to_host(&sin6.s6_addr[8]); diff --git a/src/loop.cpp b/src/loop.cpp index 5fbb7cca..7deff2a2 100644 --- a/src/loop.cpp +++ b/src/loop.cpp @@ -39,7 +39,7 @@ namespace oxen::quic - this is an equally annoying typedef for `suseconds_t` Alas, yet again another mac idiosyncrasy... */ - timeval loop_time_to_timeval(std::chrono::microseconds t) + static timeval loop_time_to_timeval(std::chrono::microseconds t) { return timeval{ .tv_sec = static_cast(t / 1s), @@ -53,7 +53,7 @@ namespace oxen::quic if (event_add(ev.get(), &interval) != 0) { - log::critical(log_cat, "EventHandler failed to start repeating event!"); + log::warning(log_cat, "EventHandler failed to start repeating event!"); return false; } @@ -69,7 +69,7 @@ namespace oxen::quic if (event_del(ev.get()) != 0) { - log::critical(log_cat, "EventHandler failed to pause repeating event!"); + log::warning(log_cat, "EventHandler failed to pause repeating event!"); return false; } @@ -104,7 +104,7 @@ namespace oxen::quic auto* self = reinterpret_cast(s); if (not self->f) { - log::critical(log_cat, "Ticker does not have a callback to execute!"); + log::warning(log_cat, "Ticker does not have a callback to execute!"); return; } // execute callback @@ -112,13 +112,13 @@ namespace oxen::quic } catch (const std::exception& e) { - log::critical(log_cat, "Ticker caught exception: {}", e.what()); + log::warning(log_cat, "Ticker caught exception: {}", e.what()); } }, this)); if ((one_off or start_immediately) and not start()) - log::critical(log_cat, "Failed to immediately start one-off event!"); + log::warning(log_cat, "Failed to immediately start one-off event!"); } Ticker::~Ticker() @@ -177,7 +177,7 @@ namespace oxen::quic ev_loop = std::shared_ptr{event_base_new_with_config(ev_conf.get()), event_base_free}; - log::info(log_cat, "Started libevent loop with backend {}", event_base_get_method(ev_loop.get())); + log::debug(log_cat, "Started libevent loop with backend {}", event_base_get_method(ev_loop.get())); setup_job_waker(); @@ -194,12 +194,12 @@ namespace oxen::quic p.get_future().get(); running.store(true); - log::info(log_cat, "loop is started"); + log::info(log_cat, "libevent loop is started"); } Loop::~Loop() { - log::info(log_cat, "Shutting down loop..."); + log::debug(log_cat, "Shutting down loop..."); stop_thread(); @@ -223,6 +223,8 @@ namespace oxen::quic void Loop::stop_thread(bool immediate) { + log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); + if (loop_thread) immediate ? event_base_loopbreak(ev_loop.get()) : event_base_loopexit(ev_loop.get(), nullptr); diff --git a/src/network.cpp b/src/network.cpp index 137a1dba..951ee2fd 100644 --- a/src/network.cpp +++ b/src/network.cpp @@ -23,7 +23,7 @@ namespace oxen::quic Network::~Network() { - log::info(log_cat, "Shutting down network..."); + log::debug(log_cat, "Shutting down network..."); if (not shutdown_immediate) close_gracefully(); @@ -45,7 +45,7 @@ namespace oxen::quic void Network::close_gracefully() { - log::info(log_cat, "{} called", __PRETTY_FUNCTION__); + log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); std::promise pr; auto ft = pr.get_future(); diff --git a/src/stream.cpp b/src/stream.cpp index 6c5e1580..fe1e9360 100644 --- a/src/stream.cpp +++ b/src/stream.cpp @@ -31,7 +31,7 @@ namespace oxen::quic if (!close_callback) close_callback = [](Stream&, uint64_t error_code) { - log::info(log_cat, "Default stream close callback called ({})", quic_strerror(error_code)); + log::debug(log_cat, "Default stream close callback called ({})", quic_strerror(error_code)); }; log::trace(log_cat, "Stream object created"); @@ -70,7 +70,7 @@ namespace oxen::quic _is_watermarked = true; - log::info(log_cat, "Stream set watermarks!"); + log::trace(log_cat, "Stream set watermarks!"); }); } @@ -90,7 +90,7 @@ namespace oxen::quic if (_high_water) _high_water.clear(); _is_watermarked = false; - log::info(log_cat, "Stream cleared currently set watermarks!"); + log::trace(log_cat, "Stream cleared currently set watermarks!"); }); } @@ -163,9 +163,9 @@ namespace oxen::quic log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); if (_is_shutdown) - log::info(log_cat, "Stream is already shutting down"); + log::trace(log_cat, "Stream is already shutting down"); else if (_is_closing) - log::debug(log_cat, "Stream is already closing"); + log::trace(log_cat, "Stream is already closing"); else { _is_closing = _is_shutdown = true; @@ -215,7 +215,7 @@ namespace oxen::quic if (_ready) _conn->packet_io_ready(); else - log::info(log_cat, "Stream not ready for broadcast yet, data appended to buffer and on deck"); + log::debug(log_cat, "Stream not ready for broadcast yet, data appended to buffer and on deck"); } void Stream::acknowledge(size_t bytes) @@ -250,11 +250,11 @@ namespace oxen::quic if (unsent >= _high_mark) { _low_primed = true; - log::info(log_cat, "Low water hook primed!"); + log::trace(log_cat, "Low water hook primed!"); if (_high_water and _high_primed) { - log::info(log_cat, "Executing high watermark hook!"); + log::debug(log_cat, "Executing high watermark hook!"); _high_primed = false; return _high_water(*this); } @@ -264,11 +264,11 @@ namespace oxen::quic else if (unsent <= _low_mark) { _high_primed = true; - log::info(log_cat, "High water hook primed!"); + log::trace(log_cat, "High water hook primed!"); if (_low_water and _low_primed) { - log::info(log_cat, "Executing low watermark hook!"); + log::debug(log_cat, "Executing low watermark hook!"); _low_primed = false; return _low_water(*this); } @@ -303,6 +303,14 @@ namespace oxen::quic return std::make_pair(std::move(it), offset); } + void Stream::revert_stream() + { + assert(endpoint.in_event_loop()); + log::trace(log_cat, "Stream (ID:{}) reverting after early data rejected...", _stream_id); + _unacked_size = 0; + log::debug(log_cat, "Stream (ID:{}) has {}B in buffer, 0B unacked...", _stream_id, size()); + } + std::vector Stream::pending() { log::trace(log_cat, "{} called", __PRETTY_FUNCTION__); diff --git a/src/udp.cpp b/src/udp.cpp index 9d0ee86f..b74d06df 100644 --- a/src/udp.cpp +++ b/src/udp.cpp @@ -792,7 +792,7 @@ namespace oxen::quic {static_cast(hdr.msg_name), hdr.msg_namelen} #endif }, - pkt_data{data} + data_sp{data.begin(), data.end()} { assert(path.remote.is_ipv4() || path.remote.is_ipv6()); diff --git a/src/utils.cpp b/src/utils.cpp index 1e005a28..f8979fb1 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -27,7 +27,7 @@ namespace oxen::quic } } - std::chrono::steady_clock::time_point get_time() + time_point get_time() { return std::chrono::steady_clock::now(); } diff --git a/tests/001-handshake.cpp b/tests/001-handshake.cpp index c5f5a8f7..9fa82a56 100644 --- a/tests/001-handshake.cpp +++ b/tests/001-handshake.cpp @@ -340,7 +340,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local, server_established); CHECK_NOTHROW(server_endpoint->listen(server_tls)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; SECTION("Incorrect pubkey in remote") { @@ -352,7 +352,7 @@ namespace oxen::quic::test auto client_endpoint = test_net.endpoint(client_local, client_established_2, client_closed); - RemoteAddress bad_client_remote{defaults::CLIENT_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress bad_client_remote{defaults::CLIENT_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_ci = client_endpoint->connect(bad_client_remote, client_tls); @@ -368,7 +368,7 @@ namespace oxen::quic::test auto short_key = defaults::SERVER_PUBKEY.substr(0, 31); - RemoteAddress bad_client_remote{short_key, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress bad_client_remote{short_key, LOCALHOST, server_endpoint->local().port()}; REQUIRE_THROWS(client_endpoint->connect(bad_client_remote, client_tls)); } @@ -378,7 +378,7 @@ namespace oxen::quic::test // If uncommented, this line will not compile! Remote addresses must pass a remote pubkey to be // verified upon the client successfully establishing connection with a remote. - // RemoteAddress client_remote{"127.0.0.1"s, server_endpoint->local().port()}; + // RemoteAddress client_remote{LOCALHOST, server_endpoint->local().port()}; CHECK(true); } @@ -411,7 +411,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local, server_established); CHECK_NOTHROW(server_endpoint->listen(server_tls)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local, client_established); @@ -457,7 +457,7 @@ namespace oxen::quic::test CHECK_NOTHROW(server_endpoint->listen(server_tls)); auto client_endpoint = test_net.endpoint(client_local, client_established); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_ci = client_endpoint->connect(client_remote, client_tls); @@ -515,7 +515,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local, server_established); CHECK_NOTHROW(server_endpoint->listen(server_tls)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local, client_established); auto client_ci = client_endpoint->connect(client_remote, client_tls); @@ -552,7 +552,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local, server_established); CHECK_NOTHROW(server_endpoint->listen(server_tls)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local, client_established); auto client_ci = client_endpoint->connect(client_remote, client_tls); @@ -685,11 +685,11 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local, server_established, server_closed_ep_level); - RemoteAddress client_remote{S_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{S_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local, client_established); - RemoteAddress server_remote{C_PUBKEY, "127.0.0.1"s, client_endpoint->local().port()}; + RemoteAddress server_remote{C_PUBKEY, LOCALHOST, client_endpoint->local().port()}; server_endpoint->listen(server_tls); client_endpoint->listen(client_tls); @@ -718,11 +718,11 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local, server_established); - RemoteAddress client_remote{S_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{S_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local, client_established); - RemoteAddress server_remote{C_PUBKEY, "127.0.0.1"s, client_endpoint->local().port()}; + RemoteAddress server_remote{C_PUBKEY, LOCALHOST, client_endpoint->local().port()}; server_endpoint->listen(server_tls); client_endpoint->listen(client_tls); @@ -772,7 +772,7 @@ namespace oxen::quic::test auto server_endpoint = net.endpoint(server_local, server_conn_closed); auto client_endpoint = net.endpoint(client_local, client_conn_closed); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; SECTION("Client fast timeout") { @@ -817,9 +817,9 @@ namespace oxen::quic::test server_tls->set_key_verify_callback([](const ustring_view&, const ustring_view&) { // This stalls the entire network object; this is a really terrible thing to do outside // of test code, but will let us simulate a slow handshake. - log::critical(log_cat, "key verify sleeping..."); + log::critical(test_cat, "key verify sleeping..."); std::this_thread::sleep_for(30s); - log::critical(log_cat, "key verify done sleeping"); + log::critical(test_cat, "key verify done sleeping"); return true; }); #endif @@ -834,7 +834,7 @@ namespace oxen::quic::test std::shared_ptr client_ci; auto server_endpoint = net1->endpoint(server_local); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; server_endpoint.reset(); net1.reset(); // kill the server @@ -883,7 +883,7 @@ namespace oxen::quic::test std::shared_ptr client_ci; auto server_endpoint = net1->endpoint(server_local); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; server_endpoint.reset(); net1.reset(); // kill the server diff --git a/tests/002-send-receive.cpp b/tests/002-send-receive.cpp index 9e682c86..cc9af152 100644 --- a/tests/002-send-receive.cpp +++ b/tests/002-send-receive.cpp @@ -20,7 +20,7 @@ namespace oxen::quic::test std::future d_future = d_promise.get_future(); stream_data_callback server_data_cb = [&](Stream&, bstring_view dat) { - log::debug(log_cat, "Calling server stream data callback... data received..."); + log::debug(test_cat, "Calling server stream data callback... data received..."); REQUIRE(good_msg == dat); d_promise.set_value(true); }; @@ -33,7 +33,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local); REQUIRE_NOTHROW(server_endpoint->listen(server_tls, server_data_cb)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local); auto conn_interface = client_endpoint->connect(client_remote, client_tls); @@ -60,7 +60,7 @@ namespace oxen::quic::test std::atomic index = 0; stream_data_callback server_data_cb = [&](Stream&, bstring_view dat) { - log::debug(log_cat, "Calling server stream data callback... data received..."); + log::debug(test_cat, "Calling server stream data callback... data received..."); REQUIRE(good_msg == dat); d_promises.at(index).set_value(); index += 1; @@ -77,8 +77,8 @@ namespace oxen::quic::test auto server_endpoint_b = test_net.endpoint(server_b_local); REQUIRE_NOTHROW(server_endpoint_b->listen(server_tls, server_data_cb)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint_b->local().port()}; - RemoteAddress server_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint_a->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint_b->local().port()}; + RemoteAddress server_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint_a->local().port()}; auto server_ci = server_endpoint_b->connect(server_remote, server_tls); auto server_stream = server_ci->open_stream(); @@ -112,7 +112,7 @@ namespace oxen::quic::test std::atomic index = 0; stream_data_callback server_data_cb = [&](Stream&, bstring_view dat) { - log::debug(log_cat, "Calling server stream data callback... data received..."); + log::debug(test_cat, "Calling server stream data callback... data received..."); REQUIRE(good_msg == dat); d_promises.at(index).set_value(); index += 1; @@ -128,8 +128,8 @@ namespace oxen::quic::test auto server_endpoint_b = test_net.endpoint(server_b_local); REQUIRE_NOTHROW(server_endpoint_b->listen(server_tls, server_data_cb)); - RemoteAddress server_remote_a{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint_a->local().port()}; - RemoteAddress server_remote_b{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint_b->local().port()}; + RemoteAddress server_remote_a{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint_a->local().port()}; + RemoteAddress server_remote_b{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint_b->local().port()}; auto server_b_ci = server_endpoint_b->connect(server_remote_a, server_tls); auto server_b_stream = server_b_ci->open_stream(); @@ -162,7 +162,7 @@ namespace oxen::quic::test std::promise done_receiving; stream_data_callback server_data_cb = [&](Stream&, bstring_view dat) { - log::debug(log_cat, "Server stream data callback -- data received (len {})", dat.size()); + log::debug(test_cat, "Server stream data callback -- data received (len {})", dat.size()); static bstring partial; partial.append(dat); if (partial.size() < good_msg.size()) @@ -186,8 +186,8 @@ namespace oxen::quic::test auto server_endpoint_b = test_net.endpoint(server_b_local); - RemoteAddress server_remote_a{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint_a->local().port()}; - RemoteAddress server_remote_b{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint_b->local().port()}; + RemoteAddress server_remote_a{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint_a->local().port()}; + RemoteAddress server_remote_b{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint_b->local().port()}; auto conn_to_a = server_endpoint_b->connect(server_remote_a, server_tls); auto stream_to_a = conn_to_a->open_stream(); @@ -243,26 +243,26 @@ namespace oxen::quic::test { auto server_bp_cb = callback_waiter{[&](message msg) { if (msg) - log::info(log_cat, "Server bparser received: {}", msg.view()); + log::info(test_cat, "Server bparser received: {}", msg.view()); }}; stream_constructor_callback server_constructor = [&](Connection& c, Endpoint& e, std::optional) { auto s = e.make_shared(c, e); - s->register_handler("test_endpoint"s, server_bp_cb); + s->register_handler(TEST_ENDPOINT, server_bp_cb); return s; }; auto server_endpoint = test_net.endpoint(server_local); REQUIRE_NOTHROW(server_endpoint->listen(server_tls, server_constructor)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local); auto conn_interface = client_endpoint->connect(client_remote, client_tls); auto client_bp = conn_interface->open_stream(); - client_bp->command("test_endpoint"s, "test_request_body"s); + client_bp->command(TEST_ENDPOINT, "test_request_body"s); REQUIRE(server_bp_cb.wait()); } @@ -272,7 +272,7 @@ namespace oxen::quic::test auto server_bp_cb = callback_waiter{[&](message msg) { if (msg) { - log::info(log_cat, "Server bparser received: {}", msg.view()); + log::info(test_cat, "Server bparser received: {}", msg.view()); msg.respond("test_response"s); } }}; @@ -280,14 +280,14 @@ namespace oxen::quic::test auto client_bp_cb = callback_waiter{[&](message msg) { if (msg) { - log::info(log_cat, "Client bparser received: {}", msg.view()); + log::info(test_cat, "Client bparser received: {}", msg.view()); msg.respond("test_response"s); } }}; stream_constructor_callback server_constructor = [&](Connection& c, Endpoint& e, std::optional) { auto s = e.make_shared(c, e); - s->register_handler("test_endpoint"s, server_bp_cb); + s->register_handler(TEST_ENDPOINT, server_bp_cb); return s; }; @@ -298,14 +298,14 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local); REQUIRE_NOTHROW(server_endpoint->listen(server_tls, server_constructor)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local); auto conn_interface = client_endpoint->connect(client_remote, client_tls, client_constructor); std::shared_ptr client_bp = conn_interface->open_stream(); - client_bp->command("test_endpoint"s, "test_request_body"s, client_bp_cb); + client_bp->command(TEST_ENDPOINT, "test_request_body"s, client_bp_cb); REQUIRE(server_bp_cb.wait()); REQUIRE(client_bp_cb.wait()); @@ -316,7 +316,7 @@ namespace oxen::quic::test auto server_bp_cb = callback_waiter{[&](message msg) { if (msg) { - log::info(log_cat, "Server bparser received: {}", msg.view()); + log::info(test_cat, "Server bparser received: {}", msg.view()); msg.respond("test_response"s); } }}; @@ -324,28 +324,28 @@ namespace oxen::quic::test auto client_bp_cb = callback_waiter{[&](message msg) { if (msg) { - log::info(log_cat, "Client bparser received: {}", msg.view()); + log::info(test_cat, "Client bparser received: {}", msg.view()); msg.respond("test_response"s); } }}; stream_constructor_callback server_constructor = [&](Connection& c, Endpoint& e, std::optional) { auto s = e.make_shared(c, e); - s->register_handler("test_endpoint"s, server_bp_cb); + s->register_handler(TEST_ENDPOINT, server_bp_cb); return s; }; auto server_endpoint = test_net.endpoint(server_local); REQUIRE_NOTHROW(server_endpoint->listen(server_tls, server_constructor)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local); auto conn_interface = client_endpoint->connect(client_remote, client_tls); auto client_bp = conn_interface->open_stream(); - client_bp->command("test_endpoint"s, "test_request_body"s, client_bp_cb); + client_bp->command(TEST_ENDPOINT, "test_request_body"s, client_bp_cb); REQUIRE(server_bp_cb.wait()); REQUIRE(client_bp_cb.wait()); @@ -370,18 +370,18 @@ namespace oxen::quic::test auto client_bp_cb = [&](message msg) { if (msg) { - log::info(log_cat, "GOT REGULAR"); + log::info(test_cat, "GOT REGULAR"); saw_regular++; } else if (msg.is_error()) { - log::info(log_cat, "GOT ERROR"); + log::info(test_cat, "GOT ERROR"); saw_error++; } else if (msg.timed_out) { - log::info(log_cat, "GOT TIMEOUT"); + log::info(test_cat, "GOT TIMEOUT"); saw_timeout++; } @@ -402,7 +402,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local); server_endpoint->listen(server_tls, server_constructor); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local); auto conn_interface = client_endpoint->connect(client_remote, client_tls); @@ -444,7 +444,7 @@ namespace oxen::quic::test auto server_handler = [&](message msg) { if (msg) { - log::info(log_cat, "Server bparser received: {}", msg.view()); + log::info(test_cat, "Server bparser received: {}", msg.view()); if (msg.body() == req_msg) msg.respond(res_msg); else @@ -457,7 +457,7 @@ namespace oxen::quic::test { std::lock_guard lock{mut}; responses++; - log::debug(log_cat, "Client bparser received response {}: {}", responses, msg.view()); + log::debug(test_cat, "Client bparser received response {}: {}", responses, msg.view()); if (msg.body() == res_msg) good_responses++; if (responses == num_requests) @@ -465,20 +465,20 @@ namespace oxen::quic::test } else { - log::debug(log_cat, "got back a failed message response"); + log::debug(test_cat, "got back a failed message response"); } }; stream_constructor_callback server_constructor = [&](Connection& c, Endpoint& e, std::optional) { auto s = e.make_shared(c, e); - s->register_handler("test_endpoint"s, server_handler); + s->register_handler(TEST_ENDPOINT, server_handler); return s; }; auto server_endpoint = test_net.endpoint(server_local); REQUIRE_NOTHROW(server_endpoint->listen(server_tls, server_constructor)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local); auto conn_interface = client_endpoint->connect(client_remote, client_tls); @@ -487,7 +487,7 @@ namespace oxen::quic::test for (int i = 0; i < num_requests; i++) { - client_bp->command("test_endpoint"s, req_msg, client_reply_handler); + client_bp->command(TEST_ENDPOINT, req_msg, client_reply_handler); } require_future(done); @@ -535,7 +535,7 @@ namespace oxen::quic::test if (msg) { ++responses; - log::debug(log_cat, "Client bparser received response {}: {}", responses.load(), msg.view()); + log::debug(test_cat, "Client bparser received response {}: {}", responses.load(), msg.view()); if (msg.body() == res_msg) ++good_responses; if (responses == num_requests) @@ -543,20 +543,20 @@ namespace oxen::quic::test } else { - log::debug(log_cat, "got back a failed message response"); + log::debug(test_cat, "got back a failed message response"); } }; stream_constructor_callback server_constructor = [&](Connection& c, Endpoint& e, std::optional) { auto s = e.make_shared(c, e); - s->register_handler("test_endpoint"s, server_handler); + s->register_handler(TEST_ENDPOINT, server_handler); return s; }; auto server_endpoint = test_net.endpoint(server_local); REQUIRE_NOTHROW(server_endpoint->listen(server_tls, server_constructor)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local); auto conn_interface = client_endpoint->connect(client_remote, client_tls); @@ -565,7 +565,7 @@ namespace oxen::quic::test for (int i = 0; i < num_requests; i++) { - client_bp->command("test_endpoint"s, req_msg, client_reply_handler); + client_bp->command(TEST_ENDPOINT, req_msg, client_reply_handler); } require_future(done, 10s); @@ -583,21 +583,21 @@ namespace oxen::quic::test auto client_reply_handler = [&](message msg) mutable { if (msg) - log::debug(log_cat, "Client bparser received response: {}", msg.view()); + log::debug(test_cat, "Client bparser received response: {}", msg.view()); else - log::debug(log_cat, "got back a failed message response"); + log::debug(test_cat, "got back a failed message response"); }; stream_constructor_callback server_constructor = [&](Connection& c, Endpoint& e, std::optional) { auto s = e.make_shared(c, e); - s->register_handler("test_endpoint"s, server_handler); + s->register_handler(TEST_ENDPOINT, server_handler); return s; }; auto server_endpoint = test_net.endpoint(server_local); REQUIRE_NOTHROW(server_endpoint->listen(server_tls, server_constructor)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local); auto conn_interface = client_endpoint->connect(client_remote, client_tls); @@ -606,7 +606,7 @@ namespace oxen::quic::test { std::shared_ptr client_bp = conn_interface->open_stream(); CHECK_THROWS_WITH( - client_bp->command("test_endpoint"s, req_msg, client_reply_handler), "Request body too long!"); + client_bp->command(TEST_ENDPOINT, req_msg, client_reply_handler), "Request body too long!"); } SECTION("Receive failure") @@ -672,7 +672,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local); server_endpoint->listen(server_tls, server_conn_est); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local); auto conn_interface = client_endpoint->connect(client_remote, client_tls); @@ -721,7 +721,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local); server_endpoint->listen(server_tls, server_conn_est); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local); auto conn_interface = client_endpoint->connect(client_remote, client_tls, opt::idle_timeout{50ms}); diff --git a/tests/003-multiclient.cpp b/tests/003-multiclient.cpp index ca5abf59..3c8eac86 100644 --- a/tests/003-multiclient.cpp +++ b/tests/003-multiclient.cpp @@ -50,7 +50,7 @@ namespace oxen::quic::test auto p_itr = stream_promises.begin(); stream_data_callback server_data_cb = [&](Stream&, bstring_view) { - log::debug(log_cat, "Calling server stream data callback... data received..."); + log::debug(test_cat, "Calling server stream data callback... data received..."); data_check += 1; p_itr->set_value(); ++p_itr; @@ -63,10 +63,10 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local); REQUIRE_NOTHROW(server_endpoint->listen(server_tls, server_data_cb)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; std::thread async_thread_a{[&]() { - log::debug(log_cat, "Async thread A called"); + log::debug(test_cat, "Async thread A called"); // client A auto client_a = test_net.endpoint(client_a_local); @@ -86,7 +86,7 @@ namespace oxen::quic::test }}; std::thread async_thread_b{[&]() { - log::debug(log_cat, "Async thread B called"); + log::debug(test_cat, "Async thread B called"); // client C auto client_c = test_net.endpoint(client_c_local); diff --git a/tests/004-streams.cpp b/tests/004-streams.cpp index 2cb52a8d..780854f7 100644 --- a/tests/004-streams.cpp +++ b/tests/004-streams.cpp @@ -27,7 +27,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local); REQUIRE_NOTHROW(server_endpoint->listen(server_tls, max_streams)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local, client_established); auto conn_interface = client_endpoint->connect(client_remote, client_tls, max_streams); @@ -49,7 +49,7 @@ namespace oxen::quic::test Address client_local{}; stream_data_callback server_data_cb = [&](Stream&, bstring_view) { - log::debug(log_cat, "Calling server stream data callback... data received..."); + log::debug(test_cat, "Calling server stream data callback... data received..."); data_promise.set_value(); }; @@ -58,7 +58,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local); REQUIRE_NOTHROW(server_endpoint->listen(server_tls, max_streams, server_data_cb)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local); auto conn_interface = client_endpoint->connect(client_remote, client_tls, max_streams); @@ -87,7 +87,7 @@ namespace oxen::quic::test Address client_local{}; stream_data_callback server_data_cb = [&](Stream&, bstring_view) { - log::debug(log_cat, "Calling server stream data callback... data received..."); + log::debug(test_cat, "Calling server stream data callback... data received..."); data_promise.set_value(); }; @@ -96,7 +96,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local); REQUIRE_NOTHROW(server_endpoint->listen(server_tls, server_config, server_data_cb)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local, client_established); auto client_ci = client_endpoint->connect(client_remote, client_tls, client_config); @@ -152,7 +152,7 @@ namespace oxen::quic::test send_futures[n_sends - 1] = send_promises[n_sends - 1].get_future(); stream_data_callback server_data_cb = [&](Stream&, bstring_view) { - log::debug(log_cat, "Calling server stream data callback... data received... incrementing counter..."); + log::debug(test_cat, "Calling server stream data callback... data received... incrementing counter..."); try { @@ -170,7 +170,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local); REQUIRE_NOTHROW(server_endpoint->listen(server_tls, max_streams, server_data_cb)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local, client_established); auto conn_interface = client_endpoint->connect(client_remote, client_tls, max_streams); @@ -237,7 +237,7 @@ namespace oxen::quic::test void receive(bstring_view) override { - log::debug(log_cat, "Calling custom stream data callback... data received..."); + log::debug(test_cat, "Calling custom stream data callback... data received..."); p.set_value(); } }; @@ -250,7 +250,7 @@ namespace oxen::quic::test void receive(bstring_view) override { - log::debug(log_cat, "Calling custom stream data callback... data received..."); + log::debug(test_cat, "Calling custom stream data callback... data received..."); p.set_value(); } }; @@ -265,14 +265,14 @@ namespace oxen::quic::test cc_f = cc_p.get_future(); stream_data_callback standard_server_cb = [&](Stream& s, bstring_view dat) { - log::debug(log_cat, "Calling standard stream data callback... data received..."); + log::debug(test_cat, "Calling standard stream data callback... data received..."); REQUIRE(msg == dat); ss_p.set_value(); s.send(msg); }; stream_data_callback standard_client_cb = [&](Stream& s, bstring_view dat) { - log::debug(log_cat, "Calling standard stream data callback... data received..."); + log::debug(test_cat, "Calling standard stream data callback... data received..."); REQUIRE(msg == dat); cs_p.set_value(); s.send(msg); @@ -286,7 +286,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local); REQUIRE_NOTHROW(server_endpoint->listen(server_tls, standard_server_cb)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local); auto conn_interface = client_endpoint->connect(client_remote, client_tls, standard_client_cb); @@ -331,7 +331,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local); REQUIRE_NOTHROW(server_endpoint->listen(server_tls, server_constructor)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local); auto conn_interface = client_endpoint->connect(client_remote, client_tls, client_constructor); @@ -351,7 +351,7 @@ namespace oxen::quic::test void receive(bstring_view m) override { - log::info(log_cat, "Custom stream received data:\n{}", buffer_printer{m}); + log::info(test_cat, "Custom stream received data:\n{}", buffer_printer{m}); p.set_value(std::string{convert_sv(m)}); } }; @@ -389,7 +389,7 @@ namespace oxen::quic::test auto server_closed = callback_waiter{[](connection_interface&, uint64_t) {}}; stream_data_callback server_generic_data_cb = [&](Stream&, bstring_view m) { - log::debug(log_cat, "Server generic data callback called"); + log::debug(test_cat, "Server generic data callback called"); sp4.set_value(std::string{convert_sv(m)}); }; @@ -420,7 +420,7 @@ namespace oxen::quic::test server_endpoint = test_net.endpoint(server_local, server_open_all_cb, server_closed); REQUIRE_NOTHROW(server_endpoint->listen(server_tls, server_generic_data_cb)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; client_endpoint = test_net.endpoint(client_local, client_established); client_ci = client_endpoint->connect(client_remote, client_tls); @@ -459,7 +459,7 @@ namespace oxen::quic::test server_endpoint = test_net.endpoint(server_local, server_closed); REQUIRE_NOTHROW(server_endpoint->listen(server_tls, server_constructor, server_generic_data_cb)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; client_endpoint = test_net.endpoint(client_local, client_established); client_ci = client_endpoint->connect(client_remote, client_tls); @@ -505,7 +505,7 @@ namespace oxen::quic::test server_endpoint = test_net.endpoint(server_local, server_closed); REQUIRE_NOTHROW(server_endpoint->listen(server_tls, server_constructor, server_open_cb, server_generic_data_cb)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; client_endpoint = test_net.endpoint(client_local, client_established); client_ci = client_endpoint->connect(client_remote, client_tls); @@ -555,7 +555,7 @@ namespace oxen::quic::test server_endpoint->listen(server_tls); auto client_endpoint = test_net.endpoint(client_local); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_ci = client_endpoint->connect(client_remote, client_tls); auto a = client_ci->open_stream(std::promise{}); @@ -650,11 +650,11 @@ namespace oxen::quic::test }; auto client_generic_data_cb = [&](Stream&, bstring_view data) { - log::debug(log_cat, "Client generic data callback called"); + log::debug(test_cat, "Client generic data callback called"); cp4.set_value(std::string{convert_sv(data)}); }; - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local, client_established); auto client_ci = client_endpoint->connect(client_remote, client_tls, client_generic_data_cb, client_stream_ctor); @@ -718,7 +718,7 @@ namespace oxen::quic::test auto client_established = callback_waiter{[&](connection_interface& ci) { client_extracted = ci.open_stream(); - client_extracted->register_handler("test_endpoint"s, client_handler); + client_extracted->register_handler(TEST_ENDPOINT, client_handler); }}; auto server_established = callback_waiter{[&](connection_interface&) {}}; @@ -731,7 +731,7 @@ namespace oxen::quic::test { log::trace(test_cat, "Server constructing BTRequestStream!"); server_extracted = e.make_shared(c, e); - server_extracted->register_handler("test_endpoint"s, server_handler); + server_extracted->register_handler(TEST_ENDPOINT, server_handler); return server_extracted; } else @@ -748,7 +748,7 @@ namespace oxen::quic::test server_endpoint->listen(server_tls, server_established, server_constructor); auto client_endpoint = test_net.endpoint(client_local, client_established); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_ci = client_endpoint->connect(client_remote, client_tls); @@ -763,7 +763,7 @@ namespace oxen::quic::test std::shared_ptr early_access = server_ci->maybe_stream(0); REQUIRE_FALSE(early_access); - client_extracted->command("test_endpoint"s, "hi"s); + client_extracted->command(TEST_ENDPOINT, "hi"s); REQUIRE(server_handler.wait()); std::shared_ptr server_bt = server_ci->maybe_stream(0); @@ -772,7 +772,7 @@ namespace oxen::quic::test REQUIRE(server_extracted->stream_id() == server_bt->stream_id()); REQUIRE(server_extracted == server_bt); - server_extracted->command("test_endpoint"s, "hi"s); + server_extracted->command(TEST_ENDPOINT, "hi"s); REQUIRE(client_handler.wait()); } @@ -793,7 +793,7 @@ namespace oxen::quic::test auto server_established = callback_waiter{[&](connection_interface& ci) { server_bt = ci.queue_incoming_stream(); - server_bt->register_handler("test_endpoint"s, server_handler); + server_bt->register_handler(TEST_ENDPOINT, server_handler); }}; auto client_established = callback_waiter{[&](connection_interface&) {}}; @@ -802,7 +802,7 @@ namespace oxen::quic::test server_endpoint->listen(server_tls, server_established); auto client_endpoint = test_net.endpoint(client_local); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_ci = client_endpoint->connect(client_remote, client_tls, client_established); @@ -810,7 +810,7 @@ namespace oxen::quic::test REQUIRE(server_established.wait()); client_bt = client_ci->open_stream(); - client_bt->register_handler("test_endpoint"s, client_handler); + client_bt->register_handler(TEST_ENDPOINT, client_handler); REQUIRE(client_bt->stream_id() == 0); server_ci = server_endpoint->get_all_conns(Direction::INBOUND).front(); @@ -819,10 +819,10 @@ namespace oxen::quic::test REQUIRE(server_extracted); REQUIRE(server_bt == server_extracted); - client_bt->command("test_endpoint"s, "hi"s); + client_bt->command(TEST_ENDPOINT, "hi"s); REQUIRE(server_handler.wait()); - server_bt->command("test_endpoint"s, "hi"s); + server_bt->command(TEST_ENDPOINT, "hi"s); REQUIRE(client_handler.wait()); } @@ -846,17 +846,17 @@ namespace oxen::quic::test REQUIRE(msg.body() == TEST_BODY); server_counter += 1; - log::debug(log_cat, "Server received request {} of {}", server_counter.load(), n_reqs); + log::debug(test_cat, "Server received request {} of {}", server_counter.load(), n_reqs); if (server_counter == n_reqs) { - log::debug(log_cat, "Server responding to client with new request"); + log::debug(test_cat, "Server responding to client with new request"); server_bt->command(TEST_ENDPOINT, TEST_BODY); } }; auto client_handler = callback_waiter{[](message msg) { - log::debug(log_cat, "Client received server request!"); + log::debug(test_cat, "Client received server request!"); REQUIRE(msg.body() == TEST_BODY); }}; @@ -890,7 +890,7 @@ namespace oxen::quic::test server_endpoint->listen(server_tls, server_established); auto client_endpoint = test_net.endpoint(client_local); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; client_ci = client_endpoint->connect(client_remote, client_tls, client_established); client_ci_ready.set_value(); @@ -924,13 +924,13 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local); server_endpoint->listen(server_tls, [&](Stream& s, bstring_view data) { count += data.size(); - log::debug(log_cat, "Got some data {}, replying with '{}'", to_sv(data), count); + log::debug(test_cat, "Got some data {}, replying with '{}'", to_sv(data), count); s.send("{}"_format(count)); }); TestHelper::increment_ref_id(*server_endpoint, 100); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local); @@ -938,7 +938,7 @@ namespace oxen::quic::test std::shared_ptr stream; { auto conn_closed = [&](connection_interface& conn, uint64_t ec) { - log::info(log_cat, "conn {} closed (ec={})", conn.reference_id(), ec); + log::info(test_cat, "conn {} closed (ec={})", conn.reference_id(), ec); }; auto conn = client_endpoint->connect(client_remote, client_tls, conn_closed); @@ -950,7 +950,7 @@ namespace oxen::quic::test stream = conn->open_stream(stream_data_cb, stream_close_cb); stream->send("hello world"s); require_future(got_reply.get_future()); - log::debug(log_cat, "closing connection"); + log::debug(test_cat, "closing connection"); conn->close_connection(); } @@ -975,7 +975,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local); server_endpoint->listen(server_tls, [&](Stream& s, bstring_view data) { s.send(data); }); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local); std::promise got_data; @@ -1021,7 +1021,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local, [](connection_interface& ci) { ci.close_connection(123); }); server_endpoint->listen(server_tls); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local, client_established); auto conn = client_endpoint->connect(client_remote, client_tls, client_closed); @@ -1065,7 +1065,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local, [](connection_interface& ci) { ci.close_connection(123); }); server_endpoint->listen(server_tls); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local, client_established); auto conn = client_endpoint->connect(client_remote, client_tls, client_closed); diff --git a/tests/005-chunked-sender.cpp b/tests/005-chunked-sender.cpp index 6ca4d044..76e35c6b 100644 --- a/tests/005-chunked-sender.cpp +++ b/tests/005-chunked-sender.cpp @@ -43,7 +43,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local); REQUIRE_NOTHROW(server_endpoint->listen(server_tls, server_data_cb)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local); auto conn_interface = client_endpoint->connect(client_remote, client_tls); @@ -57,7 +57,7 @@ namespace oxen::quic::test stream->send_chunks( [&](const Stream& s) { - log::info(log_cat, "getting next chunk ({}) for stream {}", i, s.stream_id()); + log::info(test_cat, "getting next chunk ({}) for stream {}", i, s.stream_id()); if (i++ < 3) return fmt::format("[CHUNK-{}]", i); i--; @@ -65,7 +65,7 @@ namespace oxen::quic::test }, [&](Stream& s) { auto pointer_chunks = [&](const Stream& s) -> std::vector* { - log::info(log_cat, "getting next chunk ({}) for stream {}", i, s.stream_id()); + log::info(test_cat, "getting next chunk ({}) for stream {}", i, s.stream_id()); if (i++ < 6) { auto& vec = bufs[i % parallel_chunks]; @@ -81,7 +81,7 @@ namespace oxen::quic::test pointer_chunks, [&](Stream& s) { auto smart_ptr_chunks = [&](const Stream& s) -> std::unique_ptr> { - log::info(log_cat, "getting next chunk ({}) for stream {}", i, s.stream_id()); + log::info(test_cat, "getting next chunk ({}) for stream {}", i, s.stream_id()); if (i++ >= 10) return nullptr; auto vec = std::make_unique>(); @@ -92,7 +92,7 @@ namespace oxen::quic::test smart_ptr_chunks, [&](Stream& s) { // (Lokinet RPC was here) - log::info(log_cat, "All chunks done!"); + log::info(test_cat, "All chunks done!"); s.send("Goodbye."s); }, parallel_chunks); diff --git a/tests/006-server-send.cpp b/tests/006-server-send.cpp index de45e5a3..d45303c7 100644 --- a/tests/006-server-send.cpp +++ b/tests/006-server-send.cpp @@ -21,20 +21,20 @@ namespace oxen::quic::test stream_future = stream_promise.get_future(); stream_open_callback server_io_open_cb = [&](IOChannel& s) { - log::debug(log_cat, "Calling server stream open callback... stream opened..."); + log::debug(test_cat, "Calling server stream open callback... stream opened..."); server_stream = s.get_stream(); stream_promise.set_value(); return 0; }; stream_data_callback server_io_data_cb = [&](IOChannel&, bstring_view) { - log::debug(log_cat, "Calling server stream data callback... data received... incrementing counter..."); + log::debug(test_cat, "Calling server stream data callback... data received... incrementing counter..."); data_check += 1; server_promise.set_value(); }; stream_data_callback client_io_data_cb = [&](IOChannel&, bstring_view) { - log::debug(log_cat, "Calling client stream data callback... data received... incrementing counter..."); + log::debug(test_cat, "Calling client stream data callback... data received... incrementing counter..."); data_check += 1; client_promise.set_value(); }; @@ -47,7 +47,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local); REQUIRE_NOTHROW(server_endpoint->listen(server_tls, server_io_open_cb, server_io_data_cb)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local); auto conn_interface = client_endpoint->connect(client_remote, client_tls, client_io_data_cb); @@ -86,7 +86,7 @@ namespace oxen::quic::test } stream_open_callback server_io_open_cb = [&](Stream& s) { - log::debug(log_cat, "Calling server stream open callback... stream opened..."); + log::debug(test_cat, "Calling server stream open callback... stream opened..."); server_extracted_stream = s.get_stream(); try { @@ -101,7 +101,7 @@ namespace oxen::quic::test }; stream_open_callback client_io_open_cb = [&](Stream& s) { - log::debug(log_cat, "Calling client stream open callback... stream opened..."); + log::debug(test_cat, "Calling client stream open callback... stream opened..."); client_extracted_stream = s.get_stream(); try { @@ -116,7 +116,7 @@ namespace oxen::quic::test }; stream_data_callback server_io_data_cb = [&](Stream&, bstring_view) { - log::debug(log_cat, "Calling server stream data callback... data received... incrementing counter..."); + log::debug(test_cat, "Calling server stream data callback... data received... incrementing counter..."); data_check += 1; try { @@ -130,7 +130,7 @@ namespace oxen::quic::test }; stream_data_callback client_io_data_cb = [&](Stream&, bstring_view) { - log::debug(log_cat, "Calling client stream data callback... data received... incrementing counter..."); + log::debug(test_cat, "Calling client stream data callback... data received... incrementing counter..."); data_check += 1; try { @@ -151,7 +151,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local); REQUIRE_NOTHROW(server_endpoint->listen(server_tls, server_io_data_cb, server_io_open_cb)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local); auto client_ci = client_endpoint->connect(client_remote, client_tls, client_io_data_cb, client_io_open_cb); diff --git a/tests/007-datagrams.cpp b/tests/007-datagrams.cpp index 28527570..96df3579 100644 --- a/tests/007-datagrams.cpp +++ b/tests/007-datagrams.cpp @@ -76,7 +76,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local); REQUIRE_NOTHROW(server_endpoint->listen(server_tls)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local, client_established); auto conn_interface = client_endpoint->connect(client_remote, client_tls); @@ -104,7 +104,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local, default_gram); REQUIRE_NOTHROW(server_endpoint->listen(server_tls)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local, default_gram, client_established); auto conn_interface = client_endpoint->connect(client_remote, client_tls); @@ -133,7 +133,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local, split_dgram); REQUIRE_NOTHROW(server_endpoint->listen(server_tls)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local, split_dgram, client_established); auto conn_interface = client_endpoint->connect(client_remote, client_tls); @@ -159,13 +159,13 @@ namespace oxen::quic::test std::future data_future = data_promise.get_future(); dgram_data_callback recv_dgram_cb = [&](dgram_interface&, bstring) { - log::debug(log_cat, "Calling endpoint receive datagram callback... data received..."); + log::debug(test_cat, "Calling endpoint receive datagram callback... data received..."); data_promise.set_value(); }; std::atomic bad_call = false; dgram_data_callback overridden_dgram_cb = [&](dgram_interface&, bstring) { - log::critical(log_cat, "Wrong dgram callback invoked!"); + log::critical(test_cat, "Wrong dgram callback invoked!"); bad_call = true; }; @@ -179,7 +179,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local, default_gram, overridden_dgram_cb); REQUIRE_NOTHROW(server_endpoint->listen(server_tls, recv_dgram_cb)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client = test_net.endpoint(client_local, default_gram, client_established); auto conn_interface = client->connect(client_remote, client_tls); @@ -215,7 +215,7 @@ namespace oxen::quic::test std::future data_future = data_promise.get_future(); dgram_data_callback recv_dgram_cb = [&](dgram_interface&, bstring data) { - log::debug(log_cat, "Calling endpoint receive datagram callback... data received..."); + log::debug(test_cat, "Calling endpoint receive datagram callback... data received..."); ++data_counter; if (data == "final"_bs) data_promise.set_value(); @@ -231,7 +231,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local, split_dgram); REQUIRE_NOTHROW(server_endpoint->listen(server_tls, recv_dgram_cb)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client = test_net.endpoint(client_local, split_dgram, client_established); auto conn_interface = client->connect(client_remote, client_tls); @@ -287,7 +287,7 @@ namespace oxen::quic::test SECTION("Simple oversized datagram transmission - Clear first row") { - log::trace(log_cat, "Beginning the unit test from hell"); + log::trace(test_cat, "Beginning the unit test from hell"); auto client_established = callback_waiter{[](connection_interface&) {}}; Network test_net{}; @@ -303,7 +303,7 @@ namespace oxen::quic::test data_futures[i] = data_promises[i].get_future(); dgram_data_callback recv_dgram_cb = [&](dgram_interface&, bstring) { - log::debug(log_cat, "Calling endpoint receive datagram callback... data received..."); + log::debug(test_cat, "Calling endpoint receive datagram callback... data received..."); try { @@ -327,7 +327,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local, split_dgram, recv_dgram_cb); REQUIRE_NOTHROW(server_endpoint->listen(server_tls)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client = test_net.endpoint(client_local, split_dgram, client_established); auto conn_interface = client->connect(client_remote, client_tls); @@ -371,7 +371,7 @@ namespace oxen::quic::test SECTION("Simple datagram transmission - mixed sizes") { - log::trace(log_cat, "Beginning the unit test from hell"); + log::trace(test_cat, "Beginning the unit test from hell"); auto client_established = callback_waiter{[](connection_interface&) {}}; Network test_net{}; @@ -387,7 +387,7 @@ namespace oxen::quic::test data_futures[i] = data_promises[i].get_future(); dgram_data_callback recv_dgram_cb = [&](dgram_interface&, bstring) { - log::debug(log_cat, "Calling endpoint receive datagram callback... data received..."); + log::debug(test_cat, "Calling endpoint receive datagram callback... data received..."); try { @@ -411,7 +411,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local, split_dgram, recv_dgram_cb); REQUIRE_NOTHROW(server_endpoint->listen(server_tls)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client = test_net.endpoint(client_local, split_dgram, client_established); auto conn_interface = client->connect(client_remote, client_tls); @@ -455,7 +455,7 @@ namespace oxen::quic::test SKIP("Rotating buffer testing not enabled for this test iteration!"); SECTION("Simple datagram transmission - induced loss") { - log::trace(log_cat, "Beginning the unit test from hell"); + log::trace(test_cat, "Beginning the unit test from hell"); auto client_established = callback_waiter{[](connection_interface&) {}}; Network test_net{}; @@ -473,7 +473,7 @@ namespace oxen::quic::test bstring received{}; dgram_data_callback recv_dgram_cb = [&](dgram_interface&, bstring data) { - log::debug(log_cat, "Calling endpoint receive datagram callback... data received..."); + log::debug(test_cat, "Calling endpoint receive datagram callback... data received..."); counter += 1; received.swap(data); @@ -499,7 +499,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local, split_dgram, recv_dgram_cb); REQUIRE_NOTHROW(server_endpoint->listen(server_tls)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client = test_net.endpoint(client_local, split_dgram, client_established); auto conn_interface = client->connect(client_remote, client_tls); @@ -545,7 +545,7 @@ namespace oxen::quic::test { SECTION("Simple datagram transmission - flip flop ordering") { - log::trace(log_cat, "Beginning the unit test from hell"); + log::trace(test_cat, "Beginning the unit test from hell"); auto client_established = callback_waiter{[](connection_interface&) {}}; Network test_net{}; @@ -561,12 +561,12 @@ namespace oxen::quic::test data_futures[i] = data_promises[i].get_future(); dgram_data_callback recv_dgram_cb = [&](dgram_interface&, bstring) { - log::debug(log_cat, "Calling endpoint receive datagram callback... data received..."); + log::debug(test_cat, "Calling endpoint receive datagram callback... data received..."); try { data_counter += 1; - log::trace(log_cat, "Data counter: {}", data_counter.load()); + log::trace(test_cat, "Data counter: {}", data_counter.load()); data_promises.at(index).set_value(); index += 1; } @@ -586,7 +586,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local, split_dgram, recv_dgram_cb); REQUIRE_NOTHROW(server_endpoint->listen(server_tls)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client = test_net.endpoint(client_local, split_dgram, client_established); auto conn_interface = client->connect(client_remote, client_tls); diff --git a/tests/008-conn_hooks.cpp b/tests/008-conn_hooks.cpp index 84b17eeb..e4866e31 100644 --- a/tests/008-conn_hooks.cpp +++ b/tests/008-conn_hooks.cpp @@ -30,7 +30,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local, server_established, server_closed); REQUIRE_NOTHROW(server_endpoint->listen(server_tls)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local, client_established, client_closed); auto conn_interface = client_endpoint->connect(client_remote, client_tls); @@ -46,7 +46,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local); REQUIRE_NOTHROW(server_endpoint->listen(server_tls, server_established, server_closed)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local); auto conn_interface = client_endpoint->connect(client_remote, client_tls, client_established, client_closed); diff --git a/tests/009-alpns.cpp b/tests/009-alpns.cpp index f72ef1d1..f8a7ad35 100644 --- a/tests/009-alpns.cpp +++ b/tests/009-alpns.cpp @@ -32,7 +32,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local, timeout); REQUIRE_NOTHROW(server_endpoint->listen(server_tls)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local, client_established, timeout); @@ -48,7 +48,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local, timeout); REQUIRE_NOTHROW(server_endpoint->listen(server_tls)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local, client_established, client_closed, client_alpns, timeout); @@ -64,7 +64,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local, server_alpns, timeout); REQUIRE_NOTHROW(server_endpoint->listen(server_tls)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local, client_established, client_closed, timeout); @@ -81,7 +81,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local, server_alpns, timeout); REQUIRE_NOTHROW(server_endpoint->listen(server_tls)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local, client_established, client_closed, client_alpns, timeout); @@ -99,7 +99,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local, server_alpns, timeout); REQUIRE_NOTHROW(server_endpoint->listen(server_tls)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local, client_established, client_alpns, timeout); @@ -121,7 +121,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local, server_alpns, timeout); REQUIRE_NOTHROW(server_endpoint->listen(server_tls)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; opt::outbound_alpns client_alpns{{"foobar"_us}}; auto client_endpoint = test_net.endpoint(client_local, client_established, client_closed, client_alpns, timeout); @@ -138,7 +138,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local, server_alpns, timeout); REQUIRE_NOTHROW(server_endpoint->listen(server_tls)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; opt::alpns client_alpns{"special-alpn"}; auto client_endpoint = test_net.endpoint(client_local, client_established, client_alpns, timeout); diff --git a/tests/010-migration.cpp b/tests/010-migration.cpp index b721f1b0..e5732422 100644 --- a/tests/010-migration.cpp +++ b/tests/010-migration.cpp @@ -31,18 +31,18 @@ namespace oxen::quic::test std::shared_ptr server_ci; stream_data_callback server_data_cb = [&](Stream&, bstring_view dat) { - log::debug(log_cat, "Calling server stream data callback... data received..."); + log::debug(test_cat, "Calling server stream data callback... data received..."); REQUIRE(good_msg == dat); d_promise.set_value(); }; auto server_established = callback_waiter{[](connection_interface&) {}}; - auto client_established_b = callback_waiter{[](connection_interface&) { log::trace(log_cat, "LOOK ME UP BRO"); }}; + auto client_established_b = callback_waiter{[](connection_interface&) { log::trace(test_cat, "LOOK ME UP BRO"); }}; auto server_endpoint = test_net.endpoint(server_local, server_established); server_endpoint->listen(server_tls, server_data_cb); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_established = [&](connection_interface& ci) mutable { if (not address_flipped) @@ -60,10 +60,10 @@ namespace oxen::quic::test } // Uncomment this when NGTCP2 releases v1.2.0 - // SECTION("Immediate migration") - // { - // TestHelper::migrate_connection_immediate(conn, client_secondary); - // } + SECTION("Immediate migration") + { + TestHelper::migrate_connection_immediate(conn, client_secondary); + } address_flipped = true; conn_promise_a.set_value(); @@ -72,7 +72,7 @@ namespace oxen::quic::test { if (not secondary_connected) { - log::trace(log_cat, "Skipping address flip!"); + log::trace(test_cat, "Skipping address flip!"); secondary_connected = true; conn_promise_b.set_value(); } @@ -100,7 +100,7 @@ namespace oxen::quic::test server_ci = server_endpoint->get_all_conns(Direction::INBOUND).front(); std::this_thread::sleep_for(5ms); - RemoteAddress client_remote_b{defaults::CLIENT_PUBKEY, "127.0.0.1"s, client_ci->local().port()}; + RemoteAddress client_remote_b{defaults::CLIENT_PUBKEY, LOCALHOST, client_ci->local().port()}; REQUIRE_FALSE(original_addr == client_ci->local()); diff --git a/tests/011-manual_transmission.cpp b/tests/011-manual_transmission.cpp index 77e95bfa..d11f87d2 100644 --- a/tests/011-manual_transmission.cpp +++ b/tests/011-manual_transmission.cpp @@ -109,7 +109,7 @@ namespace oxen::quic::test REQUIRE_NOTHROW(vanilla_server->listen(server_tls)); REQUIRE_NOTHROW(manual_server->listen(server_tls)); - RemoteAddress vanilla_client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, vanilla_server->local().port()}; + RemoteAddress vanilla_client_remote{defaults::SERVER_PUBKEY, LOCALHOST, vanilla_server->local().port()}; vanilla_client_ci = vanilla_client->connect(vanilla_client_remote, client_tls); diff --git a/tests/012-watermarks.cpp b/tests/012-watermarks.cpp index f1672a65..1d26fdd8 100644 --- a/tests/012-watermarks.cpp +++ b/tests/012-watermarks.cpp @@ -23,7 +23,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local, server_established); REQUIRE_NOTHROW(server_endpoint->listen(server_tls)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local, client_established); auto conn_interface = client_endpoint->connect(client_remote, client_tls); @@ -64,13 +64,13 @@ namespace oxen::quic::test 2000, opt::watermark{ [&](const Stream&) { - log::debug(log_cat, "Executing low hook!"); + log::debug(test_cat, "Executing low hook!"); low_count += 1; }, true}, opt::watermark{ [&](const Stream&) { - log::debug(log_cat, "Executing high hook!"); + log::debug(test_cat, "Executing high hook!"); high_count += 1; }, true}); @@ -107,13 +107,13 @@ namespace oxen::quic::test 2000, opt::watermark{ [&](const Stream&) { - log::debug(log_cat, "Executing low hook!"); + log::debug(test_cat, "Executing low hook!"); low_count += 1; }, true}, opt::watermark{ [&](const Stream&) { - log::debug(log_cat, "Executing high hook!"); + log::debug(test_cat, "Executing high hook!"); high_count += 1; }, true}); diff --git a/tests/013-eventhandler.cpp b/tests/013-eventhandler.cpp index e04f96b3..4e508fb4 100644 --- a/tests/013-eventhandler.cpp +++ b/tests/013-eventhandler.cpp @@ -44,7 +44,7 @@ namespace oxen::quic::test auto server_endpoint = test_net.endpoint(server_local); REQUIRE_NOTHROW(server_endpoint->listen(server_tls, server_data_cb)); - RemoteAddress client_remote{defaults::SERVER_PUBKEY, "127.0.0.1"s, server_endpoint->local().port()}; + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; auto client_endpoint = test_net.endpoint(client_local); auto conn_interface = client_endpoint->connect(client_remote, client_tls); diff --git a/tests/014-0rtt-resets.cpp b/tests/014-0rtt-resets.cpp new file mode 100644 index 00000000..639b2286 --- /dev/null +++ b/tests/014-0rtt-resets.cpp @@ -0,0 +1,64 @@ +#include +#include +#include +#include + +#include "utils.hpp" + +namespace oxen::quic::test +{ + using namespace std::literals; + + TEST_CASE("014 - 0rtt", "[014][0rtt]") + { + auto client_established = callback_waiter{[](connection_interface&) {}}; + auto server_established = callback_waiter{[](connection_interface&) {}}; + + Network test_net{}; + + auto [client_tls, server_tls] = defaults::tls_creds_from_ed_keys(); + + Address server_local{}; + Address client_local{}; + + auto server_endpoint = test_net.endpoint(server_local, server_established, opt::enable_0rtt_ticketing{}); + CHECK_NOTHROW(server_endpoint->listen(server_tls)); + + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; + + auto client_endpoint = test_net.endpoint(client_local, client_established, opt::enable_0rtt_ticketing{}); + auto client_ci = client_endpoint->connect(client_remote, client_tls); + + CHECK(client_established.wait()); + CHECK(server_established.wait()); + CHECK(client_ci->is_validated()); + } + + TEST_CASE("069 - stateless reset", "[069][reset]") + { + auto client_established = callback_waiter{[](connection_interface&) {}}; + auto server_established = callback_waiter{[](connection_interface&) {}}; + + Network test_net{}; + + auto [client_tls, server_tls] = defaults::tls_creds_from_ed_keys(); + + Address server_local{}; + Address client_local{}; + + auto server_endpoint = test_net.endpoint(server_local, server_established, opt::disable_stateless_reset{}); + CHECK_NOTHROW(server_endpoint->listen(server_tls)); + REQUIRE_FALSE(server_endpoint->stateless_reset_enabled()); + + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; + + auto client_endpoint = test_net.endpoint(client_local, client_established, opt::disable_stateless_reset{}); + REQUIRE_FALSE(client_endpoint->stateless_reset_enabled()); + + auto client_ci = client_endpoint->connect(client_remote, client_tls); + + CHECK(client_established.wait()); + CHECK(server_established.wait()); + CHECK(client_ci->is_validated()); + } +} // namespace oxen::quic::test diff --git a/tests/015-bt-encoding.cpp b/tests/015-bt-encoding.cpp new file mode 100644 index 00000000..f56fd748 --- /dev/null +++ b/tests/015-bt-encoding.cpp @@ -0,0 +1,197 @@ +#include +#include +#include +#include + +#include "utils.hpp" + +namespace oxen::quic::test +{ + using namespace std::literals; + + static constexpr auto _a = "apple pie"sv; + static constexpr auto _b = "tell me a good story"sv; + static constexpr auto _c = 476938; + static constexpr auto _n = "good night"sv; + + static bool bt_decode(std::string_view arg) + { + auto ret = true; + + try + { + oxenc::bt_dict_consumer btdc{arg}; + ret &= btdc.require("a") == _a; + ret &= btdc.require("b") == _b; + ret &= btdc.require("c") == _c; + ret &= btdc.require("n") == _n; + } + catch (const std::exception& e) + { + log::warning(test_cat, "BT decode exception: {}", e.what()); + ret = false; + } + + return ret; + } + + static std::string bt_encode() + { + oxenc::bt_dict_producer btdp; + + btdp.append("a", _a); + btdp.append("b", _b); + btdp.append("c", _c); + btdp.append("n", _n); + + return std::move(btdp).str(); + } + + TEST_CASE("015 - Bt-encoding; Messaging", "[015][bt][messaging]") + { + Network test_net{}; + + auto [client_tls, server_tls] = defaults::tls_creds_from_ed_keys(); + + Address server_local{}; + Address client_local{}; + + auto server_bp_cb = callback_waiter{[&](message msg) { + log::debug(test_cat, "Server bparser received: {}", msg.view()); + CHECK(bt_decode(msg.body())); + msg.respond(msg.body()); + }}; + + auto client_bp_cb = callback_waiter{[&](message msg) { + log::debug(test_cat, "Client bparser received: {}", msg.view()); + CHECK(bt_decode(msg.body())); + }}; + + auto server_conn_established = [&](connection_interface& c) { + auto s = c.queue_incoming_stream(); + s->register_handler(TEST_ENDPOINT, server_bp_cb); + }; + + stream_constructor_callback client_constructor = [&](Connection& c, Endpoint& e, std::optional) { + return e.make_shared(c, e); + }; + + auto server_endpoint = test_net.endpoint(server_local); + REQUIRE_NOTHROW(server_endpoint->listen(server_tls, server_conn_established)); + + RemoteAddress client_remote{defaults::SERVER_PUBKEY, LOCALHOST, server_endpoint->local().port()}; + + auto client_endpoint = test_net.endpoint(client_local); + auto conn_interface = client_endpoint->connect(client_remote, client_tls, client_constructor); + + std::shared_ptr client_bp = conn_interface->open_stream(); + + client_bp->command(TEST_ENDPOINT, bt_encode(), client_bp_cb); + + REQUIRE(server_bp_cb.wait()); + REQUIRE(client_bp_cb.wait()); + } + + TEST_CASE("015 - Bt-encoding; Message Chaining", "[015][bt][chaining]") + { + // clang-format off + /** + Node A Node B Node C + req(A) -> recv(A) 1) Node A dispatches req-A to Node B + chain(B) -> recv(B) 2) Node B chains req-B to Node C; Node C receives req-B from Node B + recv(C) <- <- <- chain(C) 3) Node C chains req-C to Node A; Node A receives req-A from Node C + reply(C) -> -> -> recv(C) 4) Node A replies to req-C; Node C receives req-C reply from Node A + recv(B) <- reply(B) 5) Node C replies to req-B; Node B receives req-B reply from Node B + recv(A) <- reply(A) 6) Node B replies to req-A; Node A receives req-A reply from Node B + */ + // clang-format on + + Network test_net{}; + + auto [client_tls, server_tls] = defaults::tls_creds_from_ed_keys(); + + Address node_a_local{}; + Address node_b_local{}; + Address node_c_local{}; + + std::shared_ptr node_a_bp, node_b_bp, node_c_bp; + + auto node_a_response_cb = callback_waiter{[&](message msg) { + log::debug(test_cat, "Node A received response from Node B: {}", msg.view()); + CHECK(bt_decode(msg.body())); + }}; + + auto node_a_bp_cb = [&](message msg) { + log::debug(test_cat, "Node A received request from Node C: {}", msg.view()); + CHECK(bt_decode(msg.body())); + msg.respond(msg.body()); + }; + + auto node_b_bp_cb = [&](message msg) { + log::debug(test_cat, "Node B received request from Node A: {}", msg.view()); + CHECK(bt_decode(msg.body())); + + log::debug(test_cat, "Node B chaining request to Node C", msg.view()); + auto body = msg.body_str(); + node_b_bp->command(TEST_ENDPOINT, std::move(body), [prev = std::move(msg)](message msg) mutable { + log::debug(test_cat, "Node B received response from Node C: {}", msg.view()); + prev.respond(msg.body()); + }); + }; + + auto node_c_bp_cb = [&](message msg) { + log::debug(test_cat, "Node C received request from Node B: {}", msg.view()); + CHECK(bt_decode(msg.body())); + + log::debug(test_cat, "Node C chaining request to Node A", msg.view()); + auto body = msg.body_str(); + node_c_bp->command(TEST_ENDPOINT, std::move(body), [prev = std::move(msg)](message msg) mutable { + log::debug(test_cat, "Node C received response from Node A: {}", msg.view()); + prev.respond(msg.body()); + }); + }; + + auto inbound_conn_established_a = [&](connection_interface& c) { + auto s = c.queue_incoming_stream(); + s->register_handler(TEST_ENDPOINT, node_a_bp_cb); + }; + + auto inbound_conn_established_b = [&](connection_interface& c) { + auto s = c.queue_incoming_stream(); + s->register_handler(TEST_ENDPOINT, node_b_bp_cb); + }; + + auto inbound_conn_established_c = [&](connection_interface& c) { + auto s = c.queue_incoming_stream(); + s->register_handler(TEST_ENDPOINT, node_c_bp_cb); + }; + + stream_constructor_callback outbound_stream_ctor = [&](Connection& c, Endpoint& e, std::optional) { + return e.make_shared(c, e); + }; + + auto node_a = test_net.endpoint(node_a_local); + auto node_b = test_net.endpoint(node_b_local); + auto node_c = test_net.endpoint(node_c_local); + + REQUIRE_NOTHROW(node_a->listen(server_tls, inbound_conn_established_a)); + REQUIRE_NOTHROW(node_b->listen(server_tls, inbound_conn_established_b)); + REQUIRE_NOTHROW(node_c->listen(server_tls, inbound_conn_established_c)); + + RemoteAddress node_a_remote{defaults::SERVER_PUBKEY, LOCALHOST, node_b->local().port()}; + RemoteAddress node_b_remote{defaults::SERVER_PUBKEY, LOCALHOST, node_c->local().port()}; + RemoteAddress node_c_remote{defaults::SERVER_PUBKEY, LOCALHOST, node_a->local().port()}; + + auto node_a_ci = node_a->connect(node_a_remote, client_tls, outbound_stream_ctor); + auto node_b_ci = node_b->connect(node_b_remote, client_tls, outbound_stream_ctor); + auto node_c_ci = node_c->connect(node_c_remote, client_tls, outbound_stream_ctor); + + node_a_bp = node_a_ci->open_stream(); + node_b_bp = node_b_ci->open_stream(); + node_c_bp = node_c_ci->open_stream(); + + node_a_bp->command(TEST_ENDPOINT, bt_encode(), node_a_response_cb); + + REQUIRE(node_a_response_cb.wait()); + } +} // namespace oxen::quic::test diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7fe5c66a..b23c1b61 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -40,6 +40,8 @@ if(LIBQUIC_BUILD_TESTS) 011-manual_transmission.cpp 012-watermarks.cpp 013-eventhandler.cpp + 014-0rtt-resets.cpp + 015-bt-encoding.cpp main.cpp case_logger.cpp @@ -49,9 +51,10 @@ if(LIBQUIC_BUILD_TESTS) endif() + if(LIBQUIC_BUILD_SPEEDTEST) set(LIBQUIC_SPEEDTEST_PREFIX "" CACHE STRING "Binary prefix for speedtest binaries") - set(speedtests speedtest-client speedtest-server dgram-speed-client dgram-speed-server) + set(speedtests speedtest-client speedtest-server dgram-speed-client dgram-speed-server ping-client ping-server) foreach(x ${speedtests}) add_executable(${x} ${x}.cpp) target_link_libraries(${x} PRIVATE tests_common) diff --git a/tests/ping-client.cpp b/tests/ping-client.cpp new file mode 100644 index 00000000..f344874e --- /dev/null +++ b/tests/ping-client.cpp @@ -0,0 +1,143 @@ +/* + Ping client binary +*/ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "utils.hpp" + +using namespace oxen::quic; + +constexpr auto client_msg = "good morning"_bsv; + +int main(int argc, char* argv[]) +{ + CLI::App cli{"libQUIC ping client"}; + + std::string log_file, log_level; + add_log_opts(cli, log_file, log_level); + + std::string remote_addr = "127.0.0.1:5500"; + cli.add_option("--remote", remote_addr, "Remove address to connect to")->type_name("IP:PORT")->capture_default_str(); + + std::string remote_pubkey; + cli.add_option("-p,--remote-pubkey", remote_pubkey, "Remote server pubkey (not needed with verification disabled)") + ->type_name("PUBKEY_HEX_OR_B64") + ->transform([](const std::string& val) -> std::string { + if (auto pk = decode_bytes(val)) + return std::move(*pk); + throw CLI::ValidationError{ + "Invalid value passed to --remote-pubkey: expected value encoded as hex or base64"}; + }); + + std::string local_addr = ""; + cli.add_option("--local", local_addr, "Local bind address (optional)")->type_name("IP:PORT")->capture_default_str(); + + bool no_verify = false; + cli.add_flag( + "-V,--no-verify", no_verify, "Disable key verification on incoming connections (cannot be disabled with 0-RTT)"); + + bool enable_0rtt = false; + cli.add_flag( + "-Z,--enable-0rtt", + enable_0rtt, + "Enable 0-RTT and early data for this endpoint (cannot be used without key verification)"); + + try + { + cli.parse(argc, argv); + + if (no_verify and enable_0rtt) + throw std::invalid_argument{"0-RTT must be used with key verification!"}; + + if (enable_0rtt and remote_pubkey.empty()) + throw std::invalid_argument{"0-RTT must be used with remote key!"}; + + if (not no_verify and remote_pubkey.empty()) + throw std::invalid_argument{"If remote key is provided, key verification must be turned OFF"}; + } + catch (const CLI::ParseError& e) + { + return cli.exit(e); + } + + setup_logging(log_file, log_level); + + Network client_net{}; + + auto [seed, pubkey] = generate_ed25519(); + auto client_tls = GNUTLSCreds::make_from_ed_keys(seed, pubkey); + + Address client_local{}; + if (!local_addr.empty()) + { + auto [a, p] = parse_addr(local_addr); + client_local = Address{a, p}; + } + + std::promise all_done; + std::atomic first_data{false}; + + /** 0: start connecting + 1: connection established + 2: send data down stream + 3: recv (first) stream response + 4: connection closed + */ + std::array timing; + + auto conn_established = [&](connection_interface& ci) { + timing[1] = get_timestamp().count(); + log::critical(test_cat, "Connection established to {}", ci.remote()); + }; + + auto conn_closed = [&](connection_interface& ci, uint64_t) { + timing[4] = get_timestamp().count(); + log::critical(test_cat, "Connection closed to {}", ci.remote()); + all_done.set_value(); + }; + + auto stream_recv = [&](Stream& s, bstring_view) { + // get the time first, then do ops + auto t = get_timestamp().count(); + if (not first_data.exchange(true)) + { + timing[3] = t; + log::critical(test_cat, "Received first data on connection to {}", s.remote()); + } + }; + + auto [server_a, server_p] = parse_addr(remote_addr); + Address server_addr{server_a, server_p}; + + log::info(test_cat, "Constructing endpoint on {}", client_local); + + auto client = enable_0rtt + ? client_net.endpoint(client_local, conn_established, conn_closed, opt::enable_0rtt_ticketing{}) + : client_net.endpoint(client_local, conn_established, conn_closed); + + log::info(test_cat, "Connecting to {}...", server_addr); + + timing[0] = get_timestamp().count(); + auto client_conn = no_verify ? client->connect(server_addr, client_tls, stream_recv, opt::disable_key_verification{}) + : client->connect(RemoteAddress{remote_pubkey, server_addr}, stream_recv, client_tls); + auto client_stream = client_conn->open_stream(); + timing[2] = get_timestamp().count(); + client_stream->send(client_msg); + + all_done.get_future().wait(); + + log::critical(test_cat, "\n\tConnection started: {}.{}ms", timing[0] / 1'000'000, timing[0] % 1000); + log::critical(test_cat, "\n\tConnection established: {}.{}ms", timing[1] / 1'000'000, timing[1] % 1000); + log::critical(test_cat, "\n\tFirst stream data sent: {}.{}ms", timing[2] / 1'000'000, timing[2] % 1000); + log::critical(test_cat, "\n\tFirst stream data received: {}.{}ms", timing[3] / 1'000'000, timing[3] % 1000); + log::critical(test_cat, "\n\tConnection closed: {}.{}ms", timing[4] / 1'000'000, timing[4] % 1000); +} diff --git a/tests/ping-server.cpp b/tests/ping-server.cpp new file mode 100644 index 00000000..d92b9525 --- /dev/null +++ b/tests/ping-server.cpp @@ -0,0 +1,128 @@ +/* + Ping server binary +*/ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "utils.hpp" + +using namespace oxen::quic; + +int main(int argc, char* argv[]) +{ + CLI::App cli{"libQUIC ping server"}; + + std::string log_file, log_level; + add_log_opts(cli, log_file, log_level); + + std::string server_addr = "127.0.0.1:5500"; + + cli.add_option("--listen", server_addr, "Server address to listen on")->type_name("IP:PORT")->capture_default_str(); + + bool no_verify = false; + cli.add_flag( + "-V,--no-verify", no_verify, "Disable key verification on incoming connections (cannot be disabled with 0-RTT)"); + + bool enable_0rtt = false; + cli.add_flag( + "-Z,--enable-0rtt", + enable_0rtt, + "Enable 0-RTT and early data for this endpoint (cannot be used without key verification)"); + + try + { + cli.parse(argc, argv); + + if (no_verify and enable_0rtt) + throw std::invalid_argument{"0-RTT must be used with key verification!"}; + } + catch (const CLI::ParseError& e) + { + return cli.exit(e); + } + + setup_logging(log_file, log_level); + + auto [seed, pubkey] = generate_ed25519(); + auto server_tls = GNUTLSCreds::make_from_ed_keys(seed, pubkey); + + Network server_net{}; + + auto [listen_addr, listen_port] = parse_addr(server_addr, 5500); + Address server_local{listen_addr, listen_port}; + + std::shared_ptr server; + + std::atomic first_data{false}; + + /** 0: connection established + 1: stream opened + 2: recv (first) stream data / close conn + 3: connection closed + */ + std::array timing; + + auto conn_established = [&](connection_interface& ci) { + timing[0] = get_timestamp().count(); + log::critical(test_cat, "Connection established to {}", ci.remote()); + }; + + auto conn_closed = [&](connection_interface& ci, uint64_t) { + timing[3] = get_timestamp().count(); + log::critical(test_cat, "Connection closed to {}", ci.remote()); + + log::critical(test_cat, "\n\tConnection established: {}.{}ms", timing[0] / 1'000'000, timing[0] % 1000); + log::critical(test_cat, "\n\tFirst stream opened: {}.{}ms", timing[1] / 1'000'000, timing[1] % 1000); + log::critical( + test_cat, "\n\tFirst stream data received/close sent: {}.{}ms", timing[2] / 1'000'000, timing[2] % 1000); + log::critical(test_cat, "\n\tConnection closed: {}.{}ms", timing[3] / 1'000'000, timing[3] % 1000); + + first_data = false; + }; + + auto stream_opened = [&](Stream& s) { + timing[1] = get_timestamp().count(); + log::critical(test_cat, "Stream {} opened!", s.stream_id()); + return 0; + }; + + auto stream_recv = [&](Stream& s, bstring_view) { + // get the time first, then do ops + auto t = get_timestamp().count(); + if (not first_data.exchange(true)) + { + timing[2] = t; + log::critical(test_cat, "Received first data on connection to {}", s.remote()); + s.send("good afternoon"_bsv); + server->get_conn(s.reference_id)->close_connection(); + } + }; + + try + { + log::info(test_cat, "Starting up endpoint..."); + server = server_net.endpoint(server_local, conn_established, conn_closed); + server->listen(server_tls, stream_opened, stream_recv); + log::critical( + test_cat, + "Server listening on: {}{}awaiting connections...", + server_local, + no_verify ? ", " : " with pubkey: \n\n\t{}\n\n"_format(oxenc::to_base64(pubkey))); + } + catch (const std::exception& e) + { + log::critical(test_cat, "Failed to start server: {}!", e.what()); + return 1; + } + + for (;;) + std::this_thread::sleep_for(10min); +} diff --git a/tests/utils.cpp b/tests/utils.cpp index d59cb360..0c75406c 100644 --- a/tests/utils.cpp +++ b/tests/utils.cpp @@ -19,7 +19,7 @@ namespace oxen::quic current_sock.swap(new_sock); auto rv = ngtcp2_conn_initiate_migration(conn, conn._path, get_timestamp().count()); - log::trace(log_cat, "{}", ngtcp2_strerror(rv)); + log::trace(test_cat, "{}", ngtcp2_strerror(rv)); } void TestHelper::migrate_connection_immediate(Connection& conn, Address new_bind) @@ -37,7 +37,7 @@ namespace oxen::quic current_sock.swap(new_sock); auto rv = ngtcp2_conn_initiate_immediate_migration(conn, conn._path, get_timestamp().count()); - log::trace(log_cat, "{}", ngtcp2_strerror(rv)); + log::trace(test_cat, "{}", ngtcp2_strerror(rv)); } void TestHelper::nat_rebinding(Connection& conn, Address new_bind) diff --git a/tests/utils.hpp b/tests/utils.hpp index a2ef647d..2efad42d 100644 --- a/tests/utils.hpp +++ b/tests/utils.hpp @@ -14,6 +14,7 @@ #include #include #include +#include namespace oxen::quic { @@ -21,7 +22,7 @@ namespace oxen::quic namespace log = oxen::log; using namespace log::literals; - inline auto test_cat = log::Cat("test"); + inline auto test_cat = log::Cat("quic-test"); // Borrowing these from src/internal.hpp: void logger_config(std::string out = "stderr", log::Type type = log::Type::Print, log::Level reset = log::Level::trace); @@ -29,6 +30,7 @@ namespace oxen::quic using namespace oxenc::literals; + inline const std::string LOCALHOST = "127.0.0.1"s; inline const std::string TEST_ENDPOINT = "test_endpoint"s; inline const std::string TEST_BODY = "test_body"s;