From 96c10e4a3f8b793aed12975f1b52994d4383fb09 Mon Sep 17 00:00:00 2001 From: amdfxlucas Date: Tue, 28 Nov 2023 16:18:14 +0000 Subject: [PATCH] QUIC Transport --- CMakeLists.txt | 5 + cmake/Hunter/init.cmake | 3 + cmake/dependencies.cmake | 248 +++++++++++- example/01-echo/libp2p_echo_client.cpp | 33 +- example/01-echo/libp2p_echo_server.cpp | 75 +++- .../ecdsa_provider/ecdsa_provider_impl.hpp | 2 +- include/libp2p/injector/network_injector.hpp | 181 ++++++++- .../multi/multiaddress_protocol_list.hpp | 2 +- .../{tcp/tcp_util.hpp => impl/util.hpp} | 102 +++-- include/libp2p/transport/quic.hpp | 4 + .../libp2p/transport/quic/libp2p-quic-api.hpp | 40 ++ .../libp2p/transport/quic/quic_connection.hpp | 130 +++++++ .../libp2p/transport/quic/quic_listener.hpp | 61 +++ include/libp2p/transport/quic/quic_stream.hpp | 187 +++++++++ .../libp2p/transport/quic/quic_transport.hpp | 158 ++++++++ include/libp2p/transport/tcp/tcp_listener.hpp | 2 +- .../libp2p/transport/tcp/tcp_transport.hpp | 2 +- src/basic/CMakeLists.txt | 1 + src/crypto/CMakeLists.txt | 3 +- .../ecdsa_provider/ecdsa_provider_impl.cpp | 2 +- src/layer/websocket/ws_adaptor.cpp | 4 +- src/multi/converters/converter_utils.cpp | 7 +- src/muxer/yamux/CMakeLists.txt | 1 + src/network/CMakeLists.txt | 1 + src/protocol/gossip/impl/CMakeLists.txt | 1 + src/protocol/kademlia/impl/CMakeLists.txt | 1 + src/security/tls/CMakeLists.txt | 1 + src/transport/CMakeLists.txt | 1 + src/transport/impl/CMakeLists.txt | 1 + src/transport/quic/CMakeLists.txt | 52 +++ src/transport/quic/quic_connection.cpp | 252 ++++++++++++ src/transport/quic/quic_listener.cpp | 96 +++++ src/transport/quic/quic_stream.cpp | 152 ++++++++ src/transport/quic/quic_transport.cpp | 358 ++++++++++++++++++ src/transport/tcp/tcp_connection.cpp | 2 +- src/transport/tcp/tcp_listener.cpp | 2 +- src/transport/tcp/tcp_transport.cpp | 2 +- test/acceptance/p2p/CMakeLists.txt | 1 + test/libp2p/protocol/CMakeLists.txt | 1 + test/libp2p/protocol_muxer/CMakeLists.txt | 1 + test/libp2p/transport/upgrader_test.cpp | 2 +- 41 files changed, 2123 insertions(+), 57 deletions(-) rename include/libp2p/transport/{tcp/tcp_util.hpp => impl/util.hpp} (68%) create mode 100644 include/libp2p/transport/quic.hpp create mode 100644 include/libp2p/transport/quic/libp2p-quic-api.hpp create mode 100644 include/libp2p/transport/quic/quic_connection.hpp create mode 100644 include/libp2p/transport/quic/quic_listener.hpp create mode 100644 include/libp2p/transport/quic/quic_stream.hpp create mode 100644 include/libp2p/transport/quic/quic_transport.hpp create mode 100644 src/transport/quic/CMakeLists.txt create mode 100644 src/transport/quic/quic_connection.cpp create mode 100644 src/transport/quic/quic_listener.cpp create mode 100644 src/transport/quic/quic_stream.cpp create mode 100644 src/transport/quic/quic_transport.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index fb672123e..ec4e92b33 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,5 @@ cmake_minimum_required(VERSION 3.12) +#cmake_configure_log() find_program(CCACHE_FOUND ccache) if (CCACHE_FOUND) @@ -20,8 +21,12 @@ include("cmake/Hunter/init.cmake") project(libp2p VERSION 0.1.17 LANGUAGES C CXX) +add_compile_definitions(OPENSSL_IS_BORINGSSL) + set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +option(BUILD_SHARED_LIBS "Build shared libs" ON ) + option(TESTING "Build tests" ON) option(EXAMPLES "Build examples" ON) option(CLANG_FORMAT "Enable clang-format target" ON) diff --git a/cmake/Hunter/init.cmake b/cmake/Hunter/init.cmake index 318d312d0..874de2306 100644 --- a/cmake/Hunter/init.cmake +++ b/cmake/Hunter/init.cmake @@ -28,6 +28,9 @@ set( "Binary cache server" ) +set(HUNTER_BUILD_SHARED_LIBS 1) # to build everything SHARED +#set(HUNTER_foo_CMAKE_ARGS ) # to apply flags only to foo package + include(${CMAKE_CURRENT_LIST_DIR}/HunterGate.cmake) HunterGate( diff --git a/cmake/dependencies.cmake b/cmake/dependencies.cmake index 7ea188167..c3c6f14eb 100644 --- a/cmake/dependencies.cmake +++ b/cmake/dependencies.cmake @@ -9,14 +9,254 @@ if (TESTING) hunter_add_package(GTest) find_package(GTest CONFIG REQUIRED) endif() +############################################################################# +############################################################################# +set(OPEN_SSL_INSTALL /usr/local/openssl ) + + + +set(OPEN_SSL_LIB /usr/local/openssl/lib64/libssl.so ) + + +set( OPEN_SSL_INCLUDE_DIR /usr/local/openssl/include) + +message(STATUS "openssl: ${OPEN_SSL_INCLUDE_DIR}") + +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args( + OpenSSL::SSL + "found_ssl" + OPEN_SSL_LIB + OPEN_SSL_INCLUDE_DIR +) + +mark_as_advanced( OPEN_SSL_INCLUDE_DIR OPEN_SSL_LIB) + +if(NOT TARGET OpenSSL::SSL ) + add_library( OpenSSL::SSL SHARED IMPORTED GLOBAL) + + set_target_properties( OpenSSL::SSL PROPERTIES + IMPORTED_LOCATION "${OPEN_SSL_LIB}" + INTERFACE_INCLUDE_DIRECTORIES "${OPEN_SSL_INCLUDE_DIR}" + IMPORTED_LINK_INTERFACE_LIBRARIES OpenSSL::Crypto + IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" + ) +endif() + + +############################################ + + + +set(OPENSSL_CRYPTO_LIB /usr/local/openssl/lib64/libcrypto.so ) + + +#find_path( OPENSSL_CRYPTO_INCLUDE_DIR +#NAMES openssl/ssl.h +#HINTS "${OPEN_SSL_INSTALL}/include" +#) + +find_package_handle_standard_args( + OpenSSL::Crypto + DEFAULT_MSG + OPENSSL_CRYPTO_LIB + # OPENSLL_CRYPTO_INCLUDE_DIR + OPEN_SSL_INCLUDE_DIR +) + +mark_as_advanced( OPENSSL_CRYPTO_LIB) + +if(NOT TARGET OpenSSL::Crypto ) + add_library( OpenSSL::Crypto STATIC IMPORTED GLOBAL) + set_target_properties( OpenSSL::Crypto PROPERTIES + IMPORTED_LOCATION "${OPENSSL_CRYPTO_LIB}" + INTERFACE_INCLUDE_DIRECTORIES "${OPEN_SSL_INCLUDE_DIR}" + IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" + ) +endif() + + +##################################################################### +##################################################################### + + +#find_package(ZLIB) + +set(BOR_SSL_INSTALL /home/vagrant/boringssl/install-static-rel ) + +#find_library(SSL_LIB NAMES ssl HINTS /usr/local/boringssl/lib ) +#set(SSL_LIB /usr/local/boringssl/lib/libssl.so ) +#set(SSL_LIB ${BOR_SSL_INSTALL}/lib/libssl.a) +set(SSL_LIB /home/vagrant/boringssl/build-static-rel/ssl/libssl.a ) # build with -fPIC + + +#find_path( SSL_INCLUDE_DIR +#NAMES openssl/ssl.h +##HINTS "/usr/local/boringssl/include" +#HINTS "${BOR_SSL_INSTALL}/include" +#) + +set(SSL_INCLUDE_DIR ${BOR_SSL_INSTALL}/include ) + +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args( + ssl + DEFAULT_MSG + SSL_LIB + SSL_INCLUDE_DIR +) + +mark_as_advanced( SSL_INCLUDE_DIR SSL_LIB) + +if(NOT TARGET ssl::ssl ) + add_library( ssl::ssl STATIC IMPORTED GLOBAL) + + set_target_properties( ssl::ssl PROPERTIES + IMPORTED_LOCATION "${SSL_LIB}" + INTERFACE_INCLUDE_DIRECTORIES "${SSL_INCLUDE_DIR}" + IMPORTED_LINK_INTERFACE_LIBRARIES crypto::crypto + IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" + ) +endif() +############################################ + + +#set(CRYPTO_LIB ${BOR_SSL_INSTALL}/lib/libcrypto.a) +set(CRYPTO_LIB /home/vagrant/boringssl/build-static-rel/crypto/libcrypto.a ) # build with -fPIC + + +#find_path( CRYPTO_INCLUDE_DIR +#NAMES openssl/ssl.h +#HINTS "${BOR_SSL_INSTALL}/include" +#) + +find_package_handle_standard_args( + crypto + DEFAULT_MSG + CRYPTO_LIB + SSL_INCLUDE_DIR +) + +mark_as_advanced( SSL_INCLUDE_DIR CRYPTO_LIB) + +if(NOT TARGET crypto::crypto ) + add_library( crypto::crypto STATIC IMPORTED GLOBAL) + set_target_properties( crypto::crypto PROPERTIES + IMPORTED_LOCATION "${CRYPTO_LIB}" + INTERFACE_INCLUDE_DIRECTORIES "${SSL_INCLUDE_DIR}" + IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" + ) +endif() +############################################## + +add_library( lsquic STATIC IMPORTED GLOBAL) + +#find_library(LSQUIC_LIB NAMES lsquic HINTS /usr/local/lib) + + +find_path( LSQUIC_INCLUDE_DIR +NAMES lsquic/lsquic.h +# HINTS /usr/local/include +HINTS /home/vagrant/lsquic/install-debug/include +) +#set(LSQUIC_LIB /usr/local/lib/liblsquic.so ) + + +#set(LSQUIC_LIB /home/vagrant/lsquic/install-debug/lib/liblsquic.so ) +set(LSQUIC_LIB /home/vagrant/lsquic/install-static-rel/lib/liblsquic.a ) + +find_package_handle_standard_args( + lsquic + DEFAULT_MSG + LSQUIC_LIB + LSQUIC_INCLUDE_DIR +) + +mark_as_advanced( LSQUIC_INCLUDE_DIR LSQUIC_LIB) + + + #set_target_properties( lsquic PROPERTIES + #IMPORTED_LOCATION "${LSQUIC_LIB}" + #IMPORTED_LINK_INTERFACE_LIBRARIES "$" + #INTERFACE_INCLUDE_DIRECTORIES "${LSQUIC_INCLUDE_DIR}" + #IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" + #) + + +####################################################################################### +#list(APPEND CMAKE_MODULE_PATH /usr/local/lib/cmake/nexus ) +#find_package(nexus MODULE REQUIRED) + +# https://github.com/amdfxlucas/nexus.git 'dev' branch +# build with -fvisibility-hidden and -fvisibility-inlines-hidden +# to not leak any BoringSSL symbols +# or boost::asio::ssl symbols that use the boringssl headers +# instead of the openssl ones in their definition +add_library( nexus SHARED IMPORTED GLOBAL) + +#find_library(NEXUS_LIB NAMES nexus HINTS /usr/local/lib) + + +find_path( NEXUS_INCLUDE_DIR +NAMES nexus/nexus.hpp +#HINTS /usr/local/include +HINTS /home/vagrant/nexus-bugfix/include +) + +#set(NEXUS_LIB /usr/local/lib/nexus/libnexus.so ) +#set(NEXUS_LIB /home/vagrant/nexus-bugfix/build-static-rel/src/libnexus.a ) + +#set(NEXUS_LIB /home/vagrant/nexus-bugfix/build/src/libnexus.so ) +set(NEXUS_LIB /home/vagrant/nexus-bugfix/build-shared-deb/src/libnexus.so ) + +find_package_handle_standard_args( + nexus + DEFAULT_MSG + NEXUS_LIB + NEXUS_INCLUDE_DIR +) + +mark_as_advanced( NEXUS_INCLUDE_DIR NEXUS_LIB) + set_target_properties( nexus PROPERTIES + IMPORTED_LOCATION "${NEXUS_LIB}" + # IMPORTED_LINK_INTERFACE_LIBRARIES "$" + INTERFACE_INCLUDE_DIRECTORIES "${NEXUS_INCLUDE_DIR}" + IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" + ) + + +########################################################################### + +# openssl system installation +#set(OpenSSl_DIR /usr/local/boringssl/lib/cmake/OpenSSL ) +#list(APPEND CMAKE_PREFIX_PATH /usr/local/boringssl/lib/cmake/OpenSSL ) +#find_package(OpenSSL CONFIG REQUIRED) +#find_package( OpenSSL PATHS .. NO_DEFAULT_PATH) +#----------------------------------------------------------------------------- +#include(/home/vagrant/boringssl/install-debug/lib/cmake/OpenSSL/OpenSSLConfig.cmake ) + + +####################################################################### # https://docs.hunter.sh/en/latest/packages/pkg/Boost.html -hunter_add_package(Boost COMPONENTS random filesystem program_options) -find_package(Boost CONFIG REQUIRED random filesystem program_options) +hunter_add_package(Boost COMPONENTS random filesystem program_options ) +find_package(Boost CONFIG REQUIRED random filesystem program_options ) + +# openSSL includes cannot stay on Hunter include path +# i.e. ~/.hunter/_Base/72b446a/b18b06c/eede853/Install/include / openssl +# where p2p_quic target could find them and wrongfully use them, +# instead of the boringssl headers # https://www.openssl.org/ -hunter_add_package(OpenSSL) -find_package(OpenSSL REQUIRED) +#set(HUNTER_OpenSSL_CMAKE_ARGS BUILD_SHARED_LIBS=1 ...other options ...) # applied only to OpenSSL package +#hunter_add_package(OpenSSL) +#find_package(OpenSSL REQUIRED) + +# required to build nexus +#hunter_add_package(BoringSSL) +#find_package( BoringSSL REQUIRED) # https://developers.google.com/protocol-buffers/ hunter_add_package(Protobuf) diff --git a/example/01-echo/libp2p_echo_client.cpp b/example/01-echo/libp2p_echo_client.cpp index 0fd542096..1070451aa 100644 --- a/example/01-echo/libp2p_echo_client.cpp +++ b/example/01-echo/libp2p_echo_client.cpp @@ -3,8 +3,10 @@ * All Rights Reserved * SPDX-License-Identifier: Apache-2.0 */ - +#include +#include #include +#include #include #include @@ -41,6 +43,28 @@ int main(int argc, char *argv[]) { using libp2p::crypto::PublicKey; using libp2p::common::operator""_unhex; + + + auto get_arg = [&](const std::string_view arg) ->std::optional + { + auto args = std::span(argv, argc).subspan(1); + auto match = std::ranges::find_if(args, + [&arg]( const std::string_view& token ) + { return token.find(arg) != std::string::npos; } ); + + if(match == args.end() ) + { + return std::nullopt; + } else if( *match == arg) // perfect match (no '=') -> value must be next token + { + return *(++match); + } else + { + auto pos = std::string_view(*match).find("="); + return std::string_view(*match).substr(pos+1); + } + }; + auto run_duration = std::chrono::seconds(5); std::string message("Hello from C++"); @@ -96,7 +120,12 @@ int main(int argc, char *argv[]) { libp2p::protocol::Echo echo{libp2p::protocol::EchoConfig{1}}; // create a default Host via an injector - auto injector = libp2p::injector::makeHostInjector(); + auto injector = libp2p::injector::makeHostInjector( + /* libp2p::injector::useQuicConfig( *get_arg("key_path") , + *get_arg("cert_path") , + *get_arg("ca_path") ) // use keypair from file */ + // libp2p::injector::useQuicConfig(*get_arg("ca_path" ) ) // use random keypair (doesnt compile -> boost::di complains) + ); auto host = injector.create>(); diff --git a/example/01-echo/libp2p_echo_server.cpp b/example/01-echo/libp2p_echo_server.cpp index 0be951613..bd45681bb 100644 --- a/example/01-echo/libp2p_echo_server.cpp +++ b/example/01-echo/libp2p_echo_server.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -68,10 +69,55 @@ int main(int argc, char **argv) { return std::find(args.begin(), args.end(), arg) != args.end(); }; + auto get_arg = [&](const std::string_view arg) ->std::optional + { + auto args = std::span(argv, argc).subspan(1); + auto match = std::ranges::find_if(args, + [&arg]( const std::string_view& token ) + { return token.find(arg) != std::string::npos; } ); + + if(match == args.end() ) + { + return std::nullopt; + } else if( *match == arg) // perfect match (no '=') -> value must be next token + { + return *(++match); + } else + { + auto pos = std::string_view(*match).find("="); + return std::string_view(*match).substr(pos+1); + } + }; + + auto arg_eq = [&](const std::string_view& key, const std::string_view& test) + { + if(const auto& val = get_arg(key); val) + { + return *val == test; + } + return false; + }; + + auto get_arg_or = [&](const std::string_view& key, + const std::string_view &default_val) + { + if(const auto& val = get_arg(key); val ) + { + return *val; + }else + {return default_val; + } + }; + if (has_arg("-h") or has_arg("--help")) { fmt::print("Options:\n"); fmt::print(" -h, --help\n"); fmt::print(" Print help\n"); + fmt::print(" -l3proto=ip6|ip4\n"); + fmt::print(" -l4proto=tcp|quic\n"); + fmt::print(" -ca_path\n"); + fmt::print(" -cert_path\n"); + fmt::print(" -key_path\n"); fmt::print(" -insecure\n"); fmt::print(" Use plaintext protocol instead of noise\n"); fmt::print(" --ws\n"); @@ -121,9 +167,27 @@ int main(int argc, char **argv) { log->info("Starting in secure mode"); } + std::string_view key_path; + std::string_view cert_path ; + +if(arg_eq("l4proto","quic")) +{ try{ + key_path = *get_arg("key_path"); + cert_path = *get_arg("cert_path") ; + }catch(const std::exception & e) + { + std::cout << "invalid key or cert path" << std::endl; + exit(1); + + } +} + auto injector = libp2p::injector::makeHostInjector( - libp2p::injector::useKeyPair(keypair), - libp2p::injector::useSecurityAdaptors(), + // libp2p::injector::useQuicConfig( key_path ,cert_path, *get_arg("ca_path") ), + // libp2p::injector::useKeyPairPEM(key_path,cert_path), + libp2p::injector::useKeyPair(keypair) + + ,libp2p::injector::useSecurityAdaptors(), libp2p::injector::useWssPem(R"( -----BEGIN CERTIFICATE----- MIIBODCB3qADAgECAghv+C53VY1w3TAKBggqhkjOPQQDAjAUMRIwEAYDVQQDDAls @@ -163,12 +227,17 @@ dPtse4GVRA2swbXcZX5iFVi/V8poIpdVrgn5iMadkQnYf9APWJuGcebK echo.handle(std::move(stream)); }); - std::string _ma = "/ip4/127.0.0.1/tcp/40010"; + std::string _ma = fmt::format("/{}/127.0.0.1/{}/40010", + get_arg_or("l3proto","ip4"), get_arg_or("l4proto","quic") ); + + if( arg_eq("l4proto","tcp") ) // no websocket on top of quic ?! + { if (has_arg("--wss")) { _ma += "/wss"; } else if (has_arg("--ws")) { _ma += "/ws"; } + } auto ma = libp2p::multi::Multiaddress::create(_ma).value(); // launch a Listener part of the Host diff --git a/include/libp2p/crypto/ecdsa_provider/ecdsa_provider_impl.hpp b/include/libp2p/crypto/ecdsa_provider/ecdsa_provider_impl.hpp index 33e8afbfc..d5fa4b1ef 100644 --- a/include/libp2p/crypto/ecdsa_provider/ecdsa_provider_impl.hpp +++ b/include/libp2p/crypto/ecdsa_provider/ecdsa_provider_impl.hpp @@ -46,7 +46,7 @@ namespace libp2p::crypto::ecdsa { template outcome::result convertEcKeyToBytes( const std::shared_ptr &ec_key, - int (*converter)(EC_KEY *, uint8_t **)) const; + int (*converter)(const EC_KEY *, uint8_t **)) const; /** * @brief Convert bytes to EC_KEY diff --git a/include/libp2p/injector/network_injector.hpp b/include/libp2p/injector/network_injector.hpp index ab7bec44d..e5771cff4 100644 --- a/include/libp2p/injector/network_injector.hpp +++ b/include/libp2p/injector/network_injector.hpp @@ -7,6 +7,13 @@ #pragma once #include +#include +#include +#include +#include +#include +#include +#include // implementations #include @@ -42,6 +49,7 @@ #include #include #include +#include // clang-format off /** @@ -136,6 +144,94 @@ namespace libp2p::injector { + /* BOOST_DI_INJECT(QuicConfig, (named = "ssl_context"_s) const boost::asio::ssl::context &ssl, \ + (named = "quic_settings"_s ) const nexus::quic::settings& s \ + (named = "key_path"_s ) const std::string_view& key_path \ + (named = "cert_path"_s ) const std::string_view& cert_path ); */ + +/* +return di::make_injector( + di::bind.named("quic_settings"sv).to(s), + di::bind.named("key_path"sv).to(key_path), + di::bind.named("cert_path"sv).to(cert_path) +);*/ + + +/* this can be use by clients + + use a random generated keypair to create a (unsigned) certificate, + but load a ca_file to verify the server with +*/ +/* +inline auto useQuicConfig( const std::string_view& ca_path ) // crypto::KeyPair& keypair, +{ +std::shared_ptr< libp2p::transport::QuicConfig> config += std::make_shared( ca_path); // keypair, + +return boost::di::make_injector( + boost::di::bind.to( + [cfg{std::move(config)}]() ->libp2p::transport::QuicConfig& + { + return *cfg; + } ).in(boost::di::singleton)[boost::di::override] +); +} +*/ + + +inline auto useQuicConfig(// const nexus::quic::settings& server_settings, + // const nexus::quic::settings& client_settings, + const std::string_view& key_path, + const std::string_view& cert_path, + const std::string_view& ca_path + ) +{using namespace std::literals::string_view_literals; + using namespace std::string_literals; + using namespace boost; + + +return di::make_injector( + // di::bind.named( _server_settings).to(server_settings), +// di::bind.named( _client_settings).to(client_settings), + di::bind.named(_key_path).to(key_path), + di::bind.named(_cert_path).to(cert_path), + di::bind.named(_ca_path).to(ca_path) +); + +} + +/* +inline auto useQuicConfig( const boost::asio::ssl::context &ssl, + const nexus::quic::settings& s + ) +{ + using namespace std::string_literals; + using namespace boost; + using namespace std::literals::string_view_literals; + +return di::make_injector( + di::bind.named("ssl_context"s).to(ssl), + di::bind.named("quic_settings"s).to(s) +); + } + */ +inline auto get_key_type(EVP_PKEY *pkey) { + int type = EVP_PKEY_base_id(pkey); + + switch (type) { + case EVP_PKEY_RSA: + return libp2p::crypto::Key::Type::RSA; + break; + case EVP_PKEY_ED25519: + return libp2p::crypto::Key::Type::Ed25519; + break; + // Add more cases for other key types as needed + default: + return libp2p::crypto::Key::Type::UNSPECIFIED; + } +} + + /** * @brief Instruct injector to use this keypair. Can be used once. * @@ -151,6 +247,89 @@ namespace libp2p::injector { key_pair)[boost::di::override]; } +/*! \brief retrieve private and public key from .pem encoded file + + this can be used by servers + \param key_path path to private key file + \param cert_path path to pem encoded x509 certificate containing the public key + \todo check that key and cert match +*/ + inline auto useKeyPairPEM( const std::string_view& key_path , + const std::string_view& cert_path ) + { + using cbase= boost::asio::ssl::context_base; + boost::asio::ssl::context ssl( cbase::tlsv13 ); + + ssl.use_certificate_chain_file(cert_path.data()); + ssl.use_private_key_file(key_path.data(), boost::asio::ssl::context::file_format::pem); + + + crypto::KeyPair fileKeys; + +// Extract the X509 certificate from the SSL context + X509 *cert = ::SSL_CTX_get0_certificate(ssl.native_handle() ); + if (!cert) { + throw std::runtime_error("invalid certificate file"); + // Handle error + // ERR_print_errors_fp(stderr); + // SSL_CTX_free(ctx); + // return 1; + } + + // Retrieve the public key from the X509 certificate + EVP_PKEY *public_key = ::X509_get_pubkey(cert); + if (!public_key) { + throw std::runtime_error( "invalid certificate file"); + // Handle error + // ERR_print_errors_fp(stderr); + // X509_free(cert); + // SSL_CTX_free(ctx); + //return 1; + } + fileKeys.publicKey.type = get_key_type( public_key); + +// Get the raw bytes of the public key + unsigned char *public_key_bytes = NULL; + size_t public_key_size = ::i2d_PUBKEY(public_key, &public_key_bytes); + if( public_key_size) + { + fileKeys.publicKey.data.resize(public_key_size,0); + std::memcpy( fileKeys.publicKey.data.data(), (uint8_t*)public_key_bytes, public_key_size ); + }else + { + + + size_t len=0; + size_t raw_len = ::EVP_PKEY_get_raw_public_key(public_key, NULL, &len); + if (raw_len > 0) { + + fileKeys.publicKey.data.resize(raw_len,0); + ::EVP_PKEY_get_raw_public_key(public_key, fileKeys.publicKey.data.data(),&len ); + + } + } + + // Retrieve the private key from the context + EVP_PKEY *private_key = SSL_CTX_get0_privatekey(ssl.native_handle()); + if (!private_key) { + + throw std::runtime_error("invalid private key file"); + } + + fileKeys.privateKey.type = get_key_type( private_key ); + + // Get the raw bytes of the private key + const unsigned char *private_key_bytes = (const unsigned char* ) EVP_PKEY_get0(private_key); + size_t private_key_size = EVP_PKEY_size(private_key); + + fileKeys.privateKey.data.resize(private_key_size,0); + std::memcpy( fileKeys.privateKey.data.data(), private_key_bytes, private_key_size ); + + + + return boost::di::bind().template to( std::move(fileKeys ))[boost::di::override]; + } + /** * @brief Instruct injector to use wss ssl server with key and certificates * from pem. Can be used once. @@ -343,7 +522,7 @@ namespace libp2p::injector { di::bind().template to(), // NOLINT di::bind().template to(), // NOLINT di::bind().template to(), // NOLINT - di::bind().template to(), // NOLINT + di::bind().template to(), // NOLINT // user-defined overrides... std::forward(args)... diff --git a/include/libp2p/multi/multiaddress_protocol_list.hpp b/include/libp2p/multi/multiaddress_protocol_list.hpp index 274818308..a921b5287 100644 --- a/include/libp2p/multi/multiaddress_protocol_list.hpp +++ b/include/libp2p/multi/multiaddress_protocol_list.hpp @@ -149,7 +149,7 @@ namespace libp2p::multi { {Protocol::Code::ONION, 96, "onion"}, {Protocol::Code::ONION3, 296, "onion3"}, {Protocol::Code::GARLIC64, Protocol::kVarLen, "garlic64"}, - {Protocol::Code::QUIC, 0, "quic"}, + {Protocol::Code::QUIC, 16, "quic"}, {Protocol::Code::HTTP, 0, "http"}, {Protocol::Code::HTTPS, 0, "https"}, {Protocol::Code::WS, 0, "ws"}, diff --git a/include/libp2p/transport/tcp/tcp_util.hpp b/include/libp2p/transport/impl/util.hpp similarity index 68% rename from include/libp2p/transport/tcp/tcp_util.hpp rename to include/libp2p/transport/impl/util.hpp index 73fc48360..f39236e1c 100644 --- a/include/libp2p/transport/tcp/tcp_util.hpp +++ b/include/libp2p/transport/impl/util.hpp @@ -15,7 +15,55 @@ #include #include -namespace libp2p::transport::detail { +namespace libp2p::transport::detail +{ + template // boost::asio::ip::tcp i.e. + inline outcome::result makeEndpointImpl( const multi::Multiaddress& ma ) + { + using P = multi::Protocol::Code; + using namespace boost::asio; // NOLINT + + try { + auto v = ma.getProtocolsWithValues(); + auto it = v.begin(); + if (it->first.code != P::IP4 && it->first.code != P::IP6) { + return std::errc::address_family_not_supported; + } + + auto addr = ip::make_address(it->second); + ++it; + + if constexpr ( std::is_same_v ) + { if(it->first.code != P::TCP) + return std::errc::address_family_not_supported; + } else if (std::is_same_v ) + { + if( auto _code = it->first.code; _code != P::UDP || _code!=P::QUIC ) + return std::errc::address_family_not_supported; + } + + + auto port = boost::lexical_cast(it->second); + + return T{addr, port}; + } catch (const boost::system::system_error &e) { + return e.code(); + } catch (const boost::bad_lexical_cast & /* ignored */) { + return multi::Multiaddress::Error::INVALID_PROTOCOL_VALUE; + } + } + + + inline outcome::result makeUdpEndpoint( + const multi::Multiaddress &ma) { + return makeEndpointImpl( ma); + } + + inline outcome::result makeTcpEndpoint( + const multi::Multiaddress &ma) { + return makeEndpointImpl( ma); + } + template inline outcome::result makeAddress( T &&endpoint, @@ -33,7 +81,12 @@ namespace libp2p::transport::detail { s << "/ip6/" << address.to_v6().to_string(); } - s << "/tcp/" << port; + if constexpr( std::is_same_v ) + { s << "/tcp/" << port;} + else if ( std::is_same_v ) + { + s<<"/quic/" << port; // udp would be pointless because unreliable + } if (layers != nullptr and not layers->empty()) { auto &protocol = layers->at(0).first.code; @@ -78,12 +131,23 @@ namespace libp2p::transport::detail { // Issue: https://github.com/libp2p/cpp-libp2p/issues/97 } + inline bool supportsIpQuic(const multi::Multiaddress &ma) { + using P = multi::Protocol::Code; + return (ma.hasProtocol(P::IP4) || ma.hasProtocol(P::IP6) + || ma.hasProtocol(P::DNS4) || ma.hasProtocol(P::DNS6) + || ma.hasProtocol(P::DNS)) + && ma.hasProtocol(P::QUIC); + + // TODO(xDimon): Support 'DNSADDR' addresses. + // Issue: https://github.com/libp2p/cpp-libp2p/issues/97 + } + inline auto getFirstProtocol(const multi::Multiaddress &ma) { return ma.getProtocolsWithValues().front().first.code; } // Obtain host and port strings from provided address - inline std::pair getHostAndTcpPort( + inline std::pair getHostAndPort( const multi::Multiaddress &address) { auto v = address.getProtocolsWithValues(); @@ -93,7 +157,8 @@ namespace libp2p::transport::detail { // get port it++; - BOOST_ASSERT(it->first.code == multi::Protocol::Code::TCP); + BOOST_ASSERT(it->first.code == multi::Protocol::Code::TCP + || it->first.code == multi::Protocol::Code::QUIC ); auto port = it->second; return {host, port}; @@ -114,32 +179,5 @@ namespace libp2p::transport::detail { return {begin, end}; } - inline outcome::result makeEndpoint( - const multi::Multiaddress &ma) { - using P = multi::Protocol::Code; - using namespace boost::asio; // NOLINT - - try { - auto v = ma.getProtocolsWithValues(); - auto it = v.begin(); - if (it->first.code != P::IP4 && it->first.code != P::IP6) { - return std::errc::address_family_not_supported; - } - - auto addr = ip::make_address(it->second); - ++it; - - if (it->first.code != P::TCP) { - return std::errc::address_family_not_supported; - } - - auto port = boost::lexical_cast(it->second); - - return ip::tcp::endpoint{addr, port}; - } catch (const boost::system::system_error &e) { - return e.code(); - } catch (const boost::bad_lexical_cast & /* ignored */) { - return multi::Multiaddress::Error::INVALID_PROTOCOL_VALUE; - } - } + } // namespace libp2p::transport::detail diff --git a/include/libp2p/transport/quic.hpp b/include/libp2p/transport/quic.hpp new file mode 100644 index 000000000..ba30707ab --- /dev/null +++ b/include/libp2p/transport/quic.hpp @@ -0,0 +1,4 @@ +#pragma once + + +#include diff --git a/include/libp2p/transport/quic/libp2p-quic-api.hpp b/include/libp2p/transport/quic/libp2p-quic-api.hpp new file mode 100644 index 000000000..6ea9f1951 --- /dev/null +++ b/include/libp2p/transport/quic/libp2p-quic-api.hpp @@ -0,0 +1,40 @@ +#pragma once + +// Generic helper definitions for shared library support +#if defined _WIN32 || defined __CYGWIN__ + #define LIBP2P_QUIC_HELPER_DLL_IMPORT __declspec(dllimport) + #define LIBP2P_QUIC_HELPER_DLL_EXPORT __declspec(dllexport) + #define LIBP2P_QUIC_HELPER_DLL_LOCAL +#else + #if __GNUC__ >= 4 + #define LIBP2P_QUIC_HELPER_DLL_IMPORT __attribute__ ((visibility ("default"))) + #define LIBP2P_QUIC_HELPER_DLL_EXPORT __attribute__ ((visibility ("default"))) + #define LIBP2P_QUIC_HELPER_DLL_LOCAL __attribute__ ((visibility ("hidden"))) + #else + #define LIBP2P_QUIC_HELPER_DLL_IMPORT + #define LIBP2P_QUIC_HELPER_DLL_EXPORT + #define LIBP2P_QUIC_HELPER_DLL_LOCAL + #endif +#endif + +// Now we use the generic helper definitions above to define LIBP2P_QUIC_API and LIBP2P_QUIC_LOCAL. +// LIBP2P_QUIC_API is used for the public API symbols. It either DLL imports or DLL exports (or does nothing for static build) +// LIBP2P_QUIC_LOCAL is used for non-api symbols. + +// use CMAKE_BUILD_SHARED_LIBS here ?! +//#ifdef BUILD_SHARED_LIBS // defined if LIBP2P is compiled as a DLL + #ifdef LIBP2P_QUIC_DLL_EXPORTS // defined if we are building the LIBP2P DLL (instead of using it) + #define LIBP2P_QUIC_API LIBP2P_QUIC_HELPER_DLL_EXPORT + #define LIBP2P_QUIC_API_FUNC extern LIBP2P_QUIC_HELPER_DLL_EXPORT + #else + #define LIBP2P_QUIC_API LIBP2P_QUIC_HELPER_DLL_IMPORT + #define LIBP2P_QUIC_API_FUNC extern LIBP2P_QUIC_HELPER_DLL_IMPORT + #endif // LIBP2P_QUIC_DLL_EXPORTS + #define LIBP2P_QUIC_LOCAL LIBP2P_QUIC_HELPER_DLL_LOCAL + #define LIBP2P_QUIC_LOCAL_FUNC extern LIBP2P_QUIC_HELPER_DLL_LOCAL +/*#else // LIBP2P_QUIC_DLL is not defined: this means LIBP2P is a static lib. + #define LIBP2P_QUIC_API_FUNC + #define LIBP2P_QUIC_API + #define LIBP2P_QUIC_LOCAL + #define LIBP2P_QUIC_LOCAL_FUNC +#endif // LIBP2P_QUIC_DLL */ \ No newline at end of file diff --git a/include/libp2p/transport/quic/quic_connection.hpp b/include/libp2p/transport/quic/quic_connection.hpp new file mode 100644 index 000000000..d35cab3a8 --- /dev/null +++ b/include/libp2p/transport/quic/quic_connection.hpp @@ -0,0 +1,130 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace libp2p::security { + class TlsAdaptor; +} + +namespace libp2p::connection { + class QuicStream; +} + +namespace libp2p::transport { + class QuicTransport; + class QuicListener; + + /** + * @brief boost::asio implementation of Quic connection (socket). + */ + class LIBP2P_QUIC_API QuicConnection + : public connection::CapableConnection, + public std::enable_shared_from_this, + private boost::noncopyable { + friend class connection::QuicStream; + friend class QuicTransport; + void setRemoteEndpoint(const multi::Multiaddress &remote); + + public: + ~QuicConnection() override = default; + + using Udp = boost::asio::ip::udp; + using ErrorCode = boost::system::error_code; + using ResolverResultsType = Udp::resolver::results_type; + using ResolveCallback = void(const ErrorCode &, + const ResolverResultsType &); + using ResolveCallbackFunc = std::function; + using ConnectCallback = void(const ErrorCode &, const Udp::endpoint &); + using ConnectCallbackFunc = std::function; + + QuicConnection(nexus::quic::client &c); + QuicConnection(nexus::quic::acceptor &a); + QuicConnection(nexus::quic::client &c, + const Udp::endpoint &endpoint, + const std::string_view &hostname); + + void start() override; + void stop() override; + // initiate stream synchronous + void newStream(StreamHandlerFunc cb) override; + // initiate outgoing stream + outcome::result> newStream() + override; + // set callback for incoming streams + void onStream(NewStreamHandlerFunc cb) + override; // should be renamed to setOnNewStreamHandler() or sth. + + outcome::result remoteMultiaddr() override; + outcome::result localMultiaddr() override; + + virtual outcome::result localPeer() const override; + virtual outcome::result remotePeer() const override; + + virtual outcome::result remotePublicKey() const override; + + bool isInitiator() const noexcept override; + + // Closable Interface + virtual bool isClosed() const override; + virtual outcome::result close() override; + + // TODO (artem) make RawConnection::id()->string or str() or whatever + // const std::string &str() const { return debug_str_; } + + private: + // Reader & Writer + void read(BytesOut out, size_t bytes, ReadCallbackFunc cb) override{}; + void readSome(BytesOut out, size_t bytes, ReadCallbackFunc cb) override{}; + void deferReadCallback(outcome::result res, + ReadCallbackFunc cb) override{}; + void write(BytesIn in, size_t bytes, WriteCallbackFunc cb) override{}; + void writeSome(BytesIn in, size_t bytes, WriteCallbackFunc cb) override{}; + void deferWriteCallback(std::error_code ec, + WriteCallbackFunc cb) override{}; + + void accept_streams(); + outcome::result saveMultiaddresses(); + + bool m_is_initiator = false; + + /// If true then no more callbacks will be issued + bool closed_by_host_ = false; + + /// Close reason, is set on close to respond to further calls + std::error_code close_reason_; + + boost::optional remote_multiaddress_; + boost::optional local_multiaddress_; + + std::string debug_str_; + + NewStreamHandlerFunc m_on_stream_cb; + + nexus::quic::connection m_conn; + log::Logger log_; + friend class QuicListener; + + public: + LIBP2P_METRICS_INSTANCE_COUNT_IF_ENABLED(libp2p::transport::QuicConnection); + }; +} // namespace libp2p::transport diff --git a/include/libp2p/transport/quic/quic_listener.hpp b/include/libp2p/transport/quic/quic_listener.hpp new file mode 100644 index 000000000..80a0b9a25 --- /dev/null +++ b/include/libp2p/transport/quic/quic_listener.hpp @@ -0,0 +1,61 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace boost::asio::ssl +{ + class context; +} + +namespace libp2p::transport { + class Upgrader; + + /** + * @brief QUIC Server (Listener) implementation. + */ + class LIBP2P_QUIC_API QuicListener : public TransportListener, + public std::enable_shared_from_this { + public: + using ProtoAddrVec = std::vector>; + + ~QuicListener() override = default; + + QuicListener(nexus::quic::server&, boost::asio::ssl::context &, + TransportListener::HandlerFunc handler); + + // accepted connection is started() before handler is invoked. is this a problem ?! + + outcome::result listen(const multi::Multiaddress &address) override; + bool canListen(const multi::Multiaddress &ma) const override; + outcome::result getListenMultiaddr() const override; + // closable + bool isClosed() const override; + outcome::result close() override; + + private: + // boost::asio::io_context &context_; + //std::shared_ptr upgrader_; // not needed for quic + TransportListener::HandlerFunc handle_; + + nexus::quic::server& m_server; + nexus::quic::acceptor m_acceptor; + std::optional m_listen_addr; + + int m_incoming_conn_capacity{20}; + bool m_is_open=false; + log::Logger log_; + + void doAcceptConns(); + }; + +} // namespace libp2p::transport diff --git a/include/libp2p/transport/quic/quic_stream.hpp b/include/libp2p/transport/quic/quic_stream.hpp new file mode 100644 index 000000000..44a38a3b9 --- /dev/null +++ b/include/libp2p/transport/quic/quic_stream.hpp @@ -0,0 +1,187 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace libp2p::peer { + class PeerId; +} + +namespace libp2p::transport +{ + class QuicConnection; +} +namespace libp2p::connection { + + + class LIBP2P_QUIC_API QuicStream : public libp2p::connection::Stream + { + friend class libp2p::transport::QuicConnection; + public: + QuicStream( libp2p::transport::QuicConnection& conn ,bool is_initiator ); + + ~QuicStream() override = default; + + /** + * Check, if this stream is closed from this side of the connection and + * thus cannot be read from + * @return true, if stream cannot be read from, false otherwise + */ + virtual bool isClosedForRead() const override; + + /** + * Check, if this stream is closed from the other side of the connection and + * thus cannot be written to + * @return true, if stream cannot be written to, false otherwise + */ + virtual bool isClosedForWrite() const override; + + /** + * Check, if this stream is closed bor both writes and reads + * @return true, if stream is closed entirely, false otherwise + */ + virtual bool isClosed() const override; + + /** + * Close a stream, indicating we are not going to write to it anymore; the + * other side, however, can write to it, if it was not closed from there + * before + * @param cb to be called, when the stream is closed, or error happens + */ + virtual void close( Stream::VoidResultHandlerFunc cb) override; + + /** + * @brief Close this stream entirely; this normally means an error happened, + * so it should not be used just to close the stream + */ + virtual void reset() override; + + /** + * Set a new receive window size of this stream - how much unread bytes can + * we have on our side of the stream + * @param new_size for the window + * @param cb to be called, when the operation succeeds of fails + */ + virtual void adjustWindowSize(uint32_t new_size, + libp2p::connection::Stream::VoidResultHandlerFunc cb) override; + + /** + * Is that stream opened over a connection, which was an initiator? + */ + virtual outcome::result isInitiator() const override; + + /** + * Get a peer, which the stream is connected to + * @return id of the peer + */ + virtual outcome::result remotePeerId() const override; + + /** + * Get a local multiaddress + * @return address or error + */ + virtual outcome::result localMultiaddr() const override; + + /** + * Get a multiaddress, to which the stream is connected + * @return multiaddress or error + */ + virtual outcome::result remoteMultiaddr() const override; + +// Reader +/** + * @brief Reads exactly {@code} min(out.size(), bytes) {@nocode} bytes to + * the buffer. + * @param out output argument. Read data will be written to this buffer. + * @param bytes number of bytes to read + * @param cb callback with result of operation + * + * @note caller should maintain validity of an output buffer until callback + * is executed. It is usually done with either wrapping buffer as shared + * pointer, or having buffer as part of some class/struct, and using + * enable_shared_from_this() + */ + virtual void read(BytesOut out, size_t bytes, basic::Reader::ReadCallbackFunc cb) override; + + /** + * @brief Reads up to {@code} min(out.size(), bytes) {@nocode} bytes to the + * buffer. + * @param out output argument. Read data will be written to this buffer. + * @param bytes number of bytes to read + * @param cb callback with result of operation + * + * @note caller should maintain validity of an output buffer until callback + * is executed. It is usually done with either wrapping buffer as shared + * pointer, or having buffer as part of some class/struct, and using + * enable_shared_from_this() + */ + virtual void readSome(BytesOut out, size_t bytes, basic::Reader::ReadCallbackFunc cb) override; + + /** + * @brief Defers reporting result or error to callback to avoid reentrancy + * (i.e. callback will not be called before initiator function returns) + * @param res read result + * @param cb callback + */ + virtual void deferReadCallback(outcome::result res, + basic::Reader::ReadCallbackFunc cb) override; + + // Writer + + + /** + * @brief Write exactly {@code} in.size() {@nocode} bytes. + * Won't call \param cb before all are successfully written. + * Returns immediately. + * @param in data to write. + * @param bytes number of bytes to write + * @param cb callback with result of operation + * + * @note caller should maintain validity of an input buffer until callback + * is executed. It is usually done with either wrapping buffer as shared + * pointer, or having buffer as part of some class/struct, and using + * enable_shared_from_this() + */ + virtual void write(BytesIn in, size_t bytes, basic::Writer::WriteCallbackFunc cb) override; + + /** + * @brief Write up to {@code} in.size() {@nocode} bytes. + * Calls \param cb after only some bytes has been successfully written, + * so doesn't guarantee that all will be. Returns immediately. + * @param in data to write. + * @param bytes number of bytes to write + * @param cb callback with result of operation + * + * @note caller should maintain validity of an input buffer until callback + * is executed. It is usually done with either wrapping buffer as shared + * pointer, or having buffer as part of some class/struct, and using + * enable_shared_from_this() + */ + virtual void writeSome(BytesIn in, size_t bytes, basic::Writer::WriteCallbackFunc cb) override; + + /** + * @brief Defers reporting error state to callback to avoid reentrancy + * (i.e. callback will not be called before initiator function returns) + * @param ec error code + * @param cb callback + * + * @note if (!ec) then this function does nothing + */ + virtual void deferWriteCallback(std::error_code ec, + basic::Writer::WriteCallbackFunc cb) override; + + +private: + bool m_is_initiator; + + nexus::quic::stream m_stream; + libp2p::transport::QuicConnection& m_conn; + log::Logger log_; + }; +} // namespace libp2p::connection diff --git a/include/libp2p/transport/quic/quic_transport.hpp b/include/libp2p/transport/quic/quic_transport.hpp new file mode 100644 index 000000000..1c3406873 --- /dev/null +++ b/include/libp2p/transport/quic/quic_transport.hpp @@ -0,0 +1,158 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +inline auto _quic_settings = []() {}; +inline auto _server_settings = []() {}; +inline auto _client_settings = []() {}; +inline auto _ssl_context = []() {}; +inline auto _key_path = []() {}; +inline auto _cert_path = []() {}; +inline auto _ca_path = []() {}; + +namespace libp2p::transport { + class QuicTransport; + // class QuicConnection; + using namespace std::string_literals; + using namespace std::literals::string_view_literals; + class LIBP2P_QUIC_API QuicConfig { + friend class QuicTransport; + void def_ctor_impl(); + boost::asio::ssl::context ssl; + nexus::quic::settings client_settings = + nexus::quic::default_client_settings(); + nexus::quic::settings server_settings = + nexus::quic::default_server_settings(); + + public: + /*BOOST_DI_INJECT(QuicConfig, (named = "ssl_context"sv ) const + boost::asio::ssl::context &ssl, \ (named = "quic_settings"sv ) const + nexus::quic::settings& s ); */ + + /* BOOST_DI_INJECT(QuicConfig, (named = "quic_settings"sv ) const + nexus::quic::settings& s, \ + (named = "key_path"sv ) const + std::string_view& key_path, \ (named = "cert_path"sv ) const + std::string_view& cert_path ); */ + QuicConfig(); + + /*BOOST_DI_INJECT(QuicConfig, (named = _server_settings ) const + nexus::quic::settings& , \ + (named = _client_settings ) const + nexus::quic::settings& , \ + (named = _key_path ) const std::string_view& + key_path="", \ + (named = _cert_path ) const std::string_view& + cert_path="", \ (named = _ca_path ) const std::string_view& ca_path="" + ); */ + + QuicConfig(QuicConfig &&other) = default; + + // QuicConfig( /* crypto::KeyPair& , */const std::string_view& ca_path ); + }; + + /** + * @brief QUIC Transport implementation + */ + class LIBP2P_QUIC_API QuicTransport + : public TransportAdaptor, + public std::enable_shared_from_this { + public: + using Udp = boost::asio::ip::udp; + using ErrorCode = boost::system::error_code; + using ResolverResultsType = Udp::resolver::results_type; + using ResolveCallback = void(const ErrorCode &, + const ResolverResultsType &); + using ResolveCallbackFunc = std::function; + + ~QuicTransport() override = default; + + QuicTransport(std::shared_ptr context + // , QuicConfig&& config + ); + + void dial(const peer::PeerId &remoteId, + multi::Multiaddress address, + TransportAdaptor::HandlerFunc handler) override; + + void dial(const peer::PeerId &remoteId, + multi::Multiaddress address, + TransportAdaptor::HandlerFunc handler, + std::chrono::milliseconds timeout) override; + + std::shared_ptr createListener( + TransportListener::HandlerFunc handler) override; + + bool canDial(const multi::Multiaddress &ma) const override; + + peer::ProtocolName getProtocolId() const override; + + private: + // the resolve methods are duplicated between quic-transport and + // tcp-connection they could be moved to Transport-adapter and shared + /** + * @brief Resolve service name (DNS). + * @param endpoint endpoint to resolve. + * @param cb callback executed on operation completion. + */ + void resolve(const Udp::endpoint &endpoint, ResolveCallbackFunc cb); + + /** + * @brief Resolve service name (DNS). + * @param host_name host name to resolve + * @param cb callback executed on operation completion. + */ + void resolve(const std::string &host_name, + const std::string &port, + ResolveCallbackFunc cb); + + /** + * @brief Resolve service name (DNS). + * @param protocol is either Tcp::ip4 or Tcp::ip6 protocol + * @param host_name host name to resolve + * @param cb callback executed on operation completion. + */ + void resolve(const Udp &protocol, + const std::string &host_name, + const std::string &port, + ResolveCallbackFunc cb); + + std::shared_ptr context_; + + QuicConfig m_config; + + nexus::global::context m_nexus_ctx; + + nexus::quic::client &clientByFamily(int family) { + assert(family == Udp::v4().family() || family == Udp::v6().family()); + return (family == Udp::v4().family()) ? m_clients[0] : m_clients[1]; + } + nexus::quic::client m_clients[2]; // one client for each family IPv4/6 + nexus::quic::server m_server; + + std::vector > m_conns; + + log::Logger log_; + + }; // namespace libp2p::transport + +} // namespace libp2p::transport diff --git a/include/libp2p/transport/tcp/tcp_listener.hpp b/include/libp2p/transport/tcp/tcp_listener.hpp index ba0d9bfe9..447ca54a8 100644 --- a/include/libp2p/transport/tcp/tcp_listener.hpp +++ b/include/libp2p/transport/tcp/tcp_listener.hpp @@ -8,7 +8,7 @@ #include #include -#include +#include #include #include diff --git a/include/libp2p/transport/tcp/tcp_transport.hpp b/include/libp2p/transport/tcp/tcp_transport.hpp index b85a9e733..e51e48986 100644 --- a/include/libp2p/transport/tcp/tcp_transport.hpp +++ b/include/libp2p/transport/tcp/tcp_transport.hpp @@ -8,7 +8,7 @@ #include #include -#include +#include #include #include diff --git a/src/basic/CMakeLists.txt b/src/basic/CMakeLists.txt index f18f866b2..ff1a502ef 100644 --- a/src/basic/CMakeLists.txt +++ b/src/basic/CMakeLists.txt @@ -31,6 +31,7 @@ libp2p_add_library(p2p_message_read_writer ) target_link_libraries(p2p_message_read_writer p2p_message_read_writer_error + p2p_byteutil p2p_varint_reader ) diff --git a/src/crypto/CMakeLists.txt b/src/crypto/CMakeLists.txt index 2a7ee2779..7c3d5aab6 100644 --- a/src/crypto/CMakeLists.txt +++ b/src/crypto/CMakeLists.txt @@ -37,5 +37,6 @@ libp2p_add_library(p2p_crypto_common common_functions.cpp ) target_link_libraries(p2p_crypto_common - p2p_crypto_error + p2p_crypto_error ) +target_include_directories(p2p_crypto_common PUBLIC ${OPEN_SSL_INCLUDE_DIR} ) diff --git a/src/crypto/ecdsa_provider/ecdsa_provider_impl.cpp b/src/crypto/ecdsa_provider/ecdsa_provider_impl.cpp index 8f4d854f1..2cc16c591 100644 --- a/src/crypto/ecdsa_provider/ecdsa_provider_impl.cpp +++ b/src/crypto/ecdsa_provider/ecdsa_provider_impl.cpp @@ -88,7 +88,7 @@ namespace libp2p::crypto::ecdsa { template outcome::result EcdsaProviderImpl::convertEcKeyToBytes( const std::shared_ptr &ec_key, - int (*converter)(EC_KEY *, uint8_t **)) const { + int (*converter)(const EC_KEY *, uint8_t **)) const { KeyType key{}; int generated_size = converter(ec_key.get(), nullptr); if (generated_size != key.size()) { diff --git a/src/layer/websocket/ws_adaptor.cpp b/src/layer/websocket/ws_adaptor.cpp index 62530a964..9e548a255 100644 --- a/src/layer/websocket/ws_adaptor.cpp +++ b/src/layer/websocket/ws_adaptor.cpp @@ -7,7 +7,7 @@ #include #include -#include +#include namespace libp2p::layer { @@ -44,7 +44,7 @@ namespace libp2p::layer { const multi::Multiaddress &address, std::shared_ptr conn, LayerAdaptor::LayerConnCallbackFunc cb) const { - auto host = transport::detail::getHostAndTcpPort(address).first; + auto host = transport::detail::getHostAndPort(address).first; auto ws = std::make_shared( config_, io_context_, std::move(conn), scheduler_); ws->ws_.async_handshake( diff --git a/src/multi/converters/converter_utils.cpp b/src/multi/converters/converter_utils.cpp index 911cdeb5c..262cef9fe 100644 --- a/src/multi/converters/converter_utils.cpp +++ b/src/multi/converters/converter_utils.cpp @@ -103,6 +103,7 @@ namespace libp2p::multi::converters { case Protocol::Code::TCP: return TcpConverter::addressToBytes(addr); case Protocol::Code::UDP: + case Protocol::Code::QUIC: return UdpConverter::addressToBytes(addr); case Protocol::Code::P2P: return IpfsConverter::addressToBytes(addr); @@ -118,8 +119,7 @@ namespace libp2p::multi::converters { case Protocol::Code::IP6_ZONE: case Protocol::Code::ONION3: - case Protocol::Code::GARLIC64: - case Protocol::Code::QUIC: + case Protocol::Code::GARLIC64: case Protocol::Code::WS: case Protocol::Code::WSS: case Protocol::Code::P2P_WEBSOCKET_STAR: @@ -245,6 +245,7 @@ namespace libp2p::multi::converters { } case Protocol::Code::TCP: + case Protocol::Code::QUIC: case Protocol::Code::UDP: { // Add port results += "/"; @@ -252,7 +253,7 @@ namespace libp2p::multi::converters { break; } - case Protocol::Code::QUIC: + case Protocol::Code::WS: case Protocol::Code::WSS: // No details diff --git a/src/muxer/yamux/CMakeLists.txt b/src/muxer/yamux/CMakeLists.txt index 0b3ec6fd2..b4233adc6 100644 --- a/src/muxer/yamux/CMakeLists.txt +++ b/src/muxer/yamux/CMakeLists.txt @@ -23,5 +23,6 @@ target_link_libraries(p2p_yamuxed_connection p2p_peer_id p2p_read_buffer p2p_write_queue + p2p_basic_scheduler p2p_connection_error ) diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index 6b731cad1..e7b7bc1b8 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -13,6 +13,7 @@ libp2p_add_library(p2p_default_network target_link_libraries(p2p_default_network p2p_network p2p_tcp + p2p_quic p2p_yamux p2p_mplex p2p_plaintext diff --git a/src/protocol/gossip/impl/CMakeLists.txt b/src/protocol/gossip/impl/CMakeLists.txt index fee7e17e7..e34ff442c 100644 --- a/src/protocol/gossip/impl/CMakeLists.txt +++ b/src/protocol/gossip/impl/CMakeLists.txt @@ -26,5 +26,6 @@ target_link_libraries(p2p_gossip subscription p2p_peer_id p2p_cid + p2p_basic_scheduler p2p_gossip_proto ) diff --git a/src/protocol/kademlia/impl/CMakeLists.txt b/src/protocol/kademlia/impl/CMakeLists.txt index db0bbc62c..0f5c5c007 100644 --- a/src/protocol/kademlia/impl/CMakeLists.txt +++ b/src/protocol/kademlia/impl/CMakeLists.txt @@ -21,6 +21,7 @@ libp2p_add_library(p2p_kademlia target_link_libraries(p2p_kademlia p2p_basic_scheduler p2p_byteutil + p2p_varint_reader p2p_kademlia_message p2p_kademlia_error ) diff --git a/src/security/tls/CMakeLists.txt b/src/security/tls/CMakeLists.txt index f16efd506..25fe2c106 100644 --- a/src/security/tls/CMakeLists.txt +++ b/src/security/tls/CMakeLists.txt @@ -15,3 +15,4 @@ target_link_libraries(p2p_tls p2p_logger p2p_security_error ) +target_include_directories(p2p_tls PUBLIC ${OPEN_SSL_INCLUDE_DIR}) diff --git a/src/transport/CMakeLists.txt b/src/transport/CMakeLists.txt index 70b5ca149..1ba17e7ec 100644 --- a/src/transport/CMakeLists.txt +++ b/src/transport/CMakeLists.txt @@ -6,3 +6,4 @@ add_subdirectory(impl) add_subdirectory(tcp) +add_subdirectory(quic) diff --git a/src/transport/impl/CMakeLists.txt b/src/transport/impl/CMakeLists.txt index 33f1ccf91..71b461cbf 100644 --- a/src/transport/impl/CMakeLists.txt +++ b/src/transport/impl/CMakeLists.txt @@ -12,6 +12,7 @@ libp2p_add_library(p2p_transport_parser target_link_libraries(p2p_transport_parser Boost::boost p2p_multiaddress + OpenSSL::SSL ) libp2p_add_library(p2p_upgrader diff --git a/src/transport/quic/CMakeLists.txt b/src/transport/quic/CMakeLists.txt new file mode 100644 index 000000000..15c441e97 --- /dev/null +++ b/src/transport/quic/CMakeLists.txt @@ -0,0 +1,52 @@ +# +# Copyright Quadrivium LLC +# All Rights Reserved +# SPDX-License-Identifier: Apache-2.0 +# + +set( QUIC_SOURCES +quic_transport.cpp quic_stream.cpp quic_listener.cpp quic_connection.cpp +) + +libp2p_add_library(p2p_quic SHARED ${QUIC_SOURCES}) +target_link_libraries(p2p_quic PUBLIC + p2p_multiaddress + p2p_logger + p2p_connection_error + ssl::ssl # BoringSSL + nexus +) +# prevent the openssl/ssl.h header from ever being read if it is on the include path +# because Hunter uses the same common path for all packages i.e. +#/home/vagrant/.hunter/_Base/72b446a/b18b06c/eede853/Install/include .... +# /openssl +# ... +# /boringssl +# see compile_commands.json to see what i mean +# BoringSSL's ssl.h has OPENSSL_HEADER_SSL_H as include guard +# +# target_compile_definitions(p2p_quic PRIVATE +# OPENSSL_SSL_H +# HEADER_SSL_H +# ) + +# get_property(dirs DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES) +#get_target_property(dirs p2p_quic INCLUDE_DIRECTORIES ) +#foreach(dir ${dirs}) +# message(STATUS "dir='${dir}'") +#endforeach() + +# do not leak any BoringSSL symbols +# or boost::asio::ssl symbols that use the boringssl headers +# instead of the openssl ones in their definition +target_compile_options( p2p_quic PRIVATE +-fvisibility=hidden +-fvisibility-inlines-hidden +) + +# is nexus imported by executable or build itself +if( TARGET p2p_quic ) +target_compile_definitions( p2p_quic PUBLIC + $ +$ ) +endif() diff --git a/src/transport/quic/quic_connection.cpp b/src/transport/quic/quic_connection.cpp new file mode 100644 index 000000000..31dc1b2c3 --- /dev/null +++ b/src/transport/quic/quic_connection.cpp @@ -0,0 +1,252 @@ +#include +#include + +#include +#include + +#include + +namespace libp2p::transport { + + namespace { + + inline std::error_code convert(boost::system::errc::errc_t ec) { + return {static_cast(ec), std::system_category()}; + } + + inline std::error_code convert(std::error_code ec) { + return ec; + } + } // namespace + + QuicConnection::QuicConnection(nexus::quic::client &c) + : m_is_initiator(true), + m_conn(c), + log_(log::createLogger("QuicConnection")) {} + + QuicConnection::QuicConnection(nexus::quic::acceptor &a) + : m_conn(a), log_(log::createLogger("QuicConnection")) {} + + QuicConnection::QuicConnection(nexus::quic::client &c, + const Udp::endpoint &endpoint, + const std::string_view &hostname) + : m_is_initiator(true), + m_conn(c, endpoint, hostname.data()), + log_(log::createLogger("QuicConnection")) {} + + void QuicConnection::accept_streams() { + auto instream = std::make_shared(*this, false); + + m_conn.async_accept( + instream->m_stream, + [this, s{instream}](boost::system::error_code ec) mutable { + if (ec) { + SL_TRACE(log_, "stream accept failed with {}", ec.message()); + m_on_stream_cb( + nullptr); // change signature of callback to propagate error + return; + } + // start next accept + accept_streams(); + // start reading from stream + SL_TRACE(log_, "new stream"); + + m_on_stream_cb(std::move( + s)); // what if QuicConnection::onStream( on_stream_handler ) has + // not been called yet ?! can this happen + }); + } + + /** + * @brief Function that is used to check if current object is closed. + * @return true if closed, false otherwise + */ + bool QuicConnection::isClosed() const { + return !m_conn.is_open(); + } + + /** + * @brief Closes current object + * @return nothing or error + */ + outcome::result QuicConnection::close() { + boost::system::error_code ec; + m_conn.close(ec); + + if (ec) { + return outcome::failure(ec); + } else { + return outcome::success(); + } + } + + /*start accepting incoming streams*/ + void QuicConnection::start() { + SL_TRACE(log_, "QuicConnection started"); + accept_streams(); + } + + void QuicConnection::stop() { + // nothing to do ?! accept_stream loop will cancel itself once conn gets + // closed + } + + void QuicConnection::newStream(CapableConnection::StreamHandlerFunc cp) { + auto outstream = std::make_shared(*this, true); + + m_conn.async_connect( + outstream->m_stream, + [out_stream = std::move(outstream), cp{std::move(cp)}, this]( + boost::system::error_code ec) { + if (ec) { + SL_TRACE(log_, "async_connect failed with {}", ec.message()); + cp(outcome::failure(ec)); + return; + } + SL_TRACE(log_, "async_connect stream successful"); + cp(std::move(out_stream)); + }); + } + + outcome::result> + QuicConnection::newStream() { + auto outstream = std::make_shared(*this, true); + boost::system::error_code ec; + m_conn.connect(outstream->m_stream, ec); + + if (ec) { + return outcome::failure(ec); + } else { + return outstream; + } + } + + void QuicConnection::onStream(NewStreamHandlerFunc cb) { + m_on_stream_cb = std::move(cb); + } + + /** + * Get a public key of peer this connection is established with + * @return public key + */ + outcome::result QuicConnection::remotePublicKey() const { + return {{}}; + } + + outcome::result QuicConnection::remoteMultiaddr() { + if (!remote_multiaddress_) { + auto res = saveMultiaddresses(); + if (!res) { + return res.error(); + } + } + return remote_multiaddress_.value(); + } + + outcome::result QuicConnection::localMultiaddr() { + if (!local_multiaddress_) { + auto res = saveMultiaddresses(); + if (!res) { + return res.error(); + } + } + return local_multiaddress_.value(); + return *local_multiaddress_; + } + + /** + * Get a PeerId of our local peer + * @return peer id + */ + outcome::result QuicConnection::localPeer() const { + if (auto addr = const_cast(this)->localMultiaddr(); + addr) { + if (auto val = local_multiaddress_.value().getPeerId(); val) { + return peer::PeerId::fromBase58(val.value()); + } else { + return outcome::failure(std::errc::invalid_argument); + } + } else { + return outcome::failure(std::errc::invalid_argument); + } + } + + /** + * Get a PeerId of peer this connection is established with + * @return peer id + */ + outcome::result QuicConnection::remotePeer() const { + if (auto addr = const_cast(this)->remoteMultiaddr(); + addr) { + if (auto val = remote_multiaddress_.value().getPeerId(); val) { + return peer::PeerId::fromBase58(val.value()); + } else { + return outcome::failure(std::errc::invalid_argument); + } + } else { + return outcome::failure(addr.error()); + } + } + + bool QuicConnection::isInitiator() const noexcept { + return m_is_initiator; + } + + void QuicConnection::setRemoteEndpoint(const multi::Multiaddress &remote) { + remote_multiaddress_ = remote; + } + + outcome::result QuicConnection::saveMultiaddresses() { + boost::system::error_code ec; + if (m_conn.is_open()) { + if (!local_multiaddress_) { + auto endpoint(m_conn.local_endpoint()); + if (!ec) { + OUTCOME_TRY(addr, detail::makeAddress(endpoint)); + local_multiaddress_ = std::move(addr); + } + } + if (!remote_multiaddress_) { + auto endpoint(m_conn.remote_endpoint(ec)); + if (!ec) { + OUTCOME_TRY(addr, detail::makeAddress(endpoint)); + remote_multiaddress_ = std::move(addr); + } + } + } else { + if (remote_multiaddress_) // maybe it was set by the QuicTransport with + // setRemote() + { + return outcome::success(); + } else { + return convert(boost::system::errc::not_connected); + } + } + if (ec) { + return convert(ec); + } +#ifndef NDEBUG + debug_str_ = fmt::format("{} {} {}", + local_multiaddress_->getStringAddress(), + m_is_initiator ? "->" : "<-", + remote_multiaddress_->getStringAddress()); +#endif + return outcome::success(); + } + + /* + void QuicConnection::deferReadCallback(outcome::result res, + ReadCallbackFunc cb) + { + + } + + void QuicConnection::deferWriteCallback(std::error_code ec, + WriteCallbackFunc cb) + { + + } + + */ + +} // namespace libp2p::transport \ No newline at end of file diff --git a/src/transport/quic/quic_listener.cpp b/src/transport/quic/quic_listener.cpp new file mode 100644 index 000000000..89f985dee --- /dev/null +++ b/src/transport/quic/quic_listener.cpp @@ -0,0 +1,96 @@ +#include +#include +#include +#include +#include + +namespace libp2p::transport { + + QuicListener::QuicListener(nexus::quic::server &s, + boost::asio::ssl::context &ssl, + TransportListener::HandlerFunc handler) + : m_server(s), + m_acceptor(s, ssl), + log_(log::createLogger("QuicListener")) {} + + outcome::result QuicListener::listen( + const multi::Multiaddress &address) { + if (!canListen(address)) { + SL_TRACE(log_, + "cannot listen on address: {} Address family not supported", + address.getStringAddress()); + return std::errc::address_family_not_supported; + } + + if (m_is_open) { + return std::errc::already_connected; + } + m_is_open = true; + + m_listen_addr = address; + + try { + OUTCOME_TRY(endpoint, detail::makeUdpEndpoint(address)); + + SL_TRACE(log_, "listen on {}", address.getStringAddress()); + m_acceptor.listen(endpoint, m_incoming_conn_capacity); + + // start listening + doAcceptConns(); + + return outcome::success(); + } catch (const boost::system::system_error &e) { + SL_ERROR(log_, + "Cannot listen to {}: {}", + address.getStringAddress(), + e.code().message()); + return e.code(); + } + } + + bool QuicListener::canListen(const multi::Multiaddress &ma) const { + return detail::supportsIpQuic(ma); + } + + outcome::result QuicListener::getListenMultiaddr() + const { + if (m_listen_addr.has_value()) { + return *m_listen_addr; + } else { + return outcome::failure(std::errc::invalid_argument); + } + } + + bool QuicListener::isClosed() const { + return !m_acceptor.is_open(); + } + + outcome::result QuicListener::close() { + m_acceptor.close(); + return outcome::success(); + } + + void QuicListener::doAcceptConns() { + auto conn = std::make_shared(m_acceptor); + auto &c = conn->m_conn; + m_acceptor.async_accept(c, [this, con{conn}](boost::system::error_code ec) { + if (ec) { + SL_TRACE(log_, "accept failed with {} ->shutting down", ec.message()); + /// stop accepting new connections and streams entirely, and mark + /// existing + /// connections as 'going away'. each associated acceptor is responsible + /// for + /// closing its own socket + m_server.close(); + return; + } + + con->start(); + // start next accept + doAcceptConns(); + SL_TRACE(log_, "new connection"); + handle_(std::move(con)); + }); + } + +} // namespace libp2p::transport \ No newline at end of file diff --git a/src/transport/quic/quic_stream.cpp b/src/transport/quic/quic_stream.cpp new file mode 100644 index 000000000..b9b2395b2 --- /dev/null +++ b/src/transport/quic/quic_stream.cpp @@ -0,0 +1,152 @@ +#include +#include + +#include + +namespace libp2p::connection { + + QuicStream::QuicStream(libp2p::transport::QuicConnection &conn, + bool is_initiator) + : m_is_initiator(is_initiator), + m_stream(conn.m_conn), + m_conn(conn) + + {} + + bool QuicStream::isClosedForRead() const { + return !m_stream.is_open_read(); + } + + bool QuicStream::isClosedForWrite() const { + return !m_stream.is_open_write(); + } + + bool QuicStream::isClosed() const { + return !m_stream.is_open(); + } + + void QuicStream::close(Stream::VoidResultHandlerFunc cb) { + m_stream.async_close( + [this, cb{std::move(cb)}](boost::system::error_code ec) { + if (ec) { + SL_TRACE(log_, "stream close failed with {}", ec.message()); + cb(outcome::failure(ec)); + } else { + cb(outcome::success()); + SL_TRACE(log_, "stream closed"); + } + }); + } + + void QuicStream::reset() {} + + void QuicStream::adjustWindowSize(uint32_t new_size, + Stream::VoidResultHandlerFunc cb) + {} + + outcome::result QuicStream::isInitiator() const { + return m_is_initiator; + } + + outcome::result QuicStream::remotePeerId() const { + return m_conn.remotePeer(); + } + + outcome::result QuicStream::localMultiaddr() const { + return m_conn.localMultiaddr(); + } + + outcome::result QuicStream::remoteMultiaddr() const { + return m_conn.remoteMultiaddr(); + } + + void QuicStream::read(BytesOut out, + size_t bytes, + basic::Reader::ReadCallbackFunc cb) { + auto then = [this, handle{std::move(cb)}](boost::system::error_code ec, + size_t bytes_read) { + if (ec) { + if (ec != nexus::quic::stream_error::eof) { + SL_TRACE(log_, "async_read_some returned ", ec.message()); + handle(outcome::failure(ec)); + } + return; + } + + handle(bytes_read); + }; + + boost::asio::async_read(m_stream, + boost::asio::buffer(out, bytes), + boost::asio::transfer_all(), std::move(then)); + } + + void QuicStream::readSome(BytesOut out, + size_t bytes, + basic::Reader::ReadCallbackFunc cb) { + m_stream.async_read_some( + boost::asio::buffer(out, bytes), + [this, handle{std::move(cb)}](boost::system::error_code ec, + size_t bytes_read) { + if (ec) { + if (ec != nexus::quic::stream_error::eof) { + SL_TRACE(log_, "async_read_some returned ", ec.message()); + handle(outcome::failure(ec)); + } + return; + } + + handle(bytes_read); + }); + } + + void QuicStream::deferReadCallback(outcome::result res, + basic::Reader::ReadCallbackFunc cb) {} + + void QuicStream::write(BytesIn in, + size_t bytes, + basic::Writer::WriteCallbackFunc cb) { + auto then = [this, handle{cb}](boost::system::error_code ec, + size_t bytes_written) { + if (ec) { + SL_TRACE(log_, "async_write failed with ", ec.message()); + handle(outcome::failure(ec)); + } else { + handle(bytes_written); + } + }; + // composed operation which repeatedly calls async_write_some() on m_stream + // until done + boost::asio::async_write(m_stream, + boost::asio::buffer(in.data(), bytes), + boost::asio::transfer_all(), + std::move(then)); + } + + void QuicStream::writeSome(BytesIn in, + size_t bytes, + basic::Writer::WriteCallbackFunc cb) { + auto then = [this, handle{cb}](boost::system::error_code ec, + size_t bytes_written) { + if (ec) { + SL_TRACE(log_, "async_write failed with ", ec.message()); + handle(outcome::failure(ec)); + } else { + handle(bytes_written); + } + }; + /* + boost::asio::async_write(m_stream, boost::asio::buffer( in.data(), bytes), + std::move(then), boost::asio::transfer_at_least(1) );*/ + + m_stream.async_write_some(boost::asio::buffer(in.data(), bytes), + std::move(then)); + } + + void QuicStream::deferWriteCallback( + std::error_code ec, + basic::Writer::WriteCallbackFunc + cb) // void(outcome::result /*written bytes*/) + {} + +} // namespace libp2p::connection \ No newline at end of file diff --git a/src/transport/quic/quic_transport.cpp b/src/transport/quic/quic_transport.cpp new file mode 100644 index 000000000..59c7363c3 --- /dev/null +++ b/src/transport/quic/quic_transport.cpp @@ -0,0 +1,358 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#define TRACE_ENABLED 1 +#include +#include +#include +#include +#include "../../security/tls/tls_details.hpp" + + + + +// #include + +// #include + +namespace libp2p::transport { + + int alpn_select_cb(SSL *ssl, + const unsigned char **out, + unsigned char *outlen, + const unsigned char *in, + unsigned int inlen, + void *arg) { + /* + unsigned char vector[] = { + 6, 's', 'p', 'd', 'y', '/', '1', + 8, 'h', 't', 't', 'p', '/', '1', '.', '1' + }; + unsigned int length = sizeof(vector); + */ + const unsigned char alpn[] = {4, 'e', 'c', 'h', 'o'}; + int r = ::SSL_select_next_proto(const_cast(out), + outlen, + const_cast(in), + inlen, + alpn, + sizeof(alpn)); + if (r == OPENSSL_NPN_NEGOTIATED) { + return SSL_TLSEXT_ERR_OK; + } else { + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + } + + /* + QuicConfig::QuicConfig( const boost::asio::ssl::context &ssl, + const nexus::quic::settings& s + ) + { + + } + */ + + using cbase = boost::asio::ssl::context_base; + + void QuicConfig::def_ctor_impl() { + + ::SSL_CTX_set_alpn_select_cb(ssl.native_handle(), alpn_select_cb, nullptr); + const unsigned char alpn[] = {4, 'e', 'c', 'h', 'o'}; + ::SSL_CTX_set_alpn_protos(ssl.native_handle(), alpn, sizeof(alpn)); + + + ssl.set_options(cbase::no_compression | cbase::no_sslv2 | cbase::no_sslv3 + | cbase::no_tlsv1_1 | cbase::no_tlsv1_2); + + ssl.set_verify_mode(boost::asio::ssl::verify_peer + | boost::asio::ssl::verify_fail_if_no_peer_cert + | boost::asio::ssl::verify_client_once); + // ssl.set_verify_mode(boost::asio::ssl::verify_none); + + // ssl.set_verify_callback(&libp2p::security::tls_details::verifyCallback); + + + + auto injector = libp2p::injector::makeNetworkInjector(); + + [[maybe_unused]] auto ck = libp2p::security::tls_details::makeCertificate( + injector.create(), // this will yield a random generated keypair for clients + // or the one that was specified with useKeyPair() i.e. for servers + *injector.create >()); + + + + ssl.use_certificate( + boost::asio::const_buffer(ck.certificate.data(), ck.certificate.size()), + boost::asio::ssl::context_base::asn1); + + ssl.use_private_key( + boost::asio::const_buffer(ck.private_key.data(), ck.private_key.size()), + boost::asio::ssl::context_base::asn1); + } + + QuicConfig::QuicConfig() : ssl(cbase::tlsv13) { + def_ctor_impl(); + } + + /* + this constructor can be used by clients + use a random generated keypair to create a (unsigned) certificate. + But load a ca_cert to verify the server certificate with. + */ + /* + QuicConfig::QuicConfig(// crypto::KeyPair& keypair, + const std::string_view &ca_path) + : ssl(cbase::tlsv13) { + ssl.load_verify_file(ca_path.data()); + + def_ctor_impl(); + } + */ + + /*! + \param key_path filepath to private key in ASN1 or Pem format + \param cert_path filepath to certificate in .pem format + \param ca_path path of a file containing certification authority + certificates in PEM format + */ + /* + QuicConfig::QuicConfig(const nexus::quic::settings &_server_settings, + const nexus::quic::settings &_client_settings, + const std::string_view &key_path, + const std::string_view &cert_path, + const std::string_view &ca_path) + : ssl(cbase::tlsv13), + client_settings(_client_settings), + server_settings(_server_settings) + { + + //ssl.use_certificate_chain_file(cert_path.data()); + //ssl.use_private_key_file(key_path.data(), + //boost::asio::ssl::context::file_format::pem); + // ssl.load_verify_file(ca_path.data() ); + + ssl.set_options(cbase::no_compression | cbase::no_sslv2 | cbase::no_sslv3 + | cbase::no_tlsv1_1 | cbase::no_tlsv1_2); + ::SSL_CTX_set_alpn_select_cb(ssl.native_handle(), alpn_select_cb, nullptr); + const unsigned char alpn[] = {4, 'e', 'c', 'h', 'o'}; + ::SSL_CTX_set_alpn_protos(ssl.native_handle(), alpn, sizeof(alpn)); + + } + */ + + // const char* hostname = "maybe_sth_from_my_pubkey_here"; // max. 255 chars + // SSL_set_tlsext_host_name( ssl.native_handle(), hostname ); + /* + try { + ssl_context_ = + std::make_shared(cbase::tlsv13); + + ssl_context_->set_verify_mode( + boost::asio::ssl::verify_peer + | boost::asio::ssl::verify_fail_if_no_peer_cert + | boost::asio::ssl::verify_client_once); + ssl_context_->set_verify_callback(&tls_details::verifyCallback); + + auto ck = + tls_details::makeCertificate(idmgr_->getKeyPair(), + *key_marshaller_); + + ssl_context_->use_certificate( + boost::asio::const_buffer(ck.certificate.data(), + ck.certificate.size()), + boost::asio::ssl::context_base::asn1); + + ssl_context_->use_private_key( + boost::asio::const_buffer(ck.private_key.data(), + ck.private_key.size()), + boost::asio::ssl::context_base::asn1); + } catch (const std::exception &e) { + ssl_context_.reset(); + log()->error("context init failed: {}", e.what()); + return TlsError::TLS_CTX_INIT_FAILED; + } + */ + + /*QuicTransport::QuicTransport(std::shared_ptr + context ) : context_(std::move(context)) {} + */ + + /* + - maybe use separate quic::settings for client and server ?! + */ + QuicTransport::QuicTransport(std::shared_ptr context + // , QuicConfig &&config + ) + // const Udp::Endpoint &local_endpoint ) + //: m_client(context.get_executor(), local_endpoint, ssl, s), + : context_(context), + // m_config(std::move(config)), + m_nexus_ctx(nexus::global::init_client_server()) + , m_clients{nexus::quic::client(context->get_executor(), + Udp::endpoint{Udp::v4(), 0}, + m_config.ssl + , m_config.client_settings + ) + , + nexus::quic::client(context->get_executor(), + Udp::endpoint{Udp::v6(), 0}, + m_config.ssl + , m_config.client_settings + ) + }, + m_server(context->get_executor(), m_config.server_settings) , + log_(log::createLogger("QuicTransport")) + { + SL_TRACE(log_, "client local addr: {}:{} {}:{}", + m_clients[0].local_endpoint().address().to_string(), + m_clients[0].local_endpoint().port(), + m_clients[1].local_endpoint().address().to_string(), + m_clients[1].local_endpoint().port() + ); + m_nexus_ctx.log_to_stderr("debug"); + } + + void QuicTransport::dial(const peer::PeerId &remoteId, + multi::Multiaddress address, + TransportAdaptor::HandlerFunc handler) { + dial(remoteId, + std::move(address), + std::move(handler), + std::chrono::milliseconds::zero()); + } + + void QuicTransport::dial(const peer::PeerId &remoteId, + multi::Multiaddress address, + TransportAdaptor::HandlerFunc handler, + std::chrono::milliseconds timeout) { + if (!canDial(address)) { + // TODO(107): Reentrancy + + return handler(std::errc::address_family_not_supported); + } + + auto [host, port] = detail::getHostAndPort(address); + + auto layers = detail::getLayers(address); + + // gets called, once address resolution is done + auto connect = [this, + address, + hostname = host, + handler{std::move(handler)}, + layers = std::move(layers)](auto ec, auto r) mutable { + // resolve failed + if (ec) { + return handler(ec); + } + + /// open a connection to the given remote endpoint and hostname. this + /// initiates the TLS handshake, but returns immediately without waiting + /// for the handshake to complete + const auto &endp = (*r).endpoint(); + + SL_TRACE(log_, "client dialing: {}:{}", endp.address().to_string(), endp.port() ); + + auto conn = std::make_shared( + clientByFamily(endp.protocol().family()), endp, hostname.data()); + + conn->setRemoteEndpoint( std::move(address) ); // workaround ^^ + + m_conns.emplace_back(conn); + conn->start(); + handler(conn); + + /* // no need to build manual timeout -> see + nexus::settings::handshake_timeout + conn->connect( r, + [=, handler{std::move(handler)}, + layers = std::move(layers)]( auto ec, auto &e) mutable + { + // connection handshake timeout + if (ec) { + std::ignore = conn->close(); + return handler(ec); + } + + + }, + timeout); */ + }; + + using P = multi::Protocol::Code; + switch (detail::getFirstProtocol(address)) { + case P::DNS4: + return resolve(boost::asio::ip::udp::v4(), host, port, connect); + case P::DNS6: + return resolve(boost::asio::ip::udp::v6(), host, port, connect); + default: // Could be only DNS, IP6 or IP4 as canDial already checked for + // that in the beginning of the method + return resolve(host, port, std::move(connect) ); + } + } + + std::shared_ptr QuicTransport::createListener( + TransportListener::HandlerFunc handler) { + return std::make_shared( + m_server, m_config.ssl, std::move(handler)); + } + + bool QuicTransport::canDial(const multi::Multiaddress &ma) const { + return detail::supportsIpQuic(ma); + } + + peer::ProtocolName QuicTransport::getProtocolId() const { + return "/quic/1.0.0"; + } + + void QuicTransport::resolve(const Udp::endpoint &endpoint, + ResolveCallbackFunc cb) { + auto resolver = std::make_shared(*context_); + resolver->async_resolve( + endpoint, + [resolver, cb{std::move(cb)}](const ErrorCode &ec, auto &&iterator) { + cb(ec, std::forward(iterator)); + }); + } + + void QuicTransport::resolve(const std::string &host_name, + const std::string &port, + ResolveCallbackFunc cb) { + auto resolver = std::make_shared(context_->get_executor()); + + // commented out, because it was never called. Executor just ran out of work and finished. + resolver->async_resolve( + host_name, + port, + [ res{resolver}, cb{std::move(cb)},this](const ErrorCode &ec, auto &&iterator) + { + SL_TRACE(log_, "done resolving! err: {}" , ec.message() ); + + cb(ec, std::forward(iterator)); + } + ); + // sync workaround + // cb( {}, resolver->resolve(host_name,port) ); + } + + void QuicTransport::resolve(const Udp &protocol, + const std::string &host_name, + const std::string &port, + ResolveCallbackFunc cb) { + auto resolver = std::make_shared(*context_); + resolver->async_resolve( + protocol, + host_name, + port, + [resolver, cb{std::move(cb)}](const ErrorCode &ec, auto &&iterator) + { + cb(ec, std::forward(iterator)); + }); + } + +} // namespace libp2p::transport diff --git a/src/transport/tcp/tcp_connection.cpp b/src/transport/tcp/tcp_connection.cpp index 9dcf20fd3..8d1426fdd 100644 --- a/src/transport/tcp/tcp_connection.cpp +++ b/src/transport/tcp/tcp_connection.cpp @@ -8,7 +8,7 @@ #include #include -#include +#include #define TRACE_ENABLED 0 #include diff --git a/src/transport/tcp/tcp_listener.cpp b/src/transport/tcp/tcp_listener.cpp index 53453dfab..294a633a4 100644 --- a/src/transport/tcp/tcp_listener.cpp +++ b/src/transport/tcp/tcp_listener.cpp @@ -34,7 +34,7 @@ namespace libp2p::transport { // TODO(@warchant): replace with parser PRE-129 using namespace boost::asio; // NOLINT try { - OUTCOME_TRY(endpoint, detail::makeEndpoint(address)); + OUTCOME_TRY(endpoint, detail::makeTcpEndpoint(address)); // setup acceptor, throws acceptor_.open(endpoint.protocol()); diff --git a/src/transport/tcp/tcp_transport.cpp b/src/transport/tcp/tcp_transport.cpp index 096642df9..cb64adde8 100644 --- a/src/transport/tcp/tcp_transport.cpp +++ b/src/transport/tcp/tcp_transport.cpp @@ -31,7 +31,7 @@ namespace libp2p::transport { auto conn = std::make_shared(*context_); - auto [host, port] = detail::getHostAndTcpPort(address); + auto [host, port] = detail::getHostAndPort(address); auto layers = detail::getLayers(address); diff --git a/test/acceptance/p2p/CMakeLists.txt b/test/acceptance/p2p/CMakeLists.txt index 37204e70d..81d2d74ff 100644 --- a/test/acceptance/p2p/CMakeLists.txt +++ b/test/acceptance/p2p/CMakeLists.txt @@ -15,6 +15,7 @@ target_link_libraries(all_muxers_acceptance_test p2p_multiaddress p2p_plaintext p2p_tcp + #p2p_quic p2p_testutil p2p_key_marshaller p2p_identity_manager diff --git a/test/libp2p/protocol/CMakeLists.txt b/test/libp2p/protocol/CMakeLists.txt index 922e5e087..3d11506b8 100644 --- a/test/libp2p/protocol/CMakeLists.txt +++ b/test/libp2p/protocol/CMakeLists.txt @@ -29,6 +29,7 @@ addtest(observed_addresses_test target_link_libraries(observed_addresses_test p2p_identify p2p_literals + p2p_identify ) addtest(echo_test diff --git a/test/libp2p/protocol_muxer/CMakeLists.txt b/test/libp2p/protocol_muxer/CMakeLists.txt index 6349f138e..41f726566 100644 --- a/test/libp2p/protocol_muxer/CMakeLists.txt +++ b/test/libp2p/protocol_muxer/CMakeLists.txt @@ -13,6 +13,7 @@ target_link_libraries(multiselect_test p2p_peer_id p2p_yamux p2p_tcp + #p2p_quic p2p_plaintext p2p_testutil_peer p2p_literals diff --git a/test/libp2p/transport/upgrader_test.cpp b/test/libp2p/transport/upgrader_test.cpp index 0449268ad..1d4110290 100644 --- a/test/libp2p/transport/upgrader_test.cpp +++ b/test/libp2p/transport/upgrader_test.cpp @@ -13,7 +13,7 @@ #include "libp2p/multi/multiaddress.hpp" #include "libp2p/multi/multiaddress_protocol_list.hpp" #include "libp2p/multi/multihash.hpp" -#include "libp2p/transport/tcp/tcp_util.hpp" +#include "libp2p/transport/impl/util.hpp" #include "mock/libp2p/connection/capable_connection_mock.hpp" #include "mock/libp2p/connection/layer_connection_mock.hpp" #include "mock/libp2p/connection/raw_connection_mock.hpp"