From 9c748d20d5ffd57f5c81bdba30047db4e2bfa02f Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 8 Jun 2019 22:05:25 +0900 Subject: [PATCH 001/124] [WIP] Add QUIC to h2load --- configure.ac | 16 + src/Makefile.am | 9 +- src/h2load.cc | 196 ++++++- src/h2load.h | 81 +++ src/h2load_http3_session.cc | 375 ++++++++++++ src/h2load_http3_session.h | 77 +++ src/h2load_quic.cc | 1078 +++++++++++++++++++++++++++++++++++ src/h2load_quic.h | 38 ++ src/quic.cc | 422 ++++++++++++++ src/quic.h | 104 ++++ src/util.cc | 50 ++ src/util.h | 3 + 12 files changed, 2435 insertions(+), 14 deletions(-) create mode 100644 src/h2load_http3_session.cc create mode 100644 src/h2load_http3_session.h create mode 100644 src/h2load_quic.cc create mode 100644 src/h2load_quic.h create mode 100644 src/quic.cc create mode 100644 src/quic.h diff --git a/configure.ac b/configure.ac index 35eb47c8..3fcf05aa 100644 --- a/configure.ac +++ b/configure.ac @@ -454,6 +454,20 @@ if test "x${request_libcares}" = "xyes" && AC_MSG_ERROR([libcares was requested (--with-libcares) but not found]) fi +# ngtcp2 (for src) +PKG_CHECK_MODULES([LIBNGTCP2], [libngtcp2 >= 0.0.0], [have_libngtcp2=yes], + [have_libngtcp2=no]) +if test "x${have_libngtcp2}" = "xno"; then + AC_MSG_NOTICE($LIBNGTCP2_PKG_ERRORS) +fi + +# nghttp3 (for src) +PKG_CHECK_MODULES([LIBNGHTTP3], [libnghttp3 >= 0.0.0], [have_libnghttp3=yes], + [have_libnghttp3=no]) +if test "x${have_libnghttp3}" = "xno"; then + AC_MSG_NOTICE($LIBNGHTTP3_PKT_ERRORS) +fi + # libevent_openssl (for examples) # 2.0.8 is required because we use evconnlistener_set_error_cb() have_libevent_openssl=no @@ -1011,6 +1025,8 @@ AC_MSG_NOTICE([summary of build options: Libxml2: ${have_libxml2} (CFLAGS='${LIBXML2_CFLAGS}' LIBS='${LIBXML2_LIBS}') Libev: ${have_libev} (CFLAGS='${LIBEV_CFLAGS}' LIBS='${LIBEV_LIBS}') Libc-ares: ${have_libcares} (CFLAGS='${LIBCARES_CFLAGS}' LIBS='${LIBCARES_LIBS}') + libngtcp2: ${have_libngtcp2} (CFLAGS='${LIBNGTCP2_CFLAGS}' LIBS='${LIBNGTCP2_LIBS}') + libnghttp3: ${have_libnghttp3} (CFLAGS='${LIBNGHTTP3_CFLAGS}' LIBS='${LIBNGHTTP3_LIBS}') Libevent(SSL): ${have_libevent_openssl} (CFLAGS='${LIBEVENT_OPENSSL_CFLAGS}' LIBS='${LIBEVENT_OPENSSL_LIBS}') Jansson: ${have_jansson} (CFLAGS='${JANSSON_CFLAGS}' LIBS='${JANSSON_LIBS}') Jemalloc: ${have_jemalloc} (CFLAGS='${JEMALLOC_CFLAGS}' LIBS='${JEMALLOC_LIBS}') diff --git a/src/Makefile.am b/src/Makefile.am index 033b7f8f..ee6652f5 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -46,6 +46,8 @@ AM_CPPFLAGS = \ @LIBEV_CFLAGS@ \ @OPENSSL_CFLAGS@ \ @LIBCARES_CFLAGS@ \ + @LIBNGHTTP3_CFLAGS@ \ + @LIBNGTCP2_CFLAGS@ \ @JANSSON_CFLAGS@ \ @ZLIB_CFLAGS@ \ @DEFS@ @@ -59,6 +61,8 @@ LDADD = $(top_builddir)/lib/libnghttp2.la \ @LIBEV_LIBS@ \ @OPENSSL_LIBS@ \ @LIBCARES_LIBS@ \ + @LIBNGHTTP3_LIBS@ \ + @LIBNGTCP2_LIBS@ \ @SYSTEMD_LIBS@ \ @JANSSON_LIBS@ \ @ZLIB_LIBS@ \ @@ -97,7 +101,10 @@ h2load_SOURCES = util.cc util.h \ tls.cc tls.h \ h2load_session.h \ h2load_http2_session.cc h2load_http2_session.h \ - h2load_http1_session.cc h2load_http1_session.h + h2load_http1_session.cc h2load_http1_session.h \ + h2load_http3_session.cc h2load_http3_session.h \ + h2load_quic.cc h2load_quic.h \ + quic.cc quic.h NGHTTPX_SRCS = \ util.cc util.h http2.cc http2.h timegm.c timegm.h base64.h \ diff --git a/src/h2load.cc b/src/h2load.cc index 7d3d8e04..74cbcaf7 100644 --- a/src/h2load.cc +++ b/src/h2load.cc @@ -48,10 +48,14 @@ #include +#include + #include "url-parser/url_parser.h" #include "h2load_http1_session.h" #include "h2load_http2_session.h" +#include "h2load_http3_session.h" +#include "h2load_quic.h" #include "tls.h" #include "http2.h" #include "util.h" @@ -119,6 +123,9 @@ bool Config::is_rate_mode() const { return (this->rate != 0); } bool Config::is_timing_based_mode() const { return (this->duration > 0); } bool Config::has_base_uri() const { return (!this->base_uri.empty()); } bool Config::rps_enabled() const { return this->rps > 0.0; } +bool Config::is_quic() const { + return !npn_list.empty() && npn_list[0] == NGTCP2_ALPN_H3; +} Config config; namespace { @@ -409,6 +416,7 @@ Client::Client(uint32_t id, Worker *worker, size_t req_todo) cstat{}, worker(worker), ssl(nullptr), + quic{}, next_addr(config.addrs), current_addr(nullptr), reqidx(0), @@ -420,6 +428,7 @@ Client::Client(uint32_t id, Worker *worker, size_t req_todo) req_done(0), id(id), fd(-1), + local_addr{}, new_connection_requested(false), final(false), rps_duration_started(0), @@ -449,11 +458,18 @@ Client::Client(uint32_t id, Worker *worker, size_t req_todo) ev_timer_init(&rps_watcher, rps_cb, 0., 0.); rps_watcher.data = this; + + ev_timer_init(&quic.pkt_timer, quic_pkt_timeout_cb, 0., 0.); + quic.pkt_timer.data = this; } Client::~Client() { disconnect(); + if (config.is_quic()) { + quic_free(); + } + if (ssl) { SSL_free(ssl); } @@ -466,6 +482,37 @@ int Client::do_read() { return readfn(*this); } int Client::do_write() { return writefn(*this); } int Client::make_socket(addrinfo *addr) { + int rv; + + if (config.is_quic()) { + fd = util::create_nonblock_udp_socket(addr->ai_family); + if (fd == -1) { + return -1; + } + + rv = util::bind_any_addr_udp(fd, addr->ai_family); + if (rv != 0) { + close(fd); + fd = -1; + return -1; + } + + socklen_t addrlen = sizeof(local_addr.su.storage); + rv = getsockname(fd, &local_addr.su.sa, &addrlen); + if (rv == -1) { + return -1; + } + local_addr.len = addrlen; + + if (quic_init(&local_addr.su.sa, local_addr.len, addr->ai_addr, + addr->ai_addrlen) != 0) { + std::cerr << "quic_init failed" << std::endl; + return -1; + } + + return 0; + } + fd = util::create_nonblock_socket(addr->ai_family); if (fd == -1) { return -1; @@ -475,17 +522,15 @@ int Client::make_socket(addrinfo *addr) { ssl = SSL_new(worker->ssl_ctx); } - auto config = worker->config; - - if (!util::numeric_host(config->host.c_str())) { - SSL_set_tlsext_host_name(ssl, config->host.c_str()); - } - SSL_set_fd(ssl, fd); SSL_set_connect_state(ssl); } - auto rv = ::connect(fd, addr->ai_addr, addr->ai_addrlen); + if (ssl && !util::numeric_host(config.host.c_str())) { + SSL_set_tlsext_host_name(ssl, config.host.c_str()); + } + + rv = ::connect(fd, addr->ai_addr, addr->ai_addrlen); if (rv != 0 && errno != EINPROGRESS) { if (ssl) { SSL_free(ssl); @@ -542,13 +587,20 @@ int Client::connect() { current_addr = addr; } - writefn = &Client::connected; - ev_io_set(&rev, fd, EV_READ); ev_io_set(&wev, fd, EV_WRITE); ev_io_start(worker->loop, &wev); + if (config.is_quic()) { + ev_io_start(worker->loop, &rev); + + readfn = &Client::read_quic; + writefn = &Client::write_quic; + } else { + writefn = &Client::connected; + } + return 0; } @@ -603,6 +655,11 @@ void Client::fail() { void Client::disconnect() { record_client_end_time(); + if (config.is_quic()) { + quic_close_connection(); + } + + ev_timer_stop(worker->loop, &quic.pkt_timer); ev_timer_stop(worker->loop, &conn_inactivity_watcher); ev_timer_stop(worker->loop, &conn_active_watcher); ev_timer_stop(worker->loop, &rps_watcher); @@ -765,6 +822,9 @@ void Client::report_app_info() { } void Client::terminate_session() { + if (config.is_quic()) { + quic.close_requested = true; + } session->terminate(); // http1 session needs writecb to tear down session. signal_write(); @@ -963,7 +1023,15 @@ int Client::connection_made() { if (next_proto) { auto proto = StringRef{next_proto, next_proto_len}; - if (util::check_h2_is_selected(proto)) { + if (config.is_quic()) { + if (util::streq(StringRef{&NGTCP2_ALPN_H3[1]}, proto)) { + auto s = std::make_unique(this); + if (s->init_conn() == -1) { + return -1; + } + session = std::move(s); + } + } else if (util::check_h2_is_selected(proto)) { session = std::make_unique(this); } else if (util::streq(NGHTTP2_H1_1, proto)) { session = std::make_unique(this); @@ -1285,6 +1353,15 @@ int Client::write_tls() { return 0; } +int Client::write_udp(const sockaddr *addr, socklen_t addrlen, + const uint8_t *data, size_t datalen) { + auto nwrite = sendto(fd, data, datalen, MSG_DONTWAIT, addr, addrlen); + if (nwrite < 0) { + std::cerr << "sendto: errno=" << errno << std::endl; + } + return 0; +} + void Client::record_request_time(RequestStat *req_stat) { req_stat->request_time = std::chrono::steady_clock::now(); req_stat->request_wall_time = std::chrono::system_clock::now(); @@ -1698,6 +1775,79 @@ int client_select_next_proto_cb(SSL *ssl, unsigned char **out, } // namespace #endif // !OPENSSL_NO_NEXTPROTONEG +namespace { +int quic_transport_params_add_cb(SSL *ssl, unsigned int ext_type, + unsigned int content, + const unsigned char **out, size_t *outlen, + X509 *x, size_t chainidx, int *al, + void *add_arg) { + auto c = static_cast(SSL_get_app_data(ssl)); + auto conn = c->quic.conn; + + ngtcp2_transport_params params; + + ngtcp2_conn_get_local_transport_params(conn, ¶ms); + + constexpr size_t bufsize = 128; + auto buf = std::make_unique(bufsize); + + auto nwrite = ngtcp2_encode_transport_params( + buf.get(), bufsize, NGTCP2_TRANSPORT_PARAMS_TYPE_CLIENT_HELLO, ¶ms); + if (nwrite < 0) { + std::cerr << "ngtcp2_encode_transport_params: " << ngtcp2_strerror(nwrite) + << std::endl; + *al = SSL_AD_INTERNAL_ERROR; + return -1; + } + + *out = buf.release(); + *outlen = static_cast(nwrite); + + return 1; +} +} // namespace + +namespace { +void quic_transport_params_free_cb(SSL *ssl, unsigned int ext_type, + unsigned int context, + const unsigned char *out, void *add_arg) { + delete[] const_cast(out); +} +} // namespace + +namespace { +int quic_transport_params_parse_cb(SSL *ssl, unsigned int ext_type, + unsigned int context, + const unsigned char *in, size_t inlen, + X509 *x, size_t chainidx, int *al, + void *parse_arg) { + auto c = static_cast(SSL_get_app_data(ssl)); + auto conn = c->quic.conn; + + int rv; + ngtcp2_transport_params params; + + rv = ngtcp2_decode_transport_params( + ¶ms, NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS, in, inlen); + if (rv != 0) { + std::cerr << "ngtcp2_decode_transport_params: " << ngtcp2_strerror(rv) + << std::endl; + *al = SSL_AD_ILLEGAL_PARAMETER; + return -1; + } + + rv = ngtcp2_conn_set_remote_transport_params(conn, ¶ms); + if (rv != 0) { + std::cerr << "ngtcp2_conn_set_remote_transport_params: " + << ngtcp2_strerror(rv) << std::endl; + *al = SSL_AD_ILLEGAL_PARAMETER; + return -1; + } + + return 1; +} +} // namespace + namespace { constexpr char UNIX_PATH_PREFIX[] = "unix:"; } // namespace @@ -2576,9 +2726,26 @@ int main(int argc, char **argv) { SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY); SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS); - if (nghttp2::tls::ssl_ctx_set_proto_versions( - ssl_ctx, nghttp2::tls::NGHTTP2_TLS_MIN_VERSION, - nghttp2::tls::NGHTTP2_TLS_MAX_VERSION) != 0) { + if (config.is_quic()) { + SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_3_VERSION); + SSL_CTX_set_max_proto_version(ssl_ctx, TLS1_3_VERSION); + + SSL_CTX_clear_options(ssl_ctx, SSL_OP_ENABLE_MIDDLEBOX_COMPAT); + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_QUIC_HACK); + + if (SSL_CTX_add_custom_ext( + ssl_ctx, NGTCP2_TLSEXT_QUIC_TRANSPORT_PARAMETERS, + SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS, + quic_transport_params_add_cb, quic_transport_params_free_cb, + nullptr, quic_transport_params_parse_cb, nullptr) != 1) { + std::cerr << "SSL_CTX_add_custom_ext(NGTCP2_TLSEXT_QUIC_TRANSPORT_" + "PARAMETERS) failed: " + << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + exit(EXIT_FAILURE); + } + } else if (nghttp2::tls::ssl_ctx_set_proto_versions( + ssl_ctx, nghttp2::tls::NGHTTP2_TLS_MIN_VERSION, + nghttp2::tls::NGHTTP2_TLS_MAX_VERSION) != 0) { std::cerr << "Could not set TLS versions" << std::endl; exit(EXIT_FAILURE); } @@ -2590,6 +2757,9 @@ int main(int argc, char **argv) { exit(EXIT_FAILURE); } + // TODO Use SSL_CTX_set_ciphersuites to set TLSv1.3 cipher list + // TODO Use SSL_CTX_set1_groups_list to set key share + #ifndef OPENSSL_NO_NEXTPROTONEG SSL_CTX_set_next_proto_select_cb(ssl_ctx, client_select_next_proto_cb, nullptr); diff --git a/src/h2load.h b/src/h2load.h index fead67c5..a9d617b1 100644 --- a/src/h2load.h +++ b/src/h2load.h @@ -45,11 +45,14 @@ #include +#include + #include #include #include "http2.h" +#include "quic.h" #include "memchunk.h" #include "template.h" @@ -124,6 +127,7 @@ struct Config { bool is_timing_based_mode() const; bool has_base_uri() const; bool rps_enabled() const; + bool is_quic() const; }; struct RequestStat { @@ -299,6 +303,13 @@ struct Stream { Stream(); }; +struct Crypto { + Crypto() : datalen(0), acked_offset(0) {} + std::array data; + size_t datalen; + size_t acked_offset; +}; + struct Client { DefaultMemchunks wb; std::unordered_map streams; @@ -309,6 +320,19 @@ struct Client { std::function readfn, writefn; Worker *worker; SSL *ssl; + struct { + ev_timer pkt_timer; + ngtcp2_conn *conn; + quic::Error last_error; + ngtcp2_crypto_level tx_crypto_level; + ngtcp2_crypto_level rx_crypto_level; + std::vector server_handshake; + size_t server_handshake_nread; + // Client never send CRYPTO in Short packet. + std::array crypto; + size_t max_pktlen; + bool close_requested; + } quic; ev_timer request_timeout_watcher; addrinfo *next_addr; // Address for the current address. When try_new_connection() is @@ -332,6 +356,7 @@ struct Client { // The client id per worker uint32_t id; int fd; + Address local_addr; ev_timer conn_active_watcher; ev_timer conn_inactivity_watcher; std::string selected_proto; @@ -419,6 +444,62 @@ struct Client { void record_client_end_time(); void signal_write(); + + // QUIC + int quic_init(const sockaddr *local_addr, socklen_t local_addrlen, + const sockaddr *remote_addr, socklen_t remote_addrlen); + void quic_free(); + int read_quic(); + int write_quic(); + int write_udp(const sockaddr *addr, socklen_t addrlen, const uint8_t *data, + size_t datalen); + void quic_close_connection(); + int quic_setup_initial_crypto(); + + int quic_client_initial(); + int quic_recv_crypto_data(ngtcp2_crypto_level crypto_level, + const uint8_t *data, size_t datalen); + int quic_handshake_completed(); + int quic_in_encrypt(uint8_t *dest, size_t destlen, const uint8_t *plaintext, + size_t plaintextlen, const uint8_t *key, size_t keylen, + const uint8_t *nonce, size_t noncelen, const uint8_t *ad, + size_t adlen); + int quic_in_decrypt(uint8_t *dest, size_t destlen, const uint8_t *ciphertext, + size_t ciphertextlen, const uint8_t *key, size_t keylen, + const uint8_t *nonce, size_t noncelen, const uint8_t *ad, + size_t adlen); + int quic_encrypt(uint8_t *dest, size_t destlen, const uint8_t *plaintext, + size_t plaintextlen, const uint8_t *key, size_t keylen, + const uint8_t *nonce, size_t noncelen, const uint8_t *ad, + size_t adlen); + int quic_decrypt(uint8_t *dest, size_t destlen, const uint8_t *ciphertext, + size_t ciphertextlen, const uint8_t *key, size_t keylen, + const uint8_t *nonce, size_t noncelen, const uint8_t *ad, + size_t adlen); + int quic_in_hp_mask(uint8_t *dest, size_t destlen, const uint8_t *key, + size_t keylen, const uint8_t *sample, size_t samplelen); + int quic_hp_mask(uint8_t *dest, size_t destlen, const uint8_t *key, + size_t keylen, const uint8_t *sample, size_t samplelen); + int quic_recv_stream_data(int64_t stream_id, int fin, const uint8_t *data, + size_t datalen); + int quic_stream_close(int64_t stream_id, uint16_t app_error_code); + int quic_stream_reset(int64_t stream_id, uint16_t app_error_code); + int quic_extend_max_local_streams(); + + int quic_tls_handshake(bool initial = false); + int quic_read_tls(); + + int quic_on_key(int name, const uint8_t *secret, size_t secretlen); + void quic_set_tls_alert(uint8_t alert); + + size_t quic_read_server_handshake(uint8_t *buf, size_t buflen); + int quic_write_server_handshake(ngtcp2_crypto_level crypto_level, + const uint8_t *data, size_t datalen); + void quic_write_client_handshake(const uint8_t *data, size_t datalen); + void quic_write_client_handshake(Crypto &crypto, const uint8_t *data, + size_t datalen); + int quic_pkt_timeout(); + void quic_restart_pkt_timer(); }; } // namespace h2load diff --git a/src/h2load_http3_session.cc b/src/h2load_http3_session.cc new file mode 100644 index 00000000..99d57891 --- /dev/null +++ b/src/h2load_http3_session.cc @@ -0,0 +1,375 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2019 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "h2load_http3_session.h" + +#include + +#include + +#include "h2load.h" + +namespace h2load { + +Http3Session::Http3Session(Client *client) + : client_(client), conn_(nullptr), npending_request_(0), reqidx_(0) {} + +Http3Session::~Http3Session() { nghttp3_conn_del(conn_); } + +void Http3Session::on_connect() {} + +int Http3Session::submit_request() { + if (npending_request_) { + ++npending_request_; + return 0; + } + + auto config = client_->worker->config; + reqidx_ = client_->reqidx; + + if (++client_->reqidx == config->nva.size()) { + client_->reqidx = 0; + } + + auto stream_id = submit_request_internal(); + if (stream_id < 0) { + if (stream_id == NGTCP2_ERR_STREAM_ID_BLOCKED) { + ++npending_request_; + return 0; + } + return -1; + } + + return 0; +} + +int64_t Http3Session::submit_request_internal() { + int rv; + int64_t stream_id; + + auto config = client_->worker->config; + auto &nva = config->nva[reqidx_]; + + rv = ngtcp2_conn_open_bidi_stream(client_->quic.conn, &stream_id, nullptr); + if (rv != 0) { + return rv; + } + + rv = nghttp3_conn_submit_request(conn_, stream_id, nullptr, + reinterpret_cast(nva.data()), + nva.size(), nullptr, nullptr); + if (rv != 0) { + return rv; + } + + rv = nghttp3_conn_end_stream(conn_, stream_id); + assert(0 == rv); + + client_->on_request(stream_id); + auto req_stat = client_->get_req_stat(stream_id); + assert(req_stat); + client_->record_request_time(req_stat); + + return stream_id; +} + +int Http3Session::on_read(const uint8_t *data, size_t len) { return -1; } + +int Http3Session::on_write() { return -1; } + +void Http3Session::terminate() {} + +size_t Http3Session::max_concurrent_streams() { + return (size_t)client_->worker->config->max_concurrent_streams; +} + +namespace { +int stream_close(nghttp3_conn *conn, int64_t stream_id, uint16_t error_code, + void *user_data, void *stream_user_data) { + auto s = static_cast(user_data); + if (s->stream_close(stream_id, error_code) != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + return 0; +} +} // namespace + +int Http3Session::stream_close(int64_t stream_id, uint16_t error_code) { + client_->on_stream_close(stream_id, error_code == NGHTTP3_HTTP_NO_ERROR); + return 0; +} + +namespace { +int recv_data(nghttp3_conn *conn, int64_t stream_id, const uint8_t *data, + size_t datalen, void *user_data, void *stream_user_data) { + auto s = static_cast(user_data); + s->recv_data(stream_id, data, datalen); + return 0; +} +} // namespace + +void Http3Session::recv_data(int64_t stream_id, const uint8_t *data, + size_t datalen) { + client_->record_ttfb(); + client_->worker->stats.bytes_body += datalen; + consume(stream_id, datalen); +} + +namespace { +int deferred_consume(nghttp3_conn *conn, int64_t stream_id, size_t nconsumed, + void *user_data, void *stream_user_data) { + auto s = static_cast(user_data); + s->consume(stream_id, nconsumed); + return 0; +} +} // namespace + +void Http3Session::consume(int64_t stream_id, size_t nconsumed) { + ngtcp2_conn_extend_max_stream_offset(client_->quic.conn, stream_id, + nconsumed); + ngtcp2_conn_extend_max_offset(client_->quic.conn, nconsumed); +} + +namespace { +int begin_headers(nghttp3_conn *conn, int64_t stream_id, void *user_data, + void *stream_user_data) { + auto s = static_cast(user_data); + s->begin_headers(stream_id); + return 0; +} +} // namespace + +void Http3Session::begin_headers(int64_t stream_id) { + auto payloadlen = nghttp3_conn_get_frame_payload_left(conn_, stream_id); + assert(payloadlen > 0); + + client_->worker->stats.bytes_head += payloadlen; +} + +namespace { +int recv_header(nghttp3_conn *conn, int64_t stream_id, int32_t token, + nghttp3_rcbuf *name, nghttp3_rcbuf *value, uint8_t flags, + void *user_data, void *stream_user_data) { + auto s = static_cast(user_data); + auto k = nghttp3_rcbuf_get_buf(name); + auto v = nghttp3_rcbuf_get_buf(value); + s->recv_header(stream_id, &k, &v); + return 0; +} +} // namespace + +void Http3Session::recv_header(int64_t stream_id, const nghttp3_vec *name, + const nghttp3_vec *value) { + client_->on_header(stream_id, name->base, name->len, value->base, value->len); + client_->worker->stats.bytes_head_decomp += name->len + value->len; +} + +namespace { +int send_stop_sending(nghttp3_conn *conn, int64_t stream_id, void *user_data, + void *stream_user_data) { + auto s = static_cast(user_data); + if (s->send_stop_sending(stream_id) != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + return 0; +} +} // namespace + +int Http3Session::send_stop_sending(int64_t stream_id) { + auto rv = ngtcp2_conn_shutdown_stream_read(client_->quic.conn, stream_id, + NGHTTP3_HTTP_PUSH_REFUSED); + if (rv != 0) { + std::cerr << "ngtcp2_conn_shutdown_stream_read: " << ngtcp2_strerror(rv) + << std::endl; + return -1; + } + return 0; +} + +int Http3Session::close_stream(int64_t stream_id, uint16_t error_code) { + auto rv = nghttp3_conn_close_stream(conn_, stream_id, error_code); + if (rv != 0) { + return -1; + } + return 0; +} + +int Http3Session::reset_stream(int64_t stream_id) { + auto rv = nghttp3_conn_reset_stream(conn_, stream_id); + if (rv != 0) { + return -1; + } + return 0; +} + +int Http3Session::extend_max_local_streams() { + auto config = client_->worker->config; + + for (; npending_request_; --npending_request_) { + auto stream_id = submit_request_internal(); + if (stream_id < 0) { + if (stream_id == NGTCP2_ERR_STREAM_ID_BLOCKED) { + return 0; + } + return -1; + } + + if (++reqidx_ == config->nva.size()) { + reqidx_ = 0; + } + } + + return 0; +} + +int Http3Session::init_conn() { + int rv; + + assert(conn_ == nullptr); + + if (ngtcp2_conn_get_max_local_streams_uni(client_->quic.conn) < 3) { + return -1; + } + + nghttp3_conn_callbacks callbacks{ + nullptr, // acked_stream_data + h2load::stream_close, + h2load::recv_data, + h2load::deferred_consume, + h2load::begin_headers, + h2load::recv_header, + nullptr, // end_headers + nullptr, // begin_trailers + h2load::recv_header, + nullptr, // end_trailers + nullptr, // http_begin_push_promise + nullptr, // http_recv_push_promise + nullptr, // http_end_push_promise + nullptr, // http_cancel_push + h2load::send_stop_sending, + nullptr, // push_stream, + }; + + nghttp3_conn_settings settings; + nghttp3_conn_settings_default(&settings); + settings.qpack_max_table_capacity = 4096; + settings.qpack_blocked_streams = 100; + + auto mem = nghttp3_mem_default(); + + rv = nghttp3_conn_client_new(&conn_, &callbacks, &settings, mem, this); + if (rv != 0) { + std::cerr << "nghttp3_conn_client_new: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + + int64_t ctrl_stream_id; + + rv = ngtcp2_conn_open_uni_stream(client_->quic.conn, &ctrl_stream_id, NULL); + if (rv != 0) { + std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv) + << std::endl; + return -1; + } + + rv = nghttp3_conn_bind_control_stream(conn_, ctrl_stream_id); + if (rv != 0) { + std::cerr << "nghttp3_conn_bind_control_stream: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + + int64_t qpack_enc_stream_id, qpack_dec_stream_id; + + rv = ngtcp2_conn_open_uni_stream(client_->quic.conn, &qpack_enc_stream_id, + NULL); + if (rv != 0) { + std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv) + << std::endl; + return -1; + } + + rv = ngtcp2_conn_open_uni_stream(client_->quic.conn, &qpack_dec_stream_id, + NULL); + if (rv != 0) { + std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv) + << std::endl; + return -1; + } + + rv = nghttp3_conn_bind_qpack_streams(conn_, qpack_enc_stream_id, + qpack_dec_stream_id); + if (rv != 0) { + std::cerr << "nghttp3_conn_bind_qpack_streams: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + + return 0; +} + +ssize_t Http3Session::read_stream(int64_t stream_id, const uint8_t *data, + size_t datalen, int fin) { + auto nconsumed = + nghttp3_conn_read_stream(conn_, stream_id, data, datalen, fin); + if (nconsumed < 0) { + std::cerr << "nghttp3_conn_read_stream: " << nghttp3_strerror(nconsumed) + << std::endl; + client_->quic.last_error = quic::err_application(nconsumed); + return -1; + } + return nconsumed; +} + +ssize_t Http3Session::write_stream(int64_t &stream_id, int &fin, + nghttp3_vec *vec, size_t veccnt) { + auto sveccnt = + nghttp3_conn_writev_stream(conn_, &stream_id, &fin, vec, veccnt); + if (sveccnt < 0) { + client_->quic.last_error = quic::err_application(sveccnt); + return -1; + } + return sveccnt; +} + +int Http3Session::block_stream(int64_t stream_id) { + auto rv = nghttp3_conn_block_stream(conn_, stream_id); + if (rv != 0) { + client_->quic.last_error = quic::err_application(rv); + return -1; + } + return 0; +} + +int Http3Session::add_write_offset(int64_t stream_id, size_t ndatalen) { + auto rv = nghttp3_conn_add_write_offset(conn_, stream_id, ndatalen); + if (rv != 0) { + client_->quic.last_error = quic::err_application(rv); + return -1; + } + return 0; +} + +} // namespace h2load diff --git a/src/h2load_http3_session.h b/src/h2load_http3_session.h new file mode 100644 index 00000000..4293c77d --- /dev/null +++ b/src/h2load_http3_session.h @@ -0,0 +1,77 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2019 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef H2LOAD_HTTP3_SESSION_H +#define H2LOAD_HTTP3_SESSION_H + +#include "h2load_session.h" + +#include + +namespace h2load { + +struct Client; + +class Http3Session : public Session { +public: + Http3Session(Client *client); + virtual ~Http3Session(); + virtual void on_connect(); + virtual int submit_request(); + virtual int on_read(const uint8_t *data, size_t len); + virtual int on_write(); + virtual void terminate(); + virtual size_t max_concurrent_streams(); + + int init_conn(); + int stream_close(int64_t stream_id, uint16_t error_code); + void recv_data(int64_t stream_id, const uint8_t *data, size_t datalen); + void consume(int64_t stream_id, size_t nconsumed); + void begin_headers(int64_t stream_id); + void recv_header(int64_t stream_id, const nghttp3_vec *name, + const nghttp3_vec *value); + int send_stop_sending(int64_t stream_id); + + int close_stream(int64_t stream_id, uint16_t error_code); + int reset_stream(int64_t stream_id); + int extend_max_local_streams(); + int64_t submit_request_internal(); + + ssize_t read_stream(int64_t stream_id, const uint8_t *data, size_t datalen, + int fin); + ssize_t write_stream(int64_t &stream_id, int &fin, nghttp3_vec *vec, + size_t veccnt); + int block_stream(int64_t stream_id); + int add_write_offset(int64_t stream_id, size_t ndatalen); + +private: + Client *client_; + nghttp3_conn *conn_; + size_t npending_request_; + size_t reqidx_; +}; + +} // namespace h2load + +#endif // H2LOAD_HTTP3_SESSION_H diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc new file mode 100644 index 00000000..a8158400 --- /dev/null +++ b/src/h2load_quic.cc @@ -0,0 +1,1078 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2019 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "h2load_quic.h" + +#include + +#include +#include + +#include "h2load_http3_session.h" + +namespace h2load { + +namespace { +auto randgen = util::make_mt19937(); +} // namespace + +namespace { +int client_initial(ngtcp2_conn *conn, void *user_data) { + auto c = static_cast(user_data); + + if (c->quic_client_initial() != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Client::quic_client_initial() { + if (quic_tls_handshake(true) != 0) { + return -1; + } + return 0; +} + +namespace { +int recv_crypto_data(ngtcp2_conn *conn, ngtcp2_crypto_level crypto_level, + uint64_t offset, const uint8_t *data, size_t datalen, + void *user_data) { + auto c = static_cast(user_data); + + if (c->quic_recv_crypto_data(crypto_level, data, datalen) != 0) { + return NGTCP2_ERR_CRYPTO; + } + + return 0; +} +} // namespace + +int Client::quic_recv_crypto_data(ngtcp2_crypto_level crypto_level, + const uint8_t *data, size_t datalen) { + if (quic_write_server_handshake(crypto_level, data, datalen) != 0) { + return -1; + } + + if (!ngtcp2_conn_get_handshake_completed(quic.conn)) { + if (quic_tls_handshake() != 0) { + return -1; + } + return 0; + } + + // SSL_do_handshake() might not consume all data (e.g., + // NewSessionTicket). + return quic_read_tls(); +} + +namespace { +int handshake_completed(ngtcp2_conn *conn, void *user_data) { + auto c = static_cast(user_data); + + if (c->quic_handshake_completed() != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Client::quic_handshake_completed() { + quic.tx_crypto_level = NGTCP2_CRYPTO_LEVEL_APP; + + // TODO Create Http3Session here. + return connection_made(); +} + +namespace { +ssize_t in_encrypt(ngtcp2_conn *conn, uint8_t *dest, size_t destlen, + const uint8_t *plaintext, size_t plaintextlen, + const uint8_t *key, size_t keylen, const uint8_t *nonce, + size_t noncelen, const uint8_t *ad, size_t adlen, + void *user_data) { + auto c = static_cast(user_data); + + auto nwrite = c->quic_in_encrypt(dest, destlen, plaintext, plaintextlen, key, + keylen, nonce, noncelen, ad, adlen); + if (nwrite < 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return nwrite; +} +} // namespace + +int Client::quic_in_encrypt(uint8_t *dest, size_t destlen, + const uint8_t *plaintext, size_t plaintextlen, + const uint8_t *key, size_t keylen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *ad, size_t adlen) { + return quic::encrypt(dest, destlen, plaintext, plaintextlen, key, keylen, + nonce, noncelen, ad, adlen, EVP_aes_128_gcm()); +} + +namespace { +ssize_t in_decrypt(ngtcp2_conn *conn, uint8_t *dest, size_t destlen, + const uint8_t *ciphertext, size_t ciphertextlen, + const uint8_t *key, size_t keylen, const uint8_t *nonce, + size_t noncelen, const uint8_t *ad, size_t adlen, + void *user_data) { + auto c = static_cast(user_data); + + auto nwrite = c->quic_in_decrypt(dest, destlen, ciphertext, ciphertextlen, + key, keylen, nonce, noncelen, ad, adlen); + if (nwrite < 0) { + return NGTCP2_ERR_TLS_DECRYPT; + } + + return nwrite; +} +} // namespace + +int Client::quic_in_decrypt(uint8_t *dest, size_t destlen, + const uint8_t *ciphertext, size_t ciphertextlen, + const uint8_t *key, size_t keylen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *ad, size_t adlen) { + return quic::decrypt(dest, destlen, ciphertext, ciphertextlen, key, keylen, + nonce, noncelen, ad, adlen, EVP_aes_128_gcm()); +} + +namespace { +ssize_t encrypt(ngtcp2_conn *conn, uint8_t *dest, size_t destlen, + const uint8_t *plaintext, size_t plaintextlen, + const uint8_t *key, size_t keylen, const uint8_t *nonce, + size_t noncelen, const uint8_t *ad, size_t adlen, + void *user_data) { + auto c = static_cast(user_data); + + auto nwrite = c->quic_encrypt(dest, destlen, plaintext, plaintextlen, key, + keylen, nonce, noncelen, ad, adlen); + if (nwrite < 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return nwrite; +} +} // namespace + +int Client::quic_encrypt(uint8_t *dest, size_t destlen, + const uint8_t *plaintext, size_t plaintextlen, + const uint8_t *key, size_t keylen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *ad, size_t adlen) { + return quic::encrypt(dest, destlen, plaintext, plaintextlen, key, keylen, + nonce, noncelen, ad, adlen, quic::aead(ssl)); +} + +namespace { +ssize_t decrypt(ngtcp2_conn *conn, uint8_t *dest, size_t destlen, + const uint8_t *ciphertext, size_t ciphertextlen, + const uint8_t *key, size_t keylen, const uint8_t *nonce, + size_t noncelen, const uint8_t *ad, size_t adlen, + void *user_data) { + auto c = static_cast(user_data); + + auto nwrite = c->quic_decrypt(dest, destlen, ciphertext, ciphertextlen, key, + keylen, nonce, noncelen, ad, adlen); + if (nwrite < 0) { + return NGTCP2_ERR_TLS_DECRYPT; + } + + return nwrite; +} +} // namespace + +int Client::quic_decrypt(uint8_t *dest, size_t destlen, + const uint8_t *ciphertext, size_t ciphertextlen, + const uint8_t *key, size_t keylen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *ad, size_t adlen) { + return quic::decrypt(dest, destlen, ciphertext, ciphertextlen, key, keylen, + nonce, noncelen, ad, adlen, quic::aead(ssl)); +} + +namespace { +ssize_t in_hp_mask(ngtcp2_conn *conn, uint8_t *dest, size_t destlen, + const uint8_t *key, size_t keylen, const uint8_t *sample, + size_t samplelen, void *user_data) { + auto c = static_cast(user_data); + + auto nwrite = + c->quic_in_hp_mask(dest, destlen, key, keylen, sample, samplelen); + if (nwrite < 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return nwrite; +} +} // namespace + +int Client::quic_in_hp_mask(uint8_t *dest, size_t destlen, const uint8_t *key, + size_t keylen, const uint8_t *sample, + size_t samplelen) { + return quic::hp_mask(dest, destlen, key, keylen, sample, samplelen, + EVP_aes_128_ctr()); +} + +namespace { +ssize_t hp_mask(ngtcp2_conn *conn, uint8_t *dest, size_t destlen, + const uint8_t *key, size_t keylen, const uint8_t *sample, + size_t samplelen, void *user_data) { + auto c = static_cast(user_data); + + auto nwrite = c->quic_hp_mask(dest, destlen, key, keylen, sample, samplelen); + if (nwrite < 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return nwrite; +} +} // namespace + +int Client::quic_hp_mask(uint8_t *dest, size_t destlen, const uint8_t *key, + size_t keylen, const uint8_t *sample, + size_t samplelen) { + return quic::hp_mask(dest, destlen, key, keylen, sample, samplelen, + quic::hp(ssl)); +} + +namespace { +int recv_stream_data(ngtcp2_conn *conn, int64_t stream_id, int fin, + uint64_t offset, const uint8_t *data, size_t datalen, + void *user_data, void *stream_user_data) { + auto c = static_cast(user_data); + if (c->quic_recv_stream_data(stream_id, fin, data, datalen) != 0) { + // TODO Better to do this gracefully rather than + // NGTCP2_ERR_CALLBACK_FAILURE. Perhaps, call + // ngtcp2_conn_write_application_close() ? + return -1; + } + return 0; +} +} // namespace + +int Client::quic_recv_stream_data(int64_t stream_id, int fin, + const uint8_t *data, size_t datalen) { + auto s = static_cast(session.get()); + auto nconsumed = s->read_stream(stream_id, data, datalen, fin); + if (nconsumed == -1) { + return -1; + } + + ngtcp2_conn_extend_max_stream_offset(quic.conn, stream_id, nconsumed); + ngtcp2_conn_extend_max_offset(quic.conn, nconsumed); + + return 0; +} + +namespace { +int stream_close(ngtcp2_conn *conn, int64_t stream_id, uint16_t app_error_code, + void *user_data, void *stream_user_data) { + auto c = static_cast(user_data); + if (c->quic_stream_close(stream_id, app_error_code) != 0) { + return -1; + } + return 0; +} +} // namespace + +int Client::quic_stream_close(int64_t stream_id, uint16_t app_error_code) { + auto s = static_cast(session.get()); + if (s->close_stream(stream_id, app_error_code) != 0) { + return -1; + } + return 0; +} + +namespace { +int stream_reset(ngtcp2_conn *conn, int64_t stream_id, uint64_t final_size, + uint16_t app_error_code, void *user_data, + void *stream_user_data) { + auto c = static_cast(user_data); + if (c->quic_stream_reset(stream_id, app_error_code) != 0) { + return -1; + } + return 0; +} +} // namespace + +int Client::quic_stream_reset(int64_t stream_id, uint16_t app_error_code) { + auto s = static_cast(session.get()); + if (s->reset_stream(stream_id) != 0) { + return -1; + } + return 0; +} + +namespace { +int extend_max_local_streams_bidi(ngtcp2_conn *conn, uint64_t max_streams, + void *user_data) { + auto c = static_cast(user_data); + + if (c->quic_extend_max_local_streams() != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Client::quic_extend_max_local_streams() { + auto s = static_cast(session.get()); + if (s->extend_max_local_streams() != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + return 0; +} + +namespace { +int get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, uint8_t *token, + size_t cidlen, void *user_data) { + auto dis = std::uniform_int_distribution( + 0, std::numeric_limits::max()); + auto f = [&dis]() { return dis(randgen); }; + + std::generate_n(cid->data, cidlen, f); + cid->datalen = cidlen; + std::generate_n(token, NGTCP2_STATELESS_RESET_TOKENLEN, f); + + return 0; +} +} // namespace + +namespace { +void debug_log_printf(void *user_data, const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); +} +} // namespace + +namespace { +void generate_cid(ngtcp2_cid &dest) { + auto dis = std::uniform_int_distribution( + 0, std::numeric_limits::max()); + dest.datalen = 8; + std::generate_n(dest.data, dest.datalen, [&dis]() { return dis(randgen); }); +} +} // namespace + +namespace { +ngtcp2_tstamp timestamp(struct ev_loop *loop) { + return ev_now(loop) * NGTCP2_SECONDS; +} +} // namespace + +namespace { +int bio_write(BIO *b, const char *buf, int len) { + assert(0); + return -1; +} +} // namespace + +namespace { +int bio_read(BIO *b, char *buf, int len) { + BIO_clear_retry_flags(b); + + auto c = static_cast(BIO_get_data(b)); + + len = c->quic_read_server_handshake(reinterpret_cast(buf), len); + if (len == 0) { + BIO_set_retry_read(b); + return -1; + } + + return len; +} +} // namespace + +namespace { +int bio_puts(BIO *b, const char *str) { return bio_write(b, str, strlen(str)); } +} // namespace + +namespace { +int bio_gets(BIO *b, char *buf, int len) { return -1; } +} // namespace + +namespace { +long bio_ctrl(BIO *b, int cmd, long num, void *ptr) { + switch (cmd) { + case BIO_CTRL_FLUSH: + return 1; + } + + return 0; +} +} // namespace + +namespace { +int bio_create(BIO *b) { + BIO_set_init(b, 1); + return 1; +} +} // namespace + +namespace { +int bio_destroy(BIO *b) { + if (b == nullptr) { + return 0; + } + + return 1; +} +} // namespace + +namespace { +BIO_METHOD *create_bio_method() { + static auto meth = BIO_meth_new(BIO_TYPE_FD, "bio"); + BIO_meth_set_write(meth, bio_write); + BIO_meth_set_read(meth, bio_read); + BIO_meth_set_puts(meth, bio_puts); + BIO_meth_set_gets(meth, bio_gets); + BIO_meth_set_ctrl(meth, bio_ctrl); + BIO_meth_set_create(meth, bio_create); + BIO_meth_set_destroy(meth, bio_destroy); + return meth; +} +} // namespace + +namespace { +int key_cb(SSL *ssl, int name, const unsigned char *secret, size_t secretlen, + void *arg) { + auto c = static_cast(arg); + + if (c->quic_on_key(name, secret, secretlen) != 0) { + return 0; + } + + return 1; +} +} // namespace + +namespace { +void msg_cb(int write_p, int version, int content_type, const void *buf, + size_t len, SSL *ssl, void *arg) { + if (!write_p) { + return; + } + + auto c = static_cast(arg); + auto msg = reinterpret_cast(buf); + + switch (content_type) { + case SSL3_RT_HANDSHAKE: + break; + case SSL3_RT_ALERT: + assert(len == 2); + if (msg[0] != 2 /* FATAL */) { + return; + } + c->quic_set_tls_alert(msg[1]); + return; + default: + return; + } + + c->quic_write_client_handshake(reinterpret_cast(buf), len); +} +} // namespace + +int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, + const sockaddr *remote_addr, socklen_t remote_addrlen) { + int rv; + + if (!ssl) { + ssl = SSL_new(worker->ssl_ctx); + + auto bio = BIO_new(create_bio_method()); + BIO_set_data(bio, this); + + SSL_set_bio(ssl, bio, bio); + SSL_set_app_data(ssl, this); + SSL_set_connect_state(ssl); + SSL_set_msg_callback(ssl, msg_cb); + SSL_set_msg_callback_arg(ssl, this); + SSL_set_key_callback(ssl, key_cb, this); + } + + switch (remote_addr->sa_family) { + case AF_INET: + quic.max_pktlen = NGTCP2_MAX_PKTLEN_IPV4; + break; + case AF_INET6: + quic.max_pktlen = NGTCP2_MAX_PKTLEN_IPV6; + break; + default: + return -1; + } + + auto callbacks = ngtcp2_conn_callbacks{ + h2load::client_initial, + nullptr, // recv_client_initial + h2load::recv_crypto_data, + h2load::handshake_completed, + nullptr, // recv_version_negotiation + h2load::in_encrypt, + h2load::in_decrypt, + h2load::encrypt, + h2load::decrypt, + h2load::in_hp_mask, + h2load::hp_mask, + h2load::recv_stream_data, + nullptr, // acked_crypto_offset + nullptr, // acked_stream_data_offset + nullptr, // stream_open + h2load::stream_close, + nullptr, // recv_stateless_reset + nullptr, // recv_retry + h2load::extend_max_local_streams_bidi, + nullptr, // extend_max_local_streams_uni + nullptr, // rand + get_new_connection_id, + nullptr, // remove_connection_id + nullptr, // update_key + nullptr, // path_validation + nullptr, // select_preferred_address + h2load::stream_reset, + nullptr, // extend_max_remote_streams_bidi + nullptr, // extend_max_remote_streams_uni + nullptr, // extend_max_stream_data + }; + + ngtcp2_cid scid, dcid; + generate_cid(scid); + generate_cid(dcid); + + ngtcp2_settings settings; + ngtcp2_settings_default(&settings); + settings.log_printf = debug_log_printf; + settings.log_printf = nullptr; + settings.initial_ts = timestamp(worker->loop); + settings.max_stream_data_bidi_local = 256_k; + settings.max_stream_data_bidi_remote = 256_k; + settings.max_stream_data_uni = 256_k; + settings.max_data = 1_m; + settings.max_streams_bidi = 1; + settings.max_streams_uni = 100; + settings.idle_timeout = 30 * NGTCP2_SECONDS; + + auto path = ngtcp2_path{ + {local_addrlen, + const_cast(reinterpret_cast(local_addr))}, + {remote_addrlen, + const_cast(reinterpret_cast(remote_addr))}, + }; + + rv = ngtcp2_conn_client_new(&quic.conn, &dcid, &scid, &path, NGTCP2_PROTO_VER, + &callbacks, &settings, nullptr, this); + if (rv != 0) { + return -1; + } + + rv = quic_setup_initial_crypto(); + if (rv != 0) { + ngtcp2_conn_del(quic.conn); + quic.conn = nullptr; + return -1; + } + + return 0; +} + +void Client::quic_free() { ngtcp2_conn_del(quic.conn); } + +void Client::quic_close_connection() { + if (!quic.conn) { + return; + } + + std::array buf; + ssize_t nwrite; + ngtcp2_path_storage ps; + ngtcp2_path_storage_zero(&ps); + + switch (quic.last_error.type) { + case quic::ErrorType::TransportVersionNegotiation: + return; + case quic::ErrorType::Transport: + nwrite = ngtcp2_conn_write_connection_close( + quic.conn, &ps.path, buf.data(), quic.max_pktlen, quic.last_error.code, + timestamp(worker->loop)); + break; + case quic::ErrorType::Application: + nwrite = ngtcp2_conn_write_application_close( + quic.conn, &ps.path, buf.data(), quic.max_pktlen, quic.last_error.code, + timestamp(worker->loop)); + break; + default: + assert(0); + } + + if (nwrite < 0) { + return; + } + + write_udp(reinterpret_cast(ps.path.remote.addr), + ps.path.remote.addrlen, buf.data(), nwrite); +} + +int Client::quic_setup_initial_crypto() { + int rv; + + std::array initial_secret, secret; + auto dcid = ngtcp2_conn_get_dcid(quic.conn); + rv = quic::derive_initial_secret( + initial_secret.data(), initial_secret.size(), dcid->data, dcid->datalen, + reinterpret_cast(NGTCP2_INITIAL_SALT), + str_size(NGTCP2_INITIAL_SALT)); + if (rv != 0) { + std::cerr << "quic::derive_initial_secret() failed" << std::endl; + return -1; + } + + auto aead = EVP_aes_128_gcm(); + auto prf = EVP_sha256(); + + rv = quic::derive_client_initial_secret(secret.data(), secret.size(), + initial_secret.data(), + initial_secret.size()); + if (rv != 0) { + std::cerr << "quic::derive_client_initial_secret() failed" << std::endl; + return -1; + } + + std::array key, iv, hp; + auto keylen = key.size(); + auto ivlen = iv.size(); + rv = quic::derive_packet_protection_key(key.data(), keylen, iv.data(), ivlen, + secret.data(), secret.size(), aead, + prf); + if (rv != 0) { + return -1; + } + + auto hplen = hp.size(); + rv = quic::derive_header_protection_key(hp.data(), hplen, secret.data(), + secret.size(), aead, prf); + if (rv != 0) { + return -1; + } + + ngtcp2_conn_install_initial_tx_keys(quic.conn, key.data(), keylen, iv.data(), + ivlen, hp.data(), hplen); + + rv = quic::derive_server_initial_secret(secret.data(), secret.size(), + initial_secret.data(), + initial_secret.size()); + if (rv != 0) { + std::cerr << "quic::derive_server_initial_secret() failed" << std::endl; + return -1; + } + + keylen = key.size(); + ivlen = iv.size(); + rv = quic::derive_packet_protection_key(key.data(), keylen, iv.data(), ivlen, + secret.data(), secret.size(), aead, + prf); + if (rv != 0) { + return -1; + } + + hplen = hp.size(); + rv = quic::derive_header_protection_key(hp.data(), hplen, secret.data(), + secret.size(), aead, prf); + if (rv != 0) { + return -1; + } + + ngtcp2_conn_install_initial_rx_keys(quic.conn, key.data(), keylen, iv.data(), + ivlen, hp.data(), hplen); + + return 0; +} + +int Client::quic_on_key(int name, const uint8_t *secret, size_t secretlen) { + int rv; + + switch (name) { + case SSL_KEY_CLIENT_EARLY_TRAFFIC: + case SSL_KEY_CLIENT_HANDSHAKE_TRAFFIC: + case SSL_KEY_SERVER_HANDSHAKE_TRAFFIC: + case SSL_KEY_CLIENT_APPLICATION_TRAFFIC: + case SSL_KEY_SERVER_APPLICATION_TRAFFIC: + break; + default: + return 0; + } + + auto aead = quic::aead(ssl); + auto prf = quic::prf(ssl); + + std::array key, iv, hp; + auto keylen = key.size(); + auto ivlen = iv.size(); + rv = quic::derive_packet_protection_key(key.data(), keylen, iv.data(), ivlen, + secret, secretlen, aead, prf); + if (rv != 0) { + return -1; + } + + auto hplen = hp.size(); + rv = quic::derive_header_protection_key(hp.data(), hplen, secret, secretlen, + aead, prf); + if (rv != 0) { + return -1; + } + + // TODO Just call this once. + ngtcp2_conn_set_aead_overhead(quic.conn, quic::aead_max_overhead(aead)); + + switch (name) { + case SSL_KEY_CLIENT_EARLY_TRAFFIC: + ngtcp2_conn_install_early_keys(quic.conn, key.data(), keylen, iv.data(), + ivlen, hp.data(), hplen); + break; + case SSL_KEY_CLIENT_HANDSHAKE_TRAFFIC: + ngtcp2_conn_install_handshake_tx_keys(quic.conn, key.data(), keylen, + iv.data(), ivlen, hp.data(), hplen); + quic.tx_crypto_level = NGTCP2_CRYPTO_LEVEL_HANDSHAKE; + break; + case SSL_KEY_CLIENT_APPLICATION_TRAFFIC: + ngtcp2_conn_install_tx_keys(quic.conn, key.data(), keylen, iv.data(), ivlen, + hp.data(), hplen); + break; + case SSL_KEY_SERVER_HANDSHAKE_TRAFFIC: + ngtcp2_conn_install_handshake_rx_keys(quic.conn, key.data(), keylen, + iv.data(), ivlen, hp.data(), hplen); + quic.rx_crypto_level = NGTCP2_CRYPTO_LEVEL_HANDSHAKE; + break; + case SSL_KEY_SERVER_APPLICATION_TRAFFIC: + ngtcp2_conn_install_rx_keys(quic.conn, key.data(), keylen, iv.data(), ivlen, + hp.data(), hplen); + quic.rx_crypto_level = NGTCP2_CRYPTO_LEVEL_APP; + break; + } + + return 0; +} + +int Client::quic_tls_handshake(bool initial) { + ERR_clear_error(); + + int rv; + + rv = SSL_do_handshake(ssl); + if (rv <= 0) { + auto err = SSL_get_error(ssl, rv); + switch (err) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + return 0; + case SSL_ERROR_SSL: + std::cerr << "TLS handshake error: " + << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + return -1; + default: + std::cerr << "TLS handshake error: " << err << std::endl; + return -1; + } + } + + ngtcp2_conn_handshake_completed(quic.conn); + + if (quic_read_tls() != 0) { + return -1; + } + + return 0; +} + +int Client::quic_read_tls() { + ERR_clear_error(); + + std::array buf; + size_t nread; + + for (;;) { + auto rv = SSL_read_ex(ssl, buf.data(), buf.size(), &nread); + if (rv == 1) { + continue; + } + auto err = SSL_get_error(ssl, 0); + switch (err) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + return 0; + case SSL_ERROR_SSL: + case SSL_ERROR_ZERO_RETURN: + std::cerr << "TLS read error: " + << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + return NGTCP2_ERR_CRYPTO; + default: + std::cerr << "TLS read error: " << err << std::endl; + return NGTCP2_ERR_CRYPTO; + } + } +} + +void Client::quic_set_tls_alert(uint8_t alert) { + quic.last_error = quic::err_transport_tls(alert); +} + +size_t Client::quic_read_server_handshake(uint8_t *buf, size_t buflen) { + auto n = std::min(buflen, + quic.server_handshake.size() - quic.server_handshake_nread); + std::copy_n(std::begin(quic.server_handshake) + quic.server_handshake_nread, + n, buf); + quic.server_handshake_nread += n; + return n; +} + +int Client::quic_write_server_handshake(ngtcp2_crypto_level crypto_level, + const uint8_t *data, size_t datalen) { + if (quic.rx_crypto_level != crypto_level) { + std::cerr << "Got crypto level " + << ", want " << quic.rx_crypto_level << std::endl; + return -1; + } + std::copy_n(data, datalen, std::back_inserter(quic.server_handshake)); + return 0; +} + +void Client::quic_write_client_handshake(const uint8_t *data, size_t datalen) { + assert(quic.tx_crypto_level < 2); + quic_write_client_handshake(quic.crypto[quic.tx_crypto_level], data, datalen); +} + +void Client::quic_write_client_handshake(Crypto &crypto, const uint8_t *data, + size_t datalen) { + assert(crypto.data.size() >= crypto.datalen + datalen); + + auto p = std::begin(crypto.data) + crypto.datalen; + std::copy_n(data, datalen, p); + crypto.datalen += datalen; + + ngtcp2_conn_submit_crypto_data(quic.conn, quic.tx_crypto_level, p, datalen); +} + +void quic_pkt_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) { + auto c = static_cast(w->data); + + if (c->quic_pkt_timeout() != 0) { + c->fail(); + c->worker->free_client(c); + delete c; + return; + } +} + +int Client::quic_pkt_timeout() { + int rv; + auto now = timestamp(worker->loop); + auto should_write = false; + + if (ngtcp2_conn_loss_detection_expiry(quic.conn) <= now) { + rv = ngtcp2_conn_on_loss_detection_timer(quic.conn, now); + if (rv != 0) { + quic.last_error = quic::err_transport(NGTCP2_ERR_INTERNAL); + return -1; + } + should_write = true; + } + if (ngtcp2_conn_ack_delay_expiry(quic.conn) <= now) { + ngtcp2_conn_cancel_expired_ack_delay_timer(quic.conn, now); + should_write = true; + } + if (should_write) { + return write_quic(); + } + return 0; +} + +void Client::quic_restart_pkt_timer() { + auto expiry = ngtcp2_conn_get_expiry(quic.conn); + auto now = timestamp(worker->loop); + auto t = expiry < now ? 1e-9 + : static_cast(expiry - now) / NGTCP2_SECONDS; + quic.pkt_timer.repeat = t; + ev_timer_again(worker->loop, &quic.pkt_timer); +} + +int Client::read_quic() { + std::array buf; + sockaddr_union su; + socklen_t addrlen = sizeof(su); + int rv; + + auto nread = + recvfrom(fd, buf.data(), buf.size(), MSG_DONTWAIT, &su.sa, &addrlen); + if (nread == -1) { + return 0; + } + + assert(quic.conn); + + auto path = ngtcp2_path{ + {local_addr.len, reinterpret_cast(&local_addr.su.sa)}, + {addrlen, reinterpret_cast(&su.sa)}, + }; + + rv = ngtcp2_conn_read_pkt(quic.conn, &path, buf.data(), nread, + timestamp(worker->loop)); + if (rv != 0) { + std::cerr << "ngtcp2_conn_read_pkt: " << ngtcp2_strerror(rv) << std::endl; + return -1; + } + + if (worker->current_phase == Phase::MAIN_DURATION) { + worker->stats.bytes_total += nread; + } + + return 0; +} + +int Client::write_quic() { + if (quic.close_requested) { + return -1; + } + + auto s = static_cast(session.get()); + + std::array vec; + std::array buf; + ngtcp2_path_storage ps; + + ngtcp2_path_storage_zero(&ps); + + for (;;) { + int64_t stream_id; + int fin; + ssize_t sveccnt = 0; + + if (s && ngtcp2_conn_get_max_data_left(quic.conn)) { + sveccnt = s->write_stream(stream_id, fin, vec.data(), vec.size()); + if (sveccnt == -1) { + return -1; + } + } + + ssize_t ndatalen; + if (sveccnt == 0) { + auto nwrite = + ngtcp2_conn_write_pkt(quic.conn, &ps.path, buf.data(), + quic.max_pktlen, timestamp(worker->loop)); + if (nwrite < 0) { + quic.last_error = quic::err_transport(nwrite); + return -1; + } + + quic_restart_pkt_timer(); + + if (nwrite == 0) { + ev_io_stop(worker->loop, &wev); + return 0; + } + + write_udp(reinterpret_cast(ps.path.remote.addr), + ps.path.remote.addrlen, buf.data(), nwrite); + + ev_io_start(worker->loop, &wev); + + return 0; + } + + auto v = vec.data(); + auto vcnt = static_cast(sveccnt); + for (;;) { + auto nwrite = ngtcp2_conn_writev_stream( + quic.conn, &ps.path, buf.data(), quic.max_pktlen, &ndatalen, + NGTCP2_WRITE_STREAM_FLAG_MORE, stream_id, fin, + reinterpret_cast(v), vcnt, + timestamp(worker->loop)); + if (nwrite < 0) { + auto should_break = false; + switch (nwrite) { + case NGTCP2_ERR_STREAM_DATA_BLOCKED: + if (ngtcp2_conn_get_max_data_left(quic.conn) == 0) { + return 0; + } + + if (s->block_stream(stream_id) != 0) { + return -1; + } + should_break = true; + break; + case NGTCP2_ERR_EARLY_DATA_REJECTED: + case NGTCP2_ERR_STREAM_SHUT_WR: + case NGTCP2_ERR_STREAM_NOT_FOUND: // This means that stream is + // closed. + assert(0); + // TODO Perhaps, close stream or this should not happen? + break; + case NGTCP2_ERR_WRITE_STREAM_MORE: + assert(ndatalen > 0); + if (s->add_write_offset(stream_id, ndatalen) != 0) { + return -1; + } + should_break = true; + break; + } + + if (should_break) { + break; + } + + quic.last_error = quic::err_transport(nwrite); + return -1; + } + + quic_restart_pkt_timer(); + + if (nwrite == 0) { + ev_io_stop(worker->loop, &wev); + return 0; + } + + if (ndatalen > 0) { + if (s->add_write_offset(stream_id, ndatalen) != 0) { + return -1; + } + } + + write_udp(reinterpret_cast(ps.path.remote.addr), + ps.path.remote.addrlen, buf.data(), nwrite); + + ev_io_start(worker->loop, &wev); + + return 0; + } + } +} + +} // namespace h2load diff --git a/src/h2load_quic.h b/src/h2load_quic.h new file mode 100644 index 00000000..225f00fb --- /dev/null +++ b/src/h2load_quic.h @@ -0,0 +1,38 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2019 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef H2LOAD_QUIC_H +#define H2LOAD_QUIC_H + +#include "nghttp2_config.h" + +#include + +#include "h2load.h" + +namespace h2load { +void quic_pkt_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents); +} // namespace h2load + +#endif // H2LOAD_QUIC_H diff --git a/src/quic.cc b/src/quic.cc new file mode 100644 index 00000000..c9ddd664 --- /dev/null +++ b/src/quic.cc @@ -0,0 +1,422 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2019 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "quic.h" + +#include + +#include + +#include +#include + +#include "template.h" + +using namespace nghttp2; + +namespace quic { + +const EVP_CIPHER *aead(SSL *ssl) { + switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) { + case 0x03001301u: // TLS_AES_128_GCM_SHA256 + return EVP_aes_128_gcm(); + case 0x03001302u: // TLS_AES_256_GCM_SHA384 + return EVP_aes_256_gcm(); + case 0x03001303u: // TLS_CHACHA20_POLY1305_SHA256 + return EVP_chacha20_poly1305(); + default: + assert(0); + } +} + +const EVP_CIPHER *hp(SSL *ssl) { + switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) { + case 0x03001301u: // TLS_AES_128_GCM_SHA256 + return EVP_aes_128_ctr(); + case 0x03001302u: // TLS_AES_256_GCM_SHA384 + return EVP_aes_256_ctr(); + case 0x03001303u: // TLS_CHACHA20_POLY1305_SHA256 + return EVP_chacha20(); + default: + assert(0); + } +} + +const EVP_MD *prf(SSL *ssl) { + switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) { + case 0x03001301u: // TLS_AES_128_GCM_SHA256 + case 0x03001303u: // TLS_CHACHA20_POLY1305_SHA256 + return EVP_sha256(); + case 0x03001302u: // TLS_AES_256_GCM_SHA384 + return EVP_sha384(); + default: + assert(0); + } +} + +namespace { +size_t aead_key_length(const EVP_CIPHER *aead) { + return EVP_CIPHER_key_length(aead); +} +} // namespace + +namespace { +size_t aead_nonce_length(const EVP_CIPHER *aead) { + return EVP_CIPHER_iv_length(aead); +} +} // namespace + +namespace { +size_t aead_tag_length(const EVP_CIPHER *aead) { + if (aead == EVP_aes_128_gcm() || aead == EVP_aes_256_gcm()) { + return EVP_GCM_TLS_TAG_LEN; + } + if (aead == EVP_chacha20_poly1305()) { + return EVP_CHACHAPOLY_TLS_TAG_LEN; + } + assert(0); +} +} // namespace + +size_t aead_max_overhead(const EVP_CIPHER *aead) { + return aead_tag_length(aead); +} + +int hkdf_extract(uint8_t *dest, size_t destlen, const uint8_t *secret, + size_t secretlen, const uint8_t *salt, size_t saltlen, + const EVP_MD *prf) { + auto pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, nullptr); + if (pctx == nullptr) { + return -1; + } + + auto pctx_d = defer(EVP_PKEY_CTX_free, pctx); + + if (EVP_PKEY_derive_init(pctx) != 1 || + EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY) != 1 || + EVP_PKEY_CTX_set_hkdf_md(pctx, prf) != 1 || + EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, saltlen) != 1 || + EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, secretlen) != 1 || + EVP_PKEY_derive(pctx, dest, &destlen) != 1) { + return -1; + } + + return 0; +} + +int hkdf_expand(uint8_t *dest, size_t destlen, const uint8_t *secret, + size_t secretlen, const uint8_t *info, size_t infolen, + const EVP_MD *prf) { + auto pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, nullptr); + if (pctx == nullptr) { + return -1; + } + + auto pctx_d = defer(EVP_PKEY_CTX_free, pctx); + + if (EVP_PKEY_derive_init(pctx) != 1 || + EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) != 1 || + EVP_PKEY_CTX_set_hkdf_md(pctx, prf) != 1 || + EVP_PKEY_CTX_set1_hkdf_salt(pctx, "", 0) != 1 || + EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, secretlen) != 1 || + EVP_PKEY_CTX_add1_hkdf_info(pctx, info, infolen) != 1 || + EVP_PKEY_derive(pctx, dest, &destlen) != 1) { + return -1; + } + + return 0; +} + +int hkdf_expand_label(uint8_t *dest, size_t destlen, const uint8_t *secret, + size_t secretlen, const uint8_t *label, size_t labellen, + const EVP_MD *prf) { + std::array info; + static constexpr const uint8_t LABEL[] = "tls13 "; + + auto p = std::begin(info); + *p++ = destlen / 256; + *p++ = destlen % 256; + *p++ = str_size(LABEL) + labellen; + p = std::copy_n(LABEL, str_size(LABEL), p); + p = std::copy_n(label, labellen, p); + *p++ = 0; + + return hkdf_expand(dest, destlen, secret, secretlen, info.data(), + p - std::begin(info), prf); +} + +int derive_initial_secret(uint8_t *dest, size_t destlen, const uint8_t *secret, + size_t secretlen, const uint8_t *salt, + size_t saltlen) { + return hkdf_extract(dest, destlen, secret, secretlen, salt, saltlen, + EVP_sha256()); +} + +int derive_client_initial_secret(uint8_t *dest, size_t destlen, + const uint8_t *secret, size_t secretlen) { + static constexpr uint8_t LABEL[] = "client in"; + return hkdf_expand_label(dest, destlen, secret, secretlen, LABEL, + str_size(LABEL), EVP_sha256()); +} + +int derive_server_initial_secret(uint8_t *dest, size_t destlen, + const uint8_t *secret, size_t secretlen) { + static constexpr uint8_t LABEL[] = "server in"; + return hkdf_expand_label(dest, destlen, secret, secretlen, LABEL, + str_size(LABEL), EVP_sha256()); +} + +int derive_packet_protection_key(uint8_t *key, size_t &keylen, uint8_t *iv, + size_t &ivlen, const uint8_t *secret, + size_t secretlen, const EVP_CIPHER *aead, + const EVP_MD *prf) { + int rv; + static constexpr uint8_t KEY_LABEL[] = "quic key"; + static constexpr uint8_t IV_LABEL[] = "quic iv"; + + auto req_keylen = aead_key_length(aead); + if (req_keylen > keylen) { + return -1; + } + + keylen = req_keylen; + rv = hkdf_expand_label(key, keylen, secret, secretlen, KEY_LABEL, + str_size(KEY_LABEL), prf); + if (rv != 0) { + return -1; + } + + auto req_ivlen = std::max(static_cast(8), aead_nonce_length(aead)); + if (req_ivlen > ivlen) { + return -1; + } + + ivlen = req_ivlen; + rv = hkdf_expand_label(iv, ivlen, secret, secretlen, IV_LABEL, + str_size(IV_LABEL), prf); + if (rv != 0) { + return -1; + } + + return 0; +} + +int derive_header_protection_key(uint8_t *key, size_t &keylen, + const uint8_t *secret, size_t secretlen, + const EVP_CIPHER *aead, const EVP_MD *prf) { + int rv; + static constexpr uint8_t LABEL[] = "quic hp"; + + auto req_keylen = aead_key_length(aead); + if (req_keylen > keylen) { + return -1; + } + + keylen = req_keylen; + rv = hkdf_expand_label(key, keylen, secret, secretlen, LABEL, str_size(LABEL), + prf); + if (rv != 0) { + return -1; + } + + return 0; +} + +ssize_t encrypt(uint8_t *dest, size_t destlen, const uint8_t *plaintext, + size_t plaintextlen, const uint8_t *key, size_t keylen, + const uint8_t *nonce, size_t noncelen, const uint8_t *ad, + size_t adlen, const EVP_CIPHER *aead) { + auto taglen = aead_tag_length(aead); + + if (destlen < plaintextlen + taglen) { + return -1; + } + + auto actx = EVP_CIPHER_CTX_new(); + if (actx == nullptr) { + return -1; + } + + auto actx_d = defer(EVP_CIPHER_CTX_free, actx); + + if (EVP_EncryptInit_ex(actx, aead, nullptr, nullptr, nullptr) != 1) { + return -1; + } + + if (EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_IVLEN, noncelen, nullptr) != + 1) { + return -1; + } + + if (EVP_EncryptInit_ex(actx, nullptr, nullptr, key, nonce) != 1) { + return -1; + } + + size_t outlen = 0; + int len; + + if (EVP_EncryptUpdate(actx, nullptr, &len, ad, adlen) != 1) { + return -1; + } + + if (EVP_EncryptUpdate(actx, dest, &len, plaintext, plaintextlen) != 1) { + return -1; + } + + outlen = len; + + if (EVP_EncryptFinal_ex(actx, dest + outlen, &len) != 1) { + return -1; + } + + outlen += len; + + assert(outlen + taglen <= destlen); + + if (EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_GET_TAG, taglen, dest + outlen) != + 1) { + return -1; + } + + outlen += taglen; + + return outlen; +} + +ssize_t decrypt(uint8_t *dest, size_t destlen, const uint8_t *ciphertext, + size_t ciphertextlen, const uint8_t *key, size_t keylen, + const uint8_t *nonce, size_t noncelen, const uint8_t *ad, + size_t adlen, const EVP_CIPHER *aead) { + auto taglen = aead_tag_length(aead); + + if (taglen > ciphertextlen || destlen + taglen < ciphertextlen) { + return -1; + } + + ciphertextlen -= taglen; + auto tag = ciphertext + ciphertextlen; + + auto actx = EVP_CIPHER_CTX_new(); + if (actx == nullptr) { + return -1; + } + + auto actx_d = defer(EVP_CIPHER_CTX_free, actx); + + if (EVP_DecryptInit_ex(actx, aead, nullptr, nullptr, nullptr) != 1) { + return -1; + } + + if (EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_IVLEN, noncelen, nullptr) != + 1) { + return -1; + } + + if (EVP_DecryptInit_ex(actx, nullptr, nullptr, key, nonce) != 1) { + return -1; + } + + size_t outlen; + int len; + + if (EVP_DecryptUpdate(actx, nullptr, &len, ad, adlen) != 1) { + return -1; + } + + if (EVP_DecryptUpdate(actx, dest, &len, ciphertext, ciphertextlen) != 1) { + return -1; + } + + outlen = len; + + if (EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_TAG, taglen, + const_cast(tag)) != 1) { + return -1; + } + + if (EVP_DecryptFinal_ex(actx, dest + outlen, &len) != 1) { + return -1; + } + + outlen += len; + + return outlen; +} + +ssize_t hp_mask(uint8_t *dest, size_t destlen, const uint8_t *key, + size_t keylen, const uint8_t *sample, size_t samplelen, + const EVP_CIPHER *cipher) { + static constexpr uint8_t PLAINTEXT[] = "\x00\x00\x00\x00\x00"; + + auto actx = EVP_CIPHER_CTX_new(); + if (actx == nullptr) { + return -1; + } + + auto actx_d = defer(EVP_CIPHER_CTX_free, actx); + + if (EVP_EncryptInit_ex(actx, cipher, nullptr, key, sample) != 1) { + return -1; + } + + size_t outlen = 0; + int len; + + if (EVP_EncryptUpdate(actx, dest, &len, PLAINTEXT, str_size(PLAINTEXT)) != + 1) { + return -1; + } + + assert(len == 5); + + outlen = len; + + if (EVP_EncryptFinal_ex(actx, dest + outlen, &len) != 1) { + return -1; + } + + assert(len == 0); + + return outlen; +} + +Error err_transport(int liberr) { + if (liberr == NGTCP2_ERR_RECV_VERSION_NEGOTIATION) { + return {ErrorType::TransportVersionNegotiation, 0}; + } + return {ErrorType::Transport, + ngtcp2_err_infer_quic_transport_error_code(liberr)}; +} + +Error err_transport_tls(int alert) { + return {ErrorType::Transport, ngtcp2_err_infer_quic_transport_error_code( + NGTCP2_CRYPTO_ERROR | alert)}; +} + +Error err_application(int liberr) { + return {ErrorType::Application, + nghttp3_err_infer_quic_app_error_code(liberr)}; +} + +} // namespace quic diff --git a/src/quic.h b/src/quic.h new file mode 100644 index 00000000..4b4bb1d6 --- /dev/null +++ b/src/quic.h @@ -0,0 +1,104 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2019 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef QUIC_H +#define QUIC_H + +#include "nghttp2_config.h" + +#include + +namespace quic { + +const EVP_CIPHER *aead(SSL *ssl); +const EVP_CIPHER *hp(SSL *ssl); +const EVP_MD *prf(SSL *ssl); +size_t aead_max_overhead(const EVP_CIPHER *aead); + +int hkdf_extract(uint8_t *dest, size_t destlen, const uint8_t *secret, + size_t secretlen, const uint8_t *salt, size_t saltlen, + const EVP_MD *prf); + +int hkdf_expand(uint8_t *dest, size_t destlen, const uint8_t *secret, + size_t secretlen, const uint8_t *info, size_t infolen, + const EVP_MD *prf); + +int hkdf_expand_label(uint8_t *dest, size_t destlen, const uint8_t *secret, + size_t secretlen, const uint8_t *label, size_t labellen, + const EVP_MD *prf); + +int derive_initial_secret(uint8_t *dest, size_t destlen, const uint8_t *secret, + size_t secretlen, const uint8_t *salt, + size_t saltlen); + +int derive_client_initial_secret(uint8_t *dest, size_t destlen, + const uint8_t *secret, size_t secretlen); + +int derive_server_initial_secret(uint8_t *dest, size_t destlen, + const uint8_t *secret, size_t secretlen); + +int derive_packet_protection_key(uint8_t *key, size_t &keylen, uint8_t *iv, + size_t &ivlen, const uint8_t *secret, + size_t secretlen, const EVP_CIPHER *aead, + const EVP_MD *prf); + +int derive_header_protection_key(uint8_t *key, size_t &keylen, + const uint8_t *secret, size_t secretlen, + const EVP_CIPHER *aead, const EVP_MD *prf); + +ssize_t encrypt(uint8_t *dest, size_t destlen, const uint8_t *plaintext, + size_t plaintextlen, const uint8_t *key, size_t keylen, + const uint8_t *nonce, size_t noncelen, const uint8_t *ad, + size_t adlen, const EVP_CIPHER *aead); + +ssize_t decrypt(uint8_t *dest, size_t destlen, const uint8_t *ciphertext, + size_t ciphertextlen, const uint8_t *key, size_t keylen, + const uint8_t *nonce, size_t noncelen, const uint8_t *ad, + size_t adlen, const EVP_CIPHER *aead); + +ssize_t hp_mask(uint8_t *dest, size_t destlen, const uint8_t *key, + size_t keylen, const uint8_t *sample, size_t samplelen, + const EVP_CIPHER *cipher); + +enum class ErrorType { + Transport, + TransportVersionNegotiation, + Application, +}; + +struct Error { + Error(ErrorType type, uint16_t code) : type(type), code(code) {} + Error() : type(ErrorType::Transport), code(0) {} + + ErrorType type; + uint16_t code; +}; + +Error err_transport(int liberr); +Error err_transport_tls(int alert); +Error err_application(int liberr); + +} // namespace quic + +#endif // QUIC_H diff --git a/src/util.cc b/src/util.cc index 187fd3a8..d6abdb55 100644 --- a/src/util.cc +++ b/src/util.cc @@ -945,6 +945,56 @@ int create_nonblock_socket(int family) { return fd; } +int create_nonblock_udp_socket(int family) { +#ifdef SOCK_NONBLOCK + auto fd = socket(family, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); + + if (fd == -1) { + return -1; + } +#else // !SOCK_NONBLOCK + auto fd = socket(family, SOCK_STREAM, 0); + + if (fd == -1) { + return -1; + } + + make_socket_nonblocking(fd); + make_socket_closeonexec(fd); +#endif // !SOCK_NONBLOCK + + return fd; +} + +int bind_any_addr_udp(int fd, int family) { + addrinfo hints{}; + addrinfo *res, *rp; + int rv; + + hints.ai_family = family; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_PASSIVE; + + rv = getaddrinfo(nullptr, "0", &hints, &res); + if (rv != 0) { + return -1; + } + + for (rp = res; rp; rp = rp->ai_next) { + if (bind(fd, rp->ai_addr, rp->ai_addrlen) != -1) { + break; + } + } + + freeaddrinfo(res); + + if (!rp) { + return -1; + } + + return 0; +} + bool check_socket_connected(int fd) { int error; socklen_t len = sizeof(error); diff --git a/src/util.h b/src/util.h index 3443fe05..586d39c7 100644 --- a/src/util.h +++ b/src/util.h @@ -626,6 +626,9 @@ int make_socket_nonblocking(int fd); int make_socket_nodelay(int fd); int create_nonblock_socket(int family); +int create_nonblock_udp_socket(int family); + +int bind_any_addr_udp(int fd, int family); bool check_socket_connected(int fd); From 8ea78e83617756452729cf2aa43785034d2374a4 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Mon, 10 Jun 2019 21:33:46 +0900 Subject: [PATCH 002/124] Add build instruction --- README.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.rst b/README.rst index c5c2eaa7..e011947e 100644 --- a/README.rst +++ b/README.rst @@ -16,6 +16,21 @@ An experimental high level C++ library is also available. We have Python bindings of this library, but we do not have full code coverage yet. +h2load IETF QUIC +---------------- + +In order to build h2load with IETF QUIC, you have to build ngtcp2, +nghttp3 and my patched OpenSSL. +https://github.com/ngtcp2/ngtcp2#build-from-git describes how to build +these three software. + +To run h2load against IETF QUIC server, specify h3-20 ALPN with +``--npn-list`` option like so: + +.. code-block:: text + + $ h2load --npn-list h3-20 https://localhost:4433 + Development Status ------------------ From 470c43a986ef2caf44dc7c906d5cb7e3c36fa170 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Mon, 10 Jun 2019 21:36:13 +0900 Subject: [PATCH 003/124] Fix link --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index e011947e..fa95077a 100644 --- a/README.rst +++ b/README.rst @@ -21,8 +21,8 @@ h2load IETF QUIC In order to build h2load with IETF QUIC, you have to build ngtcp2, nghttp3 and my patched OpenSSL. -https://github.com/ngtcp2/ngtcp2#build-from-git describes how to build -these three software. +https://github.com/ngtcp2/ngtcp2/tree/draft-20#build-from-git +describes how to build these three software. To run h2load against IETF QUIC server, specify h3-20 ALPN with ``--npn-list`` option like so: From bb36df8b2e636f42138810818c539a0d58dfd438 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 15 Jun 2019 14:41:25 +0900 Subject: [PATCH 004/124] h2load: Fix possible deadlock --- src/h2load_quic.cc | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index a8158400..fe5c6b80 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -962,20 +962,46 @@ int Client::write_quic() { return -1; } - auto s = static_cast(session.get()); - std::array vec; std::array buf; ngtcp2_path_storage ps; ngtcp2_path_storage_zero(&ps); + if (!session) { + auto nwrite = + ngtcp2_conn_write_pkt(quic.conn, &ps.path, buf.data(), quic.max_pktlen, + timestamp(worker->loop)); + if (nwrite < 0) { + quic.last_error = quic::err_transport(nwrite); + return -1; + } + + quic_restart_pkt_timer(); + + if (nwrite) { + write_udp(reinterpret_cast(ps.path.remote.addr), + ps.path.remote.addrlen, buf.data(), nwrite); + + ev_io_start(worker->loop, &wev); + return 0; + } + + // session might be initialized during ngtcp2_conn_write_pkt. + if (!session) { + ev_io_stop(worker->loop, &wev); + return 0; + } + } + + auto s = static_cast(session.get()); + for (;;) { int64_t stream_id; int fin; ssize_t sveccnt = 0; - if (s && ngtcp2_conn_get_max_data_left(quic.conn)) { + if (ngtcp2_conn_get_max_data_left(quic.conn)) { sveccnt = s->write_stream(stream_id, fin, vec.data(), vec.size()); if (sveccnt == -1) { return -1; From 750c23f319ec062fb8aa9e8a8eea49aa414f7ce9 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 15 Jun 2019 17:49:46 +0900 Subject: [PATCH 005/124] quic: Configure settings with options --- src/h2load_http3_session.cc | 4 +++- src/h2load_quic.cc | 11 ++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/h2load_http3_session.cc b/src/h2load_http3_session.cc index 99d57891..2ab22c0c 100644 --- a/src/h2load_http3_session.cc +++ b/src/h2load_http3_session.cc @@ -271,9 +271,11 @@ int Http3Session::init_conn() { nullptr, // push_stream, }; + auto config = client_->worker->config; + nghttp3_conn_settings settings; nghttp3_conn_settings_default(&settings); - settings.qpack_max_table_capacity = 4096; + settings.qpack_max_table_capacity = config->header_table_size; settings.qpack_blocked_streams = 100; auto mem = nghttp3_mem_default(); diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index fe5c6b80..a591b68b 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -569,16 +569,17 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, generate_cid(scid); generate_cid(dcid); + auto config = worker->config; + ngtcp2_settings settings; ngtcp2_settings_default(&settings); settings.log_printf = debug_log_printf; settings.log_printf = nullptr; settings.initial_ts = timestamp(worker->loop); - settings.max_stream_data_bidi_local = 256_k; - settings.max_stream_data_bidi_remote = 256_k; - settings.max_stream_data_uni = 256_k; - settings.max_data = 1_m; - settings.max_streams_bidi = 1; + settings.max_stream_data_bidi_local = (1 << config->window_bits) - 1; + settings.max_stream_data_uni = (1 << config->window_bits) - 1; + settings.max_data = (1 << config->connection_window_bits) - 1; + settings.max_streams_bidi = 0; settings.max_streams_uni = 100; settings.idle_timeout = 30 * NGTCP2_SECONDS; From 7cd5ed6fc6f3f27a0da27f3c3522dd3e1fd17122 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 29 Jun 2019 16:48:11 +0900 Subject: [PATCH 006/124] Handle Retry --- src/h2load_quic.cc | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index a591b68b..0467d2d6 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -107,6 +107,21 @@ int Client::quic_handshake_completed() { return connection_made(); } +namespace { +int recv_retry(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd, + const ngtcp2_pkt_retry *retry, void *user_data) { + // Re-generate handshake secrets here because connection ID might + // change. + auto c = static_cast(user_data); + + if (c->quic_setup_initial_crypto() != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + namespace { ssize_t in_encrypt(ngtcp2_conn *conn, uint8_t *dest, size_t destlen, const uint8_t *plaintext, size_t plaintextlen, @@ -550,7 +565,7 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, nullptr, // stream_open h2load::stream_close, nullptr, // recv_stateless_reset - nullptr, // recv_retry + h2load::recv_retry, h2load::extend_max_local_streams_bidi, nullptr, // extend_max_local_streams_uni nullptr, // rand From 476e9d0a486200705d9e7b9892ca754a824dab9e Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 14 Jul 2019 22:11:42 +0900 Subject: [PATCH 007/124] h3-22 --- README.rst | 12 ++++++------ src/h2load.h | 4 ++-- src/h2load_http3_session.cc | 24 +++++++++++++----------- src/h2load_http3_session.h | 6 +++--- src/h2load_quic.cc | 8 ++++---- src/quic.h | 4 ++-- 6 files changed, 30 insertions(+), 28 deletions(-) diff --git a/README.rst b/README.rst index fa95077a..15465e97 100644 --- a/README.rst +++ b/README.rst @@ -16,20 +16,20 @@ An experimental high level C++ library is also available. We have Python bindings of this library, but we do not have full code coverage yet. -h2load IETF QUIC ----------------- +Running h2load against HTTP/3 endpoint +-------------------------------------- -In order to build h2load with IETF QUIC, you have to build ngtcp2, -nghttp3 and my patched OpenSSL. +In order to build h2load with HTTP/3 support, you have to build +ngtcp2, nghttp3 and my patched OpenSSL. https://github.com/ngtcp2/ngtcp2/tree/draft-20#build-from-git describes how to build these three software. -To run h2load against IETF QUIC server, specify h3-20 ALPN with +To run h2load against HTTP/3 server, specify h3-22 ALPN with ``--npn-list`` option like so: .. code-block:: text - $ h2load --npn-list h3-20 https://localhost:4433 + $ h2load --npn-list h3-22 https://localhost:4433 Development Status ------------------ diff --git a/src/h2load.h b/src/h2load.h index a9d617b1..1225318a 100644 --- a/src/h2load.h +++ b/src/h2load.h @@ -482,8 +482,8 @@ struct Client { size_t keylen, const uint8_t *sample, size_t samplelen); int quic_recv_stream_data(int64_t stream_id, int fin, const uint8_t *data, size_t datalen); - int quic_stream_close(int64_t stream_id, uint16_t app_error_code); - int quic_stream_reset(int64_t stream_id, uint16_t app_error_code); + int quic_stream_close(int64_t stream_id, uint64_t app_error_code); + int quic_stream_reset(int64_t stream_id, uint64_t app_error_code); int quic_extend_max_local_streams(); int quic_tls_handshake(bool initial = false); diff --git a/src/h2load_http3_session.cc b/src/h2load_http3_session.cc index 2ab22c0c..b84ca393 100644 --- a/src/h2load_http3_session.cc +++ b/src/h2load_http3_session.cc @@ -76,7 +76,7 @@ int64_t Http3Session::submit_request_internal() { return rv; } - rv = nghttp3_conn_submit_request(conn_, stream_id, nullptr, + rv = nghttp3_conn_submit_request(conn_, stream_id, reinterpret_cast(nva.data()), nva.size(), nullptr, nullptr); if (rv != 0) { @@ -105,18 +105,18 @@ size_t Http3Session::max_concurrent_streams() { } namespace { -int stream_close(nghttp3_conn *conn, int64_t stream_id, uint16_t error_code, +int stream_close(nghttp3_conn *conn, int64_t stream_id, uint64_t app_error_code, void *user_data, void *stream_user_data) { auto s = static_cast(user_data); - if (s->stream_close(stream_id, error_code) != 0) { + if (s->stream_close(stream_id, app_error_code) != 0) { return NGHTTP3_ERR_CALLBACK_FAILURE; } return 0; } } // namespace -int Http3Session::stream_close(int64_t stream_id, uint16_t error_code) { - client_->on_stream_close(stream_id, error_code == NGHTTP3_HTTP_NO_ERROR); +int Http3Session::stream_close(int64_t stream_id, uint64_t app_error_code) { + client_->on_stream_close(stream_id, app_error_code == NGHTTP3_HTTP_NO_ERROR); return 0; } @@ -186,19 +186,21 @@ void Http3Session::recv_header(int64_t stream_id, const nghttp3_vec *name, } namespace { -int send_stop_sending(nghttp3_conn *conn, int64_t stream_id, void *user_data, +int send_stop_sending(nghttp3_conn *conn, int64_t stream_id, + uint64_t app_error_code, void *user_data, void *stream_user_data) { auto s = static_cast(user_data); - if (s->send_stop_sending(stream_id) != 0) { + if (s->send_stop_sending(stream_id, app_error_code) != 0) { return NGHTTP3_ERR_CALLBACK_FAILURE; } return 0; } } // namespace -int Http3Session::send_stop_sending(int64_t stream_id) { +int Http3Session::send_stop_sending(int64_t stream_id, + uint64_t app_error_code) { auto rv = ngtcp2_conn_shutdown_stream_read(client_->quic.conn, stream_id, - NGHTTP3_HTTP_PUSH_REFUSED); + app_error_code); if (rv != 0) { std::cerr << "ngtcp2_conn_shutdown_stream_read: " << ngtcp2_strerror(rv) << std::endl; @@ -207,8 +209,8 @@ int Http3Session::send_stop_sending(int64_t stream_id) { return 0; } -int Http3Session::close_stream(int64_t stream_id, uint16_t error_code) { - auto rv = nghttp3_conn_close_stream(conn_, stream_id, error_code); +int Http3Session::close_stream(int64_t stream_id, uint64_t app_error_code) { + auto rv = nghttp3_conn_close_stream(conn_, stream_id, app_error_code); if (rv != 0) { return -1; } diff --git a/src/h2load_http3_session.h b/src/h2load_http3_session.h index 4293c77d..f62b219d 100644 --- a/src/h2load_http3_session.h +++ b/src/h2load_http3_session.h @@ -45,15 +45,15 @@ public: virtual size_t max_concurrent_streams(); int init_conn(); - int stream_close(int64_t stream_id, uint16_t error_code); + int stream_close(int64_t stream_id, uint64_t app_error_code); void recv_data(int64_t stream_id, const uint8_t *data, size_t datalen); void consume(int64_t stream_id, size_t nconsumed); void begin_headers(int64_t stream_id); void recv_header(int64_t stream_id, const nghttp3_vec *name, const nghttp3_vec *value); - int send_stop_sending(int64_t stream_id); + int send_stop_sending(int64_t stream_id, uint64_t app_error_code); - int close_stream(int64_t stream_id, uint16_t error_code); + int close_stream(int64_t stream_id, uint64_t app_error_code); int reset_stream(int64_t stream_id); int extend_max_local_streams(); int64_t submit_request_internal(); diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index 0467d2d6..a51b1d55 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -305,7 +305,7 @@ int Client::quic_recv_stream_data(int64_t stream_id, int fin, } namespace { -int stream_close(ngtcp2_conn *conn, int64_t stream_id, uint16_t app_error_code, +int stream_close(ngtcp2_conn *conn, int64_t stream_id, uint64_t app_error_code, void *user_data, void *stream_user_data) { auto c = static_cast(user_data); if (c->quic_stream_close(stream_id, app_error_code) != 0) { @@ -315,7 +315,7 @@ int stream_close(ngtcp2_conn *conn, int64_t stream_id, uint16_t app_error_code, } } // namespace -int Client::quic_stream_close(int64_t stream_id, uint16_t app_error_code) { +int Client::quic_stream_close(int64_t stream_id, uint64_t app_error_code) { auto s = static_cast(session.get()); if (s->close_stream(stream_id, app_error_code) != 0) { return -1; @@ -325,7 +325,7 @@ int Client::quic_stream_close(int64_t stream_id, uint16_t app_error_code) { namespace { int stream_reset(ngtcp2_conn *conn, int64_t stream_id, uint64_t final_size, - uint16_t app_error_code, void *user_data, + uint64_t app_error_code, void *user_data, void *stream_user_data) { auto c = static_cast(user_data); if (c->quic_stream_reset(stream_id, app_error_code) != 0) { @@ -335,7 +335,7 @@ int stream_reset(ngtcp2_conn *conn, int64_t stream_id, uint64_t final_size, } } // namespace -int Client::quic_stream_reset(int64_t stream_id, uint16_t app_error_code) { +int Client::quic_stream_reset(int64_t stream_id, uint64_t app_error_code) { auto s = static_cast(session.get()); if (s->reset_stream(stream_id) != 0) { return -1; diff --git a/src/quic.h b/src/quic.h index 4b4bb1d6..b70076e8 100644 --- a/src/quic.h +++ b/src/quic.h @@ -88,11 +88,11 @@ enum class ErrorType { }; struct Error { - Error(ErrorType type, uint16_t code) : type(type), code(code) {} + Error(ErrorType type, uint64_t code) : type(type), code(code) {} Error() : type(ErrorType::Transport), code(0) {} ErrorType type; - uint16_t code; + uint64_t code; }; Error err_transport(int liberr); From 23ccaa6191dddcd901ee1e82248b97859e27b510 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 24 Jul 2019 23:13:25 +0900 Subject: [PATCH 008/124] Always call write_quic when timer expires --- src/h2load_quic.cc | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index a51b1d55..e59e5550 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -911,7 +911,6 @@ void quic_pkt_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) { int Client::quic_pkt_timeout() { int rv; auto now = timestamp(worker->loop); - auto should_write = false; if (ngtcp2_conn_loss_detection_expiry(quic.conn) <= now) { rv = ngtcp2_conn_on_loss_detection_timer(quic.conn, now); @@ -919,16 +918,12 @@ int Client::quic_pkt_timeout() { quic.last_error = quic::err_transport(NGTCP2_ERR_INTERNAL); return -1; } - should_write = true; } if (ngtcp2_conn_ack_delay_expiry(quic.conn) <= now) { ngtcp2_conn_cancel_expired_ack_delay_timer(quic.conn, now); - should_write = true; } - if (should_write) { - return write_quic(); - } - return 0; + + return write_quic(); } void Client::quic_restart_pkt_timer() { From 94d76c042d1aa15543f6dc9dcd3cdbdcef12a65f Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 24 Jul 2019 23:14:33 +0900 Subject: [PATCH 009/124] h2load: Add --groups option --- src/h2load.cc | 16 +++++++++++++++- src/h2load.h | 2 ++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/h2load.cc b/src/h2load.cc index 74cbcaf7..6ce7b023 100644 --- a/src/h2load.cc +++ b/src/h2load.cc @@ -77,6 +77,7 @@ bool recorded(const std::chrono::steady_clock::time_point &t) { Config::Config() : ciphers(tls::DEFAULT_CIPHER_LIST), + groups("P-256:X25519:P-384:P-521"), data_length(-1), addrs(nullptr), nreqs(1), @@ -2220,6 +2221,10 @@ Options: in . --rps= Specify request per second for each client. --rps and --timing-script-file are mutually exclusive. + --groups= + Specify the supported groups. + Default: )" + << config.groups << R"( -v, --verbose Output debug information. --version Display version information and exit. @@ -2280,6 +2285,7 @@ int main(int argc, char **argv) { {"log-file", required_argument, &flag, 10}, {"connect-to", required_argument, &flag, 11}, {"rps", required_argument, &flag, 12}, + {"groups", required_argument, &flag, 13}, {nullptr, 0, nullptr, 0}}; int option_index = 0; auto c = getopt_long(argc, argv, @@ -2530,6 +2536,10 @@ int main(int argc, char **argv) { config.rps = v; break; } + case 13: + // --groups + config.groups = optarg; + break; } break; default: @@ -2758,7 +2768,11 @@ int main(int argc, char **argv) { } // TODO Use SSL_CTX_set_ciphersuites to set TLSv1.3 cipher list - // TODO Use SSL_CTX_set1_groups_list to set key share + + if (SSL_CTX_set1_groups_list(ssl_ctx, config.groups.c_str()) != 1) { + std::cerr << "SSL_CTX_set1_groups_list failed" << std::endl; + exit(EXIT_FAILURE); + } #ifndef OPENSSL_NO_NEXTPROTONEG SSL_CTX_set_next_proto_select_cb(ssl_ctx, client_select_next_proto_cb, diff --git a/src/h2load.h b/src/h2load.h index 1225318a..9b25ce93 100644 --- a/src/h2load.h +++ b/src/h2load.h @@ -75,6 +75,8 @@ struct Config { std::string connect_to_host; std::string ifile; std::string ciphers; + // supported groups (or curves). + std::string groups; // length of upload data int64_t data_length; addrinfo *addrs; From 05a6ee2b494b158f7ede49066d3073546fc2ae68 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 24 Jul 2019 23:15:15 +0900 Subject: [PATCH 010/124] Show ngtcp2 debug log with --verbose --- src/h2load_quic.cc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index e59e5550..276bb4c9 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -386,6 +386,8 @@ void debug_log_printf(void *user_data, const char *fmt, ...) { va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); + + fprintf(stderr, "\n"); } } // namespace @@ -588,8 +590,9 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, ngtcp2_settings settings; ngtcp2_settings_default(&settings); - settings.log_printf = debug_log_printf; - settings.log_printf = nullptr; + if (config->verbose) { + settings.log_printf = debug_log_printf; + } settings.initial_ts = timestamp(worker->loop); settings.max_stream_data_bidi_local = (1 << config->window_bits) - 1; settings.max_stream_data_uni = (1 << config->window_bits) - 1; From c3eb7e1634062c74768c57ccf2fccc78c37c1d53 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 24 Jul 2019 23:15:38 +0900 Subject: [PATCH 011/124] Handle preferred address --- src/h2load_quic.cc | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index 276bb4c9..44fe0388 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -400,6 +400,13 @@ void generate_cid(ngtcp2_cid &dest) { } } // namespace +namespace { +int select_preferred_addr(ngtcp2_conn *conn, ngtcp2_addr *dest, + const ngtcp2_preferred_addr *paddr, void *user_data) { + return 0; +} +} // namespace + namespace { ngtcp2_tstamp timestamp(struct ev_loop *loop) { return ev_now(loop) * NGTCP2_SECONDS; @@ -575,7 +582,7 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, nullptr, // remove_connection_id nullptr, // update_key nullptr, // path_validation - nullptr, // select_preferred_address + select_preferred_addr, h2load::stream_reset, nullptr, // extend_max_remote_streams_bidi nullptr, // extend_max_remote_streams_uni From 231c6ac86239ff6ee6e8828a0fb492854a93548b Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 1 Aug 2019 20:52:16 +0900 Subject: [PATCH 012/124] Add Dockerfile --- README.rst | 14 ++++++++++++++ docker/Dockerfile | 30 ++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 docker/Dockerfile diff --git a/README.rst b/README.rst index 15465e97..6d521efb 100644 --- a/README.rst +++ b/README.rst @@ -31,6 +31,20 @@ To run h2load against HTTP/3 server, specify h3-22 ALPN with $ h2load --npn-list h3-22 https://localhost:4433 +You can use Dockerfile to skip the tedious build steps to manually +pull and build dependencies. In order to build Docker image, do this: + +.. code-block:: text + + $ cd docker + $ docker build -t nghttp2-quic . + +Run h2load: + +.. code-block:: text + + $ docker run --rm -it --network=host nghttp2-quic /usr/local/bin/h2load --npn-list h3-22 https://localhost:4433 + Development Status ------------------ diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 00000000..657fce8e --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,30 @@ +FROM k8s.gcr.io/debian-base-amd64:1.0.0 + +RUN /usr/local/bin/clean-install git g++ make binutils autoconf automake autotools-dev libtool pkg-config \ + zlib1g-dev libev-dev libjemalloc-dev ruby-dev libc-ares-dev bison \ + zlib1g libev4 libjemalloc1 libc-ares2 \ + ca-certificates psmisc \ + python && \ + git clone --depth 1 -b quic-draft-22 https://github.com/tatsuhiro-t/openssl && \ + cd openssl && ./config enable-tls1_3 --openssldir=/etc/ssl && make -j$(nproc) && make install_sw && cd .. && rm -rf openssl && \ + git clone --depth 1 https://github.com/ngtcp2/nghttp3 && \ + cd nghttp3 && autoreconf -i && \ + ./configure --enable-lib-only && \ + make -j$(nproc) && make install-strip && cd .. && rm -rf nghttp3 && \ + git clone --depth 1 -b draft-22 https://github.com/ngtcp2/ngtcp2 && \ + cd ngtcp2 && autoreconf -i && \ + ./configure && \ + make -j$(nproc) && make install-strip && cd .. && rm -rf ngtcp2 && \ + git clone --depth 1 -b quic https://github.com/nghttp2/nghttp2.git && \ + cd nghttp2 && \ + git submodule update --init && autoreconf -i && \ + ./configure --disable-examples --disable-hpack-tools --disable-python-bindings --with-mruby --with-neverbleed && \ + make -j$(nproc) install-strip && \ + cd .. && \ + rm -rf nghttp2 && \ + strip /usr/local/lib/*.so.*.* /usr/local/lib/engines-*/*.so && \ + rm -rf /usr/local/lib/libssl.so /usr/local/lib/libcrypto.so /usr/local/lib/libssl.a /usr/local/lib/libcrypto.a /usr/local/lib/pkgconfig/*ssl.pc /usr/local/include/openssl/* && \ + apt-get -y purge git g++ make binutils autoconf automake autotools-dev libtool pkg-config \ + zlib1g-dev libev-dev libjemalloc-dev ruby-dev libc-ares-dev bison && \ + apt-get -y autoremove --purge && \ + rm -rf /var/log/* From 6002fac9f1e6781c5deec3cd8c3572aec73d0230 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 8 Aug 2019 09:59:21 +0900 Subject: [PATCH 013/124] h2load: Add --tls13-ciphers option --- src/h2load.cc | 23 ++++++++++++++++++++--- src/h2load.h | 1 + 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/h2load.cc b/src/h2load.cc index 6ce7b023..07b569a3 100644 --- a/src/h2load.cc +++ b/src/h2load.cc @@ -77,6 +77,8 @@ bool recorded(const std::chrono::steady_clock::time_point &t) { Config::Config() : ciphers(tls::DEFAULT_CIPHER_LIST), + tls13_ciphers("TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_" + "CHACHA20_POLY1305_SHA256:TLS_AES_128_CCM_SHA256"), groups("P-256:X25519:P-384:P-521"), data_length(-1), addrs(nullptr), @@ -2099,10 +2101,15 @@ Options: -H, --header=
Add/Override a header to the requests. --ciphers= - Set allowed cipher list. The format of the string is - described in OpenSSL ciphers(1). + Set allowed cipher list for TLSv1.2 or ealier. The + format of the string is described in OpenSSL ciphers(1). Default: )" << config.ciphers << R"( + --tls13-ciphers= + Set allowed cipher list for TLSv1.3. The format of the + string is described in OpenSSL ciphers(1). + Default: )" + << config.tls13_ciphers << R"( -p, --no-tls-proto= Specify ALPN identifier of the protocol to be used when accessing http URI without SSL/TLS. @@ -2286,6 +2293,7 @@ int main(int argc, char **argv) { {"connect-to", required_argument, &flag, 11}, {"rps", required_argument, &flag, 12}, {"groups", required_argument, &flag, 13}, + {"tls13-ciphers", required_argument, &flag, 14}, {nullptr, 0, nullptr, 0}}; int option_index = 0; auto c = getopt_long(argc, argv, @@ -2540,6 +2548,10 @@ int main(int argc, char **argv) { // --groups config.groups = optarg; break; + case 14: + // --tls13-ciphers + config.tls13_ciphers = optarg; + break; } break; default: @@ -2767,7 +2779,12 @@ int main(int argc, char **argv) { exit(EXIT_FAILURE); } - // TODO Use SSL_CTX_set_ciphersuites to set TLSv1.3 cipher list + if (SSL_CTX_set_ciphersuites(ssl_ctx, config.tls13_ciphers.c_str()) == 0) { + std::cerr << "SSL_CTX_set_ciphersuites with " << config.tls13_ciphers + << " failed: " << ERR_error_string(ERR_get_error(), nullptr) + << std::endl; + exit(EXIT_FAILURE); + } if (SSL_CTX_set1_groups_list(ssl_ctx, config.groups.c_str()) != 1) { std::cerr << "SSL_CTX_set1_groups_list failed" << std::endl; diff --git a/src/h2load.h b/src/h2load.h index 9b25ce93..5a4777d9 100644 --- a/src/h2load.h +++ b/src/h2load.h @@ -75,6 +75,7 @@ struct Config { std::string connect_to_host; std::string ifile; std::string ciphers; + std::string tls13_ciphers; // supported groups (or curves). std::string groups; // length of upload data From 7aa4bff97b4d74624de4d4342ab2bbb0ca5136d6 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 8 Aug 2019 09:59:54 +0900 Subject: [PATCH 014/124] quic: Support TLS_AES_128_CCM_SHA256 --- src/quic.cc | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/quic.cc b/src/quic.cc index c9ddd664..9a3de297 100644 --- a/src/quic.cc +++ b/src/quic.cc @@ -45,6 +45,8 @@ const EVP_CIPHER *aead(SSL *ssl) { return EVP_aes_256_gcm(); case 0x03001303u: // TLS_CHACHA20_POLY1305_SHA256 return EVP_chacha20_poly1305(); + case 0x03001304u: // TLS_AES_128_CCM_SHA256 + return EVP_aes_128_ccm(); default: assert(0); } @@ -53,6 +55,7 @@ const EVP_CIPHER *aead(SSL *ssl) { const EVP_CIPHER *hp(SSL *ssl) { switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) { case 0x03001301u: // TLS_AES_128_GCM_SHA256 + case 0x03001304u: // TLS_AES_128_CCM_SHA256 return EVP_aes_128_ctr(); case 0x03001302u: // TLS_AES_256_GCM_SHA384 return EVP_aes_256_ctr(); @@ -67,6 +70,7 @@ const EVP_MD *prf(SSL *ssl) { switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) { case 0x03001301u: // TLS_AES_128_GCM_SHA256 case 0x03001303u: // TLS_CHACHA20_POLY1305_SHA256 + case 0x03001304u: // TLS_AES_128_CCM_SHA256 return EVP_sha256(); case 0x03001302u: // TLS_AES_256_GCM_SHA384 return EVP_sha384(); @@ -95,6 +99,9 @@ size_t aead_tag_length(const EVP_CIPHER *aead) { if (aead == EVP_chacha20_poly1305()) { return EVP_CHACHAPOLY_TLS_TAG_LEN; } + if (aead == EVP_aes_128_ccm()) { + return EVP_CCM_TLS_TAG_LEN; + } assert(0); } } // namespace @@ -269,6 +276,11 @@ ssize_t encrypt(uint8_t *dest, size_t destlen, const uint8_t *plaintext, return -1; } + if (aead == EVP_aes_128_ccm() && + EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_TAG, taglen, nullptr) != 1) { + return -1; + } + if (EVP_EncryptInit_ex(actx, nullptr, nullptr, key, nonce) != 1) { return -1; } @@ -276,6 +288,11 @@ ssize_t encrypt(uint8_t *dest, size_t destlen, const uint8_t *plaintext, size_t outlen = 0; int len; + if (aead == EVP_aes_128_ccm() && + EVP_EncryptUpdate(actx, nullptr, &len, nullptr, plaintextlen) != 1) { + return -1; + } + if (EVP_EncryptUpdate(actx, nullptr, &len, ad, adlen) != 1) { return -1; } @@ -333,6 +350,12 @@ ssize_t decrypt(uint8_t *dest, size_t destlen, const uint8_t *ciphertext, return -1; } + if (aead == EVP_aes_128_ccm() && + EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_TAG, taglen, + const_cast(tag)) != 1) { + return -1; + } + if (EVP_DecryptInit_ex(actx, nullptr, nullptr, key, nonce) != 1) { return -1; } @@ -340,6 +363,11 @@ ssize_t decrypt(uint8_t *dest, size_t destlen, const uint8_t *ciphertext, size_t outlen; int len; + if (aead == EVP_aes_128_ccm() && + EVP_DecryptUpdate(actx, nullptr, &len, nullptr, ciphertextlen) != 1) { + return -1; + } + if (EVP_DecryptUpdate(actx, nullptr, &len, ad, adlen) != 1) { return -1; } @@ -350,6 +378,10 @@ ssize_t decrypt(uint8_t *dest, size_t destlen, const uint8_t *ciphertext, outlen = len; + if (aead == EVP_aes_128_ccm()) { + return outlen; + } + if (EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_TAG, taglen, const_cast(tag)) != 1) { return -1; From 3dbe3b3e7fdd9a9504a53e4ba98d4d2dbbfdfc7f Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 25 Aug 2019 10:20:10 +0900 Subject: [PATCH 015/124] Follow ngtcp2 API changes and use libngtcp2_crypto_openssl --- configure.ac | 10 + src/Makefile.am | 2 + src/h2load.cc | 4 + src/h2load.h | 27 ++- src/h2load_http3_session.cc | 3 - src/h2load_quic.cc | 237 +++++++++------------ src/quic.cc | 398 ------------------------------------ src/quic.h | 52 +---- 8 files changed, 125 insertions(+), 608 deletions(-) diff --git a/configure.ac b/configure.ac index 3fcf05aa..def91ae8 100644 --- a/configure.ac +++ b/configure.ac @@ -461,6 +461,15 @@ if test "x${have_libngtcp2}" = "xno"; then AC_MSG_NOTICE($LIBNGTCP2_PKG_ERRORS) fi +# ngtcp2_crypto_openssl (for src) +PKG_CHECK_MODULES([LIBNGTCP2_CRYPTO_OPENSSL], + [libngtcp2_crypto_openssl >= 0.0.0], + [have_libngtcp2_crypto_openssl=yes], + [have_libngtcp2_crypto_openssl=no]) +if test "x${have_libngtcp2_crypto_openssl}" = "xno"; then + AC_MSG_NOTICE($LIBNGTCP2_CRYPTO_OPENSSL_PKG_ERRORS) +fi + # nghttp3 (for src) PKG_CHECK_MODULES([LIBNGHTTP3], [libnghttp3 >= 0.0.0], [have_libnghttp3=yes], [have_libnghttp3=no]) @@ -1026,6 +1035,7 @@ AC_MSG_NOTICE([summary of build options: Libev: ${have_libev} (CFLAGS='${LIBEV_CFLAGS}' LIBS='${LIBEV_LIBS}') Libc-ares: ${have_libcares} (CFLAGS='${LIBCARES_CFLAGS}' LIBS='${LIBCARES_LIBS}') libngtcp2: ${have_libngtcp2} (CFLAGS='${LIBNGTCP2_CFLAGS}' LIBS='${LIBNGTCP2_LIBS}') + libngtcp2_crypto_openssl: ${have_libngtcp2_crypto_openssl} (CFLAGS='${LIBNGTCP2_CRYPTO_OPENSSL_CFLAGS}' LIBS='${LIBNGTCP2_CRYPTO_OPENSSL_LIBS}') libnghttp3: ${have_libnghttp3} (CFLAGS='${LIBNGHTTP3_CFLAGS}' LIBS='${LIBNGHTTP3_LIBS}') Libevent(SSL): ${have_libevent_openssl} (CFLAGS='${LIBEVENT_OPENSSL_CFLAGS}' LIBS='${LIBEVENT_OPENSSL_LIBS}') Jansson: ${have_jansson} (CFLAGS='${JANSSON_CFLAGS}' LIBS='${JANSSON_LIBS}') diff --git a/src/Makefile.am b/src/Makefile.am index ee6652f5..08797fff 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -47,6 +47,7 @@ AM_CPPFLAGS = \ @OPENSSL_CFLAGS@ \ @LIBCARES_CFLAGS@ \ @LIBNGHTTP3_CFLAGS@ \ + @LIBNGTCP2_CRYPTO_OPENSSL_CFLAGS@ \ @LIBNGTCP2_CFLAGS@ \ @JANSSON_CFLAGS@ \ @ZLIB_CFLAGS@ \ @@ -62,6 +63,7 @@ LDADD = $(top_builddir)/lib/libnghttp2.la \ @OPENSSL_LIBS@ \ @LIBCARES_LIBS@ \ @LIBNGHTTP3_LIBS@ \ + @LIBNGTCP2_CRYPTO_OPENSSL_LIBS@ \ @LIBNGTCP2_LIBS@ \ @SYSTEMD_LIBS@ \ @JANSSON_LIBS@ \ diff --git a/src/h2load.cc b/src/h2load.cc index 07b569a3..281b5410 100644 --- a/src/h2load.cc +++ b/src/h2load.cc @@ -2250,6 +2250,8 @@ Options: } } // namespace +extern ngtcp2_crypto_ctx in_crypto_ctx; + int main(int argc, char **argv) { tls::libssl_init(); @@ -2907,6 +2909,8 @@ int main(int argc, char **argv) { exit(EXIT_FAILURE); } + ngtcp2_crypto_ctx_initial(&in_crypto_ctx); + resolve_host(); std::cout << "starting benchmark..." << std::endl; diff --git a/src/h2load.h b/src/h2load.h index 5a4777d9..6d746b35 100644 --- a/src/h2load.h +++ b/src/h2load.h @@ -46,6 +46,7 @@ #include #include +#include #include @@ -331,6 +332,7 @@ struct Client { ngtcp2_crypto_level rx_crypto_level; std::vector server_handshake; size_t server_handshake_nread; + ngtcp2_crypto_ctx crypto_ctx; // Client never send CRYPTO in Short packet. std::array crypto; size_t max_pktlen; @@ -463,26 +465,23 @@ struct Client { int quic_recv_crypto_data(ngtcp2_crypto_level crypto_level, const uint8_t *data, size_t datalen); int quic_handshake_completed(); - int quic_in_encrypt(uint8_t *dest, size_t destlen, const uint8_t *plaintext, - size_t plaintextlen, const uint8_t *key, size_t keylen, + int quic_in_encrypt(uint8_t *dest, const uint8_t *plaintext, + size_t plaintextlen, const uint8_t *key, const uint8_t *nonce, size_t noncelen, const uint8_t *ad, size_t adlen); - int quic_in_decrypt(uint8_t *dest, size_t destlen, const uint8_t *ciphertext, - size_t ciphertextlen, const uint8_t *key, size_t keylen, + int quic_in_decrypt(uint8_t *dest, const uint8_t *ciphertext, + size_t ciphertextlen, const uint8_t *key, const uint8_t *nonce, size_t noncelen, const uint8_t *ad, size_t adlen); - int quic_encrypt(uint8_t *dest, size_t destlen, const uint8_t *plaintext, - size_t plaintextlen, const uint8_t *key, size_t keylen, + int quic_encrypt(uint8_t *dest, const uint8_t *plaintext, size_t plaintextlen, + const uint8_t *key, const uint8_t *nonce, size_t noncelen, + const uint8_t *ad, size_t adlen); + int quic_decrypt(uint8_t *dest, const uint8_t *ciphertext, + size_t ciphertextlen, const uint8_t *key, const uint8_t *nonce, size_t noncelen, const uint8_t *ad, size_t adlen); - int quic_decrypt(uint8_t *dest, size_t destlen, const uint8_t *ciphertext, - size_t ciphertextlen, const uint8_t *key, size_t keylen, - const uint8_t *nonce, size_t noncelen, const uint8_t *ad, - size_t adlen); - int quic_in_hp_mask(uint8_t *dest, size_t destlen, const uint8_t *key, - size_t keylen, const uint8_t *sample, size_t samplelen); - int quic_hp_mask(uint8_t *dest, size_t destlen, const uint8_t *key, - size_t keylen, const uint8_t *sample, size_t samplelen); + int quic_in_hp_mask(uint8_t *dest, const uint8_t *key, const uint8_t *sample); + int quic_hp_mask(uint8_t *dest, const uint8_t *key, const uint8_t *sample); int quic_recv_stream_data(int64_t stream_id, int fin, const uint8_t *data, size_t datalen); int quic_stream_close(int64_t stream_id, uint64_t app_error_code); diff --git a/src/h2load_http3_session.cc b/src/h2load_http3_session.cc index b84ca393..ca4bbe05 100644 --- a/src/h2load_http3_session.cc +++ b/src/h2load_http3_session.cc @@ -83,9 +83,6 @@ int64_t Http3Session::submit_request_internal() { return rv; } - rv = nghttp3_conn_end_stream(conn_, stream_id); - assert(0 == rv); - client_->on_request(stream_id); auto req_stat = client_->get_req_stat(stream_id); assert(req_stat); diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index 44fe0388..aad45276 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -37,6 +37,8 @@ namespace { auto randgen = util::make_mt19937(); } // namespace +ngtcp2_crypto_ctx in_crypto_ctx; + namespace { int client_initial(ngtcp2_conn *conn, void *user_data) { auto c = static_cast(user_data); @@ -123,156 +125,133 @@ int recv_retry(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd, } // namespace namespace { -ssize_t in_encrypt(ngtcp2_conn *conn, uint8_t *dest, size_t destlen, - const uint8_t *plaintext, size_t plaintextlen, - const uint8_t *key, size_t keylen, const uint8_t *nonce, - size_t noncelen, const uint8_t *ad, size_t adlen, - void *user_data) { +int in_encrypt(ngtcp2_conn *conn, uint8_t *dest, const uint8_t *plaintext, + size_t plaintextlen, const uint8_t *key, const uint8_t *nonce, + size_t noncelen, const uint8_t *ad, size_t adlen, + void *user_data) { auto c = static_cast(user_data); - auto nwrite = c->quic_in_encrypt(dest, destlen, plaintext, plaintextlen, key, - keylen, nonce, noncelen, ad, adlen); - if (nwrite < 0) { + if (c->quic_in_encrypt(dest, plaintext, plaintextlen, key, nonce, noncelen, + ad, adlen) != 0) { return NGTCP2_ERR_CALLBACK_FAILURE; } - return nwrite; + return 0; } } // namespace -int Client::quic_in_encrypt(uint8_t *dest, size_t destlen, - const uint8_t *plaintext, size_t plaintextlen, - const uint8_t *key, size_t keylen, +int Client::quic_in_encrypt(uint8_t *dest, const uint8_t *plaintext, + size_t plaintextlen, const uint8_t *key, const uint8_t *nonce, size_t noncelen, const uint8_t *ad, size_t adlen) { - return quic::encrypt(dest, destlen, plaintext, plaintextlen, key, keylen, - nonce, noncelen, ad, adlen, EVP_aes_128_gcm()); + return ngtcp2_crypto_encrypt(dest, &in_crypto_ctx.aead, plaintext, + plaintextlen, key, nonce, noncelen, ad, adlen); } namespace { -ssize_t in_decrypt(ngtcp2_conn *conn, uint8_t *dest, size_t destlen, - const uint8_t *ciphertext, size_t ciphertextlen, - const uint8_t *key, size_t keylen, const uint8_t *nonce, - size_t noncelen, const uint8_t *ad, size_t adlen, - void *user_data) { +int in_decrypt(ngtcp2_conn *conn, uint8_t *dest, const uint8_t *ciphertext, + size_t ciphertextlen, const uint8_t *key, const uint8_t *nonce, + size_t noncelen, const uint8_t *ad, size_t adlen, + void *user_data) { auto c = static_cast(user_data); - auto nwrite = c->quic_in_decrypt(dest, destlen, ciphertext, ciphertextlen, - key, keylen, nonce, noncelen, ad, adlen); - if (nwrite < 0) { + if (c->quic_in_decrypt(dest, ciphertext, ciphertextlen, key, nonce, noncelen, + ad, adlen) != 0) { return NGTCP2_ERR_TLS_DECRYPT; } - return nwrite; + return 0; } } // namespace -int Client::quic_in_decrypt(uint8_t *dest, size_t destlen, - const uint8_t *ciphertext, size_t ciphertextlen, - const uint8_t *key, size_t keylen, +int Client::quic_in_decrypt(uint8_t *dest, const uint8_t *ciphertext, + size_t ciphertextlen, const uint8_t *key, const uint8_t *nonce, size_t noncelen, const uint8_t *ad, size_t adlen) { - return quic::decrypt(dest, destlen, ciphertext, ciphertextlen, key, keylen, - nonce, noncelen, ad, adlen, EVP_aes_128_gcm()); + return ngtcp2_crypto_decrypt(dest, &in_crypto_ctx.aead, ciphertext, + ciphertextlen, key, nonce, noncelen, ad, adlen); } namespace { -ssize_t encrypt(ngtcp2_conn *conn, uint8_t *dest, size_t destlen, - const uint8_t *plaintext, size_t plaintextlen, - const uint8_t *key, size_t keylen, const uint8_t *nonce, - size_t noncelen, const uint8_t *ad, size_t adlen, - void *user_data) { +int encrypt(ngtcp2_conn *conn, uint8_t *dest, const uint8_t *plaintext, + size_t plaintextlen, const uint8_t *key, const uint8_t *nonce, + size_t noncelen, const uint8_t *ad, size_t adlen, void *user_data) { auto c = static_cast(user_data); - auto nwrite = c->quic_encrypt(dest, destlen, plaintext, plaintextlen, key, - keylen, nonce, noncelen, ad, adlen); - if (nwrite < 0) { + if (c->quic_encrypt(dest, plaintext, plaintextlen, key, nonce, noncelen, ad, + adlen) != 0) { return NGTCP2_ERR_CALLBACK_FAILURE; } - return nwrite; + return 0; } } // namespace -int Client::quic_encrypt(uint8_t *dest, size_t destlen, - const uint8_t *plaintext, size_t plaintextlen, - const uint8_t *key, size_t keylen, +int Client::quic_encrypt(uint8_t *dest, const uint8_t *plaintext, + size_t plaintextlen, const uint8_t *key, const uint8_t *nonce, size_t noncelen, const uint8_t *ad, size_t adlen) { - return quic::encrypt(dest, destlen, plaintext, plaintextlen, key, keylen, - nonce, noncelen, ad, adlen, quic::aead(ssl)); + return ngtcp2_crypto_encrypt(dest, &quic.crypto_ctx.aead, plaintext, + plaintextlen, key, nonce, noncelen, ad, adlen); } namespace { -ssize_t decrypt(ngtcp2_conn *conn, uint8_t *dest, size_t destlen, - const uint8_t *ciphertext, size_t ciphertextlen, - const uint8_t *key, size_t keylen, const uint8_t *nonce, - size_t noncelen, const uint8_t *ad, size_t adlen, - void *user_data) { +int decrypt(ngtcp2_conn *conn, uint8_t *dest, const uint8_t *ciphertext, + size_t ciphertextlen, const uint8_t *key, const uint8_t *nonce, + size_t noncelen, const uint8_t *ad, size_t adlen, void *user_data) { auto c = static_cast(user_data); - auto nwrite = c->quic_decrypt(dest, destlen, ciphertext, ciphertextlen, key, - keylen, nonce, noncelen, ad, adlen); - if (nwrite < 0) { + if (c->quic_decrypt(dest, ciphertext, ciphertextlen, key, nonce, noncelen, ad, + adlen) != 0) { return NGTCP2_ERR_TLS_DECRYPT; } - return nwrite; + return 0; } } // namespace -int Client::quic_decrypt(uint8_t *dest, size_t destlen, - const uint8_t *ciphertext, size_t ciphertextlen, - const uint8_t *key, size_t keylen, +int Client::quic_decrypt(uint8_t *dest, const uint8_t *ciphertext, + size_t ciphertextlen, const uint8_t *key, const uint8_t *nonce, size_t noncelen, const uint8_t *ad, size_t adlen) { - return quic::decrypt(dest, destlen, ciphertext, ciphertextlen, key, keylen, - nonce, noncelen, ad, adlen, quic::aead(ssl)); + return ngtcp2_crypto_decrypt(dest, &quic.crypto_ctx.aead, ciphertext, + ciphertextlen, key, nonce, noncelen, ad, adlen); } namespace { -ssize_t in_hp_mask(ngtcp2_conn *conn, uint8_t *dest, size_t destlen, - const uint8_t *key, size_t keylen, const uint8_t *sample, - size_t samplelen, void *user_data) { +int in_hp_mask(ngtcp2_conn *conn, uint8_t *dest, const uint8_t *key, + const uint8_t *sample, void *user_data) { auto c = static_cast(user_data); - auto nwrite = - c->quic_in_hp_mask(dest, destlen, key, keylen, sample, samplelen); - if (nwrite < 0) { + if (c->quic_in_hp_mask(dest, key, sample) != 0) { return NGTCP2_ERR_CALLBACK_FAILURE; } - return nwrite; + return 0; } } // namespace -int Client::quic_in_hp_mask(uint8_t *dest, size_t destlen, const uint8_t *key, - size_t keylen, const uint8_t *sample, - size_t samplelen) { - return quic::hp_mask(dest, destlen, key, keylen, sample, samplelen, - EVP_aes_128_ctr()); +int Client::quic_in_hp_mask(uint8_t *dest, const uint8_t *key, + const uint8_t *sample) { + return ngtcp2_crypto_hp_mask(dest, &in_crypto_ctx.hp, key, sample); } namespace { -ssize_t hp_mask(ngtcp2_conn *conn, uint8_t *dest, size_t destlen, - const uint8_t *key, size_t keylen, const uint8_t *sample, - size_t samplelen, void *user_data) { +int hp_mask(ngtcp2_conn *conn, uint8_t *dest, const uint8_t *key, + const uint8_t *sample, void *user_data) { auto c = static_cast(user_data); - auto nwrite = c->quic_hp_mask(dest, destlen, key, keylen, sample, samplelen); - if (nwrite < 0) { + if (c->quic_hp_mask(dest, key, sample) != 0) { return NGTCP2_ERR_CALLBACK_FAILURE; } - return nwrite; + return 0; } } // namespace -int Client::quic_hp_mask(uint8_t *dest, size_t destlen, const uint8_t *key, - size_t keylen, const uint8_t *sample, - size_t samplelen) { - return quic::hp_mask(dest, destlen, key, keylen, sample, samplelen, - quic::hp(ssl)); +int Client::quic_hp_mask(uint8_t *dest, const uint8_t *key, + const uint8_t *sample) { + return ngtcp2_crypto_hp_mask(dest, &quic.crypto_ctx.hp, key, sample); } namespace { @@ -669,71 +648,45 @@ void Client::quic_close_connection() { } int Client::quic_setup_initial_crypto() { - int rv; - - std::array initial_secret, secret; + std::array tx_secret, rx_secret; auto dcid = ngtcp2_conn_get_dcid(quic.conn); - rv = quic::derive_initial_secret( - initial_secret.data(), initial_secret.size(), dcid->data, dcid->datalen, - reinterpret_cast(NGTCP2_INITIAL_SALT), - str_size(NGTCP2_INITIAL_SALT)); - if (rv != 0) { - std::cerr << "quic::derive_initial_secret() failed" << std::endl; + if (ngtcp2_crypto_derive_initial_secrets(rx_secret.data(), tx_secret.data(), + nullptr, dcid, + NGTCP2_CRYPTO_SIDE_CLIENT) != 0) { + std::cerr << "ngtcp2_crypto_derive_initial_secrets() failed" << std::endl; return -1; } - auto aead = EVP_aes_128_gcm(); - auto prf = EVP_sha256(); - - rv = quic::derive_client_initial_secret(secret.data(), secret.size(), - initial_secret.data(), - initial_secret.size()); - if (rv != 0) { - std::cerr << "quic::derive_client_initial_secret() failed" << std::endl; - return -1; - } + auto aead = &in_crypto_ctx.aead; + auto md = &in_crypto_ctx.md; std::array key, iv, hp; - auto keylen = key.size(); - auto ivlen = iv.size(); - rv = quic::derive_packet_protection_key(key.data(), keylen, iv.data(), ivlen, - secret.data(), secret.size(), aead, - prf); - if (rv != 0) { + auto keylen = ngtcp2_crypto_aead_keylen(aead); + auto ivlen = ngtcp2_crypto_packet_protection_ivlen(aead); + auto hplen = keylen; + + if (ngtcp2_crypto_derive_packet_protection_key(key.data(), iv.data(), aead, + md, tx_secret.data(), + tx_secret.size()) != 0) { return -1; } - auto hplen = hp.size(); - rv = quic::derive_header_protection_key(hp.data(), hplen, secret.data(), - secret.size(), aead, prf); - if (rv != 0) { + if (ngtcp2_crypto_derive_header_protection_key( + hp.data(), aead, md, tx_secret.data(), tx_secret.size()) != 0) { return -1; } ngtcp2_conn_install_initial_tx_keys(quic.conn, key.data(), keylen, iv.data(), ivlen, hp.data(), hplen); - rv = quic::derive_server_initial_secret(secret.data(), secret.size(), - initial_secret.data(), - initial_secret.size()); - if (rv != 0) { - std::cerr << "quic::derive_server_initial_secret() failed" << std::endl; + if (ngtcp2_crypto_derive_packet_protection_key(key.data(), iv.data(), aead, + md, rx_secret.data(), + rx_secret.size()) != 0) { return -1; } - keylen = key.size(); - ivlen = iv.size(); - rv = quic::derive_packet_protection_key(key.data(), keylen, iv.data(), ivlen, - secret.data(), secret.size(), aead, - prf); - if (rv != 0) { - return -1; - } - - hplen = hp.size(); - rv = quic::derive_header_protection_key(hp.data(), hplen, secret.data(), - secret.size(), aead, prf); - if (rv != 0) { + if (ngtcp2_crypto_derive_header_protection_key( + hp.data(), aead, md, rx_secret.data(), rx_secret.size()) != 0) { return -1; } @@ -744,8 +697,6 @@ int Client::quic_setup_initial_crypto() { } int Client::quic_on_key(int name, const uint8_t *secret, size_t secretlen) { - int rv; - switch (name) { case SSL_KEY_CLIENT_EARLY_TRAFFIC: case SSL_KEY_CLIENT_HANDSHAKE_TRAFFIC: @@ -757,28 +708,30 @@ int Client::quic_on_key(int name, const uint8_t *secret, size_t secretlen) { return 0; } - auto aead = quic::aead(ssl); - auto prf = quic::prf(ssl); + if (quic.crypto_ctx.aead.native_handle == nullptr) { + ngtcp2_crypto_ctx_tls(&quic.crypto_ctx, ssl); + ngtcp2_conn_set_aead_overhead( + quic.conn, ngtcp2_crypto_aead_taglen(&quic.crypto_ctx.aead)); + } + + auto aead = &quic.crypto_ctx.aead; + auto md = &quic.crypto_ctx.md; std::array key, iv, hp; - auto keylen = key.size(); - auto ivlen = iv.size(); - rv = quic::derive_packet_protection_key(key.data(), keylen, iv.data(), ivlen, - secret, secretlen, aead, prf); - if (rv != 0) { + auto keylen = ngtcp2_crypto_aead_keylen(aead); + auto ivlen = ngtcp2_crypto_packet_protection_ivlen(aead); + auto hplen = keylen; + + if (ngtcp2_crypto_derive_packet_protection_key(key.data(), iv.data(), aead, + md, secret, secretlen) != 0) { return -1; } - auto hplen = hp.size(); - rv = quic::derive_header_protection_key(hp.data(), hplen, secret, secretlen, - aead, prf); - if (rv != 0) { + if (ngtcp2_crypto_derive_header_protection_key(hp.data(), aead, md, secret, + secretlen) != 0) { return -1; } - // TODO Just call this once. - ngtcp2_conn_set_aead_overhead(quic.conn, quic::aead_max_overhead(aead)); - switch (name) { case SSL_KEY_CLIENT_EARLY_TRAFFIC: ngtcp2_conn_install_early_keys(quic.conn, key.data(), keylen, iv.data(), diff --git a/src/quic.cc b/src/quic.cc index 9a3de297..1abad98a 100644 --- a/src/quic.cc +++ b/src/quic.cc @@ -26,8 +26,6 @@ #include -#include - #include #include @@ -37,402 +35,6 @@ using namespace nghttp2; namespace quic { -const EVP_CIPHER *aead(SSL *ssl) { - switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) { - case 0x03001301u: // TLS_AES_128_GCM_SHA256 - return EVP_aes_128_gcm(); - case 0x03001302u: // TLS_AES_256_GCM_SHA384 - return EVP_aes_256_gcm(); - case 0x03001303u: // TLS_CHACHA20_POLY1305_SHA256 - return EVP_chacha20_poly1305(); - case 0x03001304u: // TLS_AES_128_CCM_SHA256 - return EVP_aes_128_ccm(); - default: - assert(0); - } -} - -const EVP_CIPHER *hp(SSL *ssl) { - switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) { - case 0x03001301u: // TLS_AES_128_GCM_SHA256 - case 0x03001304u: // TLS_AES_128_CCM_SHA256 - return EVP_aes_128_ctr(); - case 0x03001302u: // TLS_AES_256_GCM_SHA384 - return EVP_aes_256_ctr(); - case 0x03001303u: // TLS_CHACHA20_POLY1305_SHA256 - return EVP_chacha20(); - default: - assert(0); - } -} - -const EVP_MD *prf(SSL *ssl) { - switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) { - case 0x03001301u: // TLS_AES_128_GCM_SHA256 - case 0x03001303u: // TLS_CHACHA20_POLY1305_SHA256 - case 0x03001304u: // TLS_AES_128_CCM_SHA256 - return EVP_sha256(); - case 0x03001302u: // TLS_AES_256_GCM_SHA384 - return EVP_sha384(); - default: - assert(0); - } -} - -namespace { -size_t aead_key_length(const EVP_CIPHER *aead) { - return EVP_CIPHER_key_length(aead); -} -} // namespace - -namespace { -size_t aead_nonce_length(const EVP_CIPHER *aead) { - return EVP_CIPHER_iv_length(aead); -} -} // namespace - -namespace { -size_t aead_tag_length(const EVP_CIPHER *aead) { - if (aead == EVP_aes_128_gcm() || aead == EVP_aes_256_gcm()) { - return EVP_GCM_TLS_TAG_LEN; - } - if (aead == EVP_chacha20_poly1305()) { - return EVP_CHACHAPOLY_TLS_TAG_LEN; - } - if (aead == EVP_aes_128_ccm()) { - return EVP_CCM_TLS_TAG_LEN; - } - assert(0); -} -} // namespace - -size_t aead_max_overhead(const EVP_CIPHER *aead) { - return aead_tag_length(aead); -} - -int hkdf_extract(uint8_t *dest, size_t destlen, const uint8_t *secret, - size_t secretlen, const uint8_t *salt, size_t saltlen, - const EVP_MD *prf) { - auto pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, nullptr); - if (pctx == nullptr) { - return -1; - } - - auto pctx_d = defer(EVP_PKEY_CTX_free, pctx); - - if (EVP_PKEY_derive_init(pctx) != 1 || - EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY) != 1 || - EVP_PKEY_CTX_set_hkdf_md(pctx, prf) != 1 || - EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, saltlen) != 1 || - EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, secretlen) != 1 || - EVP_PKEY_derive(pctx, dest, &destlen) != 1) { - return -1; - } - - return 0; -} - -int hkdf_expand(uint8_t *dest, size_t destlen, const uint8_t *secret, - size_t secretlen, const uint8_t *info, size_t infolen, - const EVP_MD *prf) { - auto pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, nullptr); - if (pctx == nullptr) { - return -1; - } - - auto pctx_d = defer(EVP_PKEY_CTX_free, pctx); - - if (EVP_PKEY_derive_init(pctx) != 1 || - EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) != 1 || - EVP_PKEY_CTX_set_hkdf_md(pctx, prf) != 1 || - EVP_PKEY_CTX_set1_hkdf_salt(pctx, "", 0) != 1 || - EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, secretlen) != 1 || - EVP_PKEY_CTX_add1_hkdf_info(pctx, info, infolen) != 1 || - EVP_PKEY_derive(pctx, dest, &destlen) != 1) { - return -1; - } - - return 0; -} - -int hkdf_expand_label(uint8_t *dest, size_t destlen, const uint8_t *secret, - size_t secretlen, const uint8_t *label, size_t labellen, - const EVP_MD *prf) { - std::array info; - static constexpr const uint8_t LABEL[] = "tls13 "; - - auto p = std::begin(info); - *p++ = destlen / 256; - *p++ = destlen % 256; - *p++ = str_size(LABEL) + labellen; - p = std::copy_n(LABEL, str_size(LABEL), p); - p = std::copy_n(label, labellen, p); - *p++ = 0; - - return hkdf_expand(dest, destlen, secret, secretlen, info.data(), - p - std::begin(info), prf); -} - -int derive_initial_secret(uint8_t *dest, size_t destlen, const uint8_t *secret, - size_t secretlen, const uint8_t *salt, - size_t saltlen) { - return hkdf_extract(dest, destlen, secret, secretlen, salt, saltlen, - EVP_sha256()); -} - -int derive_client_initial_secret(uint8_t *dest, size_t destlen, - const uint8_t *secret, size_t secretlen) { - static constexpr uint8_t LABEL[] = "client in"; - return hkdf_expand_label(dest, destlen, secret, secretlen, LABEL, - str_size(LABEL), EVP_sha256()); -} - -int derive_server_initial_secret(uint8_t *dest, size_t destlen, - const uint8_t *secret, size_t secretlen) { - static constexpr uint8_t LABEL[] = "server in"; - return hkdf_expand_label(dest, destlen, secret, secretlen, LABEL, - str_size(LABEL), EVP_sha256()); -} - -int derive_packet_protection_key(uint8_t *key, size_t &keylen, uint8_t *iv, - size_t &ivlen, const uint8_t *secret, - size_t secretlen, const EVP_CIPHER *aead, - const EVP_MD *prf) { - int rv; - static constexpr uint8_t KEY_LABEL[] = "quic key"; - static constexpr uint8_t IV_LABEL[] = "quic iv"; - - auto req_keylen = aead_key_length(aead); - if (req_keylen > keylen) { - return -1; - } - - keylen = req_keylen; - rv = hkdf_expand_label(key, keylen, secret, secretlen, KEY_LABEL, - str_size(KEY_LABEL), prf); - if (rv != 0) { - return -1; - } - - auto req_ivlen = std::max(static_cast(8), aead_nonce_length(aead)); - if (req_ivlen > ivlen) { - return -1; - } - - ivlen = req_ivlen; - rv = hkdf_expand_label(iv, ivlen, secret, secretlen, IV_LABEL, - str_size(IV_LABEL), prf); - if (rv != 0) { - return -1; - } - - return 0; -} - -int derive_header_protection_key(uint8_t *key, size_t &keylen, - const uint8_t *secret, size_t secretlen, - const EVP_CIPHER *aead, const EVP_MD *prf) { - int rv; - static constexpr uint8_t LABEL[] = "quic hp"; - - auto req_keylen = aead_key_length(aead); - if (req_keylen > keylen) { - return -1; - } - - keylen = req_keylen; - rv = hkdf_expand_label(key, keylen, secret, secretlen, LABEL, str_size(LABEL), - prf); - if (rv != 0) { - return -1; - } - - return 0; -} - -ssize_t encrypt(uint8_t *dest, size_t destlen, const uint8_t *plaintext, - size_t plaintextlen, const uint8_t *key, size_t keylen, - const uint8_t *nonce, size_t noncelen, const uint8_t *ad, - size_t adlen, const EVP_CIPHER *aead) { - auto taglen = aead_tag_length(aead); - - if (destlen < plaintextlen + taglen) { - return -1; - } - - auto actx = EVP_CIPHER_CTX_new(); - if (actx == nullptr) { - return -1; - } - - auto actx_d = defer(EVP_CIPHER_CTX_free, actx); - - if (EVP_EncryptInit_ex(actx, aead, nullptr, nullptr, nullptr) != 1) { - return -1; - } - - if (EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_IVLEN, noncelen, nullptr) != - 1) { - return -1; - } - - if (aead == EVP_aes_128_ccm() && - EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_TAG, taglen, nullptr) != 1) { - return -1; - } - - if (EVP_EncryptInit_ex(actx, nullptr, nullptr, key, nonce) != 1) { - return -1; - } - - size_t outlen = 0; - int len; - - if (aead == EVP_aes_128_ccm() && - EVP_EncryptUpdate(actx, nullptr, &len, nullptr, plaintextlen) != 1) { - return -1; - } - - if (EVP_EncryptUpdate(actx, nullptr, &len, ad, adlen) != 1) { - return -1; - } - - if (EVP_EncryptUpdate(actx, dest, &len, plaintext, plaintextlen) != 1) { - return -1; - } - - outlen = len; - - if (EVP_EncryptFinal_ex(actx, dest + outlen, &len) != 1) { - return -1; - } - - outlen += len; - - assert(outlen + taglen <= destlen); - - if (EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_GET_TAG, taglen, dest + outlen) != - 1) { - return -1; - } - - outlen += taglen; - - return outlen; -} - -ssize_t decrypt(uint8_t *dest, size_t destlen, const uint8_t *ciphertext, - size_t ciphertextlen, const uint8_t *key, size_t keylen, - const uint8_t *nonce, size_t noncelen, const uint8_t *ad, - size_t adlen, const EVP_CIPHER *aead) { - auto taglen = aead_tag_length(aead); - - if (taglen > ciphertextlen || destlen + taglen < ciphertextlen) { - return -1; - } - - ciphertextlen -= taglen; - auto tag = ciphertext + ciphertextlen; - - auto actx = EVP_CIPHER_CTX_new(); - if (actx == nullptr) { - return -1; - } - - auto actx_d = defer(EVP_CIPHER_CTX_free, actx); - - if (EVP_DecryptInit_ex(actx, aead, nullptr, nullptr, nullptr) != 1) { - return -1; - } - - if (EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_IVLEN, noncelen, nullptr) != - 1) { - return -1; - } - - if (aead == EVP_aes_128_ccm() && - EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_TAG, taglen, - const_cast(tag)) != 1) { - return -1; - } - - if (EVP_DecryptInit_ex(actx, nullptr, nullptr, key, nonce) != 1) { - return -1; - } - - size_t outlen; - int len; - - if (aead == EVP_aes_128_ccm() && - EVP_DecryptUpdate(actx, nullptr, &len, nullptr, ciphertextlen) != 1) { - return -1; - } - - if (EVP_DecryptUpdate(actx, nullptr, &len, ad, adlen) != 1) { - return -1; - } - - if (EVP_DecryptUpdate(actx, dest, &len, ciphertext, ciphertextlen) != 1) { - return -1; - } - - outlen = len; - - if (aead == EVP_aes_128_ccm()) { - return outlen; - } - - if (EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_TAG, taglen, - const_cast(tag)) != 1) { - return -1; - } - - if (EVP_DecryptFinal_ex(actx, dest + outlen, &len) != 1) { - return -1; - } - - outlen += len; - - return outlen; -} - -ssize_t hp_mask(uint8_t *dest, size_t destlen, const uint8_t *key, - size_t keylen, const uint8_t *sample, size_t samplelen, - const EVP_CIPHER *cipher) { - static constexpr uint8_t PLAINTEXT[] = "\x00\x00\x00\x00\x00"; - - auto actx = EVP_CIPHER_CTX_new(); - if (actx == nullptr) { - return -1; - } - - auto actx_d = defer(EVP_CIPHER_CTX_free, actx); - - if (EVP_EncryptInit_ex(actx, cipher, nullptr, key, sample) != 1) { - return -1; - } - - size_t outlen = 0; - int len; - - if (EVP_EncryptUpdate(actx, dest, &len, PLAINTEXT, str_size(PLAINTEXT)) != - 1) { - return -1; - } - - assert(len == 5); - - outlen = len; - - if (EVP_EncryptFinal_ex(actx, dest + outlen, &len) != 1) { - return -1; - } - - assert(len == 0); - - return outlen; -} - Error err_transport(int liberr) { if (liberr == NGTCP2_ERR_RECV_VERSION_NEGOTIATION) { return {ErrorType::TransportVersionNegotiation, 0}; diff --git a/src/quic.h b/src/quic.h index b70076e8..1bb3096f 100644 --- a/src/quic.h +++ b/src/quic.h @@ -27,60 +27,10 @@ #include "nghttp2_config.h" -#include +#include "stdint.h" namespace quic { -const EVP_CIPHER *aead(SSL *ssl); -const EVP_CIPHER *hp(SSL *ssl); -const EVP_MD *prf(SSL *ssl); -size_t aead_max_overhead(const EVP_CIPHER *aead); - -int hkdf_extract(uint8_t *dest, size_t destlen, const uint8_t *secret, - size_t secretlen, const uint8_t *salt, size_t saltlen, - const EVP_MD *prf); - -int hkdf_expand(uint8_t *dest, size_t destlen, const uint8_t *secret, - size_t secretlen, const uint8_t *info, size_t infolen, - const EVP_MD *prf); - -int hkdf_expand_label(uint8_t *dest, size_t destlen, const uint8_t *secret, - size_t secretlen, const uint8_t *label, size_t labellen, - const EVP_MD *prf); - -int derive_initial_secret(uint8_t *dest, size_t destlen, const uint8_t *secret, - size_t secretlen, const uint8_t *salt, - size_t saltlen); - -int derive_client_initial_secret(uint8_t *dest, size_t destlen, - const uint8_t *secret, size_t secretlen); - -int derive_server_initial_secret(uint8_t *dest, size_t destlen, - const uint8_t *secret, size_t secretlen); - -int derive_packet_protection_key(uint8_t *key, size_t &keylen, uint8_t *iv, - size_t &ivlen, const uint8_t *secret, - size_t secretlen, const EVP_CIPHER *aead, - const EVP_MD *prf); - -int derive_header_protection_key(uint8_t *key, size_t &keylen, - const uint8_t *secret, size_t secretlen, - const EVP_CIPHER *aead, const EVP_MD *prf); - -ssize_t encrypt(uint8_t *dest, size_t destlen, const uint8_t *plaintext, - size_t plaintextlen, const uint8_t *key, size_t keylen, - const uint8_t *nonce, size_t noncelen, const uint8_t *ad, - size_t adlen, const EVP_CIPHER *aead); - -ssize_t decrypt(uint8_t *dest, size_t destlen, const uint8_t *ciphertext, - size_t ciphertextlen, const uint8_t *key, size_t keylen, - const uint8_t *nonce, size_t noncelen, const uint8_t *ad, - size_t adlen, const EVP_CIPHER *aead); - -ssize_t hp_mask(uint8_t *dest, size_t destlen, const uint8_t *key, - size_t keylen, const uint8_t *sample, size_t samplelen, - const EVP_CIPHER *cipher); - enum class ErrorType { Transport, TransportVersionNegotiation, From 6b8b1524448c02e81e82ee5515dd62795941a273 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 25 Aug 2019 10:43:33 +0900 Subject: [PATCH 016/124] Remove error handling which does not happen --- src/h2load_quic.cc | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index aad45276..87c519b3 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -1019,7 +1019,9 @@ int Client::write_quic() { auto should_break = false; switch (nwrite) { case NGTCP2_ERR_STREAM_DATA_BLOCKED: - if (ngtcp2_conn_get_max_data_left(quic.conn) == 0) { + case NGTCP2_ERR_STREAM_SHUT_WR: + if (nwrite == NGTCP2_ERR_STREAM_DATA_BLOCKED && + ngtcp2_conn_get_max_data_left(quic.conn) == 0) { return 0; } @@ -1028,13 +1030,6 @@ int Client::write_quic() { } should_break = true; break; - case NGTCP2_ERR_EARLY_DATA_REJECTED: - case NGTCP2_ERR_STREAM_SHUT_WR: - case NGTCP2_ERR_STREAM_NOT_FOUND: // This means that stream is - // closed. - assert(0); - // TODO Perhaps, close stream or this should not happen? - break; case NGTCP2_ERR_WRITE_STREAM_MORE: assert(ndatalen > 0); if (s->add_write_offset(stream_id, ndatalen) != 0) { From ff7067f3a3c0b863244929823bb54c022b367eab Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 29 Aug 2019 17:15:59 +0900 Subject: [PATCH 017/124] Compile with the latest ngtcp2 and ngtcp2_crypto_openssl --- src/h2load.cc | 103 +------- src/h2load.h | 37 +-- src/h2load_quic.cc | 572 +++++++++------------------------------------ 3 files changed, 116 insertions(+), 596 deletions(-) diff --git a/src/h2load.cc b/src/h2load.cc index 281b5410..e0f75596 100644 --- a/src/h2load.cc +++ b/src/h2load.cc @@ -1027,12 +1027,9 @@ int Client::connection_made() { if (next_proto) { auto proto = StringRef{next_proto, next_proto_len}; if (config.is_quic()) { - if (util::streq(StringRef{&NGTCP2_ALPN_H3[1]}, proto)) { - auto s = std::make_unique(this); - if (s->init_conn() == -1) { - return -1; - } - session = std::move(s); + assert(session); + if (!util::streq(StringRef{&NGTCP2_ALPN_H3[1]}, proto)) { + return -1; } } else if (util::check_h2_is_selected(proto)) { session = std::make_unique(this); @@ -1043,6 +1040,9 @@ int Client::connection_made() { // Just assign next_proto to selected_proto anyway to show the // negotiation result. selected_proto = proto.str(); + } else if (config.is_quic()) { + std::cerr << "QUIC requires ALPN negotiation" << std::endl; + return -1; } else { std::cout << "No protocol negotiated. Fallback behaviour may be activated" << std::endl; @@ -1778,79 +1778,6 @@ int client_select_next_proto_cb(SSL *ssl, unsigned char **out, } // namespace #endif // !OPENSSL_NO_NEXTPROTONEG -namespace { -int quic_transport_params_add_cb(SSL *ssl, unsigned int ext_type, - unsigned int content, - const unsigned char **out, size_t *outlen, - X509 *x, size_t chainidx, int *al, - void *add_arg) { - auto c = static_cast(SSL_get_app_data(ssl)); - auto conn = c->quic.conn; - - ngtcp2_transport_params params; - - ngtcp2_conn_get_local_transport_params(conn, ¶ms); - - constexpr size_t bufsize = 128; - auto buf = std::make_unique(bufsize); - - auto nwrite = ngtcp2_encode_transport_params( - buf.get(), bufsize, NGTCP2_TRANSPORT_PARAMS_TYPE_CLIENT_HELLO, ¶ms); - if (nwrite < 0) { - std::cerr << "ngtcp2_encode_transport_params: " << ngtcp2_strerror(nwrite) - << std::endl; - *al = SSL_AD_INTERNAL_ERROR; - return -1; - } - - *out = buf.release(); - *outlen = static_cast(nwrite); - - return 1; -} -} // namespace - -namespace { -void quic_transport_params_free_cb(SSL *ssl, unsigned int ext_type, - unsigned int context, - const unsigned char *out, void *add_arg) { - delete[] const_cast(out); -} -} // namespace - -namespace { -int quic_transport_params_parse_cb(SSL *ssl, unsigned int ext_type, - unsigned int context, - const unsigned char *in, size_t inlen, - X509 *x, size_t chainidx, int *al, - void *parse_arg) { - auto c = static_cast(SSL_get_app_data(ssl)); - auto conn = c->quic.conn; - - int rv; - ngtcp2_transport_params params; - - rv = ngtcp2_decode_transport_params( - ¶ms, NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS, in, inlen); - if (rv != 0) { - std::cerr << "ngtcp2_decode_transport_params: " << ngtcp2_strerror(rv) - << std::endl; - *al = SSL_AD_ILLEGAL_PARAMETER; - return -1; - } - - rv = ngtcp2_conn_set_remote_transport_params(conn, ¶ms); - if (rv != 0) { - std::cerr << "ngtcp2_conn_set_remote_transport_params: " - << ngtcp2_strerror(rv) << std::endl; - *al = SSL_AD_ILLEGAL_PARAMETER; - return -1; - } - - return 1; -} -} // namespace - namespace { constexpr char UNIX_PATH_PREFIX[] = "unix:"; } // namespace @@ -2250,8 +2177,6 @@ Options: } } // namespace -extern ngtcp2_crypto_ctx in_crypto_ctx; - int main(int argc, char **argv) { tls::libssl_init(); @@ -2753,20 +2678,6 @@ int main(int argc, char **argv) { if (config.is_quic()) { SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_3_VERSION); SSL_CTX_set_max_proto_version(ssl_ctx, TLS1_3_VERSION); - - SSL_CTX_clear_options(ssl_ctx, SSL_OP_ENABLE_MIDDLEBOX_COMPAT); - SSL_CTX_set_mode(ssl_ctx, SSL_MODE_QUIC_HACK); - - if (SSL_CTX_add_custom_ext( - ssl_ctx, NGTCP2_TLSEXT_QUIC_TRANSPORT_PARAMETERS, - SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS, - quic_transport_params_add_cb, quic_transport_params_free_cb, - nullptr, quic_transport_params_parse_cb, nullptr) != 1) { - std::cerr << "SSL_CTX_add_custom_ext(NGTCP2_TLSEXT_QUIC_TRANSPORT_" - "PARAMETERS) failed: " - << ERR_error_string(ERR_get_error(), nullptr) << std::endl; - exit(EXIT_FAILURE); - } } else if (nghttp2::tls::ssl_ctx_set_proto_versions( ssl_ctx, nghttp2::tls::NGHTTP2_TLS_MIN_VERSION, nghttp2::tls::NGHTTP2_TLS_MAX_VERSION) != 0) { @@ -2909,8 +2820,6 @@ int main(int argc, char **argv) { exit(EXIT_FAILURE); } - ngtcp2_crypto_ctx_initial(&in_crypto_ctx); - resolve_host(); std::cout << "starting benchmark..." << std::endl; diff --git a/src/h2load.h b/src/h2load.h index 6d746b35..c65f3f0f 100644 --- a/src/h2load.h +++ b/src/h2load.h @@ -328,11 +328,6 @@ struct Client { ev_timer pkt_timer; ngtcp2_conn *conn; quic::Error last_error; - ngtcp2_crypto_level tx_crypto_level; - ngtcp2_crypto_level rx_crypto_level; - std::vector server_handshake; - size_t server_handshake_nread; - ngtcp2_crypto_ctx crypto_ctx; // Client never send CRYPTO in Short packet. std::array crypto; size_t max_pktlen; @@ -461,45 +456,21 @@ struct Client { void quic_close_connection(); int quic_setup_initial_crypto(); - int quic_client_initial(); int quic_recv_crypto_data(ngtcp2_crypto_level crypto_level, const uint8_t *data, size_t datalen); int quic_handshake_completed(); - int quic_in_encrypt(uint8_t *dest, const uint8_t *plaintext, - size_t plaintextlen, const uint8_t *key, - const uint8_t *nonce, size_t noncelen, const uint8_t *ad, - size_t adlen); - int quic_in_decrypt(uint8_t *dest, const uint8_t *ciphertext, - size_t ciphertextlen, const uint8_t *key, - const uint8_t *nonce, size_t noncelen, const uint8_t *ad, - size_t adlen); - int quic_encrypt(uint8_t *dest, const uint8_t *plaintext, size_t plaintextlen, - const uint8_t *key, const uint8_t *nonce, size_t noncelen, - const uint8_t *ad, size_t adlen); - int quic_decrypt(uint8_t *dest, const uint8_t *ciphertext, - size_t ciphertextlen, const uint8_t *key, - const uint8_t *nonce, size_t noncelen, const uint8_t *ad, - size_t adlen); - int quic_in_hp_mask(uint8_t *dest, const uint8_t *key, const uint8_t *sample); - int quic_hp_mask(uint8_t *dest, const uint8_t *key, const uint8_t *sample); int quic_recv_stream_data(int64_t stream_id, int fin, const uint8_t *data, size_t datalen); int quic_stream_close(int64_t stream_id, uint64_t app_error_code); int quic_stream_reset(int64_t stream_id, uint64_t app_error_code); int quic_extend_max_local_streams(); - int quic_tls_handshake(bool initial = false); - int quic_read_tls(); - - int quic_on_key(int name, const uint8_t *secret, size_t secretlen); + int quic_on_key(ngtcp2_crypto_level level, const uint8_t *rx_secret, + const uint8_t *tx_secret, size_t secretlen); void quic_set_tls_alert(uint8_t alert); - size_t quic_read_server_handshake(uint8_t *buf, size_t buflen); - int quic_write_server_handshake(ngtcp2_crypto_level crypto_level, - const uint8_t *data, size_t datalen); - void quic_write_client_handshake(const uint8_t *data, size_t datalen); - void quic_write_client_handshake(Crypto &crypto, const uint8_t *data, - size_t datalen); + void quic_write_client_handshake(ngtcp2_crypto_level level, + const uint8_t *data, size_t datalen); int quic_pkt_timeout(); void quic_restart_pkt_timer(); }; diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index 87c519b3..a010a682 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -27,7 +27,6 @@ #include #include -#include #include "h2load_http3_session.h" @@ -37,13 +36,11 @@ namespace { auto randgen = util::make_mt19937(); } // namespace -ngtcp2_crypto_ctx in_crypto_ctx; - namespace { int client_initial(ngtcp2_conn *conn, void *user_data) { auto c = static_cast(user_data); - if (c->quic_client_initial() != 0) { + if (c->quic_recv_crypto_data(NGTCP2_CRYPTO_LEVEL_INITIAL, nullptr, 0) != 0) { return NGTCP2_ERR_CALLBACK_FAILURE; } @@ -51,13 +48,6 @@ int client_initial(ngtcp2_conn *conn, void *user_data) { } } // namespace -int Client::quic_client_initial() { - if (quic_tls_handshake(true) != 0) { - return -1; - } - return 0; -} - namespace { int recv_crypto_data(ngtcp2_conn *conn, ngtcp2_crypto_level crypto_level, uint64_t offset, const uint8_t *data, size_t datalen, @@ -74,20 +64,8 @@ int recv_crypto_data(ngtcp2_conn *conn, ngtcp2_crypto_level crypto_level, int Client::quic_recv_crypto_data(ngtcp2_crypto_level crypto_level, const uint8_t *data, size_t datalen) { - if (quic_write_server_handshake(crypto_level, data, datalen) != 0) { - return -1; - } - - if (!ngtcp2_conn_get_handshake_completed(quic.conn)) { - if (quic_tls_handshake() != 0) { - return -1; - } - return 0; - } - - // SSL_do_handshake() might not consume all data (e.g., - // NewSessionTicket). - return quic_read_tls(); + return ngtcp2_crypto_read_write_crypto_data(quic.conn, ssl, crypto_level, + data, datalen); } namespace { @@ -102,12 +80,7 @@ int handshake_completed(ngtcp2_conn *conn, void *user_data) { } } // namespace -int Client::quic_handshake_completed() { - quic.tx_crypto_level = NGTCP2_CRYPTO_LEVEL_APP; - - // TODO Create Http3Session here. - return connection_made(); -} +int Client::quic_handshake_completed() { return connection_made(); } namespace { int recv_retry(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd, @@ -124,136 +97,6 @@ int recv_retry(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd, } } // namespace -namespace { -int in_encrypt(ngtcp2_conn *conn, uint8_t *dest, const uint8_t *plaintext, - size_t plaintextlen, const uint8_t *key, const uint8_t *nonce, - size_t noncelen, const uint8_t *ad, size_t adlen, - void *user_data) { - auto c = static_cast(user_data); - - if (c->quic_in_encrypt(dest, plaintext, plaintextlen, key, nonce, noncelen, - ad, adlen) != 0) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} -} // namespace - -int Client::quic_in_encrypt(uint8_t *dest, const uint8_t *plaintext, - size_t plaintextlen, const uint8_t *key, - const uint8_t *nonce, size_t noncelen, - const uint8_t *ad, size_t adlen) { - return ngtcp2_crypto_encrypt(dest, &in_crypto_ctx.aead, plaintext, - plaintextlen, key, nonce, noncelen, ad, adlen); -} - -namespace { -int in_decrypt(ngtcp2_conn *conn, uint8_t *dest, const uint8_t *ciphertext, - size_t ciphertextlen, const uint8_t *key, const uint8_t *nonce, - size_t noncelen, const uint8_t *ad, size_t adlen, - void *user_data) { - auto c = static_cast(user_data); - - if (c->quic_in_decrypt(dest, ciphertext, ciphertextlen, key, nonce, noncelen, - ad, adlen) != 0) { - return NGTCP2_ERR_TLS_DECRYPT; - } - - return 0; -} -} // namespace - -int Client::quic_in_decrypt(uint8_t *dest, const uint8_t *ciphertext, - size_t ciphertextlen, const uint8_t *key, - const uint8_t *nonce, size_t noncelen, - const uint8_t *ad, size_t adlen) { - return ngtcp2_crypto_decrypt(dest, &in_crypto_ctx.aead, ciphertext, - ciphertextlen, key, nonce, noncelen, ad, adlen); -} - -namespace { -int encrypt(ngtcp2_conn *conn, uint8_t *dest, const uint8_t *plaintext, - size_t plaintextlen, const uint8_t *key, const uint8_t *nonce, - size_t noncelen, const uint8_t *ad, size_t adlen, void *user_data) { - auto c = static_cast(user_data); - - if (c->quic_encrypt(dest, plaintext, plaintextlen, key, nonce, noncelen, ad, - adlen) != 0) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} -} // namespace - -int Client::quic_encrypt(uint8_t *dest, const uint8_t *plaintext, - size_t plaintextlen, const uint8_t *key, - const uint8_t *nonce, size_t noncelen, - const uint8_t *ad, size_t adlen) { - return ngtcp2_crypto_encrypt(dest, &quic.crypto_ctx.aead, plaintext, - plaintextlen, key, nonce, noncelen, ad, adlen); -} - -namespace { -int decrypt(ngtcp2_conn *conn, uint8_t *dest, const uint8_t *ciphertext, - size_t ciphertextlen, const uint8_t *key, const uint8_t *nonce, - size_t noncelen, const uint8_t *ad, size_t adlen, void *user_data) { - auto c = static_cast(user_data); - - if (c->quic_decrypt(dest, ciphertext, ciphertextlen, key, nonce, noncelen, ad, - adlen) != 0) { - return NGTCP2_ERR_TLS_DECRYPT; - } - - return 0; -} -} // namespace - -int Client::quic_decrypt(uint8_t *dest, const uint8_t *ciphertext, - size_t ciphertextlen, const uint8_t *key, - const uint8_t *nonce, size_t noncelen, - const uint8_t *ad, size_t adlen) { - return ngtcp2_crypto_decrypt(dest, &quic.crypto_ctx.aead, ciphertext, - ciphertextlen, key, nonce, noncelen, ad, adlen); -} - -namespace { -int in_hp_mask(ngtcp2_conn *conn, uint8_t *dest, const uint8_t *key, - const uint8_t *sample, void *user_data) { - auto c = static_cast(user_data); - - if (c->quic_in_hp_mask(dest, key, sample) != 0) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} -} // namespace - -int Client::quic_in_hp_mask(uint8_t *dest, const uint8_t *key, - const uint8_t *sample) { - return ngtcp2_crypto_hp_mask(dest, &in_crypto_ctx.hp, key, sample); -} - -namespace { -int hp_mask(ngtcp2_conn *conn, uint8_t *dest, const uint8_t *key, - const uint8_t *sample, void *user_data) { - auto c = static_cast(user_data); - - if (c->quic_hp_mask(dest, key, sample) != 0) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} -} // namespace - -int Client::quic_hp_mask(uint8_t *dest, const uint8_t *key, - const uint8_t *sample) { - return ngtcp2_crypto_hp_mask(dest, &quic.crypto_ctx.hp, key, sample); -} - namespace { int recv_stream_data(ngtcp2_conn *conn, int64_t stream_id, int fin, uint64_t offset, const uint8_t *data, size_t datalen, @@ -393,119 +236,67 @@ ngtcp2_tstamp timestamp(struct ev_loop *loop) { } // namespace namespace { -int bio_write(BIO *b, const char *buf, int len) { - assert(0); - return -1; -} -} // namespace - -namespace { -int bio_read(BIO *b, char *buf, int len) { - BIO_clear_retry_flags(b); - - auto c = static_cast(BIO_get_data(b)); - - len = c->quic_read_server_handshake(reinterpret_cast(buf), len); - if (len == 0) { - BIO_set_retry_read(b); - return -1; - } - - return len; -} -} // namespace - -namespace { -int bio_puts(BIO *b, const char *str) { return bio_write(b, str, strlen(str)); } -} // namespace - -namespace { -int bio_gets(BIO *b, char *buf, int len) { return -1; } -} // namespace - -namespace { -long bio_ctrl(BIO *b, int cmd, long num, void *ptr) { - switch (cmd) { - case BIO_CTRL_FLUSH: - return 1; - } - - return 0; -} -} // namespace - -namespace { -int bio_create(BIO *b) { - BIO_set_init(b, 1); - return 1; -} -} // namespace - -namespace { -int bio_destroy(BIO *b) { - if (b == nullptr) { - return 0; - } - - return 1; -} -} // namespace - -namespace { -BIO_METHOD *create_bio_method() { - static auto meth = BIO_meth_new(BIO_TYPE_FD, "bio"); - BIO_meth_set_write(meth, bio_write); - BIO_meth_set_read(meth, bio_read); - BIO_meth_set_puts(meth, bio_puts); - BIO_meth_set_gets(meth, bio_gets); - BIO_meth_set_ctrl(meth, bio_ctrl); - BIO_meth_set_create(meth, bio_create); - BIO_meth_set_destroy(meth, bio_destroy); - return meth; -} -} // namespace - -namespace { -int key_cb(SSL *ssl, int name, const unsigned char *secret, size_t secretlen, - void *arg) { - auto c = static_cast(arg); - - if (c->quic_on_key(name, secret, secretlen) != 0) { - return 0; - } - - return 1; -} -} // namespace - -namespace { -void msg_cb(int write_p, int version, int content_type, const void *buf, - size_t len, SSL *ssl, void *arg) { - if (!write_p) { - return; - } - - auto c = static_cast(arg); - auto msg = reinterpret_cast(buf); - - switch (content_type) { - case SSL3_RT_HANDSHAKE: - break; - case SSL3_RT_ALERT: - assert(len == 2); - if (msg[0] != 2 /* FATAL */) { - return; - } - c->quic_set_tls_alert(msg[1]); - return; +ngtcp2_crypto_level from_ossl_level(OSSL_ENCRYPTION_LEVEL ossl_level) { + switch (ossl_level) { + case ssl_encryption_initial: + return NGTCP2_CRYPTO_LEVEL_INITIAL; + case ssl_encryption_early_data: + return NGTCP2_CRYPTO_LEVEL_EARLY; + case ssl_encryption_handshake: + return NGTCP2_CRYPTO_LEVEL_HANDSHAKE; + case ssl_encryption_application: + return NGTCP2_CRYPTO_LEVEL_APP; default: - return; + assert(0); + } +} +} // namespace + +namespace { +int set_encryption_secrets(SSL *ssl, OSSL_ENCRYPTION_LEVEL ossl_level, + const uint8_t *rx_secret, const uint8_t *tx_secret, + size_t secret_len) { + auto c = static_cast(SSL_get_app_data(ssl)); + + if (c->quic_on_key(from_ossl_level(ossl_level), rx_secret, tx_secret, + secret_len) != 0) { + return 0; } - c->quic_write_client_handshake(reinterpret_cast(buf), len); + return 1; } } // namespace +namespace { +int add_handshake_data(SSL *ssl, OSSL_ENCRYPTION_LEVEL ossl_level, + const uint8_t *data, size_t len) { + auto c = static_cast(SSL_get_app_data(ssl)); + c->quic_write_client_handshake(from_ossl_level(ossl_level), data, len); + return 1; +} +} // namespace + +namespace { +int flush_flight(SSL *ssl) { return 1; } +} // namespace + +namespace { +int send_alert(SSL *ssl, enum ssl_encryption_level_t level, uint8_t alert) { + auto c = static_cast(SSL_get_app_data(ssl)); + c->quic_set_tls_alert(alert); + return 1; +} +} // namespace + +namespace { +auto quic_method = SSL_QUIC_METHOD{ + set_encryption_secrets, + add_handshake_data, + flush_flight, + send_alert, +}; +} // namespace + int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, const sockaddr *remote_addr, socklen_t remote_addrlen) { int rv; @@ -513,15 +304,9 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, if (!ssl) { ssl = SSL_new(worker->ssl_ctx); - auto bio = BIO_new(create_bio_method()); - BIO_set_data(bio, this); - - SSL_set_bio(ssl, bio, bio); SSL_set_app_data(ssl, this); SSL_set_connect_state(ssl); - SSL_set_msg_callback(ssl, msg_cb); - SSL_set_msg_callback_arg(ssl, this); - SSL_set_key_callback(ssl, key_cb, this); + SSL_set_quic_method(ssl, &quic_method); } switch (remote_addr->sa_family) { @@ -541,12 +326,9 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, h2load::recv_crypto_data, h2load::handshake_completed, nullptr, // recv_version_negotiation - h2load::in_encrypt, - h2load::in_decrypt, - h2load::encrypt, - h2load::decrypt, - h2load::in_hp_mask, - h2load::hp_mask, + ngtcp2_crypto_encrypt_cb, + ngtcp2_crypto_decrypt_cb, + ngtcp2_crypto_hp_mask_cb, h2load::recv_stream_data, nullptr, // acked_crypto_offset nullptr, // acked_stream_data_offset @@ -600,6 +382,25 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, return -1; } + ngtcp2_transport_params params; + ngtcp2_conn_get_local_transport_params(quic.conn, ¶ms); + + std::array buf; + + auto nwrite = ngtcp2_encode_transport_params( + buf.data(), buf.size(), NGTCP2_TRANSPORT_PARAMS_TYPE_CLIENT_HELLO, + ¶ms); + if (nwrite < 0) { + std::cerr << "ngtcp2_encode_transport_params: " << ngtcp2_strerror(nwrite) + << std::endl; + return -1; + } + + if (SSL_set_quic_transport_params(ssl, buf.data(), nwrite) != 1) { + std::cerr << "SSL_set_quic_transport_params failed" << std::endl; + return -1; + } + rv = quic_setup_initial_crypto(); if (rv != 0) { ngtcp2_conn_del(quic.conn); @@ -648,216 +449,55 @@ void Client::quic_close_connection() { } int Client::quic_setup_initial_crypto() { - std::array tx_secret, rx_secret; auto dcid = ngtcp2_conn_get_dcid(quic.conn); - if (ngtcp2_crypto_derive_initial_secrets(rx_secret.data(), tx_secret.data(), - nullptr, dcid, - NGTCP2_CRYPTO_SIDE_CLIENT) != 0) { - std::cerr << "ngtcp2_crypto_derive_initial_secrets() failed" << std::endl; + + if (ngtcp2_crypto_derive_and_install_initial_key( + quic.conn, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, dcid, NGTCP2_CRYPTO_SIDE_CLIENT) != 0) { + std::cerr << "ngtcp2_crypto_derive_and_install_initial_key() failed" + << std::endl; return -1; } - auto aead = &in_crypto_ctx.aead; - auto md = &in_crypto_ctx.md; - - std::array key, iv, hp; - auto keylen = ngtcp2_crypto_aead_keylen(aead); - auto ivlen = ngtcp2_crypto_packet_protection_ivlen(aead); - auto hplen = keylen; - - if (ngtcp2_crypto_derive_packet_protection_key(key.data(), iv.data(), aead, - md, tx_secret.data(), - tx_secret.size()) != 0) { - return -1; - } - - if (ngtcp2_crypto_derive_header_protection_key( - hp.data(), aead, md, tx_secret.data(), tx_secret.size()) != 0) { - return -1; - } - - ngtcp2_conn_install_initial_tx_keys(quic.conn, key.data(), keylen, iv.data(), - ivlen, hp.data(), hplen); - - if (ngtcp2_crypto_derive_packet_protection_key(key.data(), iv.data(), aead, - md, rx_secret.data(), - rx_secret.size()) != 0) { - return -1; - } - - if (ngtcp2_crypto_derive_header_protection_key( - hp.data(), aead, md, rx_secret.data(), rx_secret.size()) != 0) { - return -1; - } - - ngtcp2_conn_install_initial_rx_keys(quic.conn, key.data(), keylen, iv.data(), - ivlen, hp.data(), hplen); - - return 0; -} - -int Client::quic_on_key(int name, const uint8_t *secret, size_t secretlen) { - switch (name) { - case SSL_KEY_CLIENT_EARLY_TRAFFIC: - case SSL_KEY_CLIENT_HANDSHAKE_TRAFFIC: - case SSL_KEY_SERVER_HANDSHAKE_TRAFFIC: - case SSL_KEY_CLIENT_APPLICATION_TRAFFIC: - case SSL_KEY_SERVER_APPLICATION_TRAFFIC: - break; - default: - return 0; - } - - if (quic.crypto_ctx.aead.native_handle == nullptr) { - ngtcp2_crypto_ctx_tls(&quic.crypto_ctx, ssl); - ngtcp2_conn_set_aead_overhead( - quic.conn, ngtcp2_crypto_aead_taglen(&quic.crypto_ctx.aead)); - } - - auto aead = &quic.crypto_ctx.aead; - auto md = &quic.crypto_ctx.md; - - std::array key, iv, hp; - auto keylen = ngtcp2_crypto_aead_keylen(aead); - auto ivlen = ngtcp2_crypto_packet_protection_ivlen(aead); - auto hplen = keylen; - - if (ngtcp2_crypto_derive_packet_protection_key(key.data(), iv.data(), aead, - md, secret, secretlen) != 0) { - return -1; - } - - if (ngtcp2_crypto_derive_header_protection_key(hp.data(), aead, md, secret, - secretlen) != 0) { - return -1; - } - - switch (name) { - case SSL_KEY_CLIENT_EARLY_TRAFFIC: - ngtcp2_conn_install_early_keys(quic.conn, key.data(), keylen, iv.data(), - ivlen, hp.data(), hplen); - break; - case SSL_KEY_CLIENT_HANDSHAKE_TRAFFIC: - ngtcp2_conn_install_handshake_tx_keys(quic.conn, key.data(), keylen, - iv.data(), ivlen, hp.data(), hplen); - quic.tx_crypto_level = NGTCP2_CRYPTO_LEVEL_HANDSHAKE; - break; - case SSL_KEY_CLIENT_APPLICATION_TRAFFIC: - ngtcp2_conn_install_tx_keys(quic.conn, key.data(), keylen, iv.data(), ivlen, - hp.data(), hplen); - break; - case SSL_KEY_SERVER_HANDSHAKE_TRAFFIC: - ngtcp2_conn_install_handshake_rx_keys(quic.conn, key.data(), keylen, - iv.data(), ivlen, hp.data(), hplen); - quic.rx_crypto_level = NGTCP2_CRYPTO_LEVEL_HANDSHAKE; - break; - case SSL_KEY_SERVER_APPLICATION_TRAFFIC: - ngtcp2_conn_install_rx_keys(quic.conn, key.data(), keylen, iv.data(), ivlen, - hp.data(), hplen); - quic.rx_crypto_level = NGTCP2_CRYPTO_LEVEL_APP; - break; - } - return 0; } -int Client::quic_tls_handshake(bool initial) { - ERR_clear_error(); +int Client::quic_on_key(ngtcp2_crypto_level level, const uint8_t *rx_secret, + const uint8_t *tx_secret, size_t secretlen) { + if (ngtcp2_crypto_derive_and_install_key( + quic.conn, ssl, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + level, rx_secret, tx_secret, secretlen, + NGTCP2_CRYPTO_SIDE_CLIENT) != 0) { + std::cerr << "ngtcp2_crypto_derive_and_install_key() failed" << std::endl; + return -1; + } - int rv; - - rv = SSL_do_handshake(ssl); - if (rv <= 0) { - auto err = SSL_get_error(ssl, rv); - switch (err) { - case SSL_ERROR_WANT_READ: - case SSL_ERROR_WANT_WRITE: - return 0; - case SSL_ERROR_SSL: - std::cerr << "TLS handshake error: " - << ERR_error_string(ERR_get_error(), nullptr) << std::endl; - return -1; - default: - std::cerr << "TLS handshake error: " << err << std::endl; + if (level == NGTCP2_CRYPTO_LEVEL_APP) { + auto s = std::make_unique(this); + if (s->init_conn() == -1) { return -1; } - } - - ngtcp2_conn_handshake_completed(quic.conn); - - if (quic_read_tls() != 0) { - return -1; + session = std::move(s); } return 0; } -int Client::quic_read_tls() { - ERR_clear_error(); - - std::array buf; - size_t nread; - - for (;;) { - auto rv = SSL_read_ex(ssl, buf.data(), buf.size(), &nread); - if (rv == 1) { - continue; - } - auto err = SSL_get_error(ssl, 0); - switch (err) { - case SSL_ERROR_WANT_READ: - case SSL_ERROR_WANT_WRITE: - return 0; - case SSL_ERROR_SSL: - case SSL_ERROR_ZERO_RETURN: - std::cerr << "TLS read error: " - << ERR_error_string(ERR_get_error(), nullptr) << std::endl; - return NGTCP2_ERR_CRYPTO; - default: - std::cerr << "TLS read error: " << err << std::endl; - return NGTCP2_ERR_CRYPTO; - } - } -} - void Client::quic_set_tls_alert(uint8_t alert) { quic.last_error = quic::err_transport_tls(alert); } -size_t Client::quic_read_server_handshake(uint8_t *buf, size_t buflen) { - auto n = std::min(buflen, - quic.server_handshake.size() - quic.server_handshake_nread); - std::copy_n(std::begin(quic.server_handshake) + quic.server_handshake_nread, - n, buf); - quic.server_handshake_nread += n; - return n; -} - -int Client::quic_write_server_handshake(ngtcp2_crypto_level crypto_level, - const uint8_t *data, size_t datalen) { - if (quic.rx_crypto_level != crypto_level) { - std::cerr << "Got crypto level " - << ", want " << quic.rx_crypto_level << std::endl; - return -1; - } - std::copy_n(data, datalen, std::back_inserter(quic.server_handshake)); - return 0; -} - -void Client::quic_write_client_handshake(const uint8_t *data, size_t datalen) { - assert(quic.tx_crypto_level < 2); - quic_write_client_handshake(quic.crypto[quic.tx_crypto_level], data, datalen); -} - -void Client::quic_write_client_handshake(Crypto &crypto, const uint8_t *data, - size_t datalen) { +void Client::quic_write_client_handshake(ngtcp2_crypto_level level, + const uint8_t *data, size_t datalen) { + assert(level < 2); + auto &crypto = quic.crypto[level]; assert(crypto.data.size() >= crypto.datalen + datalen); auto p = std::begin(crypto.data) + crypto.datalen; std::copy_n(data, datalen, p); crypto.datalen += datalen; - ngtcp2_conn_submit_crypto_data(quic.conn, quic.tx_crypto_level, p, datalen); + ngtcp2_conn_submit_crypto_data(quic.conn, level, p, datalen); } void quic_pkt_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) { From 96685638019f4cfdf09368e0f9bc56a2f71c4c5f Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 29 Aug 2019 17:40:22 +0900 Subject: [PATCH 018/124] Update docker build and doc --- README.rst | 6 +++--- docker/Dockerfile | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 6d521efb..e0341c9b 100644 --- a/README.rst +++ b/README.rst @@ -21,7 +21,7 @@ Running h2load against HTTP/3 endpoint In order to build h2load with HTTP/3 support, you have to build ngtcp2, nghttp3 and my patched OpenSSL. -https://github.com/ngtcp2/ngtcp2/tree/draft-20#build-from-git +https://github.com/ngtcp2/ngtcp2/tree/draft-22#build-from-git describes how to build these three software. To run h2load against HTTP/3 server, specify h3-22 ALPN with @@ -29,7 +29,7 @@ To run h2load against HTTP/3 server, specify h3-22 ALPN with .. code-block:: text - $ h2load --npn-list h3-22 https://localhost:4433 + $ h2load --npn-list h3-22 https://127.0.0.1:4433 You can use Dockerfile to skip the tedious build steps to manually pull and build dependencies. In order to build Docker image, do this: @@ -43,7 +43,7 @@ Run h2load: .. code-block:: text - $ docker run --rm -it --network=host nghttp2-quic /usr/local/bin/h2load --npn-list h3-22 https://localhost:4433 + $ docker run --rm -it --network=host nghttp2-quic /usr/local/bin/h2load --npn-list h3-22 https://127.0.0.1:4433 Development Status ------------------ diff --git a/docker/Dockerfile b/docker/Dockerfile index 657fce8e..9549a503 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -5,7 +5,7 @@ RUN /usr/local/bin/clean-install git g++ make binutils autoconf automake autotoo zlib1g libev4 libjemalloc1 libc-ares2 \ ca-certificates psmisc \ python && \ - git clone --depth 1 -b quic-draft-22 https://github.com/tatsuhiro-t/openssl && \ + git clone --depth 1 -b openssl-quic-draft-22 https://github.com/tatsuhiro-t/openssl && \ cd openssl && ./config enable-tls1_3 --openssldir=/etc/ssl && make -j$(nproc) && make install_sw && cd .. && rm -rf openssl && \ git clone --depth 1 https://github.com/ngtcp2/nghttp3 && \ cd nghttp3 && autoreconf -i && \ From 8b5cbf8066ec63d5ebd315490ac722d1b6cc177c Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 29 Aug 2019 17:44:55 +0900 Subject: [PATCH 019/124] Update doc --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index e0341c9b..097c2c5e 100644 --- a/README.rst +++ b/README.rst @@ -16,8 +16,8 @@ An experimental high level C++ library is also available. We have Python bindings of this library, but we do not have full code coverage yet. -Running h2load against HTTP/3 endpoint --------------------------------------- +Running h2load against HTTP/3 server +------------------------------------ In order to build h2load with HTTP/3 support, you have to build ngtcp2, nghttp3 and my patched OpenSSL. From 2da0db70de97bdfcfbcf137deb2eaeb7fd4790a9 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 31 Aug 2019 17:33:00 +0900 Subject: [PATCH 020/124] Fix return value --- src/h2load_quic.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index a010a682..c43ab310 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -106,7 +106,7 @@ int recv_stream_data(ngtcp2_conn *conn, int64_t stream_id, int fin, // TODO Better to do this gracefully rather than // NGTCP2_ERR_CALLBACK_FAILURE. Perhaps, call // ngtcp2_conn_write_application_close() ? - return -1; + return NGTCP2_ERR_CALLBACK_FAILURE; } return 0; } From 33d2a932947b3fcadb47581da75b1e8dcbbe1c6a Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 31 Aug 2019 17:33:11 +0900 Subject: [PATCH 021/124] Add missing acked_stream_data_offset callback --- src/h2load.h | 1 + src/h2load_http3_session.cc | 9 +++++++++ src/h2load_http3_session.h | 1 + src/h2load_quic.cc | 22 +++++++++++++++++++++- 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/h2load.h b/src/h2load.h index c65f3f0f..e01a3c8c 100644 --- a/src/h2load.h +++ b/src/h2load.h @@ -461,6 +461,7 @@ struct Client { int quic_handshake_completed(); int quic_recv_stream_data(int64_t stream_id, int fin, const uint8_t *data, size_t datalen); + int quic_acked_stream_data_offset(int64_t stream_id, size_t datalen); int quic_stream_close(int64_t stream_id, uint64_t app_error_code); int quic_stream_reset(int64_t stream_id, uint64_t app_error_code); int quic_extend_max_local_streams(); diff --git a/src/h2load_http3_session.cc b/src/h2load_http3_session.cc index ca4bbe05..f15168c0 100644 --- a/src/h2load_http3_session.cc +++ b/src/h2load_http3_session.cc @@ -373,4 +373,13 @@ int Http3Session::add_write_offset(int64_t stream_id, size_t ndatalen) { return 0; } +int Http3Session::add_ack_offset(int64_t stream_id, size_t datalen) { + auto rv = nghttp3_conn_add_ack_offset(conn_, stream_id, datalen); + if (rv != 0) { + client_->quic.last_error = quic::err_application(rv); + return -1; + } + return 0; +} + } // namespace h2load diff --git a/src/h2load_http3_session.h b/src/h2load_http3_session.h index f62b219d..48b4f80c 100644 --- a/src/h2load_http3_session.h +++ b/src/h2load_http3_session.h @@ -64,6 +64,7 @@ public: size_t veccnt); int block_stream(int64_t stream_id); int add_write_offset(int64_t stream_id, size_t ndatalen); + int add_ack_offset(int64_t stream_id, size_t datalen); private: Client *client_; diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index c43ab310..984b0bd9 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -126,6 +126,26 @@ int Client::quic_recv_stream_data(int64_t stream_id, int fin, return 0; } +namespace { +int acked_stream_data_offset(ngtcp2_conn *conn, int64_t stream_id, + uint64_t offset, size_t datalen, void *user_data, + void *stream_user_data) { + auto c = static_cast(user_data); + if (c->quic_acked_stream_data_offset(stream_id, datalen) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + return 0; +} +} // namespace + +int Client::quic_acked_stream_data_offset(int64_t stream_id, size_t datalen) { + auto s = static_cast(session.get()); + if (s->add_ack_offset(stream_id, datalen) != 0) { + return -1; + } + return 0; +} + namespace { int stream_close(ngtcp2_conn *conn, int64_t stream_id, uint64_t app_error_code, void *user_data, void *stream_user_data) { @@ -331,7 +351,7 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, ngtcp2_crypto_hp_mask_cb, h2load::recv_stream_data, nullptr, // acked_crypto_offset - nullptr, // acked_stream_data_offset + h2load::acked_stream_data_offset, nullptr, // stream_open h2load::stream_close, nullptr, // recv_stateless_reset From b3a2f8837c06e73232dadcefc1505851988402bc Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 7 Sep 2019 23:27:29 +0900 Subject: [PATCH 022/124] Avoid setting 0 to repeat field --- src/h2load_quic.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index 984b0bd9..c2414762 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -552,8 +552,8 @@ int Client::quic_pkt_timeout() { void Client::quic_restart_pkt_timer() { auto expiry = ngtcp2_conn_get_expiry(quic.conn); auto now = timestamp(worker->loop); - auto t = expiry < now ? 1e-9 - : static_cast(expiry - now) / NGTCP2_SECONDS; + auto t = expiry > now ? static_cast(expiry - now) / NGTCP2_SECONDS + : 1e-9; quic.pkt_timer.repeat = t; ev_timer_again(worker->loop, &quic.pkt_timer); } From 1aae4503030820f783e15dd3393e2493ea57bdbc Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 8 Sep 2019 11:14:50 +0900 Subject: [PATCH 023/124] Handle sending just fine --- src/h2load_quic.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index c2414762..df12f125 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -631,8 +631,8 @@ int Client::write_quic() { auto s = static_cast(session.get()); for (;;) { - int64_t stream_id; - int fin; + int64_t stream_id = -1; + int fin = 0; ssize_t sveccnt = 0; if (ngtcp2_conn_get_max_data_left(quic.conn)) { @@ -643,7 +643,7 @@ int Client::write_quic() { } ssize_t ndatalen; - if (sveccnt == 0) { + if (sveccnt == 0 && stream_id == -1) { auto nwrite = ngtcp2_conn_write_pkt(quic.conn, &ps.path, buf.data(), quic.max_pktlen, timestamp(worker->loop)); @@ -714,7 +714,7 @@ int Client::write_quic() { return 0; } - if (ndatalen > 0) { + if (ndatalen >= 0) { if (s->add_write_offset(stream_id, ndatalen) != 0) { return -1; } From 53a860a5bf650a544f9fbdc4452bf1876f5ce944 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 8 Sep 2019 12:31:37 +0900 Subject: [PATCH 024/124] Simplify write_quic --- src/h2load.cc | 3 ++ src/h2load_quic.cc | 128 ++++++++++++--------------------------------- 2 files changed, 35 insertions(+), 96 deletions(-) diff --git a/src/h2load.cc b/src/h2load.cc index e0f75596..d5a48e5d 100644 --- a/src/h2load.cc +++ b/src/h2load.cc @@ -1362,6 +1362,9 @@ int Client::write_udp(const sockaddr *addr, socklen_t addrlen, if (nwrite < 0) { std::cerr << "sendto: errno=" << errno << std::endl; } + + ev_io_stop(worker->loop, &wev); + return 0; } diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index df12f125..e6f019c4 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -602,32 +602,6 @@ int Client::write_quic() { ngtcp2_path_storage_zero(&ps); - if (!session) { - auto nwrite = - ngtcp2_conn_write_pkt(quic.conn, &ps.path, buf.data(), quic.max_pktlen, - timestamp(worker->loop)); - if (nwrite < 0) { - quic.last_error = quic::err_transport(nwrite); - return -1; - } - - quic_restart_pkt_timer(); - - if (nwrite) { - write_udp(reinterpret_cast(ps.path.remote.addr), - ps.path.remote.addrlen, buf.data(), nwrite); - - ev_io_start(worker->loop, &wev); - return 0; - } - - // session might be initialized during ngtcp2_conn_write_pkt. - if (!session) { - ev_io_stop(worker->loop, &wev); - return 0; - } - } - auto s = static_cast(session.get()); for (;;) { @@ -635,7 +609,7 @@ int Client::write_quic() { int fin = 0; ssize_t sveccnt = 0; - if (ngtcp2_conn_get_max_data_left(quic.conn)) { + if (session && ngtcp2_conn_get_max_data_left(quic.conn)) { sveccnt = s->write_stream(stream_id, fin, vec.data(), vec.size()); if (sveccnt == -1) { return -1; @@ -643,90 +617,52 @@ int Client::write_quic() { } ssize_t ndatalen; - if (sveccnt == 0 && stream_id == -1) { - auto nwrite = - ngtcp2_conn_write_pkt(quic.conn, &ps.path, buf.data(), - quic.max_pktlen, timestamp(worker->loop)); - if (nwrite < 0) { - quic.last_error = quic::err_transport(nwrite); - return -1; - } - - quic_restart_pkt_timer(); - - if (nwrite == 0) { - ev_io_stop(worker->loop, &wev); - return 0; - } - - write_udp(reinterpret_cast(ps.path.remote.addr), - ps.path.remote.addrlen, buf.data(), nwrite); - - ev_io_start(worker->loop, &wev); - - return 0; - } - auto v = vec.data(); auto vcnt = static_cast(sveccnt); - for (;;) { - auto nwrite = ngtcp2_conn_writev_stream( - quic.conn, &ps.path, buf.data(), quic.max_pktlen, &ndatalen, - NGTCP2_WRITE_STREAM_FLAG_MORE, stream_id, fin, - reinterpret_cast(v), vcnt, - timestamp(worker->loop)); - if (nwrite < 0) { - auto should_break = false; - switch (nwrite) { - case NGTCP2_ERR_STREAM_DATA_BLOCKED: - case NGTCP2_ERR_STREAM_SHUT_WR: - if (nwrite == NGTCP2_ERR_STREAM_DATA_BLOCKED && - ngtcp2_conn_get_max_data_left(quic.conn) == 0) { - return 0; - } - if (s->block_stream(stream_id) != 0) { - return -1; - } - should_break = true; - break; - case NGTCP2_ERR_WRITE_STREAM_MORE: - assert(ndatalen > 0); - if (s->add_write_offset(stream_id, ndatalen) != 0) { - return -1; - } - should_break = true; - break; + auto nwrite = ngtcp2_conn_writev_stream( + quic.conn, &ps.path, buf.data(), quic.max_pktlen, &ndatalen, + NGTCP2_WRITE_STREAM_FLAG_MORE, stream_id, fin, + reinterpret_cast(v), vcnt, timestamp(worker->loop)); + if (nwrite < 0) { + switch (nwrite) { + case NGTCP2_ERR_STREAM_DATA_BLOCKED: + case NGTCP2_ERR_STREAM_SHUT_WR: + if (nwrite == NGTCP2_ERR_STREAM_DATA_BLOCKED && + ngtcp2_conn_get_max_data_left(quic.conn) == 0) { + return 0; } - if (should_break) { - break; + if (s->block_stream(stream_id) != 0) { + return -1; } - - quic.last_error = quic::err_transport(nwrite); - return -1; - } - - quic_restart_pkt_timer(); - - if (nwrite == 0) { - ev_io_stop(worker->loop, &wev); - return 0; - } - - if (ndatalen >= 0) { + continue; + case NGTCP2_ERR_WRITE_STREAM_MORE: + assert(ndatalen > 0); if (s->add_write_offset(stream_id, ndatalen) != 0) { return -1; } + continue; } - write_udp(reinterpret_cast(ps.path.remote.addr), - ps.path.remote.addrlen, buf.data(), nwrite); + quic.last_error = quic::err_transport(nwrite); + return -1; + } - ev_io_start(worker->loop, &wev); + quic_restart_pkt_timer(); + if (nwrite == 0) { return 0; } + + if (ndatalen >= 0) { + if (s->add_write_offset(stream_id, ndatalen) != 0) { + return -1; + } + } + + write_udp(reinterpret_cast(ps.path.remote.addr), + ps.path.remote.addrlen, buf.data(), nwrite); } } From f7414700f4164f80ab3d55966354d4dbec5d0f8c Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Mon, 16 Sep 2019 13:28:18 +0900 Subject: [PATCH 025/124] Small adjustment of successful HTTP/3 error code Non-zero successful error code is a bit annoying because ngtcp2 does not know it. Enforcing successful application error code to 0 is a lot simpler. --- src/h2load_quic.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index e6f019c4..445a005e 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -159,7 +159,8 @@ int stream_close(ngtcp2_conn *conn, int64_t stream_id, uint64_t app_error_code, int Client::quic_stream_close(int64_t stream_id, uint64_t app_error_code) { auto s = static_cast(session.get()); - if (s->close_stream(stream_id, app_error_code) != 0) { + if (s->close_stream(stream_id, app_error_code == 0 ? NGHTTP3_HTTP_NO_ERROR + : app_error_code) != 0) { return -1; } return 0; From 655510ce28ad47a5b1a1d89532f0be954fb26a61 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 22 Sep 2019 11:15:32 +0900 Subject: [PATCH 026/124] h3-23 --- README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 097c2c5e..59e1a98c 100644 --- a/README.rst +++ b/README.rst @@ -24,12 +24,12 @@ ngtcp2, nghttp3 and my patched OpenSSL. https://github.com/ngtcp2/ngtcp2/tree/draft-22#build-from-git describes how to build these three software. -To run h2load against HTTP/3 server, specify h3-22 ALPN with +To run h2load against HTTP/3 server, specify h3-23 ALPN with ``--npn-list`` option like so: .. code-block:: text - $ h2load --npn-list h3-22 https://127.0.0.1:4433 + $ h2load --npn-list h3-23 https://127.0.0.1:4433 You can use Dockerfile to skip the tedious build steps to manually pull and build dependencies. In order to build Docker image, do this: @@ -43,7 +43,7 @@ Run h2load: .. code-block:: text - $ docker run --rm -it --network=host nghttp2-quic /usr/local/bin/h2load --npn-list h3-22 https://127.0.0.1:4433 + $ docker run --rm -it --network=host nghttp2-quic /usr/local/bin/h2load --npn-list h3-23 https://127.0.0.1:4433 Development Status ------------------ From 610add1f593acaf373684362b648ef23a20723ff Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 2 Oct 2019 23:57:47 +0900 Subject: [PATCH 027/124] Send SNI --- src/h2load.cc | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/h2load.cc b/src/h2load.cc index d5a48e5d..9654a295 100644 --- a/src/h2load.cc +++ b/src/h2load.cc @@ -512,27 +512,29 @@ int Client::make_socket(addrinfo *addr) { std::cerr << "quic_init failed" << std::endl; return -1; } - - return 0; - } - - fd = util::create_nonblock_socket(addr->ai_family); - if (fd == -1) { - return -1; - } - if (config.scheme == "https") { - if (!ssl) { - ssl = SSL_new(worker->ssl_ctx); + } else { + fd = util::create_nonblock_socket(addr->ai_family); + if (fd == -1) { + return -1; } + if (config.scheme == "https") { + if (!ssl) { + ssl = SSL_new(worker->ssl_ctx); + } - SSL_set_fd(ssl, fd); - SSL_set_connect_state(ssl); + SSL_set_fd(ssl, fd); + SSL_set_connect_state(ssl); + } } if (ssl && !util::numeric_host(config.host.c_str())) { SSL_set_tlsext_host_name(ssl, config.host.c_str()); } + if (config.is_quic()) { + return 0; + } + rv = ::connect(fd, addr->ai_addr, addr->ai_addrlen); if (rv != 0 && errno != EINPROGRESS) { if (ssl) { From 78c2c33b9ece976c008cbba1860a2c6919ecbbf1 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 3 Oct 2019 10:11:37 +0900 Subject: [PATCH 028/124] Compile with the latest ngtcp2 --- src/h2load_quic.cc | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index 445a005e..52f17f21 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -383,12 +383,13 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, settings.log_printf = debug_log_printf; } settings.initial_ts = timestamp(worker->loop); - settings.max_stream_data_bidi_local = (1 << config->window_bits) - 1; - settings.max_stream_data_uni = (1 << config->window_bits) - 1; - settings.max_data = (1 << config->connection_window_bits) - 1; - settings.max_streams_bidi = 0; - settings.max_streams_uni = 100; - settings.idle_timeout = 30 * NGTCP2_SECONDS; + auto ¶ms = settings.transport_params; + params.initial_max_stream_data_bidi_local = (1 << config->window_bits) - 1; + params.initial_max_stream_data_uni = (1 << config->window_bits) - 1; + params.initial_max_data = (1 << config->connection_window_bits) - 1; + params.initial_max_streams_bidi = 0; + params.initial_max_streams_uni = 100; + params.idle_timeout = 30 * NGTCP2_SECONDS; auto path = ngtcp2_path{ {local_addrlen, @@ -403,9 +404,6 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, return -1; } - ngtcp2_transport_params params; - ngtcp2_conn_get_local_transport_params(quic.conn, ¶ms); - std::array buf; auto nwrite = ngtcp2_encode_transport_params( From 73fd20a608967ef62ceffc23aad89921b14e486b Mon Sep 17 00:00:00 2001 From: Lucas Pardue Date: Thu, 3 Oct 2019 16:24:36 +0100 Subject: [PATCH 029/124] Add SSLKEYLOGFILE support --- src/h2load.cc | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/h2load.cc b/src/h2load.cc index 9654a295..ebb7ec24 100644 --- a/src/h2load.cc +++ b/src/h2load.cc @@ -75,6 +75,15 @@ bool recorded(const std::chrono::steady_clock::time_point &t) { } } // namespace +namespace { +std::ofstream keylog_file; +void keylog_callback(const SSL *ssl, const char *line) { + keylog_file.write(line, strlen(line)); + keylog_file.put('\n'); + keylog_file.flush(); +} +} // namespace + Config::Config() : ciphers(tls::DEFAULT_CIPHER_LIST), tls13_ciphers("TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_" @@ -2723,6 +2732,14 @@ int main(int argc, char **argv) { SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size()); #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + auto keylog_filename = getenv("SSLKEYLOGFILE"); + if (keylog_filename) { + keylog_file.open(keylog_filename, std::ios_base::app); + if (keylog_file) { + SSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback); + } + } + std::string user_agent = "h2load nghttp2/" NGHTTP2_VERSION; Headers shared_nva; shared_nva.emplace_back(":scheme", config.scheme); From 558970e281e9d2bd663490914d7385770556c993 Mon Sep 17 00:00:00 2001 From: Dmitri Tikhonov Date: Mon, 4 Nov 2019 08:41:36 -0500 Subject: [PATCH 030/124] Update Dockerfile to use I-D 23 branches of ngtcp2 and openssl --- docker/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 9549a503..60a444f3 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -5,13 +5,13 @@ RUN /usr/local/bin/clean-install git g++ make binutils autoconf automake autotoo zlib1g libev4 libjemalloc1 libc-ares2 \ ca-certificates psmisc \ python && \ - git clone --depth 1 -b openssl-quic-draft-22 https://github.com/tatsuhiro-t/openssl && \ + git clone --depth 1 -b openssl-quic-draft-23 https://github.com/tatsuhiro-t/openssl && \ cd openssl && ./config enable-tls1_3 --openssldir=/etc/ssl && make -j$(nproc) && make install_sw && cd .. && rm -rf openssl && \ git clone --depth 1 https://github.com/ngtcp2/nghttp3 && \ cd nghttp3 && autoreconf -i && \ ./configure --enable-lib-only && \ make -j$(nproc) && make install-strip && cd .. && rm -rf nghttp3 && \ - git clone --depth 1 -b draft-22 https://github.com/ngtcp2/ngtcp2 && \ + git clone --depth 1 -b master https://github.com/ngtcp2/ngtcp2 && \ cd ngtcp2 && autoreconf -i && \ ./configure && \ make -j$(nproc) && make install-strip && cd .. && rm -rf ngtcp2 && \ From 747edb3a99c929182543b69847f1d9aa85512074 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 6 Nov 2019 22:49:22 +0900 Subject: [PATCH 031/124] quic draft-24 --- README.rst | 6 +++--- docker/Dockerfile | 4 ++-- src/h2load.h | 3 +++ src/h2load_http3_session.cc | 2 +- src/h2load_quic.cc | 37 +++++++++++++++++++++++++++++++++++-- 5 files changed, 44 insertions(+), 8 deletions(-) diff --git a/README.rst b/README.rst index 59e1a98c..c8a93acf 100644 --- a/README.rst +++ b/README.rst @@ -24,12 +24,12 @@ ngtcp2, nghttp3 and my patched OpenSSL. https://github.com/ngtcp2/ngtcp2/tree/draft-22#build-from-git describes how to build these three software. -To run h2load against HTTP/3 server, specify h3-23 ALPN with +To run h2load against HTTP/3 server, specify h3-24 ALPN with ``--npn-list`` option like so: .. code-block:: text - $ h2load --npn-list h3-23 https://127.0.0.1:4433 + $ h2load --npn-list h3-24 https://127.0.0.1:4433 You can use Dockerfile to skip the tedious build steps to manually pull and build dependencies. In order to build Docker image, do this: @@ -43,7 +43,7 @@ Run h2load: .. code-block:: text - $ docker run --rm -it --network=host nghttp2-quic /usr/local/bin/h2load --npn-list h3-23 https://127.0.0.1:4433 + $ docker run --rm -it --network=host nghttp2-quic /usr/local/bin/h2load --npn-list h3-24 https://127.0.0.1:4433 Development Status ------------------ diff --git a/docker/Dockerfile b/docker/Dockerfile index 60a444f3..d08dc5e0 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -5,13 +5,13 @@ RUN /usr/local/bin/clean-install git g++ make binutils autoconf automake autotoo zlib1g libev4 libjemalloc1 libc-ares2 \ ca-certificates psmisc \ python && \ - git clone --depth 1 -b openssl-quic-draft-23 https://github.com/tatsuhiro-t/openssl && \ + git clone --depth 1 -b openssl-quic-draft-24 https://github.com/tatsuhiro-t/openssl && \ cd openssl && ./config enable-tls1_3 --openssldir=/etc/ssl && make -j$(nproc) && make install_sw && cd .. && rm -rf openssl && \ git clone --depth 1 https://github.com/ngtcp2/nghttp3 && \ cd nghttp3 && autoreconf -i && \ ./configure --enable-lib-only && \ make -j$(nproc) && make install-strip && cd .. && rm -rf nghttp3 && \ - git clone --depth 1 -b master https://github.com/ngtcp2/ngtcp2 && \ + git clone --depth 1 https://github.com/ngtcp2/ngtcp2 && \ cd ngtcp2 && autoreconf -i && \ ./configure && \ make -j$(nproc) && make install-strip && cd .. && rm -rf ngtcp2 && \ diff --git a/src/h2load.h b/src/h2load.h index e01a3c8c..928578aa 100644 --- a/src/h2load.h +++ b/src/h2load.h @@ -332,6 +332,8 @@ struct Client { std::array crypto; size_t max_pktlen; bool close_requested; + std::vector rx_secret; + std::vector tx_secret; } quic; ev_timer request_timeout_watcher; addrinfo *next_addr; @@ -465,6 +467,7 @@ struct Client { int quic_stream_close(int64_t stream_id, uint64_t app_error_code); int quic_stream_reset(int64_t stream_id, uint64_t app_error_code); int quic_extend_max_local_streams(); + int quic_update_key(); int quic_on_key(ngtcp2_crypto_level level, const uint8_t *rx_secret, const uint8_t *tx_secret, size_t secretlen); diff --git a/src/h2load_http3_session.cc b/src/h2load_http3_session.cc index f15168c0..6a3378bf 100644 --- a/src/h2load_http3_session.cc +++ b/src/h2load_http3_session.cc @@ -113,7 +113,7 @@ int stream_close(nghttp3_conn *conn, int64_t stream_id, uint64_t app_error_code, } // namespace int Http3Session::stream_close(int64_t stream_id, uint64_t app_error_code) { - client_->on_stream_close(stream_id, app_error_code == NGHTTP3_HTTP_NO_ERROR); + client_->on_stream_close(stream_id, app_error_code == NGHTTP3_H3_NO_ERROR); return 0; } diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index 52f17f21..2cbb0178 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -159,7 +159,7 @@ int stream_close(ngtcp2_conn *conn, int64_t stream_id, uint64_t app_error_code, int Client::quic_stream_close(int64_t stream_id, uint64_t app_error_code) { auto s = static_cast(session.get()); - if (s->close_stream(stream_id, app_error_code == 0 ? NGHTTP3_HTTP_NO_ERROR + if (s->close_stream(stream_id, app_error_code == 0 ? NGHTTP3_H3_NO_ERROR : app_error_code) != 0) { return -1; } @@ -222,6 +222,36 @@ int get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, uint8_t *token, } } // namespace +namespace { +int update_key(ngtcp2_conn *conn, void *user_data) { + auto c = static_cast(user_data); + + if (c->quic_update_key() != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Client::quic_update_key() { + std::array rx_secret, tx_secret; + + if (ngtcp2_crypto_update_and_install_key( + quic.conn, rx_secret.data(), tx_secret.data(), nullptr, nullptr, + nullptr, nullptr, quic.rx_secret.data(), quic.tx_secret.data(), + quic.rx_secret.size()) != 0) { + return -1; + } + + quic.rx_secret.assign(std::begin(rx_secret), + std::begin(rx_secret) + quic.rx_secret.size()); + quic.tx_secret.assign(std::begin(tx_secret), + std::begin(tx_secret) + quic.tx_secret.size()); + + return 0; +} + namespace { void debug_log_printf(void *user_data, const char *fmt, ...) { va_list ap; @@ -362,7 +392,7 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, nullptr, // rand get_new_connection_id, nullptr, // remove_connection_id - nullptr, // update_key + update_key, nullptr, // path_validation select_preferred_addr, h2load::stream_reset, @@ -497,6 +527,9 @@ int Client::quic_on_key(ngtcp2_crypto_level level, const uint8_t *rx_secret, return -1; } session = std::move(s); + + quic.rx_secret.assign(rx_secret, rx_secret + secretlen); + quic.tx_secret.assign(tx_secret, tx_secret + secretlen); } return 0; From 4621f88441f0f536c399a7da290598b8285a3dee Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Fri, 8 Nov 2019 00:06:09 +0900 Subject: [PATCH 032/124] Follow ngtcp2 API update --- src/h2load.h | 3 ++- src/h2load_quic.cc | 16 +++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/h2load.h b/src/h2load.h index 928578aa..b6ef65c8 100644 --- a/src/h2load.h +++ b/src/h2load.h @@ -467,7 +467,8 @@ struct Client { int quic_stream_close(int64_t stream_id, uint64_t app_error_code); int quic_stream_reset(int64_t stream_id, uint64_t app_error_code); int quic_extend_max_local_streams(); - int quic_update_key(); + int quic_update_key(uint8_t *rx_key, uint8_t *rx_iv, uint8_t *tx_key, + uint8_t *tx_iv); int quic_on_key(ngtcp2_crypto_level level, const uint8_t *rx_secret, const uint8_t *tx_secret, size_t secretlen); diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index 2cbb0178..3398cb18 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -223,10 +223,11 @@ int get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, uint8_t *token, } // namespace namespace { -int update_key(ngtcp2_conn *conn, void *user_data) { +int update_key(ngtcp2_conn *conn, uint8_t *rx_key, uint8_t *rx_iv, + uint8_t *tx_key, uint8_t *tx_iv, void *user_data) { auto c = static_cast(user_data); - if (c->quic_update_key() != 0) { + if (c->quic_update_key(rx_key, rx_iv, tx_key, tx_iv) != 0) { return NGTCP2_ERR_CALLBACK_FAILURE; } @@ -234,13 +235,14 @@ int update_key(ngtcp2_conn *conn, void *user_data) { } } // namespace -int Client::quic_update_key() { +int Client::quic_update_key(uint8_t *rx_key, uint8_t *rx_iv, uint8_t *tx_key, + uint8_t *tx_iv) { std::array rx_secret, tx_secret; - if (ngtcp2_crypto_update_and_install_key( - quic.conn, rx_secret.data(), tx_secret.data(), nullptr, nullptr, - nullptr, nullptr, quic.rx_secret.data(), quic.tx_secret.data(), - quic.rx_secret.size()) != 0) { + if (ngtcp2_crypto_update_key(quic.conn, rx_secret.data(), tx_secret.data(), + rx_key, rx_iv, tx_key, tx_iv, + quic.rx_secret.data(), quic.tx_secret.data(), + quic.rx_secret.size()) != 0) { return -1; } From b3fbebed552fdce1c4d65cbb2d26dae56f53fd9f Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 9 Nov 2019 18:00:46 +0900 Subject: [PATCH 033/124] Use correct type --- src/h2load_quic.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index 3398cb18..2a9b3952 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -470,7 +470,7 @@ void Client::quic_close_connection() { } std::array buf; - ssize_t nwrite; + ngtcp2_ssize nwrite; ngtcp2_path_storage ps; ngtcp2_path_storage_zero(&ps); @@ -650,7 +650,7 @@ int Client::write_quic() { } } - ssize_t ndatalen; + ngtcp2_ssize ndatalen; auto v = vec.data(); auto vcnt = static_cast(sveccnt); From c591ab5e6f6572462339b96eb1815a0dac020a5b Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 20 Nov 2019 23:40:21 +0900 Subject: [PATCH 034/124] Only count STREAM data as bytes_total --- src/h2load_quic.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index 2a9b3952..d81ca340 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -114,6 +114,10 @@ int recv_stream_data(ngtcp2_conn *conn, int64_t stream_id, int fin, int Client::quic_recv_stream_data(int64_t stream_id, int fin, const uint8_t *data, size_t datalen) { + if (worker->current_phase == Phase::MAIN_DURATION) { + worker->stats.bytes_total += datalen; + } + auto s = static_cast(session.get()); auto nconsumed = s->read_stream(stream_id, data, datalen, fin); if (nconsumed == -1) { From a93eb8b8f55209080584b74ff765bea743328ec6 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 20 Nov 2019 23:40:51 +0900 Subject: [PATCH 035/124] Optimize QUIC write --- src/h2load.cc | 3 +-- src/h2load_quic.cc | 41 +++++++++++++++++++++++------------------ 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/h2load.cc b/src/h2load.cc index ebb7ec24..2deb21ef 100644 --- a/src/h2load.cc +++ b/src/h2load.cc @@ -214,8 +214,7 @@ void readcb(struct ev_loop *loop, ev_io *w, int revents) { delete client; return; } - writecb(loop, &client->wev, revents); - // client->disconnect() and client->fail() may be called + client->signal_write(); } } // namespace diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index d81ca340..5f69d278 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -601,35 +601,40 @@ int Client::read_quic() { sockaddr_union su; socklen_t addrlen = sizeof(su); int rv; + size_t pktcnt = 0; - auto nread = - recvfrom(fd, buf.data(), buf.size(), MSG_DONTWAIT, &su.sa, &addrlen); - if (nread == -1) { - return 0; - } + for (;;) { + auto nread = + recvfrom(fd, buf.data(), buf.size(), MSG_DONTWAIT, &su.sa, &addrlen); + if (nread == -1) { + return 0; + } - assert(quic.conn); + assert(quic.conn); - auto path = ngtcp2_path{ - {local_addr.len, reinterpret_cast(&local_addr.su.sa)}, - {addrlen, reinterpret_cast(&su.sa)}, - }; + auto path = ngtcp2_path{ + {local_addr.len, reinterpret_cast(&local_addr.su.sa)}, + {addrlen, reinterpret_cast(&su.sa)}, + }; - rv = ngtcp2_conn_read_pkt(quic.conn, &path, buf.data(), nread, - timestamp(worker->loop)); - if (rv != 0) { - std::cerr << "ngtcp2_conn_read_pkt: " << ngtcp2_strerror(rv) << std::endl; - return -1; - } + rv = ngtcp2_conn_read_pkt(quic.conn, &path, buf.data(), nread, + timestamp(worker->loop)); + if (rv != 0) { + std::cerr << "ngtcp2_conn_read_pkt: " << ngtcp2_strerror(rv) << std::endl; + return -1; + } - if (worker->current_phase == Phase::MAIN_DURATION) { - worker->stats.bytes_total += nread; + if (pktcnt == 10) { + break; + } } return 0; } int Client::write_quic() { + ev_io_stop(worker->loop, &wev); + if (quic.close_requested) { return -1; } From 1684091234b5b52788f7b42e96b28121d007f829 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 21 Nov 2019 23:34:58 +0900 Subject: [PATCH 036/124] Bump base image and use OpenSSL_1_1_1d-quic-draft-24 --- docker/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index d08dc5e0..9ad6811b 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,11 +1,11 @@ -FROM k8s.gcr.io/debian-base-amd64:1.0.0 +FROM k8s.gcr.io/debian-base-amd64:v2.0.0 RUN /usr/local/bin/clean-install git g++ make binutils autoconf automake autotools-dev libtool pkg-config \ zlib1g-dev libev-dev libjemalloc-dev ruby-dev libc-ares-dev bison \ zlib1g libev4 libjemalloc1 libc-ares2 \ ca-certificates psmisc \ python && \ - git clone --depth 1 -b openssl-quic-draft-24 https://github.com/tatsuhiro-t/openssl && \ + git clone --depth 1 -b OpenSSL_1_1_1d-quic-draft-24 https://github.com/tatsuhiro-t/openssl && \ cd openssl && ./config enable-tls1_3 --openssldir=/etc/ssl && make -j$(nproc) && make install_sw && cd .. && rm -rf openssl && \ git clone --depth 1 https://github.com/ngtcp2/nghttp3 && \ cd nghttp3 && autoreconf -i && \ From 9701e5e6e448153621eb68d40cc26a0ae5e127d9 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 28 Dec 2019 10:09:17 +0900 Subject: [PATCH 037/124] Fix compile error --- src/h2load.h | 2 -- src/h2load_quic.cc | 37 +------------------------------------ third-party/neverbleed | 2 +- 3 files changed, 2 insertions(+), 39 deletions(-) diff --git a/src/h2load.h b/src/h2load.h index b6ef65c8..270ee23b 100644 --- a/src/h2load.h +++ b/src/h2load.h @@ -332,8 +332,6 @@ struct Client { std::array crypto; size_t max_pktlen; bool close_requested; - std::vector rx_secret; - std::vector tx_secret; } quic; ev_timer request_timeout_watcher; addrinfo *next_addr; diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index 5f69d278..f845b845 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -226,38 +226,6 @@ int get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, uint8_t *token, } } // namespace -namespace { -int update_key(ngtcp2_conn *conn, uint8_t *rx_key, uint8_t *rx_iv, - uint8_t *tx_key, uint8_t *tx_iv, void *user_data) { - auto c = static_cast(user_data); - - if (c->quic_update_key(rx_key, rx_iv, tx_key, tx_iv) != 0) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} -} // namespace - -int Client::quic_update_key(uint8_t *rx_key, uint8_t *rx_iv, uint8_t *tx_key, - uint8_t *tx_iv) { - std::array rx_secret, tx_secret; - - if (ngtcp2_crypto_update_key(quic.conn, rx_secret.data(), tx_secret.data(), - rx_key, rx_iv, tx_key, tx_iv, - quic.rx_secret.data(), quic.tx_secret.data(), - quic.rx_secret.size()) != 0) { - return -1; - } - - quic.rx_secret.assign(std::begin(rx_secret), - std::begin(rx_secret) + quic.rx_secret.size()); - quic.tx_secret.assign(std::begin(tx_secret), - std::begin(tx_secret) + quic.tx_secret.size()); - - return 0; -} - namespace { void debug_log_printf(void *user_data, const char *fmt, ...) { va_list ap; @@ -398,7 +366,7 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, nullptr, // rand get_new_connection_id, nullptr, // remove_connection_id - update_key, + ngtcp2_crypto_update_key_cb, nullptr, // path_validation select_preferred_addr, h2load::stream_reset, @@ -533,9 +501,6 @@ int Client::quic_on_key(ngtcp2_crypto_level level, const uint8_t *rx_secret, return -1; } session = std::move(s); - - quic.rx_secret.assign(rx_secret, rx_secret + secretlen); - quic.tx_secret.assign(tx_secret, tx_secret + secretlen); } return 0; diff --git a/third-party/neverbleed b/third-party/neverbleed index b967ca05..5cf5da80 160000 --- a/third-party/neverbleed +++ b/third-party/neverbleed @@ -1 +1 @@ -Subproject commit b967ca054f48a36f82d8fcdd32e54ec5144f2751 +Subproject commit 5cf5da80b715223d2614374aec40f2663dc68d6a From 5c0da486b9995399a65bbafc820e76cf2e5a3073 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 30 Jan 2020 17:01:36 +0900 Subject: [PATCH 038/124] Remove unused member function declaration --- src/h2load.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/h2load.h b/src/h2load.h index 270ee23b..e01a3c8c 100644 --- a/src/h2load.h +++ b/src/h2load.h @@ -465,8 +465,6 @@ struct Client { int quic_stream_close(int64_t stream_id, uint64_t app_error_code); int quic_stream_reset(int64_t stream_id, uint64_t app_error_code); int quic_extend_max_local_streams(); - int quic_update_key(uint8_t *rx_key, uint8_t *rx_iv, uint8_t *tx_key, - uint8_t *tx_iv); int quic_on_key(ngtcp2_crypto_level level, const uint8_t *rx_secret, const uint8_t *tx_secret, size_t secretlen); From 0b61e46f95b7be97197edae6095c169128dc1e2d Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 30 Jan 2020 17:01:56 +0900 Subject: [PATCH 039/124] draft-25 --- README.rst | 10 +++++----- src/h2load_quic.cc | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index c8a93acf..97929866 100644 --- a/README.rst +++ b/README.rst @@ -21,15 +21,15 @@ Running h2load against HTTP/3 server In order to build h2load with HTTP/3 support, you have to build ngtcp2, nghttp3 and my patched OpenSSL. -https://github.com/ngtcp2/ngtcp2/tree/draft-22#build-from-git -describes how to build these three software. +https://github.com/ngtcp2/ngtcp2#build-from-git describes how to build +these three software. -To run h2load against HTTP/3 server, specify h3-24 ALPN with +To run h2load against HTTP/3 server, specify h3-25 ALPN with ``--npn-list`` option like so: .. code-block:: text - $ h2load --npn-list h3-24 https://127.0.0.1:4433 + $ h2load --npn-list h3-25 https://127.0.0.1:4433 You can use Dockerfile to skip the tedious build steps to manually pull and build dependencies. In order to build Docker image, do this: @@ -43,7 +43,7 @@ Run h2load: .. code-block:: text - $ docker run --rm -it --network=host nghttp2-quic /usr/local/bin/h2load --npn-list h3-24 https://127.0.0.1:4433 + $ docker run --rm -it --network=host nghttp2-quic /usr/local/bin/h2load --npn-list h3-25 https://127.0.0.1:4433 Development Status ------------------ diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index f845b845..5cd7990f 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -393,7 +393,7 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, params.initial_max_data = (1 << config->connection_window_bits) - 1; params.initial_max_streams_bidi = 0; params.initial_max_streams_uni = 100; - params.idle_timeout = 30 * NGTCP2_SECONDS; + params.max_idle_timeout = 30 * NGTCP2_SECONDS; auto path = ngtcp2_path{ {local_addrlen, From c724585bcecc177e83b4b9e4b90cba8fa6a18af4 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 30 Jan 2020 17:03:59 +0900 Subject: [PATCH 040/124] Update Dockerfile --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 9ad6811b..cb20b502 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -5,7 +5,7 @@ RUN /usr/local/bin/clean-install git g++ make binutils autoconf automake autotoo zlib1g libev4 libjemalloc1 libc-ares2 \ ca-certificates psmisc \ python && \ - git clone --depth 1 -b OpenSSL_1_1_1d-quic-draft-24 https://github.com/tatsuhiro-t/openssl && \ + git clone --depth 1 -b OpenSSL_1_1_1d-quic-draft-25 https://github.com/tatsuhiro-t/openssl && \ cd openssl && ./config enable-tls1_3 --openssldir=/etc/ssl && make -j$(nproc) && make install_sw && cd .. && rm -rf openssl && \ git clone --depth 1 https://github.com/ngtcp2/nghttp3 && \ cd nghttp3 && autoreconf -i && \ From 2722119776f3053248d0ac2fdafde9e1ef69c1a4 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 12 Feb 2020 11:22:19 +0900 Subject: [PATCH 041/124] Handle stream limit increment --- src/h2load_http3_session.cc | 16 ++++++++++++++-- third-party/neverbleed | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/h2load_http3_session.cc b/src/h2load_http3_session.cc index 6a3378bf..aac8df45 100644 --- a/src/h2load_http3_session.cc +++ b/src/h2load_http3_session.cc @@ -113,6 +113,10 @@ int stream_close(nghttp3_conn *conn, int64_t stream_id, uint64_t app_error_code, } // namespace int Http3Session::stream_close(int64_t stream_id, uint64_t app_error_code) { + if (!ngtcp2_is_bidi_stream(stream_id)) { + assert(!ngtcp2_conn_is_local_stream(client_->quic.conn, stream_id)); + ngtcp2_conn_extend_max_streams_uni(client_->quic.conn, 1); + } client_->on_stream_close(stream_id, app_error_code == NGHTTP3_H3_NO_ERROR); return 0; } @@ -208,10 +212,18 @@ int Http3Session::send_stop_sending(int64_t stream_id, int Http3Session::close_stream(int64_t stream_id, uint64_t app_error_code) { auto rv = nghttp3_conn_close_stream(conn_, stream_id, app_error_code); - if (rv != 0) { + switch (rv) { + case 0: + return 0; + case NGHTTP3_ERR_STREAM_NOT_FOUND: + if (!ngtcp2_is_bidi_stream(stream_id)) { + assert(!ngtcp2_conn_is_local_stream(client_->quic.conn, stream_id)); + ngtcp2_conn_extend_max_streams_uni(client_->quic.conn, 1); + } + return 0; + default: return -1; } - return 0; } int Http3Session::reset_stream(int64_t stream_id) { diff --git a/third-party/neverbleed b/third-party/neverbleed index 5cf5da80..b967ca05 160000 --- a/third-party/neverbleed +++ b/third-party/neverbleed @@ -1 +1 @@ -Subproject commit 5cf5da80b715223d2614374aec40f2663dc68d6a +Subproject commit b967ca054f48a36f82d8fcdd32e54ec5144f2751 From 76009ce7b985aa9d0de5f5bbce40312a7f90bf8b Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 1 Mar 2020 12:42:59 +0900 Subject: [PATCH 042/124] draft-27 --- README.rst | 6 +++--- docker/Dockerfile | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 97929866..605d8560 100644 --- a/README.rst +++ b/README.rst @@ -24,12 +24,12 @@ ngtcp2, nghttp3 and my patched OpenSSL. https://github.com/ngtcp2/ngtcp2#build-from-git describes how to build these three software. -To run h2load against HTTP/3 server, specify h3-25 ALPN with +To run h2load against HTTP/3 server, specify h3-27 ALPN with ``--npn-list`` option like so: .. code-block:: text - $ h2load --npn-list h3-25 https://127.0.0.1:4433 + $ h2load --npn-list h3-27 https://127.0.0.1:4433 You can use Dockerfile to skip the tedious build steps to manually pull and build dependencies. In order to build Docker image, do this: @@ -43,7 +43,7 @@ Run h2load: .. code-block:: text - $ docker run --rm -it --network=host nghttp2-quic /usr/local/bin/h2load --npn-list h3-25 https://127.0.0.1:4433 + $ docker run --rm -it --network=host nghttp2-quic /usr/local/bin/h2load --npn-list h3-27 https://127.0.0.1:4433 Development Status ------------------ diff --git a/docker/Dockerfile b/docker/Dockerfile index cb20b502..ab9eb0f9 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -5,7 +5,7 @@ RUN /usr/local/bin/clean-install git g++ make binutils autoconf automake autotoo zlib1g libev4 libjemalloc1 libc-ares2 \ ca-certificates psmisc \ python && \ - git clone --depth 1 -b OpenSSL_1_1_1d-quic-draft-25 https://github.com/tatsuhiro-t/openssl && \ + git clone --depth 1 -b OpenSSL_1_1_1d-quic-draft-27 https://github.com/tatsuhiro-t/openssl && \ cd openssl && ./config enable-tls1_3 --openssldir=/etc/ssl && make -j$(nproc) && make install_sw && cd .. && rm -rf openssl && \ git clone --depth 1 https://github.com/ngtcp2/nghttp3 && \ cd nghttp3 && autoreconf -i && \ From 4b45142e72c22476266701e7895fcd5350f9d270 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 2 Apr 2020 17:44:41 +0900 Subject: [PATCH 043/124] Fix compile error with the latest ngtcp2 --- src/h2load_quic.cc | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index 5cd7990f..86b6f074 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -476,7 +476,7 @@ int Client::quic_setup_initial_crypto() { if (ngtcp2_crypto_derive_and_install_initial_key( quic.conn, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, dcid, NGTCP2_CRYPTO_SIDE_CLIENT) != 0) { + nullptr, nullptr, nullptr, dcid) != 0) { std::cerr << "ngtcp2_crypto_derive_and_install_initial_key() failed" << std::endl; return -1; @@ -487,11 +487,20 @@ int Client::quic_setup_initial_crypto() { int Client::quic_on_key(ngtcp2_crypto_level level, const uint8_t *rx_secret, const uint8_t *tx_secret, size_t secretlen) { - if (ngtcp2_crypto_derive_and_install_key( - quic.conn, ssl, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - level, rx_secret, tx_secret, secretlen, - NGTCP2_CRYPTO_SIDE_CLIENT) != 0) { - std::cerr << "ngtcp2_crypto_derive_and_install_key() failed" << std::endl; + if (level != NGTCP2_CRYPTO_LEVEL_EARLY && + ngtcp2_crypto_derive_and_install_rx_key(quic.conn, ssl, nullptr, nullptr, + nullptr, level, rx_secret, + secretlen) != 0) { + std::cerr << "ngtcp2_crypto_derive_and_install_rx_key() failed" + << std::endl; + return -1; + } + + if (ngtcp2_crypto_derive_and_install_tx_key(quic.conn, ssl, nullptr, nullptr, + nullptr, level, tx_secret, + secretlen) != 0) { + std::cerr << "ngtcp2_crypto_derive_and_install_tx_key() failed" + << std::endl; return -1; } From 749015eb86bc996d31319f78870319690ca49499 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 2 Apr 2020 17:50:11 +0900 Subject: [PATCH 044/124] Ensure complete packet is written --- src/h2load_quic.cc | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index 86b6f074..1e29d4d9 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -647,7 +647,13 @@ int Client::write_quic() { case NGTCP2_ERR_STREAM_SHUT_WR: if (nwrite == NGTCP2_ERR_STREAM_DATA_BLOCKED && ngtcp2_conn_get_max_data_left(quic.conn) == 0) { - return 0; + /* Call ngtcp2_conn_writev_stream to ensure that a complete + packet is written to the buffer. */ + nwrite = ngtcp2_conn_writev_stream( + quic.conn, &ps.path, buf.data(), quic.max_pktlen, nullptr, + NGTCP2_WRITE_STREAM_FLAG_NONE, /* stream_id = */ 0, /* fin = */ 0, + nullptr, 0, timestamp(worker->loop)); + break; } if (s->block_stream(stream_id) != 0) { @@ -662,8 +668,10 @@ int Client::write_quic() { continue; } - quic.last_error = quic::err_transport(nwrite); - return -1; + if (nwrite < 0) { + quic.last_error = quic::err_transport(nwrite); + return -1; + } } quic_restart_pkt_timer(); From a60a34331b060580a16fb31f425b8dfa9de766f9 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 4 Apr 2020 16:10:04 +0900 Subject: [PATCH 045/124] Revert "Ensure complete packet is written" This reverts commit c19046b09f8e66713f0e067f986ed92d676eb6b6. --- src/h2load_quic.cc | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index 1e29d4d9..86b6f074 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -647,13 +647,7 @@ int Client::write_quic() { case NGTCP2_ERR_STREAM_SHUT_WR: if (nwrite == NGTCP2_ERR_STREAM_DATA_BLOCKED && ngtcp2_conn_get_max_data_left(quic.conn) == 0) { - /* Call ngtcp2_conn_writev_stream to ensure that a complete - packet is written to the buffer. */ - nwrite = ngtcp2_conn_writev_stream( - quic.conn, &ps.path, buf.data(), quic.max_pktlen, nullptr, - NGTCP2_WRITE_STREAM_FLAG_NONE, /* stream_id = */ 0, /* fin = */ 0, - nullptr, 0, timestamp(worker->loop)); - break; + return 0; } if (s->block_stream(stream_id) != 0) { @@ -668,10 +662,8 @@ int Client::write_quic() { continue; } - if (nwrite < 0) { - quic.last_error = quic::err_transport(nwrite); - return -1; - } + quic.last_error = quic::err_transport(nwrite); + return -1; } quic_restart_pkt_timer(); From 8d89a8dcb029251305d0dfb3f245b87be2339adb Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 4 Apr 2020 16:28:38 +0900 Subject: [PATCH 046/124] Assert ndatalen --- src/h2load_quic.cc | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index 86b6f074..f515f8c7 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -645,10 +645,7 @@ int Client::write_quic() { switch (nwrite) { case NGTCP2_ERR_STREAM_DATA_BLOCKED: case NGTCP2_ERR_STREAM_SHUT_WR: - if (nwrite == NGTCP2_ERR_STREAM_DATA_BLOCKED && - ngtcp2_conn_get_max_data_left(quic.conn) == 0) { - return 0; - } + assert(ndatalen == -1); if (s->block_stream(stream_id) != 0) { return -1; @@ -666,18 +663,14 @@ int Client::write_quic() { return -1; } + assert(ndatalen == -1); + quic_restart_pkt_timer(); if (nwrite == 0) { return 0; } - if (ndatalen >= 0) { - if (s->add_write_offset(stream_id, ndatalen) != 0) { - return -1; - } - } - write_udp(reinterpret_cast(ps.path.remote.addr), ps.path.remote.addrlen, buf.data(), nwrite); } From 1acebb1cc4daafe90820806895d20971ca4bf9fe Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Fri, 22 May 2020 22:11:50 +0900 Subject: [PATCH 047/124] draft-28 --- src/h2load.cc | 4 +-- src/h2load.h | 1 - src/h2load_quic.cc | 72 ++++------------------------------------------ 3 files changed, 7 insertions(+), 70 deletions(-) diff --git a/src/h2load.cc b/src/h2load.cc index 2deb21ef..a1350578 100644 --- a/src/h2load.cc +++ b/src/h2load.cc @@ -136,7 +136,7 @@ bool Config::is_timing_based_mode() const { return (this->duration > 0); } bool Config::has_base_uri() const { return (!this->base_uri.empty()); } bool Config::rps_enabled() const { return this->rps > 0.0; } bool Config::is_quic() const { - return !npn_list.empty() && npn_list[0] == NGTCP2_ALPN_H3; + return !npn_list.empty() && npn_list[0] == NGHTTP3_ALPN_H3; } Config config; @@ -1038,7 +1038,7 @@ int Client::connection_made() { auto proto = StringRef{next_proto, next_proto_len}; if (config.is_quic()) { assert(session); - if (!util::streq(StringRef{&NGTCP2_ALPN_H3[1]}, proto)) { + if (!util::streq(StringRef{&NGHTTP3_ALPN_H3[1]}, proto)) { return -1; } } else if (util::check_h2_is_selected(proto)) { diff --git a/src/h2load.h b/src/h2load.h index e01a3c8c..0f3b522b 100644 --- a/src/h2load.h +++ b/src/h2load.h @@ -454,7 +454,6 @@ struct Client { int write_udp(const sockaddr *addr, socklen_t addrlen, const uint8_t *data, size_t datalen); void quic_close_connection(); - int quic_setup_initial_crypto(); int quic_recv_crypto_data(ngtcp2_crypto_level crypto_level, const uint8_t *data, size_t datalen); diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index f515f8c7..5817b13b 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -36,18 +36,6 @@ namespace { auto randgen = util::make_mt19937(); } // namespace -namespace { -int client_initial(ngtcp2_conn *conn, void *user_data) { - auto c = static_cast(user_data); - - if (c->quic_recv_crypto_data(NGTCP2_CRYPTO_LEVEL_INITIAL, nullptr, 0) != 0) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} -} // namespace - namespace { int recv_crypto_data(ngtcp2_conn *conn, ngtcp2_crypto_level crypto_level, uint64_t offset, const uint8_t *data, size_t datalen, @@ -82,21 +70,6 @@ int handshake_completed(ngtcp2_conn *conn, void *user_data) { int Client::quic_handshake_completed() { return connection_made(); } -namespace { -int recv_retry(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd, - const ngtcp2_pkt_retry *retry, void *user_data) { - // Re-generate handshake secrets here because connection ID might - // change. - auto c = static_cast(user_data); - - if (c->quic_setup_initial_crypto() != 0) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} -} // namespace - namespace { int recv_stream_data(ngtcp2_conn *conn, int64_t stream_id, int fin, uint64_t offset, const uint8_t *data, size_t datalen, @@ -346,7 +319,7 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, } auto callbacks = ngtcp2_conn_callbacks{ - h2load::client_initial, + ngtcp2_crypto_client_initial_cb, nullptr, // recv_client_initial h2load::recv_crypto_data, h2load::handshake_completed, @@ -360,7 +333,7 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, nullptr, // stream_open h2load::stream_close, nullptr, // recv_stateless_reset - h2load::recv_retry, + ngtcp2_crypto_recv_retry_cb, h2load::extend_max_local_streams_bidi, nullptr, // extend_max_local_streams_uni nullptr, // rand @@ -394,6 +367,7 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, params.initial_max_streams_bidi = 0; params.initial_max_streams_uni = 100; params.max_idle_timeout = 30 * NGTCP2_SECONDS; + params.max_udp_payload_size = quic.max_pktlen; auto path = ngtcp2_path{ {local_addrlen, @@ -408,28 +382,7 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, return -1; } - std::array buf; - - auto nwrite = ngtcp2_encode_transport_params( - buf.data(), buf.size(), NGTCP2_TRANSPORT_PARAMS_TYPE_CLIENT_HELLO, - ¶ms); - if (nwrite < 0) { - std::cerr << "ngtcp2_encode_transport_params: " << ngtcp2_strerror(nwrite) - << std::endl; - return -1; - } - - if (SSL_set_quic_transport_params(ssl, buf.data(), nwrite) != 1) { - std::cerr << "SSL_set_quic_transport_params failed" << std::endl; - return -1; - } - - rv = quic_setup_initial_crypto(); - if (rv != 0) { - ngtcp2_conn_del(quic.conn); - quic.conn = nullptr; - return -1; - } + ngtcp2_conn_set_tls(quic.conn, ssl); return 0; } @@ -471,24 +424,9 @@ void Client::quic_close_connection() { ps.path.remote.addrlen, buf.data(), nwrite); } -int Client::quic_setup_initial_crypto() { - auto dcid = ngtcp2_conn_get_dcid(quic.conn); - - if (ngtcp2_crypto_derive_and_install_initial_key( - quic.conn, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, dcid) != 0) { - std::cerr << "ngtcp2_crypto_derive_and_install_initial_key() failed" - << std::endl; - return -1; - } - - return 0; -} - int Client::quic_on_key(ngtcp2_crypto_level level, const uint8_t *rx_secret, const uint8_t *tx_secret, size_t secretlen) { - if (level != NGTCP2_CRYPTO_LEVEL_EARLY && - ngtcp2_crypto_derive_and_install_rx_key(quic.conn, ssl, nullptr, nullptr, + if (ngtcp2_crypto_derive_and_install_rx_key(quic.conn, ssl, nullptr, nullptr, nullptr, level, rx_secret, secretlen) != 0) { std::cerr << "ngtcp2_crypto_derive_and_install_rx_key() failed" From b2c099bac69063235474ce8e042a9453d81c76d2 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Fri, 22 May 2020 22:59:49 +0900 Subject: [PATCH 048/124] Use ngtcp2_conn_handle_expiry --- src/h2load_quic.cc | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index 5817b13b..5e2fa311 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -485,15 +485,10 @@ int Client::quic_pkt_timeout() { int rv; auto now = timestamp(worker->loop); - if (ngtcp2_conn_loss_detection_expiry(quic.conn) <= now) { - rv = ngtcp2_conn_on_loss_detection_timer(quic.conn, now); - if (rv != 0) { - quic.last_error = quic::err_transport(NGTCP2_ERR_INTERNAL); - return -1; - } - } - if (ngtcp2_conn_ack_delay_expiry(quic.conn) <= now) { - ngtcp2_conn_cancel_expired_ack_delay_timer(quic.conn, now); + rv = ngtcp2_conn_handle_expiry(quic.conn, now); + if (rv != 0) { + quic.last_error = quic::err_transport(NGTCP2_ERR_INTERNAL); + return -1; } return write_quic(); From 387b67472cb13c238af08921564d8a754bb01b47 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 23 May 2020 09:29:03 +0900 Subject: [PATCH 049/124] Compile latest ngtcp2 crypto lib --- src/h2load_quic.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index 5e2fa311..c1c99603 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -52,8 +52,8 @@ int recv_crypto_data(ngtcp2_conn *conn, ngtcp2_crypto_level crypto_level, int Client::quic_recv_crypto_data(ngtcp2_crypto_level crypto_level, const uint8_t *data, size_t datalen) { - return ngtcp2_crypto_read_write_crypto_data(quic.conn, ssl, crypto_level, - data, datalen); + return ngtcp2_crypto_read_write_crypto_data(quic.conn, crypto_level, data, + datalen); } namespace { @@ -426,7 +426,7 @@ void Client::quic_close_connection() { int Client::quic_on_key(ngtcp2_crypto_level level, const uint8_t *rx_secret, const uint8_t *tx_secret, size_t secretlen) { - if (ngtcp2_crypto_derive_and_install_rx_key(quic.conn, ssl, nullptr, nullptr, + if (ngtcp2_crypto_derive_and_install_rx_key(quic.conn, nullptr, nullptr, nullptr, level, rx_secret, secretlen) != 0) { std::cerr << "ngtcp2_crypto_derive_and_install_rx_key() failed" @@ -434,7 +434,7 @@ int Client::quic_on_key(ngtcp2_crypto_level level, const uint8_t *rx_secret, return -1; } - if (ngtcp2_crypto_derive_and_install_tx_key(quic.conn, ssl, nullptr, nullptr, + if (ngtcp2_crypto_derive_and_install_tx_key(quic.conn, nullptr, nullptr, nullptr, level, tx_secret, secretlen) != 0) { std::cerr << "ngtcp2_crypto_derive_and_install_tx_key() failed" From b8c1f4f138e9f257f1a95e41e1b7c5f7dba1cba6 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 23 May 2020 19:01:23 +0900 Subject: [PATCH 050/124] Compile with latest ngtcp2 --- src/h2load_quic.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index c1c99603..b2133511 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -382,7 +382,7 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, return -1; } - ngtcp2_conn_set_tls(quic.conn, ssl); + ngtcp2_conn_set_tls_native_handle(quic.conn, ssl); return 0; } From 4bc7710de935122c59029678cb41b01b76548e74 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 4 Jun 2020 00:49:40 +0900 Subject: [PATCH 051/124] Fix compile error --- src/h2load_quic.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index b2133511..6fb587dc 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -105,7 +105,7 @@ int Client::quic_recv_stream_data(int64_t stream_id, int fin, namespace { int acked_stream_data_offset(ngtcp2_conn *conn, int64_t stream_id, - uint64_t offset, size_t datalen, void *user_data, + uint64_t offset, uint64_t datalen, void *user_data, void *stream_user_data) { auto c = static_cast(user_data); if (c->quic_acked_stream_data_offset(stream_id, datalen) != 0) { From acb661df729a4c7d76fc3ad08b59a5c13d773483 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 4 Jun 2020 15:00:32 +0900 Subject: [PATCH 052/124] Fix bug for platform which does not have SOCK_NONBLOCK --- src/util.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util.cc b/src/util.cc index d6abdb55..5f84b556 100644 --- a/src/util.cc +++ b/src/util.cc @@ -953,7 +953,7 @@ int create_nonblock_udp_socket(int family) { return -1; } #else // !SOCK_NONBLOCK - auto fd = socket(family, SOCK_STREAM, 0); + auto fd = socket(family, SOCK_DGRAM, 0); if (fd == -1) { return -1; From 813d5e1ddf0d6434d80a691a14a1ed5786416a2c Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 7 Jun 2020 22:49:34 +0900 Subject: [PATCH 053/124] Compile with latest ngtcp2 --- src/h2load.h | 4 ++-- src/h2load_http3_session.cc | 8 ++++---- src/h2load_http3_session.h | 4 ++-- src/h2load_quic.cc | 19 ++++++++++++------- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/h2load.h b/src/h2load.h index 0f3b522b..fdacf727 100644 --- a/src/h2load.h +++ b/src/h2load.h @@ -458,8 +458,8 @@ struct Client { int quic_recv_crypto_data(ngtcp2_crypto_level crypto_level, const uint8_t *data, size_t datalen); int quic_handshake_completed(); - int quic_recv_stream_data(int64_t stream_id, int fin, const uint8_t *data, - size_t datalen); + int quic_recv_stream_data(uint32_t flags, int64_t stream_id, + const uint8_t *data, size_t datalen); int quic_acked_stream_data_offset(int64_t stream_id, size_t datalen); int quic_stream_close(int64_t stream_id, uint64_t app_error_code); int quic_stream_reset(int64_t stream_id, uint64_t app_error_code); diff --git a/src/h2load_http3_session.cc b/src/h2load_http3_session.cc index aac8df45..3fad5dc7 100644 --- a/src/h2load_http3_session.cc +++ b/src/h2load_http3_session.cc @@ -343,10 +343,10 @@ int Http3Session::init_conn() { return 0; } -ssize_t Http3Session::read_stream(int64_t stream_id, const uint8_t *data, - size_t datalen, int fin) { - auto nconsumed = - nghttp3_conn_read_stream(conn_, stream_id, data, datalen, fin); +ssize_t Http3Session::read_stream(uint32_t flags, int64_t stream_id, + const uint8_t *data, size_t datalen) { + auto nconsumed = nghttp3_conn_read_stream( + conn_, stream_id, data, datalen, flags & NGTCP2_STREAM_DATA_FLAG_FIN); if (nconsumed < 0) { std::cerr << "nghttp3_conn_read_stream: " << nghttp3_strerror(nconsumed) << std::endl; diff --git a/src/h2load_http3_session.h b/src/h2load_http3_session.h index 48b4f80c..cd3d96ba 100644 --- a/src/h2load_http3_session.h +++ b/src/h2load_http3_session.h @@ -58,8 +58,8 @@ public: int extend_max_local_streams(); int64_t submit_request_internal(); - ssize_t read_stream(int64_t stream_id, const uint8_t *data, size_t datalen, - int fin); + ssize_t read_stream(uint32_t flags, int64_t stream_id, const uint8_t *data, + size_t datalen); ssize_t write_stream(int64_t &stream_id, int &fin, nghttp3_vec *vec, size_t veccnt); int block_stream(int64_t stream_id); diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index 6fb587dc..10b7caa4 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -71,11 +71,11 @@ int handshake_completed(ngtcp2_conn *conn, void *user_data) { int Client::quic_handshake_completed() { return connection_made(); } namespace { -int recv_stream_data(ngtcp2_conn *conn, int64_t stream_id, int fin, +int recv_stream_data(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id, uint64_t offset, const uint8_t *data, size_t datalen, void *user_data, void *stream_user_data) { auto c = static_cast(user_data); - if (c->quic_recv_stream_data(stream_id, fin, data, datalen) != 0) { + if (c->quic_recv_stream_data(flags, stream_id, data, datalen) != 0) { // TODO Better to do this gracefully rather than // NGTCP2_ERR_CALLBACK_FAILURE. Perhaps, call // ngtcp2_conn_write_application_close() ? @@ -85,14 +85,14 @@ int recv_stream_data(ngtcp2_conn *conn, int64_t stream_id, int fin, } } // namespace -int Client::quic_recv_stream_data(int64_t stream_id, int fin, +int Client::quic_recv_stream_data(uint32_t flags, int64_t stream_id, const uint8_t *data, size_t datalen) { if (worker->current_phase == Phase::MAIN_DURATION) { worker->stats.bytes_total += datalen; } auto s = static_cast(session.get()); - auto nconsumed = s->read_stream(stream_id, data, datalen, fin); + auto nconsumed = s->read_stream(flags, stream_id, data, datalen); if (nconsumed == -1) { return -1; } @@ -570,10 +570,15 @@ int Client::write_quic() { auto v = vec.data(); auto vcnt = static_cast(sveccnt); + uint32_t flags = NGTCP2_WRITE_STREAM_FLAG_MORE; + if (fin) { + flags |= NGTCP2_WRITE_STREAM_FLAG_FIN; + } + auto nwrite = ngtcp2_conn_writev_stream( - quic.conn, &ps.path, buf.data(), quic.max_pktlen, &ndatalen, - NGTCP2_WRITE_STREAM_FLAG_MORE, stream_id, fin, - reinterpret_cast(v), vcnt, timestamp(worker->loop)); + quic.conn, &ps.path, buf.data(), quic.max_pktlen, &ndatalen, flags, + stream_id, reinterpret_cast(v), vcnt, + timestamp(worker->loop)); if (nwrite < 0) { switch (nwrite) { case NGTCP2_ERR_STREAM_DATA_BLOCKED: From f73d58d74effb612779ee746e7cba5a8371777cb Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 14 Jun 2020 10:07:35 +0900 Subject: [PATCH 054/124] quic draft-29 --- README.rst | 6 +++--- docker/Dockerfile | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 605d8560..90faa40d 100644 --- a/README.rst +++ b/README.rst @@ -24,12 +24,12 @@ ngtcp2, nghttp3 and my patched OpenSSL. https://github.com/ngtcp2/ngtcp2#build-from-git describes how to build these three software. -To run h2load against HTTP/3 server, specify h3-27 ALPN with +To run h2load against HTTP/3 server, specify h3-29 ALPN with ``--npn-list`` option like so: .. code-block:: text - $ h2load --npn-list h3-27 https://127.0.0.1:4433 + $ h2load --npn-list h3-29 https://127.0.0.1:4433 You can use Dockerfile to skip the tedious build steps to manually pull and build dependencies. In order to build Docker image, do this: @@ -43,7 +43,7 @@ Run h2load: .. code-block:: text - $ docker run --rm -it --network=host nghttp2-quic /usr/local/bin/h2load --npn-list h3-27 https://127.0.0.1:4433 + $ docker run --rm -it --network=host nghttp2-quic /usr/local/bin/h2load --npn-list h3-29 https://127.0.0.1:4433 Development Status ------------------ diff --git a/docker/Dockerfile b/docker/Dockerfile index ab9eb0f9..cfb7c92c 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -5,7 +5,7 @@ RUN /usr/local/bin/clean-install git g++ make binutils autoconf automake autotoo zlib1g libev4 libjemalloc1 libc-ares2 \ ca-certificates psmisc \ python && \ - git clone --depth 1 -b OpenSSL_1_1_1d-quic-draft-27 https://github.com/tatsuhiro-t/openssl && \ + git clone --depth 1 -b OpenSSL_1_1_1d-quic-draft-29 https://github.com/tatsuhiro-t/openssl && \ cd openssl && ./config enable-tls1_3 --openssldir=/etc/ssl && make -j$(nproc) && make install_sw && cd .. && rm -rf openssl && \ git clone --depth 1 https://github.com/ngtcp2/nghttp3 && \ cd nghttp3 && autoreconf -i && \ From a3346fbad8f69c3eb2b599a2ade38394d5eff531 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 28 Jun 2020 19:10:49 +0900 Subject: [PATCH 055/124] Compile with latest ngtcp2 --- src/h2load_quic.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index 10b7caa4..b3b2b6f1 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -346,6 +346,11 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, nullptr, // extend_max_remote_streams_bidi nullptr, // extend_max_remote_streams_uni nullptr, // extend_max_stream_data + nullptr, // dcid_status + nullptr, // handshake_confirmed + nullptr, // recv_new_token + ngtcp2_crypto_delete_crypto_aead_ctx_cb, + ngtcp2_crypto_delete_crypto_cipher_ctx_cb, }; ngtcp2_cid scid, dcid; From 3900f758eab674d36275249993eae4fd9544f608 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 5 Jul 2020 18:58:59 +0900 Subject: [PATCH 056/124] QUIC needs termination without session --- src/h2load.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/h2load.cc b/src/h2load.cc index a1350578..f749130f 100644 --- a/src/h2load.cc +++ b/src/h2load.cc @@ -838,7 +838,9 @@ void Client::terminate_session() { if (config.is_quic()) { quic.close_requested = true; } - session->terminate(); + if (session) { + session->terminate(); + } // http1 session needs writecb to tear down session. signal_write(); } @@ -1496,7 +1498,7 @@ Worker::~Worker() { void Worker::stop_all_clients() { for (auto client : clients) { - if (client && client->session) { + if (client) { client->terminate_session(); } } From 10ec8c9558a7f6d3765d15e335dcee0762dde701 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 15 Jul 2020 23:52:17 +0900 Subject: [PATCH 057/124] Compile with the latest ngtcp2 --- src/h2load_quic.cc | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index b3b2b6f1..670ba6f0 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -375,10 +375,8 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, params.max_udp_payload_size = quic.max_pktlen; auto path = ngtcp2_path{ - {local_addrlen, - const_cast(reinterpret_cast(local_addr))}, - {remote_addrlen, - const_cast(reinterpret_cast(remote_addr))}, + {local_addrlen, const_cast(local_addr)}, + {remote_addrlen, const_cast(remote_addr)}, }; rv = ngtcp2_conn_client_new(&quic.conn, &dcid, &scid, &path, NGTCP2_PROTO_VER, @@ -525,8 +523,8 @@ int Client::read_quic() { assert(quic.conn); auto path = ngtcp2_path{ - {local_addr.len, reinterpret_cast(&local_addr.su.sa)}, - {addrlen, reinterpret_cast(&su.sa)}, + {local_addr.len, &local_addr.su.sa}, + {addrlen, &su.sa}, }; rv = ngtcp2_conn_read_pkt(quic.conn, &path, buf.data(), nread, From 4b5bcb56bc08cf76c1b6a40336fc36842f4d45cc Mon Sep 17 00:00:00 2001 From: George Liu Date: Wed, 22 Jul 2020 19:19:31 +1000 Subject: [PATCH 058/124] fix quic branch Dockerfile libjemalloc1 package doesn't exist as it's now libjemalloc2 named Get:1 http://security.debian.org/debian-security buster/updates InRelease [65.4 kB] Get:2 http://deb.debian.org/debian buster InRelease [121 kB] Get:3 http://deb.debian.org/debian buster-updates InRelease [51.9 kB] Get:4 http://security.debian.org/debian-security buster/updates/main amd64 Packages [213 kB] Get:5 http://deb.debian.org/debian buster/main amd64 Packages [7905 kB] Get:6 http://deb.debian.org/debian buster-updates/main amd64 Packages [7868 B] Fetched 8364 kB in 1s (6499 kB/s) Reading package lists... Reading package lists... Building dependency tree... Reading state information... E: Unable to locate package libjemalloc1 fix reference to OpenSSL 1.1.1 branch Cloning into 'openssl'... warning: Could not find remote branch OpenSSL_1_1_1d-quic-draft-29 to clone. fatal: Remote branch OpenSSL_1_1_1d-quic-draft-29 not found in upstream origin --- docker/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index cfb7c92c..bfd902f6 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -2,10 +2,10 @@ FROM k8s.gcr.io/debian-base-amd64:v2.0.0 RUN /usr/local/bin/clean-install git g++ make binutils autoconf automake autotools-dev libtool pkg-config \ zlib1g-dev libev-dev libjemalloc-dev ruby-dev libc-ares-dev bison \ - zlib1g libev4 libjemalloc1 libc-ares2 \ + zlib1g libev4 libjemalloc2 libc-ares2 \ ca-certificates psmisc \ python && \ - git clone --depth 1 -b OpenSSL_1_1_1d-quic-draft-29 https://github.com/tatsuhiro-t/openssl && \ + git clone --depth 1 -b OpenSSL_1_1_1g-quic-draft-29 https://github.com/tatsuhiro-t/openssl && \ cd openssl && ./config enable-tls1_3 --openssldir=/etc/ssl && make -j$(nproc) && make install_sw && cd .. && rm -rf openssl && \ git clone --depth 1 https://github.com/ngtcp2/nghttp3 && \ cd nghttp3 && autoreconf -i && \ From 3d708f7dc465146fa60979048c81490c4157f156 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 5 Aug 2020 19:25:08 +0900 Subject: [PATCH 059/124] Compile with the latest ngtcp2 --- src/h2load_quic.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index 670ba6f0..ae2db7c0 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -592,7 +592,7 @@ int Client::write_quic() { return -1; } continue; - case NGTCP2_ERR_WRITE_STREAM_MORE: + case NGTCP2_ERR_WRITE_MORE: assert(ndatalen > 0); if (s->add_write_offset(stream_id, ndatalen) != 0) { return -1; From e4a8c4813c3108133b7af8cbcaf05b01400aa8cc Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 26 Aug 2020 15:20:20 +0900 Subject: [PATCH 060/124] Compile with the latest ngtcp2 --- src/h2load_quic.cc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index ae2db7c0..582e5c79 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -512,6 +512,7 @@ int Client::read_quic() { socklen_t addrlen = sizeof(su); int rv; size_t pktcnt = 0; + ngtcp2_pkt_info pi{}; for (;;) { auto nread = @@ -527,7 +528,7 @@ int Client::read_quic() { {addrlen, &su.sa}, }; - rv = ngtcp2_conn_read_pkt(quic.conn, &path, buf.data(), nread, + rv = ngtcp2_conn_read_pkt(quic.conn, &path, &pi, buf.data(), nread, timestamp(worker->loop)); if (rv != 0) { std::cerr << "ngtcp2_conn_read_pkt: " << ngtcp2_strerror(rv) << std::endl; @@ -579,8 +580,8 @@ int Client::write_quic() { } auto nwrite = ngtcp2_conn_writev_stream( - quic.conn, &ps.path, buf.data(), quic.max_pktlen, &ndatalen, flags, - stream_id, reinterpret_cast(v), vcnt, + quic.conn, &ps.path, nullptr, buf.data(), quic.max_pktlen, &ndatalen, + flags, stream_id, reinterpret_cast(v), vcnt, timestamp(worker->loop)); if (nwrite < 0) { switch (nwrite) { From 51987107a21aa6925d12b8ab3fef15b3a131b516 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 24 Sep 2020 23:41:09 +0900 Subject: [PATCH 061/124] Compile with the latest ngtcp2 --- docker/Dockerfile | 2 +- src/h2load_quic.cc | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index bfd902f6..b8f816d3 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -5,7 +5,7 @@ RUN /usr/local/bin/clean-install git g++ make binutils autoconf automake autotoo zlib1g libev4 libjemalloc2 libc-ares2 \ ca-certificates psmisc \ python && \ - git clone --depth 1 -b OpenSSL_1_1_1g-quic-draft-29 https://github.com/tatsuhiro-t/openssl && \ + git clone --depth 1 -b OpenSSL_1_1_1g-quic-draft-30 https://github.com/tatsuhiro-t/openssl && \ cd openssl && ./config enable-tls1_3 --openssldir=/etc/ssl && make -j$(nproc) && make install_sw && cd .. && rm -rf openssl && \ git clone --depth 1 https://github.com/ngtcp2/nghttp3 && \ cd nghttp3 && autoreconf -i && \ diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index 582e5c79..f1ac1694 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -379,8 +379,9 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, {remote_addrlen, const_cast(remote_addr)}, }; - rv = ngtcp2_conn_client_new(&quic.conn, &dcid, &scid, &path, NGTCP2_PROTO_VER, - &callbacks, &settings, nullptr, this); + rv = ngtcp2_conn_client_new(&quic.conn, &dcid, &scid, &path, + NGTCP2_PROTO_VER_MIN, &callbacks, &settings, + nullptr, this); if (rv != 0) { return -1; } @@ -407,13 +408,13 @@ void Client::quic_close_connection() { return; case quic::ErrorType::Transport: nwrite = ngtcp2_conn_write_connection_close( - quic.conn, &ps.path, buf.data(), quic.max_pktlen, quic.last_error.code, - timestamp(worker->loop)); + quic.conn, &ps.path, nullptr, buf.data(), quic.max_pktlen, + quic.last_error.code, timestamp(worker->loop)); break; case quic::ErrorType::Application: nwrite = ngtcp2_conn_write_application_close( - quic.conn, &ps.path, buf.data(), quic.max_pktlen, quic.last_error.code, - timestamp(worker->loop)); + quic.conn, &ps.path, nullptr, buf.data(), quic.max_pktlen, + quic.last_error.code, timestamp(worker->loop)); break; default: assert(0); From 5ae62dd9d7bec265e40a77c8ff65a5097568b87f Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Mon, 28 Sep 2020 18:14:32 +0900 Subject: [PATCH 062/124] Cap --window-bits to 23 for QUIC --- src/h2load.cc | 1 + src/h2load_quic.cc | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/h2load.cc b/src/h2load.cc index f749130f..a976f6bb 100644 --- a/src/h2load.cc +++ b/src/h2load.cc @@ -2033,6 +2033,7 @@ Options: Default: 1 -w, --window-bits= Sets the stream level initial window size to (2**)-1. + For QUIC, is capped to 23 (roughly 8MiB). Default: )" << config.window_bits << R"( -W, --connection-window-bits= diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index f1ac1694..b08ffd49 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -366,8 +366,10 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, } settings.initial_ts = timestamp(worker->loop); auto ¶ms = settings.transport_params; - params.initial_max_stream_data_bidi_local = (1 << config->window_bits) - 1; - params.initial_max_stream_data_uni = (1 << config->window_bits) - 1; + auto max_stream_data = + std::min((1 << 23) - 1, (1 << config->window_bits) - 1); + params.initial_max_stream_data_bidi_local = max_stream_data; + params.initial_max_stream_data_uni = max_stream_data; params.initial_max_data = (1 << config->connection_window_bits) - 1; params.initial_max_streams_bidi = 0; params.initial_max_streams_uni = 100; From 6ce952ad4aa05dad1d50218432a14e76b71188e9 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Mon, 28 Sep 2020 18:19:07 +0900 Subject: [PATCH 063/124] Set X25519 as default --- src/h2load.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/h2load.cc b/src/h2load.cc index a976f6bb..665bc4d1 100644 --- a/src/h2load.cc +++ b/src/h2load.cc @@ -88,7 +88,7 @@ Config::Config() : ciphers(tls::DEFAULT_CIPHER_LIST), tls13_ciphers("TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_" "CHACHA20_POLY1305_SHA256:TLS_AES_128_CCM_SHA256"), - groups("P-256:X25519:P-384:P-521"), + groups("X25519:P-256:P-384:P-521"), data_length(-1), addrs(nullptr), nreqs(1), From 6b4be30c6405568cfd3b691f057544feebb79011 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Tue, 29 Sep 2020 00:58:47 +0900 Subject: [PATCH 064/124] Cap --window-bits to 26 for QUIC --- src/h2load.cc | 2 +- src/h2load_quic.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/h2load.cc b/src/h2load.cc index 665bc4d1..0cbcaebb 100644 --- a/src/h2load.cc +++ b/src/h2load.cc @@ -2033,7 +2033,7 @@ Options: Default: 1 -w, --window-bits= Sets the stream level initial window size to (2**)-1. - For QUIC, is capped to 23 (roughly 8MiB). + For QUIC, is capped to 26 (roughly 64MiB). Default: )" << config.window_bits << R"( -W, --connection-window-bits= diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index b08ffd49..720b7038 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -367,7 +367,7 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, settings.initial_ts = timestamp(worker->loop); auto ¶ms = settings.transport_params; auto max_stream_data = - std::min((1 << 23) - 1, (1 << config->window_bits) - 1); + std::min((1 << 26) - 1, (1 << config->window_bits) - 1); params.initial_max_stream_data_bidi_local = max_stream_data; params.initial_max_stream_data_uni = max_stream_data; params.initial_max_data = (1 << config->connection_window_bits) - 1; From 68a5652733ebd4b9fdf6c54684bd4e1b4c9e026d Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 22 Oct 2020 17:45:41 +0900 Subject: [PATCH 065/124] Build with draft-32 openssl --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index b8f816d3..9ad84929 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -5,7 +5,7 @@ RUN /usr/local/bin/clean-install git g++ make binutils autoconf automake autotoo zlib1g libev4 libjemalloc2 libc-ares2 \ ca-certificates psmisc \ python && \ - git clone --depth 1 -b OpenSSL_1_1_1g-quic-draft-30 https://github.com/tatsuhiro-t/openssl && \ + git clone --depth 1 -b OpenSSL_1_1_1g-quic-draft-32 https://github.com/tatsuhiro-t/openssl && \ cd openssl && ./config enable-tls1_3 --openssldir=/etc/ssl && make -j$(nproc) && make install_sw && cd .. && rm -rf openssl && \ git clone --depth 1 https://github.com/ngtcp2/nghttp3 && \ cd nghttp3 && autoreconf -i && \ From 1c8e5046e5566e2e3d9ba45469485e8ef3dffc2d Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Tue, 1 Dec 2020 14:57:51 +0900 Subject: [PATCH 066/124] Compile with the latest ngtcp2 --- src/h2load_quic.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index 720b7038..8863238f 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -243,7 +243,7 @@ ngtcp2_crypto_level from_ossl_level(OSSL_ENCRYPTION_LEVEL ossl_level) { case ssl_encryption_handshake: return NGTCP2_CRYPTO_LEVEL_HANDSHAKE; case ssl_encryption_application: - return NGTCP2_CRYPTO_LEVEL_APP; + return NGTCP2_CRYPTO_LEVEL_APPLICATION; default: assert(0); } @@ -448,7 +448,7 @@ int Client::quic_on_key(ngtcp2_crypto_level level, const uint8_t *rx_secret, return -1; } - if (level == NGTCP2_CRYPTO_LEVEL_APP) { + if (level == NGTCP2_CRYPTO_LEVEL_APPLICATION) { auto s = std::make_unique(this); if (s->init_conn() == -1) { return -1; From 7ca2a8213d4efe7d36752040f5e7638ac8004102 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 3 Dec 2020 22:29:30 +0900 Subject: [PATCH 067/124] h2load: Enable --data for HTTP/3 --- src/h2load.cc | 9 +++++++++ src/h2load.h | 2 ++ src/h2load_http3_session.cc | 32 +++++++++++++++++++++++++++++--- src/h2load_http3_session.h | 2 ++ 4 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/h2load.cc b/src/h2load.cc index 0cbcaebb..a51b2372 100644 --- a/src/h2load.cc +++ b/src/h2load.cc @@ -34,6 +34,7 @@ #ifdef HAVE_FCNTL_H # include #endif // HAVE_FCNTL_H +#include #include #include @@ -90,6 +91,7 @@ Config::Config() "CHACHA20_POLY1305_SHA256:TLS_AES_128_CCM_SHA256"), groups("X25519:P-256:P-384:P-521"), data_length(-1), + data(nullptr), addrs(nullptr), nreqs(1), nclients(1), @@ -2661,6 +2663,13 @@ int main(int argc, char **argv) { exit(EXIT_FAILURE); } config.data_length = data_stat.st_size; + auto addr = mmap(nullptr, config.data_length, PROT_READ, MAP_SHARED, + config.data_fd, 0); + if (addr == MAP_FAILED) { + std::cerr << "-d: Could not mmap file " << datafile << std::endl; + exit(EXIT_FAILURE); + } + config.data = static_cast(addr); } if (!logfile.empty()) { diff --git a/src/h2load.h b/src/h2load.h index fdacf727..3f51c2c8 100644 --- a/src/h2load.h +++ b/src/h2load.h @@ -81,6 +81,8 @@ struct Config { std::string groups; // length of upload data int64_t data_length; + // memory mapped upload data + uint8_t *data; addrinfo *addrs; size_t nreqs; size_t nclients; diff --git a/src/h2load_http3_session.cc b/src/h2load_http3_session.cc index 3fad5dc7..24e99b9d 100644 --- a/src/h2load_http3_session.cc +++ b/src/h2load_http3_session.cc @@ -64,6 +64,29 @@ int Http3Session::submit_request() { return 0; } +namespace { +nghttp3_ssize read_data(nghttp3_conn *conn, int64_t stream_id, nghttp3_vec *vec, + size_t veccnt, uint32_t *pflags, void *user_data, + void *stream_user_data) { + auto s = static_cast(user_data); + + s->read_data(vec, veccnt, pflags); + + return 1; +} +} // namespace + +void Http3Session::read_data(nghttp3_vec *vec, size_t veccnt, + uint32_t *pflags) { + assert(veccnt > 0); + + auto config = client_->worker->config; + + vec[0].base = config->data; + vec[0].len = config->data_length; + *pflags |= NGHTTP3_DATA_FLAG_EOF; +} + int64_t Http3Session::submit_request_internal() { int rv; int64_t stream_id; @@ -76,9 +99,12 @@ int64_t Http3Session::submit_request_internal() { return rv; } - rv = nghttp3_conn_submit_request(conn_, stream_id, - reinterpret_cast(nva.data()), - nva.size(), nullptr, nullptr); + nghttp3_data_reader dr{}; + dr.read_data = h2load::read_data; + + rv = nghttp3_conn_submit_request( + conn_, stream_id, reinterpret_cast(nva.data()), nva.size(), + config->data_fd == -1 ? nullptr : &dr, nullptr); if (rv != 0) { return rv; } diff --git a/src/h2load_http3_session.h b/src/h2load_http3_session.h index cd3d96ba..aaca13dc 100644 --- a/src/h2load_http3_session.h +++ b/src/h2load_http3_session.h @@ -66,6 +66,8 @@ public: int add_write_offset(int64_t stream_id, size_t ndatalen); int add_ack_offset(int64_t stream_id, size_t datalen); + void read_data(nghttp3_vec *vec, size_t veccnt, uint32_t *pflags); + private: Client *client_; nghttp3_conn *conn_; From fa8c16ae0152747eaa448e3aa946b2f9124d953d Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Mon, 7 Dec 2020 22:31:58 +0900 Subject: [PATCH 068/124] Compile with the latest ngtcp2 and nghttp3 --- src/h2load_http3_session.cc | 6 +++--- src/h2load_quic.cc | 28 +++++++--------------------- 2 files changed, 10 insertions(+), 24 deletions(-) diff --git a/src/h2load_http3_session.cc b/src/h2load_http3_session.cc index 24e99b9d..ca36a3a8 100644 --- a/src/h2load_http3_session.cc +++ b/src/h2load_http3_session.cc @@ -289,7 +289,7 @@ int Http3Session::init_conn() { return -1; } - nghttp3_conn_callbacks callbacks{ + nghttp3_callbacks callbacks{ nullptr, // acked_stream_data h2load::stream_close, h2load::recv_data, @@ -310,8 +310,8 @@ int Http3Session::init_conn() { auto config = client_->worker->config; - nghttp3_conn_settings settings; - nghttp3_conn_settings_default(&settings); + nghttp3_settings settings; + nghttp3_settings_default(&settings); settings.qpack_max_table_capacity = config->header_table_size; settings.qpack_blocked_streams = 100; diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index 8863238f..5231666d 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -26,6 +26,8 @@ #include +#include + #include #include "h2load_http3_session.h" @@ -233,31 +235,14 @@ ngtcp2_tstamp timestamp(struct ev_loop *loop) { } } // namespace -namespace { -ngtcp2_crypto_level from_ossl_level(OSSL_ENCRYPTION_LEVEL ossl_level) { - switch (ossl_level) { - case ssl_encryption_initial: - return NGTCP2_CRYPTO_LEVEL_INITIAL; - case ssl_encryption_early_data: - return NGTCP2_CRYPTO_LEVEL_EARLY; - case ssl_encryption_handshake: - return NGTCP2_CRYPTO_LEVEL_HANDSHAKE; - case ssl_encryption_application: - return NGTCP2_CRYPTO_LEVEL_APPLICATION; - default: - assert(0); - } -} -} // namespace - namespace { int set_encryption_secrets(SSL *ssl, OSSL_ENCRYPTION_LEVEL ossl_level, const uint8_t *rx_secret, const uint8_t *tx_secret, size_t secret_len) { auto c = static_cast(SSL_get_app_data(ssl)); - if (c->quic_on_key(from_ossl_level(ossl_level), rx_secret, tx_secret, - secret_len) != 0) { + if (c->quic_on_key(ngtcp2_crypto_from_ossl_encryption_level(ossl_level), + rx_secret, tx_secret, secret_len) != 0) { return 0; } @@ -269,7 +254,8 @@ namespace { int add_handshake_data(SSL *ssl, OSSL_ENCRYPTION_LEVEL ossl_level, const uint8_t *data, size_t len) { auto c = static_cast(SSL_get_app_data(ssl)); - c->quic_write_client_handshake(from_ossl_level(ossl_level), data, len); + c->quic_write_client_handshake( + ngtcp2_crypto_from_ossl_encryption_level(ossl_level), data, len); return 1; } } // namespace @@ -318,7 +304,7 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, return -1; } - auto callbacks = ngtcp2_conn_callbacks{ + auto callbacks = ngtcp2_callbacks{ ngtcp2_crypto_client_initial_cb, nullptr, // recv_client_initial h2load::recv_crypto_data, From 95102c1c6cecbd0582844ef23b61ec36d0ba0981 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Tue, 22 Dec 2020 16:01:36 +0900 Subject: [PATCH 069/124] Compile with the latest ngtcp2 --- src/h2load_quic.cc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index 5231666d..daae278d 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -241,8 +241,9 @@ int set_encryption_secrets(SSL *ssl, OSSL_ENCRYPTION_LEVEL ossl_level, size_t secret_len) { auto c = static_cast(SSL_get_app_data(ssl)); - if (c->quic_on_key(ngtcp2_crypto_from_ossl_encryption_level(ossl_level), - rx_secret, tx_secret, secret_len) != 0) { + if (c->quic_on_key( + ngtcp2_crypto_openssl_from_ossl_encryption_level(ossl_level), + rx_secret, tx_secret, secret_len) != 0) { return 0; } @@ -255,7 +256,7 @@ int add_handshake_data(SSL *ssl, OSSL_ENCRYPTION_LEVEL ossl_level, const uint8_t *data, size_t len) { auto c = static_cast(SSL_get_app_data(ssl)); c->quic_write_client_handshake( - ngtcp2_crypto_from_ossl_encryption_level(ossl_level), data, len); + ngtcp2_crypto_openssl_from_ossl_encryption_level(ossl_level), data, len); return 1; } } // namespace From d2d2c31ec71f54046f827ef4ee4c6c6e7a0eb511 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 24 Jan 2021 15:56:48 +0900 Subject: [PATCH 070/124] Follow ngtcp2_conn_writev_stream specification change --- src/h2load_quic.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index daae278d..86c85a4f 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -593,10 +593,10 @@ int Client::write_quic() { quic.last_error = quic::err_transport(nwrite); return -1; + } else if (ndatalen > 0 && s->add_write_offset(stream_id, ndatalen) != 0) { + return -1; } - assert(ndatalen == -1); - quic_restart_pkt_timer(); if (nwrite == 0) { From f1ff2af47a580df2bf61bf29168f893051f05cd3 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Mon, 25 Jan 2021 22:30:30 +0900 Subject: [PATCH 071/124] Deal with 0 length HTTP data write case --- src/h2load_quic.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index 86c85a4f..c2d3f531 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -584,7 +584,7 @@ int Client::write_quic() { } continue; case NGTCP2_ERR_WRITE_MORE: - assert(ndatalen > 0); + assert(ndatalen >= 0); if (s->add_write_offset(stream_id, ndatalen) != 0) { return -1; } @@ -593,7 +593,7 @@ int Client::write_quic() { quic.last_error = quic::err_transport(nwrite); return -1; - } else if (ndatalen > 0 && s->add_write_offset(stream_id, ndatalen) != 0) { + } else if (ndatalen >= 0 && s->add_write_offset(stream_id, ndatalen) != 0) { return -1; } From 35d8ef33efe62e296f8ba09d21edfdc6b404a405 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 31 Jan 2021 10:56:45 +0900 Subject: [PATCH 072/124] Compile with the latest ngtcp2 --- src/h2load_quic.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index c2d3f531..b52b8abc 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -352,7 +352,9 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, settings.log_printf = debug_log_printf; } settings.initial_ts = timestamp(worker->loop); - auto ¶ms = settings.transport_params; + + ngtcp2_transport_params params; + ngtcp2_transport_params_default(¶ms); auto max_stream_data = std::min((1 << 26) - 1, (1 << config->window_bits) - 1); params.initial_max_stream_data_bidi_local = max_stream_data; @@ -370,7 +372,7 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, rv = ngtcp2_conn_client_new(&quic.conn, &dcid, &scid, &path, NGTCP2_PROTO_VER_MIN, &callbacks, &settings, - nullptr, this); + ¶ms, nullptr, this); if (rv != 0) { return -1; } From 09a2e50fc253afc30c63aaf48e47e38b2ace17ce Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 20 Feb 2021 17:31:52 +0900 Subject: [PATCH 073/124] Support both h3 and h3-29 ALPN and their corresponding QUIC versions --- README.rst | 6 +++--- src/h2load.cc | 6 ++++-- src/h2load_quic.cc | 15 ++++++++++++--- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/README.rst b/README.rst index 90faa40d..0ecfb529 100644 --- a/README.rst +++ b/README.rst @@ -24,12 +24,12 @@ ngtcp2, nghttp3 and my patched OpenSSL. https://github.com/ngtcp2/ngtcp2#build-from-git describes how to build these three software. -To run h2load against HTTP/3 server, specify h3-29 ALPN with +To run h2load against HTTP/3 server, specify h3 or h3-29 ALPN with ``--npn-list`` option like so: .. code-block:: text - $ h2load --npn-list h3-29 https://127.0.0.1:4433 + $ h2load --npn-list h3 https://127.0.0.1:4433 You can use Dockerfile to skip the tedious build steps to manually pull and build dependencies. In order to build Docker image, do this: @@ -43,7 +43,7 @@ Run h2load: .. code-block:: text - $ docker run --rm -it --network=host nghttp2-quic /usr/local/bin/h2load --npn-list h3-29 https://127.0.0.1:4433 + $ docker run --rm -it --network=host nghttp2-quic /usr/local/bin/h2load --npn-list h3 https://127.0.0.1:4433 Development Status ------------------ diff --git a/src/h2load.cc b/src/h2load.cc index a51b2372..fca66a9e 100644 --- a/src/h2load.cc +++ b/src/h2load.cc @@ -138,7 +138,8 @@ bool Config::is_timing_based_mode() const { return (this->duration > 0); } bool Config::has_base_uri() const { return (!this->base_uri.empty()); } bool Config::rps_enabled() const { return this->rps > 0.0; } bool Config::is_quic() const { - return !npn_list.empty() && npn_list[0] == NGHTTP3_ALPN_H3; + return !npn_list.empty() && + (npn_list[0] == NGHTTP3_ALPN_H3 || npn_list[0] == "\x5h3-29"); } Config config; @@ -1042,7 +1043,8 @@ int Client::connection_made() { auto proto = StringRef{next_proto, next_proto_len}; if (config.is_quic()) { assert(session); - if (!util::streq(StringRef{&NGHTTP3_ALPN_H3[1]}, proto)) { + if (!util::streq(StringRef{&NGHTTP3_ALPN_H3[1]}, proto) && + !util::streq_l("h3-29", proto)) { return -1; } } else if (util::check_h2_is_selected(proto)) { diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index b52b8abc..31a576c8 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -370,9 +370,18 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, {remote_addrlen, const_cast(remote_addr)}, }; - rv = ngtcp2_conn_client_new(&quic.conn, &dcid, &scid, &path, - NGTCP2_PROTO_VER_MIN, &callbacks, &settings, - ¶ms, nullptr, this); + assert(config->npn_list.size()); + + uint32_t quic_version; + + if (config->npn_list[0] == NGHTTP3_ALPN_H3) { + quic_version = NGTCP2_PROTO_VER_V1; + } else { + quic_version = NGTCP2_PROTO_VER_MIN; + } + + rv = ngtcp2_conn_client_new(&quic.conn, &dcid, &scid, &path, quic_version, + &callbacks, &settings, ¶ms, nullptr, this); if (rv != 0) { return -1; } From 4d140ea6bd150af5cea2ab63501063246c3630f2 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 20 Feb 2021 17:33:25 +0900 Subject: [PATCH 074/124] Update Dockerfile --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 9ad84929..115e8b38 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -5,7 +5,7 @@ RUN /usr/local/bin/clean-install git g++ make binutils autoconf automake autotoo zlib1g libev4 libjemalloc2 libc-ares2 \ ca-certificates psmisc \ python && \ - git clone --depth 1 -b OpenSSL_1_1_1g-quic-draft-32 https://github.com/tatsuhiro-t/openssl && \ + git clone --depth 1 -b OpenSSL_1_1_1g-quic-draft-33 https://github.com/tatsuhiro-t/openssl && \ cd openssl && ./config enable-tls1_3 --openssldir=/etc/ssl && make -j$(nproc) && make install_sw && cd .. && rm -rf openssl && \ git clone --depth 1 https://github.com/ngtcp2/nghttp3 && \ cd nghttp3 && autoreconf -i && \ From e584d9cd2e50c5f2a166e7b57617853d457b36f4 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Tue, 23 Feb 2021 17:39:38 +0900 Subject: [PATCH 075/124] Measure the number of UDP datagrams sent and received --- src/h2load.cc | 59 +++++++++++++++++++++++++++------------------- src/h2load.h | 4 ++++ src/h2load_quic.cc | 2 ++ 3 files changed, 41 insertions(+), 24 deletions(-) diff --git a/src/h2load.cc b/src/h2load.cc index fca66a9e..abe9332b 100644 --- a/src/h2load.cc +++ b/src/h2load.cc @@ -160,7 +160,9 @@ Stats::Stats(size_t req_todo, size_t nclients) bytes_head(0), bytes_head_decomp(0), bytes_body(0), - status() {} + status(), + udp_dgram_recv(0), + udp_dgram_sent(0) {} Stream::Stream() : req_stat{}, status_success(-1) {} @@ -1377,6 +1379,8 @@ int Client::write_udp(const sockaddr *addr, socklen_t addrlen, auto nwrite = sendto(fd, data, datalen, MSG_DONTWAIT, addr, addrlen); if (nwrite < 0) { std::cerr << "sendto: errno=" << errno << std::endl; + } else { + ++worker->stats.udp_dgram_sent; } ev_io_stop(worker->loop, &wev); @@ -2971,6 +2975,8 @@ int main(int argc, char **argv) { stats.bytes_head += s.bytes_head; stats.bytes_head_decomp += s.bytes_head_decomp; stats.bytes_body += s.bytes_body; + stats.udp_dgram_recv += s.udp_dgram_recv; + stats.udp_dgram_sent += s.udp_dgram_sent; for (size_t i = 0; i < stats.status.size(); ++i) { stats.status[i] += s.status[i]; @@ -3029,30 +3035,35 @@ traffic: )" << util::utos_funit(stats.bytes_total) << util::utos_funit(stats.bytes_head) << "B (" << stats.bytes_head << ") headers (space savings " << header_space_savings * 100 << "%), " << util::utos_funit(stats.bytes_body) << "B (" - << stats.bytes_body << R"() data - min max mean sd +/- sd + << stats.bytes_body << R"() data)" << std::endl; + if (config.is_quic()) { + std::cout << "UDP datagram: " << stats.udp_dgram_sent << " sent, " + << stats.udp_dgram_recv << " received" << std::endl; + } + std::cout + << R"( min max mean sd +/- sd time for request: )" - << std::setw(10) << util::format_duration(ts.request.min) << " " - << std::setw(10) << util::format_duration(ts.request.max) << " " - << std::setw(10) << util::format_duration(ts.request.mean) << " " - << std::setw(10) << util::format_duration(ts.request.sd) - << std::setw(9) << util::dtos(ts.request.within_sd) << "%" - << "\ntime for connect: " << std::setw(10) - << util::format_duration(ts.connect.min) << " " << std::setw(10) - << util::format_duration(ts.connect.max) << " " << std::setw(10) - << util::format_duration(ts.connect.mean) << " " << std::setw(10) - << util::format_duration(ts.connect.sd) << std::setw(9) - << util::dtos(ts.connect.within_sd) << "%" - << "\ntime to 1st byte: " << std::setw(10) - << util::format_duration(ts.ttfb.min) << " " << std::setw(10) - << util::format_duration(ts.ttfb.max) << " " << std::setw(10) - << util::format_duration(ts.ttfb.mean) << " " << std::setw(10) - << util::format_duration(ts.ttfb.sd) << std::setw(9) - << util::dtos(ts.ttfb.within_sd) << "%" - << "\nreq/s : " << std::setw(10) << ts.rps.min << " " - << std::setw(10) << ts.rps.max << " " << std::setw(10) - << ts.rps.mean << " " << std::setw(10) << ts.rps.sd << std::setw(9) - << util::dtos(ts.rps.within_sd) << "%" << std::endl; + << std::setw(10) << util::format_duration(ts.request.min) << " " + << std::setw(10) << util::format_duration(ts.request.max) << " " + << std::setw(10) << util::format_duration(ts.request.mean) << " " + << std::setw(10) << util::format_duration(ts.request.sd) << std::setw(9) + << util::dtos(ts.request.within_sd) << "%" + << "\ntime for connect: " << std::setw(10) + << util::format_duration(ts.connect.min) << " " << std::setw(10) + << util::format_duration(ts.connect.max) << " " << std::setw(10) + << util::format_duration(ts.connect.mean) << " " << std::setw(10) + << util::format_duration(ts.connect.sd) << std::setw(9) + << util::dtos(ts.connect.within_sd) << "%" + << "\ntime to 1st byte: " << std::setw(10) + << util::format_duration(ts.ttfb.min) << " " << std::setw(10) + << util::format_duration(ts.ttfb.max) << " " << std::setw(10) + << util::format_duration(ts.ttfb.mean) << " " << std::setw(10) + << util::format_duration(ts.ttfb.sd) << std::setw(9) + << util::dtos(ts.ttfb.within_sd) << "%" + << "\nreq/s : " << std::setw(10) << ts.rps.min << " " + << std::setw(10) << ts.rps.max << " " << std::setw(10) << ts.rps.mean + << " " << std::setw(10) << ts.rps.sd << std::setw(9) + << util::dtos(ts.rps.within_sd) << "%" << std::endl; SSL_CTX_free(ssl_ctx); diff --git a/src/h2load.h b/src/h2load.h index 3f51c2c8..348fcdc9 100644 --- a/src/h2load.h +++ b/src/h2load.h @@ -230,6 +230,10 @@ struct Stats { std::vector req_stats; // The statistics per client std::vector client_stats; + // The number of UDP datagrams received. + size_t udp_dgram_recv; + // The number of UDP datagrams sent. + size_t udp_dgram_sent; }; enum ClientState { CLIENT_IDLE, CLIENT_CONNECTED }; diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index 31a576c8..7a8c262d 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -524,6 +524,8 @@ int Client::read_quic() { assert(quic.conn); + ++worker->stats.udp_dgram_recv; + auto path = ngtcp2_path{ {local_addr.len, &local_addr.su.sa}, {addrlen, &su.sa}, From 095493209190dc82f1d1cde1047069ed1eeb1c65 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Fri, 5 Mar 2021 23:00:30 +0900 Subject: [PATCH 076/124] Rewrite docker file Rewrite docker file so that: - avoid k8s debian-base - build h2load as statically as possible --- docker/Dockerfile | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 115e8b38..3f5cf706 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,10 +1,10 @@ -FROM k8s.gcr.io/debian-base-amd64:v2.0.0 +FROM debian:10 as build -RUN /usr/local/bin/clean-install git g++ make binutils autoconf automake autotools-dev libtool pkg-config \ - zlib1g-dev libev-dev libjemalloc-dev ruby-dev libc-ares-dev bison \ - zlib1g libev4 libjemalloc2 libc-ares2 \ - ca-certificates psmisc \ - python && \ +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + git g++ make binutils autoconf automake autotools-dev libtool \ + pkg-config \ + zlib1g-dev libev-dev libjemalloc-dev ruby-dev libc-ares-dev bison && \ git clone --depth 1 -b OpenSSL_1_1_1g-quic-draft-33 https://github.com/tatsuhiro-t/openssl && \ cd openssl && ./config enable-tls1_3 --openssldir=/etc/ssl && make -j$(nproc) && make install_sw && cd .. && rm -rf openssl && \ git clone --depth 1 https://github.com/ngtcp2/nghttp3 && \ @@ -18,13 +18,19 @@ RUN /usr/local/bin/clean-install git g++ make binutils autoconf automake autotoo git clone --depth 1 -b quic https://github.com/nghttp2/nghttp2.git && \ cd nghttp2 && \ git submodule update --init && autoreconf -i && \ - ./configure --disable-examples --disable-hpack-tools --disable-python-bindings --with-mruby --with-neverbleed && \ - make -j$(nproc) install-strip && \ - cd .. && \ - rm -rf nghttp2 && \ - strip /usr/local/lib/*.so.*.* /usr/local/lib/engines-*/*.so && \ - rm -rf /usr/local/lib/libssl.so /usr/local/lib/libcrypto.so /usr/local/lib/libssl.a /usr/local/lib/libcrypto.a /usr/local/lib/pkgconfig/*ssl.pc /usr/local/include/openssl/* && \ - apt-get -y purge git g++ make binutils autoconf automake autotools-dev libtool pkg-config \ - zlib1g-dev libev-dev libjemalloc-dev ruby-dev libc-ares-dev bison && \ - apt-get -y autoremove --purge && \ - rm -rf /var/log/* + ./configure --disable-examples --disable-hpack-tools \ + --disable-python-bindings --with-mruby --with-neverbleed \ + LIBTOOL_LDFLAGS="-static-libtool-libs" \ + LIBS="-ldl -pthread" \ + OPENSSL_LIBS="-l:libssl.a -l:libcrypto.a" \ + LIBEV_LIBS="-l:libev.a" \ + JEMALLOC_LIBS="-l:libjemalloc.a" \ + LIBCARES_LIBS="-l:libcares.a" \ + ZLIB_LIBS="-l:libz.a" && \ + make -j$(nproc) install-strip + +FROM gcr.io/distroless/cc-debian10 + +COPY --from=build /usr/local/bin/h2load /usr/local/bin/ + +ENTRYPOINT ["/usr/local/bin/h2load"] From 1eb818b64cc35e0b8ad35710ee95726af5a5f35d Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 18 Dec 2019 14:23:05 +0900 Subject: [PATCH 077/124] QUIC UDP GSO --- src/h2load.cc | 39 +++++++++++++++++++++++++++++--- src/h2load.h | 4 +++- src/h2load_http3_session.cc | 9 ++++++++ src/h2load_http3_session.h | 1 + src/h2load_quic.cc | 45 ++++++++++++++++++++++++++++++------- 5 files changed, 86 insertions(+), 12 deletions(-) diff --git a/src/h2load.cc b/src/h2load.cc index abe9332b..d8c2e5f6 100644 --- a/src/h2load.cc +++ b/src/h2load.cc @@ -35,6 +35,7 @@ # include #endif // HAVE_FCNTL_H #include +#include #include #include @@ -117,7 +118,8 @@ Config::Config() timing_script(false), base_uri_unix(false), unix_addr{}, - rps(0.) {} + rps(0.), + no_udp_gso(false) {} Config::~Config() { if (addrs) { @@ -1375,8 +1377,32 @@ int Client::write_tls() { } int Client::write_udp(const sockaddr *addr, socklen_t addrlen, - const uint8_t *data, size_t datalen) { - auto nwrite = sendto(fd, data, datalen, MSG_DONTWAIT, addr, addrlen); + const uint8_t *data, size_t datalen, size_t gso_size) { + iovec msg_iov; + msg_iov.iov_base = const_cast(data); + msg_iov.iov_len = datalen; + + msghdr msg{}; + msg.msg_name = const_cast(addr); + msg.msg_namelen = addrlen; + msg.msg_iov = &msg_iov; + msg.msg_iovlen = 1; + +#ifdef UDP_SEGMENT + std::array msg_ctrl{}; + if (gso_size && datalen > gso_size) { + msg.msg_control = msg_ctrl.data(); + msg.msg_controllen = msg_ctrl.size(); + + auto cm = CMSG_FIRSTHDR(&msg); + cm->cmsg_level = SOL_UDP; + cm->cmsg_type = UDP_SEGMENT; + cm->cmsg_len = CMSG_LEN(sizeof(uint16_t)); + *(reinterpret_cast(CMSG_DATA(cm))) = gso_size; + } +#endif // UDP_SEGMENT + + auto nwrite = sendmsg(fd, &msg, 0); if (nwrite < 0) { std::cerr << "sendto: errno=" << errno << std::endl; } else { @@ -2183,6 +2209,8 @@ Options: Specify the supported groups. Default: )" << config.groups << R"( + --no-udp-gso + Disable UDP GSO. -v, --verbose Output debug information. --version Display version information and exit. @@ -2245,6 +2273,7 @@ int main(int argc, char **argv) { {"rps", required_argument, &flag, 12}, {"groups", required_argument, &flag, 13}, {"tls13-ciphers", required_argument, &flag, 14}, + {"no-udp-gso", no_argument, &flag, 15}, {nullptr, 0, nullptr, 0}}; int option_index = 0; auto c = getopt_long(argc, argv, @@ -2503,6 +2532,10 @@ int main(int argc, char **argv) { // --tls13-ciphers config.tls13_ciphers = optarg; break; + case 15: + // --no-udp-gso + config.no_udp_gso = true; + break; } break; default: diff --git a/src/h2load.h b/src/h2load.h index 348fcdc9..afb369a6 100644 --- a/src/h2load.h +++ b/src/h2load.h @@ -125,6 +125,8 @@ struct Config { std::vector npn_list; // The number of request per second for each client. double rps; + // Disables GSO for UDP connections. + bool no_udp_gso; Config(); ~Config(); @@ -458,7 +460,7 @@ struct Client { int read_quic(); int write_quic(); int write_udp(const sockaddr *addr, socklen_t addrlen, const uint8_t *data, - size_t datalen); + size_t datalen, size_t gso_size); void quic_close_connection(); int quic_recv_crypto_data(ngtcp2_crypto_level crypto_level, diff --git a/src/h2load_http3_session.cc b/src/h2load_http3_session.cc index ca36a3a8..881cbe66 100644 --- a/src/h2load_http3_session.cc +++ b/src/h2load_http3_session.cc @@ -402,6 +402,15 @@ int Http3Session::block_stream(int64_t stream_id) { return 0; } +int Http3Session::shutdown_stream_write(int64_t stream_id) { + auto rv = nghttp3_conn_shutdown_stream_write(conn_, stream_id); + if (rv != 0) { + client_->quic.last_error = quic::err_application(rv); + return -1; + } + return 0; +} + int Http3Session::add_write_offset(int64_t stream_id, size_t ndatalen) { auto rv = nghttp3_conn_add_write_offset(conn_, stream_id, ndatalen); if (rv != 0) { diff --git a/src/h2load_http3_session.h b/src/h2load_http3_session.h index aaca13dc..db6c4244 100644 --- a/src/h2load_http3_session.h +++ b/src/h2load_http3_session.h @@ -63,6 +63,7 @@ public: ssize_t write_stream(int64_t &stream_id, int &fin, nghttp3_vec *vec, size_t veccnt); int block_stream(int64_t stream_id); + int shutdown_stream_write(int64_t stream_id); int add_write_offset(int64_t stream_id, size_t ndatalen); int add_ack_offset(int64_t stream_id, size_t datalen); diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index 7a8c262d..9d8693f0 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -24,6 +24,8 @@ */ #include "h2load_quic.h" +#include + #include #include @@ -425,7 +427,7 @@ void Client::quic_close_connection() { } write_udp(reinterpret_cast(ps.path.remote.addr), - ps.path.remote.addrlen, buf.data(), nwrite); + ps.path.remote.addrlen, buf.data(), nwrite, 0); } int Client::quic_on_key(ngtcp2_crypto_level level, const uint8_t *rx_secret, @@ -554,7 +556,18 @@ int Client::write_quic() { } std::array vec; - std::array buf; + size_t pktcnt = 0; + size_t max_pktcnt = +#ifdef UDP_SEGMENT + worker->config->no_udp_gso + ? 1 + : std::min(static_cast(10), + static_cast(64_k / quic.max_pktlen)); +#else // !UDP_SEGMENT + 1; +#endif // !UDP_SEGMENT + std::array buf; + uint8_t *bufpos = buf.data(); ngtcp2_path_storage ps; ngtcp2_path_storage_zero(&ps); @@ -583,16 +596,20 @@ int Client::write_quic() { } auto nwrite = ngtcp2_conn_writev_stream( - quic.conn, &ps.path, nullptr, buf.data(), quic.max_pktlen, &ndatalen, - flags, stream_id, reinterpret_cast(v), vcnt, + quic.conn, &ps.path, nullptr, bufpos, quic.max_pktlen, &ndatalen, flags, + stream_id, reinterpret_cast(v), vcnt, timestamp(worker->loop)); if (nwrite < 0) { switch (nwrite) { case NGTCP2_ERR_STREAM_DATA_BLOCKED: + assert(ndatalen == -1); + if (s->block_stream(stream_id) != 0) { + return -1; + } + continue; case NGTCP2_ERR_STREAM_SHUT_WR: assert(ndatalen == -1); - - if (s->block_stream(stream_id) != 0) { + if (s->shutdown_stream_write(stream_id) != 0) { return -1; } continue; @@ -613,11 +630,23 @@ int Client::write_quic() { quic_restart_pkt_timer(); if (nwrite == 0) { + if (bufpos - buf.data()) { + write_udp(ps.path.remote.addr, ps.path.remote.addrlen, buf.data(), + bufpos - buf.data(), quic.max_pktlen); + } return 0; } - write_udp(reinterpret_cast(ps.path.remote.addr), - ps.path.remote.addrlen, buf.data(), nwrite); + bufpos += nwrite; + + // Assume that the path does not change. + if (++pktcnt == max_pktcnt || + static_cast(nwrite) < quic.max_pktlen) { + write_udp(ps.path.remote.addr, ps.path.remote.addrlen, buf.data(), + bufpos - buf.data(), quic.max_pktlen); + signal_write(); + return 0; + } } } From 48e10c57da8c7ab385053c24f978d21f62a1e182 Mon Sep 17 00:00:00 2001 From: Hajime Fujita Date: Fri, 12 Mar 2021 19:12:46 +0000 Subject: [PATCH 078/124] h2load: Add qlog output support --- src/h2load.cc | 22 ++++++++++++++++++++++ src/h2load.h | 4 ++++ src/h2load_quic.cc | 35 ++++++++++++++++++++++++++++++++++- 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/src/h2load.cc b/src/h2load.cc index d8c2e5f6..dc713c2a 100644 --- a/src/h2load.cc +++ b/src/h2load.cc @@ -111,6 +111,7 @@ Config::Config() encoder_header_table_size(4_k), data_fd(-1), log_fd(-1), + qlog_file_base(), port(0), default_port(0), connect_to_port(0), @@ -2200,6 +2201,13 @@ Options: response time when using one worker thread, but may appear slightly out of order with multiple threads due to buffering. Status code is -1 for failed streams. + --qlog-file-base= + Enable qlog output and specify base file name for qlogs. + Qlog is emitted for each connection. + For a given base name "base", each output file name + becomes "base.M.N.qlog" where M is worker ID and N is + client ID (e.g. "base.0.3.qlog"). + Only effective in QUIC runs. --connect-to=[:] Host and port to connect instead of using the authority in . @@ -2238,6 +2246,7 @@ int main(int argc, char **argv) { std::string datafile; std::string logfile; + std::string qlog_base; bool nreqs_set_manually = false; while (1) { static int flag = 0; @@ -2274,6 +2283,7 @@ int main(int argc, char **argv) { {"groups", required_argument, &flag, 13}, {"tls13-ciphers", required_argument, &flag, 14}, {"no-udp-gso", no_argument, &flag, 15}, + {"qlog-file-base", required_argument, &flag, 16}, {nullptr, 0, nullptr, 0}}; int option_index = 0; auto c = getopt_long(argc, argv, @@ -2536,6 +2546,10 @@ int main(int argc, char **argv) { // --no-udp-gso config.no_udp_gso = true; break; + case 16: + // --qlog-file-base + qlog_base = optarg; + break; } break; default: @@ -2720,6 +2734,14 @@ int main(int argc, char **argv) { } } + if (!qlog_base.empty()) { + if (!config.is_quic()) { + std::cerr << "Warning: --qlog-file-base: only effective in quic, ignoring." << std::endl; + } else { + config.qlog_file_base = qlog_base; + } + } + struct sigaction act {}; act.sa_handler = SIG_IGN; sigaction(SIGPIPE, &act, nullptr); diff --git a/src/h2load.h b/src/h2load.h index afb369a6..431cd999 100644 --- a/src/h2load.h +++ b/src/h2load.h @@ -109,6 +109,8 @@ struct Config { int data_fd; // file descriptor to write per-request stats to. int log_fd; + // base file name of qlog output files + std::string qlog_file_base; uint16_t port; uint16_t default_port; uint16_t connect_to_port; @@ -340,6 +342,7 @@ struct Client { std::array crypto; size_t max_pktlen; bool close_requested; + FILE *qlog_file; } quic; ev_timer request_timeout_watcher; addrinfo *next_addr; @@ -481,6 +484,7 @@ struct Client { const uint8_t *data, size_t datalen); int quic_pkt_timeout(); void quic_restart_pkt_timer(); + void quic_write_qlog(const void *data, size_t datalen); }; } // namespace h2load diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index 9d8693f0..aa7bf291 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -34,6 +34,8 @@ #include "h2load_http3_session.h" +#include + namespace h2load { namespace { @@ -284,6 +286,20 @@ auto quic_method = SSL_QUIC_METHOD{ }; } // namespace +// qlog write callback -- excerpted from ngtcp2/examples/client_base.cc +namespace { +void qlog_write_cb(void *user_data, uint32_t flags, const void *data, + size_t datalen) { + auto c = static_cast(user_data); + c->quic_write_qlog(data, datalen); +} +} // namespace + +void Client::quic_write_qlog(const void *data, size_t datalen) { + assert(quic.qlog_file != nullptr); + fwrite(data, 1, datalen, quic.qlog_file); +} + int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, const sockaddr *remote_addr, socklen_t remote_addrlen) { int rv; @@ -354,6 +370,17 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, settings.log_printf = debug_log_printf; } settings.initial_ts = timestamp(worker->loop); + if (!config->qlog_file_base.empty()) { + assert(quic.qlog_file == nullptr); + std::ostringstream oss(config->qlog_file_base, std::ios::app); + oss << '.' << worker->id << '.' << id << ".qlog"; + quic.qlog_file = fopen(oss.str().c_str(), "w"); + if (quic.qlog_file == nullptr) { + std::cerr << "Failed to open a qlog file: " << oss.str() << std::endl; + return -1; + } + settings.qlog.write = qlog_write_cb; + } ngtcp2_transport_params params; ngtcp2_transport_params_default(¶ms); @@ -393,7 +420,13 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, return 0; } -void Client::quic_free() { ngtcp2_conn_del(quic.conn); } +void Client::quic_free() { + ngtcp2_conn_del(quic.conn); + if (quic.qlog_file != nullptr) { + fclose(quic.qlog_file); + quic.qlog_file = nullptr; + } +} void Client::quic_close_connection() { if (!quic.conn) { From df400feb614856bd3b1ba5eafbb994ba38845d46 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 13 Mar 2021 10:04:56 +0900 Subject: [PATCH 079/124] make clang-format --- src/h2load.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/h2load.cc b/src/h2load.cc index dc713c2a..d08cff61 100644 --- a/src/h2load.cc +++ b/src/h2load.cc @@ -2736,7 +2736,9 @@ int main(int argc, char **argv) { if (!qlog_base.empty()) { if (!config.is_quic()) { - std::cerr << "Warning: --qlog-file-base: only effective in quic, ignoring." << std::endl; + std::cerr + << "Warning: --qlog-file-base: only effective in quic, ignoring." + << std::endl; } else { config.qlog_file_base = qlog_base; } From 5944d034dabed1533aa86152c271d7dced32c866 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 13 Mar 2021 10:08:50 +0900 Subject: [PATCH 080/124] Avoid std::ostringstream --- src/h2load_quic.cc | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index aa7bf291..322634c1 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -34,8 +34,6 @@ #include "h2load_http3_session.h" -#include - namespace h2load { namespace { @@ -372,11 +370,15 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, settings.initial_ts = timestamp(worker->loop); if (!config->qlog_file_base.empty()) { assert(quic.qlog_file == nullptr); - std::ostringstream oss(config->qlog_file_base, std::ios::app); - oss << '.' << worker->id << '.' << id << ".qlog"; - quic.qlog_file = fopen(oss.str().c_str(), "w"); + auto path = config->qlog_file_base; + path += '.'; + path += util::utos(worker->id); + path += '.'; + path += util::utos(id); + path += ".qlog"; + quic.qlog_file = fopen(path.c_str(), "w"); if (quic.qlog_file == nullptr) { - std::cerr << "Failed to open a qlog file: " << oss.str() << std::endl; + std::cerr << "Failed to open a qlog file: " << path << std::endl; return -1; } settings.qlog.write = qlog_write_cb; From bc5362413395faa090ea18a6c199d7ed177384f5 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Mon, 15 Mar 2021 23:18:44 +0900 Subject: [PATCH 081/124] Do not specify max_udp_payload_size for now --- src/h2load_quic.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index 322634c1..31e2d562 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -394,7 +394,6 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, params.initial_max_streams_bidi = 0; params.initial_max_streams_uni = 100; params.max_idle_timeout = 30 * NGTCP2_SECONDS; - params.max_udp_payload_size = quic.max_pktlen; auto path = ngtcp2_path{ {local_addrlen, const_cast(local_addr)}, From 05f3b8fa0faf7a65f153b2a4ee3f96a20fe07a5f Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 17 Mar 2021 18:47:54 +0900 Subject: [PATCH 082/124] Adopt ngtcp2_crypto_recv_crypto_data_cb --- src/h2load.h | 2 -- src/h2load_quic.cc | 22 +--------------------- 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/src/h2load.h b/src/h2load.h index 431cd999..897fd9cf 100644 --- a/src/h2load.h +++ b/src/h2load.h @@ -466,8 +466,6 @@ struct Client { size_t datalen, size_t gso_size); void quic_close_connection(); - int quic_recv_crypto_data(ngtcp2_crypto_level crypto_level, - const uint8_t *data, size_t datalen); int quic_handshake_completed(); int quic_recv_stream_data(uint32_t flags, int64_t stream_id, const uint8_t *data, size_t datalen); diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index 31e2d562..f0abd826 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -40,26 +40,6 @@ namespace { auto randgen = util::make_mt19937(); } // namespace -namespace { -int recv_crypto_data(ngtcp2_conn *conn, ngtcp2_crypto_level crypto_level, - uint64_t offset, const uint8_t *data, size_t datalen, - void *user_data) { - auto c = static_cast(user_data); - - if (c->quic_recv_crypto_data(crypto_level, data, datalen) != 0) { - return NGTCP2_ERR_CRYPTO; - } - - return 0; -} -} // namespace - -int Client::quic_recv_crypto_data(ngtcp2_crypto_level crypto_level, - const uint8_t *data, size_t datalen) { - return ngtcp2_crypto_read_write_crypto_data(quic.conn, crypto_level, data, - datalen); -} - namespace { int handshake_completed(ngtcp2_conn *conn, void *user_data) { auto c = static_cast(user_data); @@ -324,7 +304,7 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, auto callbacks = ngtcp2_callbacks{ ngtcp2_crypto_client_initial_cb, nullptr, // recv_client_initial - h2load::recv_crypto_data, + ngtcp2_crypto_recv_crypto_data_cb, h2load::handshake_completed, nullptr, // recv_version_negotiation ngtcp2_crypto_encrypt_cb, From 213cc9c4b58873ac61d02d6ea359ebf2e4128582 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 17 Mar 2021 18:56:34 +0900 Subject: [PATCH 083/124] Enlarge receive buffer --- src/h2load_quic.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index f0abd826..f408af41 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -524,7 +524,7 @@ void Client::quic_restart_pkt_timer() { } int Client::read_quic() { - std::array buf; + std::array buf; sockaddr_union su; socklen_t addrlen = sizeof(su); int rv; From f79554f918daf5b7dab61ab982f96133d3e64ddc Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 18 Mar 2021 23:07:42 +0900 Subject: [PATCH 084/124] Count outgoing packets --- src/h2load_quic.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index f408af41..4d720592 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -554,7 +554,7 @@ int Client::read_quic() { return -1; } - if (pktcnt == 10) { + if (++pktcnt == 100) { break; } } From e914b50d1627622d46ce02eec1ebc2e75766078f Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 27 Mar 2021 23:37:37 +0900 Subject: [PATCH 085/124] Compile with the latest ngtcp2 --- src/h2load_quic.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index 4d720592..d6261ebd 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -206,6 +206,7 @@ void generate_cid(ngtcp2_cid &dest) { namespace { int select_preferred_addr(ngtcp2_conn *conn, ngtcp2_addr *dest, + void **ppath_user_data, const ngtcp2_preferred_addr *paddr, void *user_data) { return 0; } From aa2c64891879219d79bc787311e60dd8d5572a1f Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 6 Jun 2021 21:24:48 +0900 Subject: [PATCH 086/124] Just use h3 ALPN --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 0ecfb529..2063d404 100644 --- a/README.rst +++ b/README.rst @@ -24,7 +24,7 @@ ngtcp2, nghttp3 and my patched OpenSSL. https://github.com/ngtcp2/ngtcp2#build-from-git describes how to build these three software. -To run h2load against HTTP/3 server, specify h3 or h3-29 ALPN with +To run h2load against HTTP/3 server, specify h3 ALPN with ``--npn-list`` option like so: .. code-block:: text From 7342de837de3ef6d9cbaa876ceab14d2faee8839 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 31 Jul 2021 17:23:50 +0900 Subject: [PATCH 087/124] Compile with the latest ngtcp2 --- src/h2load_quic.cc | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index d6261ebd..b131e1ac 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -204,14 +204,6 @@ void generate_cid(ngtcp2_cid &dest) { } } // namespace -namespace { -int select_preferred_addr(ngtcp2_conn *conn, ngtcp2_addr *dest, - void **ppath_user_data, - const ngtcp2_preferred_addr *paddr, void *user_data) { - return 0; -} -} // namespace - namespace { ngtcp2_tstamp timestamp(struct ev_loop *loop) { return ev_now(loop) * NGTCP2_SECONDS; @@ -325,7 +317,7 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, nullptr, // remove_connection_id ngtcp2_crypto_update_key_cb, nullptr, // path_validation - select_preferred_addr, + nullptr, // select_preferred_addr h2load::stream_reset, nullptr, // extend_max_remote_streams_bidi nullptr, // extend_max_remote_streams_uni From de5feff7201b6d4ac7e9a56096cd0b28cf0464e7 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 4 Aug 2021 13:26:27 +0900 Subject: [PATCH 088/124] Compile with the latest nghttp3 --- src/h2load_http3_session.cc | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/h2load_http3_session.cc b/src/h2load_http3_session.cc index 881cbe66..1ce06903 100644 --- a/src/h2load_http3_session.cc +++ b/src/h2load_http3_session.cc @@ -300,12 +300,7 @@ int Http3Session::init_conn() { nullptr, // begin_trailers h2load::recv_header, nullptr, // end_trailers - nullptr, // http_begin_push_promise - nullptr, // http_recv_push_promise - nullptr, // http_end_push_promise - nullptr, // http_cancel_push h2load::send_stop_sending, - nullptr, // push_stream, }; auto config = client_->worker->config; From 7c2cd43dfa035029fe8c7d16ec14e7d331a4399d Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Mon, 9 Aug 2021 21:54:04 +0900 Subject: [PATCH 089/124] Compile with the latest ngtcp2 and nghttp3 --- src/h2load.h | 1 + src/h2load_http3_session.cc | 4 ++-- src/h2load_http3_session.h | 2 +- src/h2load_quic.cc | 28 +++++++++++++++++++++++++++- 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/h2load.h b/src/h2load.h index 897fd9cf..2d18fbf8 100644 --- a/src/h2load.h +++ b/src/h2load.h @@ -472,6 +472,7 @@ struct Client { int quic_acked_stream_data_offset(int64_t stream_id, size_t datalen); int quic_stream_close(int64_t stream_id, uint64_t app_error_code); int quic_stream_reset(int64_t stream_id, uint64_t app_error_code); + int quic_stream_stop_sending(int64_t stream_id, uint64_t app_error_code); int quic_extend_max_local_streams(); int quic_on_key(ngtcp2_crypto_level level, const uint8_t *rx_secret, diff --git a/src/h2load_http3_session.cc b/src/h2load_http3_session.cc index 1ce06903..40970f7a 100644 --- a/src/h2load_http3_session.cc +++ b/src/h2load_http3_session.cc @@ -252,8 +252,8 @@ int Http3Session::close_stream(int64_t stream_id, uint64_t app_error_code) { } } -int Http3Session::reset_stream(int64_t stream_id) { - auto rv = nghttp3_conn_reset_stream(conn_, stream_id); +int Http3Session::shutdown_stream_read(int64_t stream_id) { + auto rv = nghttp3_conn_shutdown_stream_read(conn_, stream_id); if (rv != 0) { return -1; } diff --git a/src/h2load_http3_session.h b/src/h2load_http3_session.h index db6c4244..27ff025b 100644 --- a/src/h2load_http3_session.h +++ b/src/h2load_http3_session.h @@ -54,7 +54,7 @@ public: int send_stop_sending(int64_t stream_id, uint64_t app_error_code); int close_stream(int64_t stream_id, uint64_t app_error_code); - int reset_stream(int64_t stream_id); + int shutdown_stream_read(int64_t stream_id); int extend_max_local_streams(); int64_t submit_request_internal(); diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index b131e1ac..17f4f73c 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -141,7 +141,28 @@ int stream_reset(ngtcp2_conn *conn, int64_t stream_id, uint64_t final_size, int Client::quic_stream_reset(int64_t stream_id, uint64_t app_error_code) { auto s = static_cast(session.get()); - if (s->reset_stream(stream_id) != 0) { + if (s->shutdown_stream_read(stream_id) != 0) { + return -1; + } + return 0; +} + +namespace { +int stream_stop_sending(ngtcp2_conn *conn, int64_t stream_id, + uint64_t app_error_code, void *user_data, + void *stream_user_data) { + auto c = static_cast(user_data); + if (c->quic_stream_stop_sending(stream_id, app_error_code) != 0) { + return -1; + } + return 0; +} +} // namespace + +int Client::quic_stream_stop_sending(int64_t stream_id, + uint64_t app_error_code) { + auto s = static_cast(session.get()); + if (s->shutdown_stream_read(stream_id) != 0) { return -1; } return 0; @@ -327,6 +348,11 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, nullptr, // recv_new_token ngtcp2_crypto_delete_crypto_aead_ctx_cb, ngtcp2_crypto_delete_crypto_cipher_ctx_cb, + nullptr, // recv_datagram + nullptr, // ack_datagram + nullptr, // lost_datagram + nullptr, // get_path_challenge_data + h2load::stream_stop_sending, }; ngtcp2_cid scid, dcid; From 20cbd269c47bf79fbcc07f012733628b87a5190f Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Mon, 16 Aug 2021 16:58:11 +0900 Subject: [PATCH 090/124] Compile with the latest ngtcp2 --- src/h2load.h | 9 --------- src/h2load_quic.cc | 9 +-------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/src/h2load.h b/src/h2load.h index 2d18fbf8..061c776f 100644 --- a/src/h2load.h +++ b/src/h2load.h @@ -317,13 +317,6 @@ struct Stream { Stream(); }; -struct Crypto { - Crypto() : datalen(0), acked_offset(0) {} - std::array data; - size_t datalen; - size_t acked_offset; -}; - struct Client { DefaultMemchunks wb; std::unordered_map streams; @@ -338,8 +331,6 @@ struct Client { ev_timer pkt_timer; ngtcp2_conn *conn; quic::Error last_error; - // Client never send CRYPTO in Short packet. - std::array crypto; size_t max_pktlen; bool close_requested; FILE *qlog_file; diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc index 17f4f73c..a9709177 100644 --- a/src/h2load_quic.cc +++ b/src/h2load_quic.cc @@ -325,7 +325,6 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, ngtcp2_crypto_decrypt_cb, ngtcp2_crypto_hp_mask_cb, h2load::recv_stream_data, - nullptr, // acked_crypto_offset h2load::acked_stream_data_offset, nullptr, // stream_open h2load::stream_close, @@ -499,14 +498,8 @@ void Client::quic_set_tls_alert(uint8_t alert) { void Client::quic_write_client_handshake(ngtcp2_crypto_level level, const uint8_t *data, size_t datalen) { assert(level < 2); - auto &crypto = quic.crypto[level]; - assert(crypto.data.size() >= crypto.datalen + datalen); - auto p = std::begin(crypto.data) + crypto.datalen; - std::copy_n(data, datalen, p); - crypto.datalen += datalen; - - ngtcp2_conn_submit_crypto_data(quic.conn, level, p, datalen); + ngtcp2_conn_submit_crypto_data(quic.conn, level, data, datalen); } void quic_pkt_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) { From 01da060496a28b8b23643915a44bc9df934f85e4 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 15 Aug 2021 12:22:10 +0900 Subject: [PATCH 091/124] nghttpx: Create quic server socket --- src/Makefile.am | 1 + src/shrpx_config.cc | 22 +++- src/shrpx_config.h | 4 + src/shrpx_connection_handler.cc | 6 + src/shrpx_quic.cc | 188 ++++++++++++++++++++++++++++++++ src/shrpx_quic.h | 38 +++++++ src/shrpx_worker.cc | 18 +++ src/shrpx_worker.h | 4 + 8 files changed, 275 insertions(+), 6 deletions(-) create mode 100644 src/shrpx_quic.cc create mode 100644 src/shrpx_quic.h diff --git a/src/Makefile.am b/src/Makefile.am index 08797fff..d3a0b4c5 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -153,6 +153,7 @@ NGHTTPX_SRCS = \ shrpx_dns_resolver.cc shrpx_dns_resolver.h \ shrpx_dual_dns_resolver.cc shrpx_dual_dns_resolver.h \ shrpx_dns_tracker.cc shrpx_dns_tracker.h \ + shrpx_quic.cc shrpx_quic.h \ buffer.h memchunk.h template.h allocator.h \ xsi_strerror.c xsi_strerror.h diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc index 038c5f21..d86c61de 100644 --- a/src/shrpx_config.cc +++ b/src/shrpx_config.cc @@ -785,6 +785,7 @@ struct UpstreamParams { bool tls; bool sni_fwd; bool proxyproto; + bool quic; }; namespace { @@ -819,6 +820,8 @@ int parse_upstream_params(UpstreamParams &out, const StringRef &src_params) { out.alt_mode = UpstreamAltMode::HEALTHMON; } else if (util::strieq_l("proxyproto", param)) { out.proxyproto = true; + } else if (util::strieq_l("quic", param)) { + out.quic = true; } else if (!param.empty()) { LOG(ERROR) << "frontend: " << param << ": unknown keyword"; return -1; @@ -2637,7 +2640,6 @@ int parse_config(Config *config, int optid, const StringRef &opt, return 0; } case SHRPX_OPTID_FRONTEND: { - auto &listenerconf = config->conn.listener; auto &apiconf = config->api; auto addr_end = std::find(std::begin(optarg), std::end(optarg), ';'); @@ -2655,6 +2657,11 @@ int parse_config(Config *config, int optid, const StringRef &opt, return -1; } + if (params.quic && params.alt_mode != UpstreamAltMode::NONE) { + LOG(ERROR) << "frontend: api or healthmon cannot be used with quic"; + return -1; + } + UpstreamAddr addr{}; addr.fd = -1; addr.tls = params.tls; @@ -2666,12 +2673,15 @@ int parse_config(Config *config, int optid, const StringRef &opt, apiconf.enabled = true; } + auto &addrs = params.quic ? config->conn.quic_listener.addrs + : config->conn.listener.addrs; + if (util::istarts_with(optarg, SHRPX_UNIX_PATH_PREFIX)) { auto path = std::begin(optarg) + SHRPX_UNIX_PATH_PREFIX.size(); addr.host = make_string_ref(config->balloc, StringRef{path, addr_end}); addr.host_unix = true; - listenerconf.addrs.push_back(std::move(addr)); + addrs.push_back(std::move(addr)); return 0; } @@ -2686,21 +2696,21 @@ int parse_config(Config *config, int optid, const StringRef &opt, if (util::numeric_host(host, AF_INET)) { addr.family = AF_INET; - listenerconf.addrs.push_back(std::move(addr)); + addrs.push_back(std::move(addr)); return 0; } if (util::numeric_host(host, AF_INET6)) { addr.family = AF_INET6; - listenerconf.addrs.push_back(std::move(addr)); + addrs.push_back(std::move(addr)); return 0; } addr.family = AF_INET; - listenerconf.addrs.push_back(addr); + addrs.push_back(addr); addr.family = AF_INET6; - listenerconf.addrs.push_back(std::move(addr)); + addrs.push_back(std::move(addr)); return 0; } diff --git a/src/shrpx_config.h b/src/shrpx_config.h index e5a0fc6a..915c9da7 100644 --- a/src/shrpx_config.h +++ b/src/shrpx_config.h @@ -907,6 +907,10 @@ struct ConnectionConfig { int fastopen; } listener; + struct { + std::vector addrs; + } quic_listener; + struct { struct { ev_tstamp http2_read; diff --git a/src/shrpx_connection_handler.cc b/src/shrpx_connection_handler.cc index 84221ba7..f732f74a 100644 --- a/src/shrpx_connection_handler.cc +++ b/src/shrpx_connection_handler.cc @@ -244,6 +244,9 @@ int ConnectionHandler::create_single_worker() { return -1; } #endif // HAVE_MRUBY + if (single_worker_->setup_quic_server_socket() != 0) { + return -1; + } return 0; } @@ -305,6 +308,9 @@ int ConnectionHandler::create_worker_thread(size_t num) { return -1; } # endif // HAVE_MRUBY + if (worker->setup_quic_server_socket() != 0) { + return -1; + } workers_.push_back(std::move(worker)); worker_loops_.push_back(loop); diff --git a/src/shrpx_quic.cc b/src/shrpx_quic.cc new file mode 100644 index 00000000..48b9b94d --- /dev/null +++ b/src/shrpx_quic.cc @@ -0,0 +1,188 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_quic.h" + +#include +#include +#include + +#include + +#include "shrpx_config.h" +#include "shrpx_log.h" +#include "util.h" +#include "xsi_strerror.h" + +using namespace nghttp2; + +namespace shrpx { + +int create_quic_server_socket(UpstreamAddr &faddr) { + std::array errbuf; + int fd = -1; + int rv; + + auto service = util::utos(faddr.port); + addrinfo hints{}; + hints.ai_family = faddr.family; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_PASSIVE; +#ifdef AI_ADDRCONFIG + hints.ai_flags |= AI_ADDRCONFIG; +#endif // AI_ADDRCONFIG + + auto node = + faddr.host == StringRef::from_lit("*") ? nullptr : faddr.host.c_str(); + + addrinfo *res, *rp; + rv = getaddrinfo(node, service.c_str(), &hints, &res); +#ifdef AI_ADDRCONFIG + if (rv != 0) { + // Retry without AI_ADDRCONFIG + hints.ai_flags &= ~AI_ADDRCONFIG; + rv = getaddrinfo(node, service.c_str(), &hints, &res); + } +#endif // AI_ADDRCONFIG + if (rv != 0) { + LOG(FATAL) << "Unable to get IPv" << (faddr.family == AF_INET ? "4" : "6") + << " address for " << faddr.host << ", port " << faddr.port + << ": " << gai_strerror(rv); + return -1; + } + + auto res_d = defer(freeaddrinfo, res); + + std::array host; + + for (rp = res; rp; rp = rp->ai_next) { + rv = getnameinfo(rp->ai_addr, rp->ai_addrlen, host.data(), host.size(), + nullptr, 0, NI_NUMERICHOST); + if (rv != 0) { + LOG(WARN) << "getnameinfo() failed: " << gai_strerror(rv); + continue; + } + +#ifdef SOCK_NONBLOCK + fd = socket(rp->ai_family, rp->ai_socktype | SOCK_NONBLOCK | SOCK_CLOEXEC, + rp->ai_protocol); + if (fd == -1) { + auto error = errno; + LOG(WARN) << "socket() syscall failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + continue; + } +#else // !SOCK_NONBLOCK + fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (fd == -1) { + auto error = errno; + LOG(WARN) << "socket() syscall failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + continue; + } + util::make_socket_nonblocking(fd); + util::make_socket_closeonexec(fd); +#endif // !SOCK_NONBLOCK + + int val = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, + static_cast(sizeof(val))) == -1) { + auto error = errno; + LOG(WARN) << "Failed to set SO_REUSEADDR option to listener socket: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + continue; + } + + if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &val, + static_cast(sizeof(val))) == -1) { + auto error = errno; + LOG(WARN) << "Failed to set SO_REUSEPORT option to listener socket: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + continue; + } + + if (faddr.family == AF_INET6) { +#ifdef IPV6_V6ONLY + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, + static_cast(sizeof(val))) == -1) { + auto error = errno; + LOG(WARN) << "Failed to set IPV6_V6ONLY option to listener socket: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + continue; + } +#endif // IPV6_V6ONLY + + if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val, + static_cast(sizeof(val))) == -1) { + auto error = errno; + LOG(WARN) + << "Failed to set IPV6_RECVPKTINFO option to listener socket: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + continue; + } + } else { + if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &val, + static_cast(sizeof(val))) == -1) { + auto error = errno; + LOG(WARN) << "Failed to set IP_PKTINFO option to listener socket: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + continue; + } + } + + // TODO Enable ECN + + if (bind(fd, rp->ai_addr, rp->ai_addrlen) == -1) { + auto error = errno; + LOG(WARN) << "bind() syscall failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + continue; + } + + break; + } + + if (!rp) { + LOG(FATAL) << "Listening " << (faddr.family == AF_INET ? "IPv4" : "IPv6") + << " socket failed"; + + return -1; + } + + faddr.fd = fd; + faddr.hostport = util::make_http_hostport(mod_config()->balloc, + StringRef{host.data()}, faddr.port); + + LOG(NOTICE) << "Listening on " << faddr.hostport << ", quic"; + + return 0; +} + +} // namespace shrpx diff --git a/src/shrpx_quic.h b/src/shrpx_quic.h new file mode 100644 index 00000000..59b273e2 --- /dev/null +++ b/src/shrpx_quic.h @@ -0,0 +1,38 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_QUIC_H +#define SHRPX_QUIC_H + +#include "shrpx.h" + +struct UpstreamAddr; + +namespace shrpx { + +int create_quic_server_socket(UpstreamAddr &addr); + +} // namespace shrpx + +#endif // SHRPX_QUIC_H diff --git a/src/shrpx_worker.cc b/src/shrpx_worker.cc index cf8854b2..08966eee 100644 --- a/src/shrpx_worker.cc +++ b/src/shrpx_worker.cc @@ -39,6 +39,7 @@ #ifdef HAVE_MRUBY # include "shrpx_mruby.h" #endif // HAVE_MRUBY +#include "shrpx_quic.h" #include "util.h" #include "template.h" @@ -137,6 +138,7 @@ Worker::Worker(struct ev_loop *loop, SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx, : randgen_(util::make_mt19937()), worker_stat_{}, dns_tracker_(loop), + quic_upstream_addrs_{get_config()->conn.quic_listener.addrs}, loop_(loop), sv_ssl_ctx_(sv_ssl_ctx), cl_ssl_ctx_(cl_ssl_ctx), @@ -580,6 +582,22 @@ ConnectionHandler *Worker::get_connection_handler() const { DNSTracker *Worker::get_dns_tracker() { return &dns_tracker_; } +int Worker::setup_quic_server_socket() { + for (auto &addr : quic_upstream_addrs_) { + if (addr.host_unix) { + /* TODO Not implemented */ + assert(0); + continue; + } + + if (create_quic_server_socket(addr) != 0) { + return -1; + } + } + + return 0; +} + namespace { size_t match_downstream_addr_group_host( const RouterConfig &routerconf, const StringRef &host, diff --git a/src/shrpx_worker.h b/src/shrpx_worker.h index e736aad8..292deb32 100644 --- a/src/shrpx_worker.h +++ b/src/shrpx_worker.h @@ -321,6 +321,8 @@ public: DNSTracker *get_dns_tracker(); + int setup_quic_server_socket(); + private: #ifndef NOTHREADS std::future fut_; @@ -335,6 +337,8 @@ private: WorkerStat worker_stat_; DNSTracker dns_tracker_; + std::vector quic_upstream_addrs_; + std::shared_ptr downstreamconf_; std::unique_ptr session_cache_memcached_dispatcher_; #ifdef HAVE_MRUBY From 8b2746abf150675011ce3d5d95682b17d23b6683 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 15 Aug 2021 16:57:39 +0900 Subject: [PATCH 092/124] nghttpx: Add QUICListener --- src/Makefile.am | 1 + src/shrpx_quic_listener.cc | 97 ++++++++++++++++++++++++++++++++++++++ src/shrpx_quic_listener.h | 51 ++++++++++++++++++++ src/shrpx_worker.cc | 10 ++-- src/shrpx_worker.h | 2 + src/util.cc | 47 ++++++++++++++++-- src/util.h | 4 ++ 7 files changed, 201 insertions(+), 11 deletions(-) create mode 100644 src/shrpx_quic_listener.cc create mode 100644 src/shrpx_quic_listener.h diff --git a/src/Makefile.am b/src/Makefile.am index d3a0b4c5..497f7fa4 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -154,6 +154,7 @@ NGHTTPX_SRCS = \ shrpx_dual_dns_resolver.cc shrpx_dual_dns_resolver.h \ shrpx_dns_tracker.cc shrpx_dns_tracker.h \ shrpx_quic.cc shrpx_quic.h \ + shrpx_quic_listener.cc shrpx_quic_listener.h \ buffer.h memchunk.h template.h allocator.h \ xsi_strerror.c xsi_strerror.h diff --git a/src/shrpx_quic_listener.cc b/src/shrpx_quic_listener.cc new file mode 100644 index 00000000..33712a81 --- /dev/null +++ b/src/shrpx_quic_listener.cc @@ -0,0 +1,97 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_quic_listener.h" +#include "shrpx_worker.h" +#include "shrpx_config.h" +#include "shrpx_log.h" + +namespace shrpx { + +namespace { +void readcb(struct ev_loop *loop, ev_io *w, int revent) { + auto l = static_cast(w->data); + l->on_read(); +} +} // namespace + +QUICListener::QUICListener(const UpstreamAddr *faddr, Worker *worker) + : faddr_(faddr), worker_(worker) { + ev_io_init(&rev_, readcb, faddr_->fd, EV_READ); + rev_.data = this; + ev_io_start(worker_->get_loop(), &rev_); +} + +QUICListener::~QUICListener() { + ev_io_stop(worker_->get_loop(), &rev_); + close(faddr_->fd); +} + +void QUICListener::on_read() { + sockaddr_union su; + std::array buf; + size_t pktcnt = 0; + iovec msg_iov{buf.data(), buf.size()}; + + msghdr msg{}; + msg.msg_name = &su; + msg.msg_iov = &msg_iov; + msg.msg_iovlen = 1; + + uint8_t msg_ctrl[CMSG_SPACE(sizeof(in6_pktinfo))]; + msg.msg_control = msg_ctrl; + + for (; pktcnt < 10;) { + msg.msg_namelen = sizeof(su); + msg.msg_controllen = sizeof(msg_ctrl); + + auto nread = recvmsg(faddr_->fd, &msg, 0); + if (nread == -1) { + return; + } + + ++pktcnt; + + Address local_addr{}; + if (util::msghdr_get_local_addr(local_addr, &msg, su.storage.ss_family) != + 0) { + continue; + } + + util::set_port(local_addr, faddr_->port); + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "QUIC received packet: local=" + << util::to_numeric_addr(&local_addr) + << " remote=" << util::to_numeric_addr(&su.sa, msg.msg_namelen) + << " " << nread << " bytes"; + } + + if (nread == 0) { + continue; + } + } +} + +} // namespace shrpx diff --git a/src/shrpx_quic_listener.h b/src/shrpx_quic_listener.h new file mode 100644 index 00000000..3d709216 --- /dev/null +++ b/src/shrpx_quic_listener.h @@ -0,0 +1,51 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_QUIC_LISTENER_H +#define SHRPX_QUIC_LISTENER_H + +#include "shrpx.h" + +#include + +namespace shrpx { + +struct UpstreamAddr; +class Worker; + +class QUICListener { +public: + QUICListener(const UpstreamAddr *faddr, Worker *worker); + ~QUICListener(); + void on_read(); + +private: + const UpstreamAddr *faddr_; + Worker *worker_; + ev_io rev_; +}; + +} // namespace shrpx + +#endif // SHRPX_QUIC_LISTENER_H diff --git a/src/shrpx_worker.cc b/src/shrpx_worker.cc index 08966eee..87235ee9 100644 --- a/src/shrpx_worker.cc +++ b/src/shrpx_worker.cc @@ -40,6 +40,7 @@ # include "shrpx_mruby.h" #endif // HAVE_MRUBY #include "shrpx_quic.h" +#include "shrpx_quic_listener.h" #include "util.h" #include "template.h" @@ -584,15 +585,12 @@ DNSTracker *Worker::get_dns_tracker() { return &dns_tracker_; } int Worker::setup_quic_server_socket() { for (auto &addr : quic_upstream_addrs_) { - if (addr.host_unix) { - /* TODO Not implemented */ - assert(0); - continue; - } - + assert(!addr.host_unix); if (create_quic_server_socket(addr) != 0) { return -1; } + + quic_listeners_.emplace_back(std::make_unique(&addr, this)); } return 0; diff --git a/src/shrpx_worker.h b/src/shrpx_worker.h index 292deb32..f495f8e7 100644 --- a/src/shrpx_worker.h +++ b/src/shrpx_worker.h @@ -61,6 +61,7 @@ class ConnectBlocker; class MemcachedDispatcher; struct UpstreamAddr; class ConnectionHandler; +class QUICListener; #ifdef HAVE_MRUBY namespace mruby { @@ -338,6 +339,7 @@ private: DNSTracker dns_tracker_; std::vector quic_upstream_addrs_; + std::vector> quic_listeners_; std::shared_ptr downstreamconf_; std::unique_ptr session_cache_memcached_dispatcher_; diff --git a/src/util.cc b/src/util.cc index 5f84b556..c6ff16ab 100644 --- a/src/util.cc +++ b/src/util.cc @@ -686,18 +686,21 @@ std::string numeric_name(const struct sockaddr *sa, socklen_t salen) { } std::string to_numeric_addr(const Address *addr) { - auto family = addr->su.storage.ss_family; + return to_numeric_addr(&addr->su.sa, addr->len); +} + +std::string to_numeric_addr(const struct sockaddr *sa, socklen_t salen) { + auto family = sa->sa_family; #ifndef _WIN32 if (family == AF_UNIX) { - return addr->su.un.sun_path; + return reinterpret_cast(sa)->sun_path; } #endif // !_WIN32 std::array host; std::array serv; - auto rv = - getnameinfo(&addr->su.sa, addr->len, host.data(), host.size(), - serv.data(), serv.size(), NI_NUMERICHOST | NI_NUMERICSERV); + auto rv = getnameinfo(sa, salen, host.data(), host.size(), serv.data(), + serv.size(), NI_NUMERICHOST | NI_NUMERICSERV); if (rv != 0) { return "unknown"; } @@ -1667,6 +1670,40 @@ int daemonize(int nochdir, int noclose) { #endif // !defined(__APPLE__) } +int msghdr_get_local_addr(Address &dest, msghdr *msg, int family) { + switch (family) { + case AF_INET: + for (auto cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) { + if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) { + auto pktinfo = reinterpret_cast(CMSG_DATA(cmsg)); + dest.len = sizeof(dest.su.in); + auto &sa = dest.su.in; + sa.sin_family = AF_INET; + sa.sin_addr = pktinfo->ipi_addr; + + return 0; + } + } + + return -1; + case AF_INET6: + for (auto cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) { + if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) { + auto pktinfo = reinterpret_cast(CMSG_DATA(cmsg)); + dest.len = sizeof(dest.su.in6); + auto &sa = dest.su.in6; + sa.sin6_family = AF_INET6; + sa.sin6_addr = pktinfo->ipi6_addr; + return 0; + } + } + + return -1; + } + + return -1; +} + } // namespace util } // namespace nghttp2 diff --git a/src/util.h b/src/util.h index 586d39c7..78b51b6f 100644 --- a/src/util.h +++ b/src/util.h @@ -505,6 +505,8 @@ std::string numeric_name(const struct sockaddr *sa, socklen_t salen); // IPv6 address, address is enclosed by square brackets ([]). std::string to_numeric_addr(const Address *addr); +std::string to_numeric_addr(const struct sockaddr *sa, socklen_t salen); + // Sets |port| to |addr|. void set_port(Address &addr, uint16_t port); @@ -786,6 +788,8 @@ std::mt19937 make_mt19937(); // daemon() using fork(). int daemonize(int nochdir, int noclose); +int msghdr_get_local_addr(Address &dest, msghdr *msg, int family); + } // namespace util } // namespace nghttp2 From aeb0b0728df1bbdc7c0fc7c3552c56f23c9241fb Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 15 Aug 2021 21:36:43 +0900 Subject: [PATCH 093/124] nghttpx: Add QUICConnectionHandler and HTTP3Upstream skeleton --- src/Makefile.am | 2 + src/shrpx_http3_upstream.cc | 132 ++++++++++++++++++++++ src/shrpx_http3_upstream.h | 93 ++++++++++++++++ src/shrpx_quic.cc | 7 ++ src/shrpx_quic.h | 11 +- src/shrpx_quic_connection_handler.cc | 157 +++++++++++++++++++++++++++ src/shrpx_quic_connection_handler.h | 64 +++++++++++ 7 files changed, 465 insertions(+), 1 deletion(-) create mode 100644 src/shrpx_http3_upstream.cc create mode 100644 src/shrpx_http3_upstream.h create mode 100644 src/shrpx_quic_connection_handler.cc create mode 100644 src/shrpx_quic_connection_handler.h diff --git a/src/Makefile.am b/src/Makefile.am index 497f7fa4..e7ed9db1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -155,6 +155,8 @@ NGHTTPX_SRCS = \ shrpx_dns_tracker.cc shrpx_dns_tracker.h \ shrpx_quic.cc shrpx_quic.h \ shrpx_quic_listener.cc shrpx_quic_listener.h \ + shrpx_quic_connection_handler.cc shrpx_quic_connection_handler.h \ + shrpx_http3_upstream.cc shrpx_http3_upstream.h \ buffer.h memchunk.h template.h allocator.h \ xsi_strerror.c xsi_strerror.h diff --git a/src/shrpx_http3_upstream.cc b/src/shrpx_http3_upstream.cc new file mode 100644 index 00000000..6c2c8541 --- /dev/null +++ b/src/shrpx_http3_upstream.cc @@ -0,0 +1,132 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_http3_upstream.h" +#include "shrpx_client_handler.h" +#include "shrpx_downstream.h" +#include "shrpx_downstream_connection.h" +#include "shrpx_log.h" + +namespace shrpx { + +Http3Upstream::Http3Upstream(ClientHandler *handler) : handler_{handler} {} + +Http3Upstream::~Http3Upstream() {} + +int Http3Upstream::on_read() { return 0; } + +int Http3Upstream::on_write() { return 0; } + +int Http3Upstream::on_timeout(Downstream *downstream) { return 0; } + +int Http3Upstream::on_downstream_abort_request(Downstream *downstream, + unsigned int status_code) { + return 0; +} + +int Http3Upstream::on_downstream_abort_request_with_https_redirect( + Downstream *downstream) { + return 0; +} + +int Http3Upstream::downstream_read(DownstreamConnection *dconn) { return 0; } + +int Http3Upstream::downstream_write(DownstreamConnection *dconn) { return 0; } + +int Http3Upstream::downstream_eof(DownstreamConnection *dconn) { return 0; } + +int Http3Upstream::downstream_error(DownstreamConnection *dconn, int events) { + return 0; +} + +ClientHandler *Http3Upstream::get_client_handler() const { return handler_; } + +int Http3Upstream::on_downstream_header_complete(Downstream *downstream) { + return 0; +} + +int Http3Upstream::on_downstream_body(Downstream *downstream, + const uint8_t *data, size_t len, + bool flush) { + return 0; +} + +int Http3Upstream::on_downstream_body_complete(Downstream *downstream) { + return 0; +} + +void Http3Upstream::on_handler_delete() {} + +int Http3Upstream::on_downstream_reset(Downstream *downstream, bool no_retry) { + return 0; +} + +void Http3Upstream::pause_read(IOCtrlReason reason) {} + +int Http3Upstream::resume_read(IOCtrlReason reason, Downstream *downstream, + size_t consumed) { + return 0; +} + +int Http3Upstream::send_reply(Downstream *downstream, const uint8_t *body, + size_t bodylen) { + return 0; +} + +int Http3Upstream::initiate_push(Downstream *downstream, const StringRef &uri) { + return 0; +} + +int Http3Upstream::response_riovec(struct iovec *iov, int iovcnt) const { + return 0; +} + +void Http3Upstream::response_drain(size_t n) {} + +bool Http3Upstream::response_empty() const { return false; } + +Downstream * +Http3Upstream::on_downstream_push_promise(Downstream *downstream, + int32_t promised_stream_id) { + return nullptr; +} + +int Http3Upstream::on_downstream_push_promise_complete( + Downstream *downstream, Downstream *promised_downstream) { + return 0; +} + +bool Http3Upstream::push_enabled() const { return false; } + +void Http3Upstream::cancel_premature_downstream( + Downstream *promised_downstream) {} + +int Http3Upstream::on_read(const UpstreamAddr *faddr, + const Address &remote_addr, + const Address &local_addr, const uint8_t *data, + size_t datalen) { + return 0; +} + +} // namespace shrpx diff --git a/src/shrpx_http3_upstream.h b/src/shrpx_http3_upstream.h new file mode 100644 index 00000000..318d9cc0 --- /dev/null +++ b/src/shrpx_http3_upstream.h @@ -0,0 +1,93 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_HTTP3_UPSTREAM_H +#define SHRPX_HTTP3_UPSTREAM_H + +#include "shrpx.h" +#include "shrpx_upstream.h" +#include "network.h" + +using namespace nghttp2; + +namespace shrpx { + +struct UpstreamAddr; + +class Http3Upstream : public Upstream { +public: + Http3Upstream(ClientHandler *handler); + virtual ~Http3Upstream(); + + virtual int on_read(); + virtual int on_write(); + virtual int on_timeout(Downstream *downstream); + virtual int on_downstream_abort_request(Downstream *downstream, + unsigned int status_code); + virtual int + on_downstream_abort_request_with_https_redirect(Downstream *downstream); + virtual int downstream_read(DownstreamConnection *dconn); + virtual int downstream_write(DownstreamConnection *dconn); + virtual int downstream_eof(DownstreamConnection *dconn); + virtual int downstream_error(DownstreamConnection *dconn, int events); + virtual ClientHandler *get_client_handler() const; + + virtual int on_downstream_header_complete(Downstream *downstream); + virtual int on_downstream_body(Downstream *downstream, const uint8_t *data, + size_t len, bool flush); + virtual int on_downstream_body_complete(Downstream *downstream); + + virtual void on_handler_delete(); + virtual int on_downstream_reset(Downstream *downstream, bool no_retry); + + virtual void pause_read(IOCtrlReason reason); + virtual int resume_read(IOCtrlReason reason, Downstream *downstream, + size_t consumed); + virtual int send_reply(Downstream *downstream, const uint8_t *body, + size_t bodylen); + + virtual int initiate_push(Downstream *downstream, const StringRef &uri); + + virtual int response_riovec(struct iovec *iov, int iovcnt) const; + virtual void response_drain(size_t n); + virtual bool response_empty() const; + + virtual Downstream *on_downstream_push_promise(Downstream *downstream, + int32_t promised_stream_id); + virtual int + on_downstream_push_promise_complete(Downstream *downstream, + Downstream *promised_downstream); + virtual bool push_enabled() const; + virtual void cancel_premature_downstream(Downstream *promised_downstream); + + int on_read(const UpstreamAddr *faddr, const Address &remote_addr, + const Address &local_addr, const uint8_t *data, size_t datalen); + +private: + ClientHandler *handler_; +}; + +} // namespace shrpx + +#endif // SHRPX_HTTP3_UPSTREAM_H diff --git a/src/shrpx_quic.cc b/src/shrpx_quic.cc index 48b9b94d..175ffaeb 100644 --- a/src/shrpx_quic.cc +++ b/src/shrpx_quic.cc @@ -185,4 +185,11 @@ int create_quic_server_socket(UpstreamAddr &faddr) { return 0; } +int quic_send_packet(const UpstreamAddr *addr, const sockaddr *remote_sa, + size_t remote_salen, const sockaddr *local_sa, + size_t local_salen, const uint8_t *data, size_t datalen, + size_t gso_size) { + return 0; +} + } // namespace shrpx diff --git a/src/shrpx_quic.h b/src/shrpx_quic.h index 59b273e2..a575fee4 100644 --- a/src/shrpx_quic.h +++ b/src/shrpx_quic.h @@ -27,12 +27,21 @@ #include "shrpx.h" -struct UpstreamAddr; +#include namespace shrpx { +struct UpstreamAddr; + +constexpr size_t SHRPX_QUIC_SCIDLEN = 20; + int create_quic_server_socket(UpstreamAddr &addr); +int quic_send_packet(const UpstreamAddr *addr, const sockaddr *remote_sa, + size_t remote_salen, const sockaddr *local_sa, + size_t local_salen, const uint8_t *data, size_t datalen, + size_t gso_size); + } // namespace shrpx #endif // SHRPX_QUIC_H diff --git a/src/shrpx_quic_connection_handler.cc b/src/shrpx_quic_connection_handler.cc new file mode 100644 index 00000000..913da6b2 --- /dev/null +++ b/src/shrpx_quic_connection_handler.cc @@ -0,0 +1,157 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_quic_connection_handler.h" + +#include + +#include "shrpx_worker.h" +#include "shrpx_client_handler.h" +#include "shrpx_log.h" +#include "shrpx_quic.h" +#include "shrpx_http3_upstream.h" + +namespace shrpx { + +QUICConnectionHandler::QUICConnectionHandler(Worker *worker) + : worker_{worker} {} + +QUICConnectionHandler::~QUICConnectionHandler() {} + +namespace { +std::string make_cid_key(const uint8_t *dcid, size_t dcidlen) { + return std::string{dcid, dcid + dcidlen}; +} +} // namespace + +int QUICConnectionHandler::handle_packet(const UpstreamAddr *faddr, + const Address &remote_addr, + const Address &local_addr, + const uint8_t *data, size_t datalen) { + int rv; + uint32_t version; + const uint8_t *dcid, *scid; + size_t dcidlen, scidlen; + + rv = ngtcp2_pkt_decode_version_cid(&version, &dcid, &dcidlen, &scid, &scidlen, + data, datalen, SHRPX_QUIC_SCIDLEN); + if (rv != 0) { + if (rv == 1) { + send_version_negotiation(faddr, version, scid, scidlen, dcid, dcidlen, + remote_addr, local_addr); + } + + return 0; + } + + auto dcid_key = make_cid_key(dcid, dcidlen); + + auto it = connections_.find(dcid_key); + if (it == std::end(connections_)) { + // new connection + + ngtcp2_pkt_hd hd; + + switch (ngtcp2_accept(&hd, data, datalen)) { + case 0: + break; + case NGTCP2_ERR_RETRY: + // TODO Send retry + return 0; + case NGTCP2_ERR_VERSION_NEGOTIATION: + send_version_negotiation(faddr, version, scid, scidlen, dcid, dcidlen, + remote_addr, local_addr); + return 0; + default: + return 0; + } + + return 0; + } + + auto h = (*it).second.get(); + auto upstream = static_cast(h->get_upstream()); + + // TODO Delete h on failure + upstream->on_read(faddr, remote_addr, local_addr, data, datalen); + + return 0; +} + +namespace { +uint32_t generate_reserved_version(const Address &addr, uint32_t version) { + uint32_t h = 0x811C9DC5u; + const uint8_t *p = reinterpret_cast(&addr.su.sa); + const uint8_t *ep = p + addr.len; + + for (; p != ep; ++p) { + h ^= *p; + h *= 0x01000193u; + } + + version = htonl(version); + p = (const uint8_t *)&version; + ep = p + sizeof(version); + + for (; p != ep; ++p) { + h ^= *p; + h *= 0x01000193u; + } + + h &= 0xf0f0f0f0u; + h |= 0x0a0a0a0au; + + return h; +} +} // namespace + +int QUICConnectionHandler::send_version_negotiation( + const UpstreamAddr *faddr, uint32_t version, const uint8_t *dcid, + size_t dcidlen, const uint8_t *scid, size_t scidlen, + const Address &remote_addr, const Address &local_addr) { + std::array sv; + + sv[0] = generate_reserved_version(remote_addr, version); + sv[1] = NGTCP2_PROTO_VER_V1; + + std::array buf; + + uint8_t rand_byte; + util::random_bytes(&rand_byte, &rand_byte + 1, worker_->get_randgen()); + + auto nwrite = ngtcp2_pkt_write_version_negotiation( + buf.data(), buf.size(), rand_byte, dcid, dcidlen, scid, scidlen, + sv.data(), sv.size()); + if (nwrite < 0) { + LOG(ERROR) << "ngtcp2_pkt_write_version_negotiation: " + << ngtcp2_strerror(nwrite); + return -1; + } + + return quic_send_packet(faddr, &remote_addr.su.sa, remote_addr.len, + &local_addr.su.sa, local_addr.len, buf.data(), nwrite, + 0); +} + +} // namespace shrpx diff --git a/src/shrpx_quic_connection_handler.h b/src/shrpx_quic_connection_handler.h new file mode 100644 index 00000000..b580d128 --- /dev/null +++ b/src/shrpx_quic_connection_handler.h @@ -0,0 +1,64 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_QUIC_CONNECTION_HANDLER_H +#define SHRPX_QUIC_CONNECTION_HANDLER_H + +#include "shrpx.h" + +#include +#include +#include + +#include "network.h" + +using namespace nghttp2; + +namespace shrpx { + +struct UpstreamAddr; +class ClientHandler; +class Worker; + +class QUICConnectionHandler { +public: + QUICConnectionHandler(Worker *worker); + ~QUICConnectionHandler(); + int handle_packet(const UpstreamAddr *faddr, const Address &remote_addr, + const Address &local_addr, const uint8_t *data, + size_t datalen); + int send_version_negotiation(const UpstreamAddr *faddr, uint32_t version, + const uint8_t *dcid, size_t dcidlen, + const uint8_t *scid, size_t scidlen, + const Address &remote_addr, + const Address &local_addr); + +private: + Worker *worker_; + std::unordered_map> connections_; +}; + +} // namespace shrpx + +#endif // SHRPX_QUIC_CONNECTION_HANDLER_H From ef53db201eba78a13db7c5da44653ab92cb712e1 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 15 Aug 2021 22:57:26 +0900 Subject: [PATCH 094/124] nghttpx: Create QUIC SSL_CTX We choose an easier route to duplicate SSL_CTX for QUIC. --- src/shrpx_connection_handler.cc | 65 +++++ src/shrpx_connection_handler.h | 4 + src/shrpx_http3_upstream.cc | 20 +- src/shrpx_http3_upstream.h | 14 ++ src/shrpx_tls.cc | 406 ++++++++++++++++++++++++++++++++ src/shrpx_tls.h | 20 ++ 6 files changed, 528 insertions(+), 1 deletion(-) diff --git a/src/shrpx_connection_handler.cc b/src/shrpx_connection_handler.cc index f732f74a..db2ace7d 100644 --- a/src/shrpx_connection_handler.cc +++ b/src/shrpx_connection_handler.cc @@ -156,6 +156,17 @@ ConnectionHandler::~ConnectionHandler() { ev_timer_stop(loop_, &ocsp_timer_); ev_timer_stop(loop_, &disable_acceptor_timer_); + for (auto ssl_ctx : quic_all_ssl_ctx_) { + if (ssl_ctx == nullptr) { + continue; + } + + auto tls_ctx_data = + static_cast(SSL_CTX_get_app_data(ssl_ctx)); + delete tls_ctx_data; + SSL_CTX_free(ssl_ctx); + } + for (auto ssl_ctx : all_ssl_ctx_) { auto tls_ctx_data = static_cast(SSL_CTX_get_app_data(ssl_ctx)); @@ -209,6 +220,16 @@ int ConnectionHandler::create_single_worker() { nb_ #endif // HAVE_NEVERBLEED ); + + quic_cert_tree_ = tls::create_cert_lookup_tree(); + tls::setup_quic_server_ssl_context(quic_all_ssl_ctx_, quic_indexed_ssl_ctx_, + quic_cert_tree_.get() +#ifdef HAVE_NEVERBLEED + , + nb_ +#endif // HAVE_NEVERBLEED + ); + auto cl_ssl_ctx = tls::setup_downstream_client_ssl_context( #ifdef HAVE_NEVERBLEED nb_ @@ -217,6 +238,7 @@ int ConnectionHandler::create_single_worker() { if (cl_ssl_ctx) { all_ssl_ctx_.push_back(cl_ssl_ctx); + quic_all_ssl_ctx_.push_back(nullptr); } auto config = get_config(); @@ -233,6 +255,7 @@ int ConnectionHandler::create_single_worker() { tlsconf.cacert, memcachedconf.cert_file, memcachedconf.private_key_file, nullptr); all_ssl_ctx_.push_back(session_cache_ssl_ctx); + quic_all_ssl_ctx_.push_back(nullptr); } } @@ -263,6 +286,16 @@ int ConnectionHandler::create_worker_thread(size_t num) { nb_ # endif // HAVE_NEVERBLEED ); + + quic_cert_tree_ = tls::create_cert_lookup_tree(); + tls::setup_quic_server_ssl_context(quic_all_ssl_ctx_, quic_indexed_ssl_ctx_, + quic_cert_tree_.get() +# ifdef HAVE_NEVERBLEED + , + nb_ +# endif // HAVE_NEVERBLEED + ); + auto cl_ssl_ctx = tls::setup_downstream_client_ssl_context( # ifdef HAVE_NEVERBLEED nb_ @@ -271,6 +304,7 @@ int ConnectionHandler::create_worker_thread(size_t num) { if (cl_ssl_ctx) { all_ssl_ctx_.push_back(cl_ssl_ctx); + quic_all_ssl_ctx_.push_back(nullptr); } auto config = get_config(); @@ -294,6 +328,7 @@ int ConnectionHandler::create_worker_thread(size_t num) { tlsconf.cacert, memcachedconf.cert_file, memcachedconf.private_key_file, nullptr); all_ssl_ctx_.push_back(session_cache_ssl_ctx); + quic_all_ssl_ctx_.push_back(nullptr); } } @@ -608,6 +643,7 @@ void ConnectionHandler::handle_ocsp_complete() { ev_child_stop(loop_, &ocsp_.chldev); assert(ocsp_.next < all_ssl_ctx_.size()); + assert(all_ssl_ctx_.size() == quic_all_ssl_ctx_.size()); auto ssl_ctx = all_ssl_ctx_[ocsp_.next]; auto tls_ctx_data = @@ -635,6 +671,29 @@ void ConnectionHandler::handle_ocsp_complete() { if (tlsconf.ocsp.no_verify || tls::verify_ocsp_response(ssl_ctx, ocsp_.resp.data(), ocsp_.resp.size()) == 0) { + // We have list of SSL_CTX with the same certificate in + // quic_all_ssl_ctx_ as well. Some SSL_CTXs are missing there in + // that case we get nullptr. + auto quic_ssl_ctx = quic_all_ssl_ctx_[ocsp_.next]; + if (quic_ssl_ctx) { + auto quic_tls_ctx_data = static_cast( + SSL_CTX_get_app_data(quic_ssl_ctx)); +#ifndef OPENSSL_IS_BORINGSSL +# ifdef HAVE_ATOMIC_STD_SHARED_PTR + std::atomic_store_explicit( + &quic_tls_ctx_data->ocsp_data, + std::make_shared>(ocsp_.resp), + std::memory_order_release); +# else // !HAVE_ATOMIC_STD_SHARED_PTR + std::lock_guard g(quic_tls_ctx_data->mu); + quic_tls_ctx_data->ocsp_data = + std::make_shared>(ocsp_.resp); +# endif // !HAVE_ATOMIC_STD_SHARED_PTR +#else // OPENSSL_IS_BORINGSSL + SSL_CTX_set_ocsp_response(ssl_ctx, ocsp_.resp.data(), ocsp_.resp.size()); +#endif // OPENSSL_IS_BORINGSSL + } + #ifndef OPENSSL_IS_BORINGSSL # ifdef HAVE_ATOMIC_STD_SHARED_PTR std::atomic_store_explicit( @@ -815,6 +874,7 @@ SSL_CTX *ConnectionHandler::create_tls_ticket_key_memcached_ssl_ctx() { nullptr); all_ssl_ctx_.push_back(ssl_ctx); + quic_all_ssl_ctx_.push_back(nullptr); return ssl_ctx; } @@ -877,6 +937,11 @@ ConnectionHandler::get_indexed_ssl_ctx(size_t idx) const { return indexed_ssl_ctx_[idx]; } +const std::vector & +ConnectionHandler::get_quic_indexed_ssl_ctx(size_t idx) const { + return quic_indexed_ssl_ctx_[idx]; +} + void ConnectionHandler::set_enable_acceptor_on_ocsp_completion(bool f) { enable_acceptor_on_ocsp_completion_ = f; } diff --git a/src/shrpx_connection_handler.h b/src/shrpx_connection_handler.h index 535bc2c3..eb86b855 100644 --- a/src/shrpx_connection_handler.h +++ b/src/shrpx_connection_handler.h @@ -159,6 +159,7 @@ public: SSL_CTX *get_ssl_ctx(size_t idx) const; const std::vector &get_indexed_ssl_ctx(size_t idx) const; + const std::vector &get_quic_indexed_ssl_ctx(size_t idx) const; #ifdef HAVE_NEVERBLEED void set_neverbleed(neverbleed_t *nb); @@ -187,6 +188,8 @@ private: // selection among them are performed by hostname presented by SNI, // and signature algorithm presented by client. std::vector> indexed_ssl_ctx_; + std::vector quic_all_ssl_ctx_; + std::vector> quic_indexed_ssl_ctx_; OCSPUpdateContext ocsp_; std::mt19937 &gen_; // ev_loop for each worker @@ -203,6 +206,7 @@ private: // Otherwise, nullptr and workers_ has instances of Worker instead. std::unique_ptr single_worker_; std::unique_ptr cert_tree_; + std::unique_ptr quic_cert_tree_; std::unique_ptr tls_ticket_key_memcached_dispatcher_; // Current TLS session ticket keys. Note that TLS connection does // not refer to this field directly. They use TicketKeys object in diff --git a/src/shrpx_http3_upstream.cc b/src/shrpx_http3_upstream.cc index 6c2c8541..b0da58c8 100644 --- a/src/shrpx_http3_upstream.cc +++ b/src/shrpx_http3_upstream.cc @@ -30,7 +30,8 @@ namespace shrpx { -Http3Upstream::Http3Upstream(ClientHandler *handler) : handler_{handler} {} +Http3Upstream::Http3Upstream(ClientHandler *handler) + : handler_{handler}, tls_alert_{0} {} Http3Upstream::~Http3Upstream() {} @@ -129,4 +130,21 @@ int Http3Upstream::on_read(const UpstreamAddr *faddr, return 0; } +int Http3Upstream::on_rx_secret(ngtcp2_crypto_level level, + const uint8_t *secret, size_t secretlen) { + return 0; +} + +int Http3Upstream::on_tx_secret(ngtcp2_crypto_level level, + const uint8_t *secret, size_t secretlen) { + return 0; +} + +int Http3Upstream::add_crypto_data(ngtcp2_crypto_level level, + const uint8_t *data, size_t datalen) { + return 0; +} + +void Http3Upstream::set_tls_alert(uint8_t alert) { tls_alert_ = alert; } + } // namespace shrpx diff --git a/src/shrpx_http3_upstream.h b/src/shrpx_http3_upstream.h index 318d9cc0..d979fff7 100644 --- a/src/shrpx_http3_upstream.h +++ b/src/shrpx_http3_upstream.h @@ -26,6 +26,9 @@ #define SHRPX_HTTP3_UPSTREAM_H #include "shrpx.h" + +#include + #include "shrpx_upstream.h" #include "network.h" @@ -84,8 +87,19 @@ public: int on_read(const UpstreamAddr *faddr, const Address &remote_addr, const Address &local_addr, const uint8_t *data, size_t datalen); + int on_rx_secret(ngtcp2_crypto_level level, const uint8_t *secret, + size_t secretlen); + int on_tx_secret(ngtcp2_crypto_level level, const uint8_t *secret, + size_t secretlen); + + int add_crypto_data(ngtcp2_crypto_level level, const uint8_t *data, + size_t datalen); + + void set_tls_alert(uint8_t alert); + private: ClientHandler *handler_; + uint8_t tls_alert_; }; } // namespace shrpx diff --git a/src/shrpx_tls.cc b/src/shrpx_tls.cc index 3294a541..9ca883e8 100644 --- a/src/shrpx_tls.cc +++ b/src/shrpx_tls.cc @@ -51,6 +51,10 @@ #include +#include +#include +#include + #include "shrpx_log.h" #include "shrpx_client_handler.h" #include "shrpx_config.h" @@ -60,6 +64,7 @@ #include "shrpx_memcached_request.h" #include "shrpx_memcached_dispatcher.h" #include "shrpx_connection_handler.h" +#include "shrpx_http3_upstream.h" #include "util.h" #include "tls.h" #include "template.h" @@ -600,6 +605,32 @@ int alpn_select_proto_cb(SSL *ssl, const unsigned char **out, } // namespace #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L +#if OPENSSL_VERSION_NUMBER >= 0x10002000L +namespace { +int quic_alpn_select_proto_cb(SSL *ssl, const unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) { + for (auto p = in, end = in + inlen; p < end;) { + auto proto_id = p + 1; + auto proto_len = *p; + + if (proto_id + proto_len <= end && + util::streq_l("h3", StringRef{proto_id, proto_len})) { + + *out = reinterpret_cast(proto_id); + *outlen = proto_len; + + return SSL_TLSEXT_ERR_OK; + } + + p += 1 + proto_len; + } + + return SSL_TLSEXT_ERR_NOACK; +} +} // namespace +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + #if !LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L # ifndef TLSEXT_TYPE_signed_certificate_timestamp @@ -1042,6 +1073,322 @@ SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file, return ssl_ctx; } +namespace { +int quic_set_encryption_secrets(SSL *ssl, OSSL_ENCRYPTION_LEVEL ossl_level, + const uint8_t *rx_secret, + const uint8_t *tx_secret, size_t secretlen) { + auto conn = static_cast(SSL_get_app_data(ssl)); + auto handler = static_cast(conn->data); + auto upstream = static_cast(handler->get_upstream()); + auto level = ngtcp2_crypto_openssl_from_ossl_encryption_level(ossl_level); + + if (upstream->on_rx_secret(level, rx_secret, secretlen) != 0) { + return 0; + } + + if (tx_secret && upstream->on_tx_secret(level, tx_secret, secretlen) != 0) { + return 0; + } + + return 1; +} +} // namespace + +namespace { +int quic_add_handshake_data(SSL *ssl, OSSL_ENCRYPTION_LEVEL ossl_level, + const uint8_t *data, size_t len) { + auto conn = static_cast(SSL_get_app_data(ssl)); + auto handler = static_cast(conn->data); + auto upstream = static_cast(handler->get_upstream()); + auto level = ngtcp2_crypto_openssl_from_ossl_encryption_level(ossl_level); + + upstream->add_crypto_data(level, data, len); + + return 1; +} +} // namespace + +namespace { +int quic_flush_flight(SSL *ssl) { return 1; } +} // namespace + +namespace { +int quic_send_alert(SSL *ssl, OSSL_ENCRYPTION_LEVEL ossl_level, uint8_t alert) { + auto conn = static_cast(SSL_get_app_data(ssl)); + auto handler = static_cast(conn->data); + auto upstream = static_cast(handler->get_upstream()); + + upstream->set_tls_alert(alert); + + return 1; +} +} // namespace + +namespace { +auto quic_method = SSL_QUIC_METHOD{ + quic_set_encryption_secrets, + quic_add_handshake_data, + quic_flush_flight, + quic_send_alert, +}; +} // namespace + +SSL_CTX *create_quic_ssl_context(const char *private_key_file, + const char *cert_file, + const std::vector &sct_data +#ifdef HAVE_NEVERBLEED + , + neverbleed_t *nb +#endif // HAVE_NEVERBLEED +) { + auto ssl_ctx = SSL_CTX_new(TLS_server_method()); + if (!ssl_ctx) { + LOG(FATAL) << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + + constexpr auto ssl_opts = + (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION | SSL_OP_SINGLE_ECDH_USE | + SSL_OP_SINGLE_DH_USE | + SSL_OP_CIPHER_SERVER_PREFERENCE +#if OPENSSL_1_1_1_API + // The reason for disabling built-in anti-replay in OpenSSL is + // that it only works if client gets back to the same server. + // The freshness check described in + // https://tools.ietf.org/html/rfc8446#section-8.3 is still + // performed. + | SSL_OP_NO_ANTI_REPLAY +#endif // OPENSSL_1_1_1_API + ; + + auto config = mod_config(); + auto &tlsconf = config->tls; + + SSL_CTX_set_options(ssl_ctx, ssl_opts); + + SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_3_VERSION); + SSL_CTX_set_max_proto_version(ssl_ctx, TLS1_3_VERSION); + + const unsigned char sid_ctx[] = "shrpx"; + SSL_CTX_set_session_id_context(ssl_ctx, sid_ctx, sizeof(sid_ctx) - 1); + SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_SERVER); + + if (!tlsconf.session_cache.memcached.host.empty()) { + SSL_CTX_sess_set_new_cb(ssl_ctx, tls_session_new_cb); + SSL_CTX_sess_set_get_cb(ssl_ctx, tls_session_get_cb); + } + + SSL_CTX_set_timeout(ssl_ctx, tlsconf.session_timeout.count()); + + if (SSL_CTX_set_cipher_list(ssl_ctx, tlsconf.ciphers.c_str()) == 0) { + LOG(FATAL) << "SSL_CTX_set_cipher_list " << tlsconf.ciphers + << " failed: " << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + +#if OPENSSL_1_1_1_API + if (SSL_CTX_set_ciphersuites(ssl_ctx, tlsconf.tls13_ciphers.c_str()) == 0) { + LOG(FATAL) << "SSL_CTX_set_ciphersuites " << tlsconf.tls13_ciphers + << " failed: " << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +#endif // OPENSSL_1_1_1_API + +#ifndef OPENSSL_NO_EC +# if !LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L + if (SSL_CTX_set1_curves_list(ssl_ctx, tlsconf.ecdh_curves.c_str()) != 1) { + LOG(FATAL) << "SSL_CTX_set1_curves_list " << tlsconf.ecdh_curves + << " failed"; + DIE(); + } +# if !defined(OPENSSL_IS_BORINGSSL) && !OPENSSL_1_1_API + // It looks like we need this function call for OpenSSL 1.0.2. This + // function was deprecated in OpenSSL 1.1.0 and BoringSSL. + SSL_CTX_set_ecdh_auto(ssl_ctx, 1); +# endif // !defined(OPENSSL_IS_BORINGSSL) && !OPENSSL_1_1_API +# else // LIBRESSL_LEGACY_API || OPENSSL_VERSION_NUBMER < 0x10002000L + // Use P-256, which is sufficiently secure at the time of this + // writing. + auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + if (ecdh == nullptr) { + LOG(FATAL) << "EC_KEY_new_by_curv_name failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + SSL_CTX_set_tmp_ecdh(ssl_ctx, ecdh); + EC_KEY_free(ecdh); +# endif // LIBRESSL_LEGACY_API || OPENSSL_VERSION_NUBMER < 0x10002000L +#endif // OPENSSL_NO_EC + + if (!tlsconf.dh_param_file.empty()) { + // Read DH parameters from file + auto bio = BIO_new_file(tlsconf.dh_param_file.c_str(), "r"); + if (bio == nullptr) { + LOG(FATAL) << "BIO_new_file() failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + auto dh = PEM_read_bio_DHparams(bio, nullptr, nullptr, nullptr); + if (dh == nullptr) { + LOG(FATAL) << "PEM_read_bio_DHparams() failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + SSL_CTX_set_tmp_dh(ssl_ctx, dh); + DH_free(dh); + BIO_free(bio); + } + + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS); + + if (SSL_CTX_set_default_verify_paths(ssl_ctx) != 1) { + LOG(WARN) << "Could not load system trusted ca certificates: " + << ERR_error_string(ERR_get_error(), nullptr); + } + + if (!tlsconf.cacert.empty()) { + if (SSL_CTX_load_verify_locations(ssl_ctx, tlsconf.cacert.c_str(), + nullptr) != 1) { + LOG(FATAL) << "Could not load trusted ca certificates from " + << tlsconf.cacert << ": " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + } + + if (!tlsconf.private_key_passwd.empty()) { + SSL_CTX_set_default_passwd_cb(ssl_ctx, ssl_pem_passwd_cb); + SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx, config); + } + +#ifndef HAVE_NEVERBLEED + if (SSL_CTX_use_PrivateKey_file(ssl_ctx, private_key_file, + SSL_FILETYPE_PEM) != 1) { + LOG(FATAL) << "SSL_CTX_use_PrivateKey_file failed: " + << ERR_error_string(ERR_get_error(), nullptr); + } +#else // HAVE_NEVERBLEED + std::array errbuf; + if (neverbleed_load_private_key_file(nb, ssl_ctx, private_key_file, + errbuf.data()) != 1) { + LOG(FATAL) << "neverbleed_load_private_key_file failed: " << errbuf.data(); + DIE(); + } +#endif // HAVE_NEVERBLEED + + if (SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_file) != 1) { + LOG(FATAL) << "SSL_CTX_use_certificate_file failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + if (SSL_CTX_check_private_key(ssl_ctx) != 1) { + LOG(FATAL) << "SSL_CTX_check_private_key failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + if (tlsconf.client_verify.enabled) { + if (!tlsconf.client_verify.cacert.empty()) { + if (SSL_CTX_load_verify_locations( + ssl_ctx, tlsconf.client_verify.cacert.c_str(), nullptr) != 1) { + + LOG(FATAL) << "Could not load trusted ca certificates from " + << tlsconf.client_verify.cacert << ": " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + // It is heard that SSL_CTX_load_verify_locations() may leave + // error even though it returns success. See + // http://forum.nginx.org/read.php?29,242540 + ERR_clear_error(); + auto list = SSL_load_client_CA_file(tlsconf.client_verify.cacert.c_str()); + if (!list) { + LOG(FATAL) << "Could not load ca certificates from " + << tlsconf.client_verify.cacert << ": " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + SSL_CTX_set_client_CA_list(ssl_ctx, list); + } + SSL_CTX_set_verify(ssl_ctx, + SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE | + SSL_VERIFY_FAIL_IF_NO_PEER_CERT, + verify_callback); + } + SSL_CTX_set_tlsext_servername_callback(ssl_ctx, servername_callback); + SSL_CTX_set_tlsext_ticket_key_cb(ssl_ctx, ticket_key_cb); +#ifndef OPENSSL_IS_BORINGSSL + SSL_CTX_set_tlsext_status_cb(ssl_ctx, ocsp_resp_cb); +#endif // OPENSSL_IS_BORINGSSL + +#ifdef OPENSSL_IS_BORINGSSL + SSL_CTX_set_early_data_enabled(ssl_ctx, 1); +#endif // OPENSSL_IS_BORINGSSL + +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + // ALPN selection callback + SSL_CTX_set_alpn_select_cb(ssl_ctx, quic_alpn_select_proto_cb, nullptr); +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + +#if !LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L && \ + !defined(OPENSSL_IS_BORINGSSL) + // SSL_extension_supported(TLSEXT_TYPE_signed_certificate_timestamp) + // returns 1, which means OpenSSL internally handles it. But + // OpenSSL handles signed_certificate_timestamp extension specially, + // and it lets custom handler to process the extension. + if (!sct_data.empty()) { +# if OPENSSL_1_1_1_API + // It is not entirely clear to me that SSL_EXT_CLIENT_HELLO is + // required here. sct_parse_cb is called without + // SSL_EXT_CLIENT_HELLO being set. But the passed context value + // is SSL_EXT_CLIENT_HELLO. + if (SSL_CTX_add_custom_ext( + ssl_ctx, TLSEXT_TYPE_signed_certificate_timestamp, + SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_2_SERVER_HELLO | + SSL_EXT_TLS1_3_CERTIFICATE | SSL_EXT_IGNORE_ON_RESUMPTION, + sct_add_cb, sct_free_cb, nullptr, sct_parse_cb, nullptr) != 1) { + LOG(FATAL) << "SSL_CTX_add_custom_ext failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +# else // !OPENSSL_1_1_1_API + if (SSL_CTX_add_server_custom_ext( + ssl_ctx, TLSEXT_TYPE_signed_certificate_timestamp, + legacy_sct_add_cb, legacy_sct_free_cb, nullptr, legacy_sct_parse_cb, + nullptr) != 1) { + LOG(FATAL) << "SSL_CTX_add_server_custom_ext failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +# endif // !OPENSSL_1_1_1_API + } +#endif // !LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L && + // !defined(OPENSSL_IS_BORINGSSL) + +#if OPENSSL_1_1_1_API + if (SSL_CTX_set_max_early_data(ssl_ctx, + std::numeric_limits::max()) != 1) { + LOG(FATAL) << "SSL_CTX_set_max_early_data failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +#endif // OPENSSL_1_1_1_API + +#ifndef OPENSSL_NO_PSK + SSL_CTX_set_psk_server_callback(ssl_ctx, psk_server_cb); +#endif // !LIBRESSL_NO_PSK + + SSL_CTX_set_quic_method(ssl_ctx, &quic_method); + + auto tls_ctx_data = new TLSContextData(); + tls_ctx_data->cert_file = cert_file; + tls_ctx_data->sct_data = sct_data; + + SSL_CTX_set_app_data(ssl_ctx, tls_ctx_data); + + return ssl_ctx; +} + namespace { int select_h2_next_proto_cb(SSL *ssl, unsigned char **out, unsigned char *outlen, const unsigned char *in, @@ -1747,6 +2094,10 @@ bool in_proto_list(const std::vector &protos, } bool upstream_tls_enabled(const ConnectionConfig &connconf) { + if (connconf.quic_listener.addrs.size()) { + return true; + } + const auto &faddrs = connconf.listener.addrs; return std::any_of(std::begin(faddrs), std::end(faddrs), [](const UpstreamAddr &faddr) { return faddr.tls; }); @@ -1826,6 +2177,61 @@ setup_server_ssl_context(std::vector &all_ssl_ctx, return ssl_ctx; } +SSL_CTX *setup_quic_server_ssl_context( + std::vector &all_ssl_ctx, + std::vector> &indexed_ssl_ctx, + CertLookupTree *cert_tree +#ifdef HAVE_NEVERBLEED + , + neverbleed_t *nb +#endif // HAVE_NEVERBLEED +) { + auto config = get_config(); + + if (!upstream_tls_enabled(config->conn)) { + return nullptr; + } + + auto &tlsconf = config->tls; + + auto ssl_ctx = + create_quic_ssl_context(tlsconf.private_key_file.c_str(), + tlsconf.cert_file.c_str(), tlsconf.sct_data +#ifdef HAVE_NEVERBLEED + , + nb +#endif // HAVE_NEVERBLEED + ); + + all_ssl_ctx.push_back(ssl_ctx); + + assert(cert_tree); + + if (cert_lookup_tree_add_ssl_ctx(cert_tree, indexed_ssl_ctx, ssl_ctx) == -1) { + LOG(FATAL) << "Failed to add default certificate."; + DIE(); + } + + for (auto &c : tlsconf.subcerts) { + auto ssl_ctx = create_quic_ssl_context(c.private_key_file.c_str(), + c.cert_file.c_str(), c.sct_data +#ifdef HAVE_NEVERBLEED + , + nb +#endif // HAVE_NEVERBLEED + ); + all_ssl_ctx.push_back(ssl_ctx); + + if (cert_lookup_tree_add_ssl_ctx(cert_tree, indexed_ssl_ctx, ssl_ctx) == + -1) { + LOG(FATAL) << "Failed to add sub certificate."; + DIE(); + } + } + + return ssl_ctx; +} + SSL_CTX *setup_downstream_client_ssl_context( #ifdef HAVE_NEVERBLEED neverbleed_t *nb diff --git a/src/shrpx_tls.h b/src/shrpx_tls.h index 1ca20330..ebc4aa27 100644 --- a/src/shrpx_tls.h +++ b/src/shrpx_tls.h @@ -100,6 +100,16 @@ SSL_CTX *create_ssl_client_context( unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg)); +SSL_CTX *create_quic_ssl_client_context( +#ifdef HAVE_NEVERBLEED + neverbleed_t *nb, +#endif // HAVE_NEVERBLEED + const StringRef &cacert, const StringRef &cert_file, + const StringRef &private_key_file, + int (*next_proto_select_cb)(SSL *s, unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg)); + ClientHandler *accept_connection(Worker *worker, int fd, sockaddr *addr, int addrlen, const UpstreamAddr *faddr); @@ -217,6 +227,16 @@ setup_server_ssl_context(std::vector &all_ssl_ctx, #endif // HAVE_NEVERBLEED ); +SSL_CTX *setup_quic_server_ssl_context( + std::vector &all_ssl_ctx, + std::vector> &indexed_ssl_ctx, + CertLookupTree *cert_tree +#ifdef HAVE_NEVERBLEED + , + neverbleed_t *nb +#endif // HAVE_NEVERBLEED +); + // Setups client side SSL_CTX. SSL_CTX *setup_downstream_client_ssl_context( #ifdef HAVE_NEVERBLEED From 940fdd557350a7fea72520242e1128f14fec74ee Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Mon, 16 Aug 2021 15:11:18 +0900 Subject: [PATCH 095/124] nghttpx: Read quic packet --- src/shrpx_client_handler.cc | 37 +++- src/shrpx_client_handler.h | 6 + src/shrpx_config.cc | 3 + src/shrpx_config.h | 10 ++ src/shrpx_connection.cc | 11 +- src/shrpx_connection_handler.cc | 22 +-- src/shrpx_http3_upstream.cc | 248 ++++++++++++++++++++++++++- src/shrpx_http3_upstream.h | 9 + src/shrpx_quic.cc | 59 +++++++ src/shrpx_quic.h | 33 ++++ src/shrpx_quic_connection_handler.cc | 79 ++++++++- src/shrpx_quic_connection_handler.h | 10 +- src/shrpx_quic_listener.cc | 11 +- src/shrpx_worker.cc | 16 +- src/shrpx_worker.h | 12 +- 15 files changed, 531 insertions(+), 35 deletions(-) diff --git a/src/shrpx_client_handler.cc b/src/shrpx_client_handler.cc index 9bbec8b0..f7185f84 100644 --- a/src/shrpx_client_handler.cc +++ b/src/shrpx_client_handler.cc @@ -51,6 +51,7 @@ #include "shrpx_api_downstream_connection.h" #include "shrpx_health_monitor_downstream_connection.h" #include "shrpx_null_downstream_connection.h" +#include "shrpx_http3_upstream.h" #include "shrpx_log.h" #include "util.h" #include "template.h" @@ -286,6 +287,15 @@ int ClientHandler::write_tls() { } } +int ClientHandler::read_quic(const UpstreamAddr *faddr, + const Address &remote_addr, + const Address &local_addr, const uint8_t *data, + size_t datalen) { + auto upstream = static_cast(upstream_.get()); + + return upstream->on_read(faddr, remote_addr, local_addr, data, datalen); +} + int ClientHandler::upstream_noop() { return 0; } int ClientHandler::upstream_read() { @@ -402,7 +412,8 @@ ClientHandler::ClientHandler(Worker *worker, int fd, SSL *ssl, get_config()->conn.upstream.ratelimit.write, get_config()->conn.upstream.ratelimit.read, writecb, readcb, timeoutcb, this, get_config()->tls.dyn_rec.warmup_threshold, - get_config()->tls.dyn_rec.idle_timeout, Proto::NONE), + get_config()->tls.dyn_rec.idle_timeout, + faddr->quic ? Proto::HTTP3 : Proto::NONE), ipaddr_(make_string_ref(balloc_, ipaddr)), port_(make_string_ref(balloc_, port)), faddr_(faddr), @@ -423,14 +434,16 @@ ClientHandler::ClientHandler(Worker *worker, int fd, SSL *ssl, auto config = get_config(); - if (faddr_->accept_proxy_protocol || - config->conn.upstream.accept_proxy_protocol) { - read_ = &ClientHandler::read_clear; - write_ = &ClientHandler::noop; - on_read_ = &ClientHandler::proxy_protocol_read; - on_write_ = &ClientHandler::upstream_noop; - } else { - setup_upstream_io_callback(); + if (!faddr->quic) { + if (faddr_->accept_proxy_protocol || + config->conn.upstream.accept_proxy_protocol) { + read_ = &ClientHandler::read_clear; + write_ = &ClientHandler::noop; + on_read_ = &ClientHandler::proxy_protocol_read; + on_write_ = &ClientHandler::upstream_noop; + } else { + setup_upstream_io_callback(); + } } auto &fwdconf = config->http.forwarded; @@ -492,6 +505,12 @@ void ClientHandler::setup_upstream_io_callback() { } } +void ClientHandler::setup_http3_upstream( + std::unique_ptr &&upstream) { + upstream_ = std::move(upstream); + alpn_ = StringRef::from_lit("h3"); +} + ClientHandler::~ClientHandler() { if (LOG_ENABLED(INFO)) { CLOG(INFO, this) << "Deleting"; diff --git a/src/shrpx_client_handler.h b/src/shrpx_client_handler.h index bc56d482..dbdff961 100644 --- a/src/shrpx_client_handler.h +++ b/src/shrpx_client_handler.h @@ -53,6 +53,7 @@ class Downstream; struct WorkerStat; struct DownstreamAddrGroup; struct DownstreamAddr; +class Http3Upstream; class ClientHandler { public: @@ -70,6 +71,9 @@ public: int read_tls(); int write_tls(); + int read_quic(const UpstreamAddr *faddr, const Address &remote_addr, + const Address &local_addr, const uint8_t *data, size_t datalen); + int upstream_noop(); int upstream_read(); int upstream_http2_connhd_read(); @@ -143,6 +147,8 @@ public: void setup_upstream_io_callback(); + void setup_http3_upstream(std::unique_ptr &&upstream); + // Returns string suitable for use in "by" parameter of Forwarded // header field. StringRef get_forwarded_by() const; diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc index d86c61de..8742ad6a 100644 --- a/src/shrpx_config.cc +++ b/src/shrpx_config.cc @@ -2668,6 +2668,7 @@ int parse_config(Config *config, int optid, const StringRef &opt, addr.sni_fwd = params.sni_fwd; addr.alt_mode = params.alt_mode; addr.accept_proxy_protocol = params.proxyproto; + addr.quic = params.quic; if (addr.alt_mode == UpstreamAltMode::API) { apiconf.enabled = true; @@ -3990,6 +3991,8 @@ StringRef strproto(Proto proto) { return StringRef::from_lit("http/1.1"); case Proto::HTTP2: return StringRef::from_lit("h2"); + case Proto::HTTP3: + return StringRef::from_lit("h3"); case Proto::MEMCACHED: return StringRef::from_lit("memcached"); } diff --git a/src/shrpx_config.h b/src/shrpx_config.h index 915c9da7..6c426685 100644 --- a/src/shrpx_config.h +++ b/src/shrpx_config.h @@ -370,6 +370,7 @@ enum class Proto { NONE, HTTP1, HTTP2, + HTTP3, MEMCACHED, }; @@ -458,6 +459,7 @@ struct UpstreamAddr { bool sni_fwd; // true if client is supposed to send PROXY protocol v1 header. bool accept_proxy_protocol; + bool quic; int fd; }; @@ -702,6 +704,12 @@ struct TLSConfig { bool no_postpone_early_data; }; +struct QUICConfig { + struct { + std::array secret; + } stateless_reset; +}; + // custom error page struct ErrorPage { // not NULL-terminated @@ -954,6 +962,7 @@ struct Config { http{}, http2{}, tls{}, + quic{}, logging{}, conn{}, api{}, @@ -987,6 +996,7 @@ struct Config { HttpConfig http; Http2Config http2; TLSConfig tls; + QUICConfig quic; LoggingConfig logging; ConnectionConfig conn; APIConfig api; diff --git a/src/shrpx_connection.cc b/src/shrpx_connection.cc index ec94b07c..daec6296 100644 --- a/src/shrpx_connection.cc +++ b/src/shrpx_connection.cc @@ -312,10 +312,13 @@ BIO_METHOD *create_bio_method() { void Connection::set_ssl(SSL *ssl) { tls.ssl = ssl; - auto &tlsconf = get_config()->tls; - auto bio = BIO_new(tlsconf.bio_method); - BIO_set_data(bio, this); - SSL_set_bio(tls.ssl, bio, bio); + if (proto != Proto::HTTP3) { + auto &tlsconf = get_config()->tls; + auto bio = BIO_new(tlsconf.bio_method); + BIO_set_data(bio, this); + SSL_set_bio(tls.ssl, bio, bio); + } + SSL_set_app_data(tls.ssl, this); } diff --git a/src/shrpx_connection_handler.cc b/src/shrpx_connection_handler.cc index db2ace7d..ddeb51ec 100644 --- a/src/shrpx_connection_handler.cc +++ b/src/shrpx_connection_handler.cc @@ -222,11 +222,11 @@ int ConnectionHandler::create_single_worker() { ); quic_cert_tree_ = tls::create_cert_lookup_tree(); - tls::setup_quic_server_ssl_context(quic_all_ssl_ctx_, quic_indexed_ssl_ctx_, - quic_cert_tree_.get() + auto quic_sv_ssl_ctx = tls::setup_quic_server_ssl_context( + quic_all_ssl_ctx_, quic_indexed_ssl_ctx_, quic_cert_tree_.get() #ifdef HAVE_NEVERBLEED - , - nb_ + , + nb_ #endif // HAVE_NEVERBLEED ); @@ -261,7 +261,8 @@ int ConnectionHandler::create_single_worker() { single_worker_ = std::make_unique( loop_, sv_ssl_ctx, cl_ssl_ctx, session_cache_ssl_ctx, cert_tree_.get(), - ticket_keys_, this, config->conn.downstream); + quic_sv_ssl_ctx, quic_cert_tree_.get(), ticket_keys_, this, + config->conn.downstream); #ifdef HAVE_MRUBY if (single_worker_->create_mruby_context() != 0) { return -1; @@ -288,11 +289,11 @@ int ConnectionHandler::create_worker_thread(size_t num) { ); quic_cert_tree_ = tls::create_cert_lookup_tree(); - tls::setup_quic_server_ssl_context(quic_all_ssl_ctx_, quic_indexed_ssl_ctx_, - quic_cert_tree_.get() + auto quic_sv_ssl_ctx = tls::setup_quic_server_ssl_context( + quic_all_ssl_ctx_, quic_indexed_ssl_ctx_, quic_cert_tree_.get() # ifdef HAVE_NEVERBLEED - , - nb_ + , + nb_ # endif // HAVE_NEVERBLEED ); @@ -337,7 +338,8 @@ int ConnectionHandler::create_worker_thread(size_t num) { auto worker = std::make_unique( loop, sv_ssl_ctx, cl_ssl_ctx, session_cache_ssl_ctx, cert_tree_.get(), - ticket_keys_, this, config->conn.downstream); + quic_sv_ssl_ctx, quic_cert_tree_.get(), ticket_keys_, this, + config->conn.downstream); # ifdef HAVE_MRUBY if (worker->create_mruby_context() != 0) { return -1; diff --git a/src/shrpx_http3_upstream.cc b/src/shrpx_http3_upstream.cc index b0da58c8..34e7e0cb 100644 --- a/src/shrpx_http3_upstream.cc +++ b/src/shrpx_http3_upstream.cc @@ -23,17 +23,201 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "shrpx_http3_upstream.h" + +#include + +#include + #include "shrpx_client_handler.h" #include "shrpx_downstream.h" #include "shrpx_downstream_connection.h" #include "shrpx_log.h" +#include "shrpx_quic.h" +#include "shrpx_worker.h" +#include "util.h" namespace shrpx { Http3Upstream::Http3Upstream(ClientHandler *handler) - : handler_{handler}, tls_alert_{0} {} + : handler_{handler}, + conn_{nullptr}, + last_error_{QUICErrorType::Transport, 0}, + tls_alert_{0} {} -Http3Upstream::~Http3Upstream() {} +Http3Upstream::~Http3Upstream() { + if (conn_) { + auto worker = handler_->get_worker(); + auto quic_client_handler = worker->get_quic_connection_handler(); + + quic_client_handler->remove_connection_id(&initial_client_dcid_); + + std::vector scids(ngtcp2_conn_get_num_scid(conn_)); + ngtcp2_conn_get_scid(conn_, scids.data()); + + for (auto &cid : scids) { + quic_client_handler->remove_connection_id(&cid); + } + + ngtcp2_conn_del(conn_); + } +} + +namespace { +void log_printf(void *user_data, const char *fmt, ...) { + va_list ap; + std::array buf; + + va_start(ap, fmt); + auto nwrite = vsnprintf(buf.data(), buf.size(), fmt, ap); + va_end(ap); + + if (nwrite >= buf.size()) { + nwrite = buf.size() - 1; + } + + buf[nwrite++] = '\n'; + + write(fileno(stderr), buf.data(), nwrite); +} +} // namespace + +namespace { +void rand(uint8_t *dest, size_t destlen, const ngtcp2_rand_ctx *rand_ctx) { + util::random_bytes(dest, dest + destlen, + *static_cast(rand_ctx->native_handle)); +} +} // namespace + +namespace { +int get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, uint8_t *token, + size_t cidlen, void *user_data) { + if (generate_quic_connection_id(cid, cidlen) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + auto config = get_config(); + auto &quicconf = config->quic; + auto &secret = quicconf.stateless_reset.secret; + + if (generate_quic_stateless_reset_token(token, cid, secret.data(), + secret.size()) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +namespace { +int remove_connection_id(ngtcp2_conn *conn, const ngtcp2_cid *cid, + void *user_data) { + auto upstream = static_cast(user_data); + auto handler = upstream->get_client_handler(); + auto worker = handler->get_worker(); + auto quic_conn_handler = worker->get_quic_connection_handler(); + + quic_conn_handler->remove_connection_id(cid); + + return 0; +} +} // namespace + +int Http3Upstream::init(const UpstreamAddr *faddr, const Address &remote_addr, + const Address &local_addr, + const ngtcp2_pkt_hd &initial_hd) { + int rv; + + auto worker = handler_->get_worker(); + + auto callbacks = ngtcp2_callbacks{ + nullptr, // client_initial + ngtcp2_crypto_recv_client_initial_cb, + ngtcp2_crypto_recv_crypto_data_cb, + nullptr, // handshake_completed + nullptr, // recv_version_negotiation + ngtcp2_crypto_encrypt_cb, + ngtcp2_crypto_decrypt_cb, + ngtcp2_crypto_hp_mask_cb, + nullptr, // recv_stream_data + nullptr, // acked_crypto_offset + nullptr, // acked_stream_data_offset + nullptr, // stream_open + nullptr, // stream_close + nullptr, // recv_stateless_reset + nullptr, // recv_retry + nullptr, // extend_max_local_streams_bidi + nullptr, // extend_max_local_streams_uni + rand, + get_new_connection_id, + remove_connection_id, + ngtcp2_crypto_update_key_cb, + nullptr, // path_validation + nullptr, // select_preferred_addr + nullptr, // stream_reset + nullptr, // extend_max_remote_streams_bidi + nullptr, // extend_max_remote_streams_uni + nullptr, // extend_max_stream_data + nullptr, // dcid_status + nullptr, // handshake_confirmed + nullptr, // recv_new_token + ngtcp2_crypto_delete_crypto_aead_ctx_cb, + ngtcp2_crypto_delete_crypto_cipher_ctx_cb, + nullptr, // recv_datagram + nullptr, // ack_datagram + nullptr, // lost_datagram + ngtcp2_crypto_get_path_challenge_data_cb, + nullptr, // stream_stop_sending + }; + + initial_client_dcid_ = initial_hd.dcid; + + ngtcp2_cid scid; + + if (generate_quic_connection_id(&scid, SHRPX_QUIC_SCIDLEN) != 0) { + return -1; + } + + ngtcp2_settings settings; + ngtcp2_settings_default(&settings); + settings.log_printf = log_printf; + settings.initial_ts = quic_timestamp(); + settings.cc_algo = NGTCP2_CC_ALGO_BBR; + settings.max_window = 6_m; + settings.max_stream_window = 6_m; + settings.max_udp_payload_size = SHRPX_MAX_UDP_PAYLOAD_SIZE; + settings.rand_ctx = {&worker->get_randgen()}; + + ngtcp2_transport_params params; + ngtcp2_transport_params_default(¶ms); + params.initial_max_data = 1_m; + params.initial_max_stream_data_bidi_remote = 256_k; + params.initial_max_stream_data_uni = 256_k; + params.max_idle_timeout = 30 * NGTCP2_SECONDS; + params.original_dcid = initial_hd.dcid; + + auto path = ngtcp2_path{ + {local_addr.len, const_cast(&local_addr.su.sa)}, + {remote_addr.len, const_cast(&remote_addr.su.sa)}, + const_cast(faddr), + }; + + rv = ngtcp2_conn_server_new(&conn_, &initial_hd.scid, &scid, &path, + initial_hd.version, &callbacks, &settings, + ¶ms, nullptr, this); + if (rv != 0) { + LOG(ERROR) << "ngtcp2_conn_server_new: " << ngtcp2_strerror(rv); + return -1; + } + + ngtcp2_conn_set_tls_native_handle(conn_, handler_->get_ssl()); + + auto quic_connection_handler = worker->get_quic_connection_handler(); + + quic_connection_handler->add_connection_id(&initial_client_dcid_, handler_); + quic_connection_handler->add_connection_id(&scid, handler_); + + return 0; +} int Http3Upstream::on_read() { return 0; } @@ -127,16 +311,76 @@ int Http3Upstream::on_read(const UpstreamAddr *faddr, const Address &remote_addr, const Address &local_addr, const uint8_t *data, size_t datalen) { + int rv; + ngtcp2_pkt_info pi{}; + + auto path = ngtcp2_path{ + { + local_addr.len, + const_cast(&local_addr.su.sa), + }, + { + remote_addr.len, + const_cast(&remote_addr.su.sa), + }, + const_cast(faddr), + }; + + rv = ngtcp2_conn_read_pkt(conn_, &path, &pi, data, datalen, quic_timestamp()); + if (rv != 0) { + LOG(ERROR) << "ngtcp2_conn_read_pkt: " << ngtcp2_strerror(rv); + + switch (rv) { + case NGTCP2_ERR_DRAINING: + // TODO Start drain period + return -1; + case NGTCP2_ERR_RETRY: + // TODO Send Retry packet + return -1; + case NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM: + case NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM: + case NGTCP2_ERR_TRANSPORT_PARAM: + // If rv indicates transport_parameters related error, we should + // send TRANSPORT_PARAMETER_ERROR even if last_error_.code is + // already set. This is because OpenSSL might set Alert. + last_error_ = quic_err_transport(rv); + break; + case NGTCP2_ERR_DROP_CONN: + return -1; + default: + if (!last_error_.code) { + last_error_ = quic_err_transport(rv); + } + } + + // TODO Send connection close + return handle_error(); + } + return 0; } +int Http3Upstream::handle_error() { return -1; } + int Http3Upstream::on_rx_secret(ngtcp2_crypto_level level, const uint8_t *secret, size_t secretlen) { + if (ngtcp2_crypto_derive_and_install_rx_key(conn_, nullptr, nullptr, nullptr, + level, secret, secretlen) != 0) { + LOG(ERROR) << "ngtcp2_crypto_derive_and_install_rx_key failed"; + return -1; + } + return 0; } int Http3Upstream::on_tx_secret(ngtcp2_crypto_level level, const uint8_t *secret, size_t secretlen) { + if (ngtcp2_crypto_derive_and_install_tx_key(conn_, nullptr, nullptr, nullptr, + level, secret, secretlen) != 0) { + LOG(ERROR) << "ngtcp2_crypto_derive_and_install_tx_key failed"; + return -1; + } + return 0; } diff --git a/src/shrpx_http3_upstream.h b/src/shrpx_http3_upstream.h index d979fff7..486f680a 100644 --- a/src/shrpx_http3_upstream.h +++ b/src/shrpx_http3_upstream.h @@ -30,6 +30,7 @@ #include #include "shrpx_upstream.h" +#include "shrpx_quic.h" #include "network.h" using namespace nghttp2; @@ -84,6 +85,9 @@ public: virtual bool push_enabled() const; virtual void cancel_premature_downstream(Downstream *promised_downstream); + int init(const UpstreamAddr *faddr, const Address &remote_addr, + const Address &local_addr, const ngtcp2_pkt_hd &initial_hd); + int on_read(const UpstreamAddr *faddr, const Address &remote_addr, const Address &local_addr, const uint8_t *data, size_t datalen); @@ -97,8 +101,13 @@ public: void set_tls_alert(uint8_t alert); + int handle_error(); + private: ClientHandler *handler_; + ngtcp2_cid initial_client_dcid_; + ngtcp2_conn *conn_; + QUICError last_error_; uint8_t tls_alert_; }; diff --git a/src/shrpx_quic.cc b/src/shrpx_quic.cc index 175ffaeb..708886c0 100644 --- a/src/shrpx_quic.cc +++ b/src/shrpx_quic.cc @@ -29,6 +29,13 @@ #include #include +#include + +#include + +#include + +#include #include "shrpx_config.h" #include "shrpx_log.h" @@ -39,6 +46,34 @@ using namespace nghttp2; namespace shrpx { +QUICError quic_err_transport(int liberr) { + if (liberr == NGTCP2_ERR_RECV_VERSION_NEGOTIATION) { + return {QUICErrorType::TransportVersionNegotiation, 0}; + } + return {QUICErrorType::Transport, + ngtcp2_err_infer_quic_transport_error_code(liberr)}; +} + +QUICError quic_err_idle_timeout() { + return {QUICErrorType::TransportIdleTimeout, 0}; +} + +QUICError quic_err_tls(int alert) { + return {QUICErrorType::Transport, + static_cast(NGTCP2_CRYPTO_ERROR | alert)}; +} + +QUICError quic_err_app(int liberr) { + return {QUICErrorType::Application, + nghttp3_err_infer_quic_app_error_code(liberr)}; +} + +ngtcp2_tstamp quic_timestamp() { + return std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()) + .count(); +} + int create_quic_server_socket(UpstreamAddr &faddr) { std::array errbuf; int fd = -1; @@ -192,4 +227,28 @@ int quic_send_packet(const UpstreamAddr *addr, const sockaddr *remote_sa, return 0; } +int generate_quic_connection_id(ngtcp2_cid *cid, size_t cidlen) { + if (RAND_bytes(cid->data, cidlen) != 1) { + return -1; + } + + cid->datalen = cidlen; + + return 0; +} + +int generate_quic_stateless_reset_token(uint8_t *token, const ngtcp2_cid *cid, + const uint8_t *secret, + size_t secretlen) { + ngtcp2_crypto_md md; + ngtcp2_crypto_md_init(&md, const_cast(EVP_sha256())); + + if (ngtcp2_crypto_generate_stateless_reset_token(token, &md, secret, + secretlen, cid) != 0) { + return -1; + } + + return 0; +} + } // namespace shrpx diff --git a/src/shrpx_quic.h b/src/shrpx_quic.h index a575fee4..8350adc7 100644 --- a/src/shrpx_quic.h +++ b/src/shrpx_quic.h @@ -29,11 +29,38 @@ #include +#include + namespace shrpx { struct UpstreamAddr; constexpr size_t SHRPX_QUIC_SCIDLEN = 20; +constexpr size_t SHRPX_MAX_UDP_PAYLOAD_SIZE = 1280; + +enum class QUICErrorType { + Application, + Transport, + TransportVersionNegotiation, + TransportIdleTimeout, +}; + +struct QUICError { + QUICError(QUICErrorType type, uint64_t code) : type{type}, code{code} {} + + QUICErrorType type; + uint64_t code; +}; + +QUICError quic_err_transport(int liberr); + +QUICError quic_err_idle_timeout(); + +QUICError quic_err_tls(int alert); + +QUICError quic_err_app(int liberr); + +ngtcp2_tstamp quic_timestamp(); int create_quic_server_socket(UpstreamAddr &addr); @@ -42,6 +69,12 @@ int quic_send_packet(const UpstreamAddr *addr, const sockaddr *remote_sa, size_t local_salen, const uint8_t *data, size_t datalen, size_t gso_size); +int generate_quic_connection_id(ngtcp2_cid *cid, size_t cidlen); + +int generate_quic_stateless_reset_token(uint8_t *token, const ngtcp2_cid *cid, + const uint8_t *secret, + size_t secretlen); + } // namespace shrpx #endif // SHRPX_QUIC_H diff --git a/src/shrpx_quic_connection_handler.cc b/src/shrpx_quic_connection_handler.cc index 913da6b2..7ff08767 100644 --- a/src/shrpx_quic_connection_handler.cc +++ b/src/shrpx_quic_connection_handler.cc @@ -45,6 +45,12 @@ std::string make_cid_key(const uint8_t *dcid, size_t dcidlen) { } } // namespace +namespace { +std::string make_cid_key(const ngtcp2_cid *cid) { + return make_cid_key(cid->data, cid->datalen); +} +} // namespace + int QUICConnectionHandler::handle_packet(const UpstreamAddr *faddr, const Address &remote_addr, const Address &local_addr, @@ -67,6 +73,8 @@ int QUICConnectionHandler::handle_packet(const UpstreamAddr *faddr, auto dcid_key = make_cid_key(dcid, dcidlen); + ClientHandler *handler; + auto it = connections_.find(dcid_key); if (it == std::end(connections_)) { // new connection @@ -87,18 +95,66 @@ int QUICConnectionHandler::handle_packet(const UpstreamAddr *faddr, return 0; } - return 0; + handler = handle_new_connection(faddr, remote_addr, local_addr, hd); + if (handler == nullptr) { + return 0; + } + } else { + handler = (*it).second; } - auto h = (*it).second.get(); - auto upstream = static_cast(h->get_upstream()); - - // TODO Delete h on failure - upstream->on_read(faddr, remote_addr, local_addr, data, datalen); + if (handler->read_quic(faddr, remote_addr, local_addr, data, datalen) != 0) { + delete handler; + } return 0; } +ClientHandler *QUICConnectionHandler::handle_new_connection( + const UpstreamAddr *faddr, const Address &remote_addr, + const Address &local_addr, const ngtcp2_pkt_hd &hd) { + std::array host; + std::array service; + int rv; + + rv = getnameinfo(&remote_addr.su.sa, remote_addr.len, host.data(), + host.size(), service.data(), service.size(), + NI_NUMERICHOST | NI_NUMERICSERV); + if (rv != 0) { + LOG(ERROR) << "getnameinfo() failed: " << gai_strerror(rv); + + return nullptr; + } + + auto ssl_ctx = worker_->get_quic_sv_ssl_ctx(); + + assert(ssl_ctx); + + auto ssl = tls::create_ssl(ssl_ctx); + if (ssl == nullptr) { + return nullptr; + } + + // Disable TLS session ticket if we don't have working ticket + // keys. + if (!worker_->get_ticket_keys()) { + SSL_set_options(ssl, SSL_OP_NO_TICKET); + } + + auto handler = std::make_unique( + worker_, faddr->fd, ssl, StringRef{host.data()}, + StringRef{service.data()}, remote_addr.su.sa.sa_family, faddr); + + auto upstream = std::make_unique(handler.get()); + if (upstream->init(faddr, remote_addr, local_addr, hd) != 0) { + return nullptr; + } + + handler->setup_http3_upstream(std::move(upstream)); + + return handler.release(); +} + namespace { uint32_t generate_reserved_version(const Address &addr, uint32_t version) { uint32_t h = 0x811C9DC5u; @@ -154,4 +210,15 @@ int QUICConnectionHandler::send_version_negotiation( 0); } +void QUICConnectionHandler::add_connection_id(const ngtcp2_cid *cid, + ClientHandler *handler) { + auto key = make_cid_key(cid); + connections_.emplace(key, handler); +} + +void QUICConnectionHandler::remove_connection_id(const ngtcp2_cid *cid) { + auto key = make_cid_key(cid); + connections_.erase(key); +} + } // namespace shrpx diff --git a/src/shrpx_quic_connection_handler.h b/src/shrpx_quic_connection_handler.h index b580d128..38b2f217 100644 --- a/src/shrpx_quic_connection_handler.h +++ b/src/shrpx_quic_connection_handler.h @@ -31,6 +31,8 @@ #include #include +#include + #include "network.h" using namespace nghttp2; @@ -53,10 +55,16 @@ public: const uint8_t *scid, size_t scidlen, const Address &remote_addr, const Address &local_addr); + ClientHandler *handle_new_connection(const UpstreamAddr *faddr, + const Address &remote_addr, + const Address &local_addr, + const ngtcp2_pkt_hd &hd); + void add_connection_id(const ngtcp2_cid *cid, ClientHandler *handler); + void remove_connection_id(const ngtcp2_cid *cid); private: Worker *worker_; - std::unordered_map> connections_; + std::unordered_map connections_; }; } // namespace shrpx diff --git a/src/shrpx_quic_listener.cc b/src/shrpx_quic_listener.cc index 33712a81..b06ec557 100644 --- a/src/shrpx_quic_listener.cc +++ b/src/shrpx_quic_listener.cc @@ -37,7 +37,7 @@ void readcb(struct ev_loop *loop, ev_io *w, int revent) { } // namespace QUICListener::QUICListener(const UpstreamAddr *faddr, Worker *worker) - : faddr_(faddr), worker_(worker) { + : faddr_{faddr}, worker_{worker} { ev_io_init(&rev_, readcb, faddr_->fd, EV_READ); rev_.data = this; ev_io_start(worker_->get_loop(), &rev_); @@ -62,6 +62,8 @@ void QUICListener::on_read() { uint8_t msg_ctrl[CMSG_SPACE(sizeof(in6_pktinfo))]; msg.msg_control = msg_ctrl; + auto quic_conn_handler = worker_->get_quic_connection_handler(); + for (; pktcnt < 10;) { msg.msg_namelen = sizeof(su); msg.msg_controllen = sizeof(msg_ctrl); @@ -91,6 +93,13 @@ void QUICListener::on_read() { if (nread == 0) { continue; } + + Address remote_addr; + remote_addr.su = su; + remote_addr.len = msg.msg_namelen; + + quic_conn_handler->handle_packet(faddr_, remote_addr, local_addr, + buf.data(), nread); } } diff --git a/src/shrpx_worker.cc b/src/shrpx_worker.cc index 87235ee9..0a4ed3c7 100644 --- a/src/shrpx_worker.cc +++ b/src/shrpx_worker.cc @@ -132,7 +132,8 @@ create_downstream_key(const std::shared_ptr &shared_addr, Worker::Worker(struct ev_loop *loop, SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx, SSL_CTX *tls_session_cache_memcached_ssl_ctx, - tls::CertLookupTree *cert_tree, + tls::CertLookupTree *cert_tree, SSL_CTX *quic_sv_ssl_ctx, + tls::CertLookupTree *quic_cert_tree, const std::shared_ptr &ticket_keys, ConnectionHandler *conn_handler, std::shared_ptr downstreamconf) @@ -145,6 +146,9 @@ Worker::Worker(struct ev_loop *loop, SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx, cl_ssl_ctx_(cl_ssl_ctx), cert_tree_(cert_tree), conn_handler_(conn_handler), + quic_sv_ssl_ctx_{quic_sv_ssl_ctx}, + quic_cert_tree_{quic_cert_tree}, + quic_conn_handler_{this}, ticket_keys_(ticket_keys), connect_blocker_( std::make_unique(randgen_, loop_, nullptr, nullptr)), @@ -507,6 +511,10 @@ void Worker::process_events() { tls::CertLookupTree *Worker::get_cert_lookup_tree() const { return cert_tree_; } +tls::CertLookupTree *Worker::get_quic_cert_lookup_tree() const { + return quic_cert_tree_; +} + std::shared_ptr Worker::get_ticket_keys() { #ifdef HAVE_ATOMIC_STD_SHARED_PTR return std::atomic_load_explicit(&ticket_keys_, std::memory_order_acquire); @@ -537,6 +545,8 @@ SSL_CTX *Worker::get_sv_ssl_ctx() const { return sv_ssl_ctx_; } SSL_CTX *Worker::get_cl_ssl_ctx() const { return cl_ssl_ctx_; } +SSL_CTX *Worker::get_quic_sv_ssl_ctx() const { return quic_sv_ssl_ctx_; } + void Worker::set_graceful_shutdown(bool f) { graceful_shutdown_ = f; } bool Worker::get_graceful_shutdown() const { return graceful_shutdown_; } @@ -581,6 +591,10 @@ ConnectionHandler *Worker::get_connection_handler() const { return conn_handler_; } +QUICConnectionHandler *Worker::get_quic_connection_handler() { + return &quic_conn_handler_; +} + DNSTracker *Worker::get_dns_tracker() { return &dns_tracker_; } int Worker::setup_quic_server_socket() { diff --git a/src/shrpx_worker.h b/src/shrpx_worker.h index f495f8e7..02ece027 100644 --- a/src/shrpx_worker.h +++ b/src/shrpx_worker.h @@ -50,6 +50,7 @@ #include "shrpx_live_check.h" #include "shrpx_connect_blocker.h" #include "shrpx_dns_tracker.h" +#include "shrpx_quic_connection_handler.h" #include "allocator.h" using namespace nghttp2; @@ -270,7 +271,8 @@ class Worker { public: Worker(struct ev_loop *loop, SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx, SSL_CTX *tls_session_cache_memcached_ssl_ctx, - tls::CertLookupTree *cert_tree, + tls::CertLookupTree *cert_tree, SSL_CTX *quic_sv_ssl_ctx, + tls::CertLookupTree *quic_cert_tree, const std::shared_ptr &ticket_keys, ConnectionHandler *conn_handler, std::shared_ptr downstreamconf); @@ -281,6 +283,7 @@ public: void send(const WorkerEvent &event); tls::CertLookupTree *get_cert_lookup_tree() const; + tls::CertLookupTree *get_quic_cert_lookup_tree() const; // These 2 functions make a lock m_ to get/set ticket keys // atomically. @@ -291,6 +294,7 @@ public: struct ev_loop *get_loop() const; SSL_CTX *get_sv_ssl_ctx() const; SSL_CTX *get_cl_ssl_ctx() const; + SSL_CTX *get_quic_sv_ssl_ctx() const; void set_graceful_shutdown(bool f); bool get_graceful_shutdown() const; @@ -320,6 +324,8 @@ public: ConnectionHandler *get_connection_handler() const; + QUICConnectionHandler *get_quic_connection_handler(); + DNSTracker *get_dns_tracker(); int setup_quic_server_socket(); @@ -354,6 +360,10 @@ private: SSL_CTX *cl_ssl_ctx_; tls::CertLookupTree *cert_tree_; ConnectionHandler *conn_handler_; + SSL_CTX *quic_sv_ssl_ctx_; + tls::CertLookupTree *quic_cert_tree_; + + QUICConnectionHandler quic_conn_handler_; #ifndef HAVE_ATOMIC_STD_SHARED_PTR std::mutex ticket_keys_m_; From 49b8c56fde2d4f790a641345a345e25d63ae7e34 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Mon, 16 Aug 2021 17:05:48 +0900 Subject: [PATCH 096/124] nghttpx: Use existing QUIC error object --- src/Makefile.am | 1 + src/quic.cc | 4 ++++ src/quic.h | 2 ++ src/shrpx_http3_upstream.cc | 10 +++------- src/shrpx_http3_upstream.h | 4 ++-- src/shrpx_quic.cc | 22 ---------------------- src/shrpx_quic.h | 22 ---------------------- 7 files changed, 12 insertions(+), 53 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index e7ed9db1..344cc7cf 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -157,6 +157,7 @@ NGHTTPX_SRCS = \ shrpx_quic_listener.cc shrpx_quic_listener.h \ shrpx_quic_connection_handler.cc shrpx_quic_connection_handler.h \ shrpx_http3_upstream.cc shrpx_http3_upstream.h \ + quic.cc quic.h \ buffer.h memchunk.h template.h allocator.h \ xsi_strerror.c xsi_strerror.h diff --git a/src/quic.cc b/src/quic.cc index 1abad98a..1c68a5be 100644 --- a/src/quic.cc +++ b/src/quic.cc @@ -43,6 +43,10 @@ Error err_transport(int liberr) { ngtcp2_err_infer_quic_transport_error_code(liberr)}; } +Error err_transport_idle_timeout() { + return {ErrorType::TransportIdleTimeout, 0}; +} + Error err_transport_tls(int alert) { return {ErrorType::Transport, ngtcp2_err_infer_quic_transport_error_code( NGTCP2_CRYPTO_ERROR | alert)}; diff --git a/src/quic.h b/src/quic.h index 1bb3096f..d72ae1fe 100644 --- a/src/quic.h +++ b/src/quic.h @@ -34,6 +34,7 @@ namespace quic { enum class ErrorType { Transport, TransportVersionNegotiation, + TransportIdleTimeout, Application, }; @@ -46,6 +47,7 @@ struct Error { }; Error err_transport(int liberr); +Error err_transport_idle_timeout(); Error err_transport_tls(int alert); Error err_application(int liberr); diff --git a/src/shrpx_http3_upstream.cc b/src/shrpx_http3_upstream.cc index 34e7e0cb..83b957bd 100644 --- a/src/shrpx_http3_upstream.cc +++ b/src/shrpx_http3_upstream.cc @@ -39,10 +39,7 @@ namespace shrpx { Http3Upstream::Http3Upstream(ClientHandler *handler) - : handler_{handler}, - conn_{nullptr}, - last_error_{QUICErrorType::Transport, 0}, - tls_alert_{0} {} + : handler_{handler}, conn_{nullptr}, tls_alert_{0} {} Http3Upstream::~Http3Upstream() { if (conn_) { @@ -139,7 +136,6 @@ int Http3Upstream::init(const UpstreamAddr *faddr, const Address &remote_addr, ngtcp2_crypto_decrypt_cb, ngtcp2_crypto_hp_mask_cb, nullptr, // recv_stream_data - nullptr, // acked_crypto_offset nullptr, // acked_stream_data_offset nullptr, // stream_open nullptr, // stream_close @@ -343,13 +339,13 @@ int Http3Upstream::on_read(const UpstreamAddr *faddr, // If rv indicates transport_parameters related error, we should // send TRANSPORT_PARAMETER_ERROR even if last_error_.code is // already set. This is because OpenSSL might set Alert. - last_error_ = quic_err_transport(rv); + last_error_ = quic::err_transport(rv); break; case NGTCP2_ERR_DROP_CONN: return -1; default: if (!last_error_.code) { - last_error_ = quic_err_transport(rv); + last_error_ = quic::err_transport(rv); } } diff --git a/src/shrpx_http3_upstream.h b/src/shrpx_http3_upstream.h index 486f680a..f41f6597 100644 --- a/src/shrpx_http3_upstream.h +++ b/src/shrpx_http3_upstream.h @@ -30,7 +30,7 @@ #include #include "shrpx_upstream.h" -#include "shrpx_quic.h" +#include "quic.h" #include "network.h" using namespace nghttp2; @@ -107,7 +107,7 @@ private: ClientHandler *handler_; ngtcp2_cid initial_client_dcid_; ngtcp2_conn *conn_; - QUICError last_error_; + quic::Error last_error_; uint8_t tls_alert_; }; diff --git a/src/shrpx_quic.cc b/src/shrpx_quic.cc index 708886c0..cd989f9f 100644 --- a/src/shrpx_quic.cc +++ b/src/shrpx_quic.cc @@ -46,28 +46,6 @@ using namespace nghttp2; namespace shrpx { -QUICError quic_err_transport(int liberr) { - if (liberr == NGTCP2_ERR_RECV_VERSION_NEGOTIATION) { - return {QUICErrorType::TransportVersionNegotiation, 0}; - } - return {QUICErrorType::Transport, - ngtcp2_err_infer_quic_transport_error_code(liberr)}; -} - -QUICError quic_err_idle_timeout() { - return {QUICErrorType::TransportIdleTimeout, 0}; -} - -QUICError quic_err_tls(int alert) { - return {QUICErrorType::Transport, - static_cast(NGTCP2_CRYPTO_ERROR | alert)}; -} - -QUICError quic_err_app(int liberr) { - return {QUICErrorType::Application, - nghttp3_err_infer_quic_app_error_code(liberr)}; -} - ngtcp2_tstamp quic_timestamp() { return std::chrono::duration_cast( std::chrono::steady_clock::now().time_since_epoch()) diff --git a/src/shrpx_quic.h b/src/shrpx_quic.h index 8350adc7..cf405e61 100644 --- a/src/shrpx_quic.h +++ b/src/shrpx_quic.h @@ -38,28 +38,6 @@ struct UpstreamAddr; constexpr size_t SHRPX_QUIC_SCIDLEN = 20; constexpr size_t SHRPX_MAX_UDP_PAYLOAD_SIZE = 1280; -enum class QUICErrorType { - Application, - Transport, - TransportVersionNegotiation, - TransportIdleTimeout, -}; - -struct QUICError { - QUICError(QUICErrorType type, uint64_t code) : type{type}, code{code} {} - - QUICErrorType type; - uint64_t code; -}; - -QUICError quic_err_transport(int liberr); - -QUICError quic_err_idle_timeout(); - -QUICError quic_err_tls(int alert); - -QUICError quic_err_app(int liberr); - ngtcp2_tstamp quic_timestamp(); int create_quic_server_socket(UpstreamAddr &addr); From e70f0db83cffc2e151f92e4f1d1711d526ff835f Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Mon, 16 Aug 2021 19:48:12 +0900 Subject: [PATCH 097/124] nghttpx: QUIC handshake now works --- src/shrpx_client_handler.cc | 7 +- src/shrpx_client_handler.h | 1 + src/shrpx_connection.cc | 4 +- src/shrpx_http3_upstream.cc | 122 ++++++++++++++++++++++++++- src/shrpx_quic.cc | 79 ++++++++++++++++- src/shrpx_quic.h | 2 +- src/shrpx_quic_connection_handler.cc | 7 ++ src/shrpx_tls.cc | 4 + 8 files changed, 220 insertions(+), 6 deletions(-) diff --git a/src/shrpx_client_handler.cc b/src/shrpx_client_handler.cc index f7185f84..341566b6 100644 --- a/src/shrpx_client_handler.cc +++ b/src/shrpx_client_handler.cc @@ -296,6 +296,8 @@ int ClientHandler::read_quic(const UpstreamAddr *faddr, return upstream->on_read(faddr, remote_addr, local_addr, data, datalen); } +int ClientHandler::write_quic() { return upstream_->on_write(); } + int ClientHandler::upstream_noop() { return 0; } int ClientHandler::upstream_read() { @@ -429,7 +431,9 @@ ClientHandler::ClientHandler(Worker *worker, int fd, SSL *ssl, reneg_shutdown_timer_.data = this; - conn_.rlimit.startw(); + if (!faddr->quic) { + conn_.rlimit.startw(); + } ev_timer_again(conn_.loop, &conn_.rt); auto config = get_config(); @@ -509,6 +513,7 @@ void ClientHandler::setup_http3_upstream( std::unique_ptr &&upstream) { upstream_ = std::move(upstream); alpn_ = StringRef::from_lit("h3"); + write_ = &ClientHandler::write_quic; } ClientHandler::~ClientHandler() { diff --git a/src/shrpx_client_handler.h b/src/shrpx_client_handler.h index dbdff961..ef467810 100644 --- a/src/shrpx_client_handler.h +++ b/src/shrpx_client_handler.h @@ -148,6 +148,7 @@ public: void setup_upstream_io_callback(); void setup_http3_upstream(std::unique_ptr &&upstream); + int write_quic(); // Returns string suitable for use in "by" parameter of Forwarded // header field. diff --git a/src/shrpx_connection.cc b/src/shrpx_connection.cc index daec6296..d07314b6 100644 --- a/src/shrpx_connection.cc +++ b/src/shrpx_connection.cc @@ -74,7 +74,7 @@ Connection::Connection(struct ev_loop *loop, int fd, SSL *ssl, read_timeout(read_timeout) { ev_io_init(&wev, writecb, fd, EV_WRITE); - ev_io_init(&rev, readcb, fd, EV_READ); + ev_io_init(&rev, readcb, proto == Proto::HTTP3 ? 0 : fd, EV_READ); wev.data = this; rev.data = this; @@ -128,7 +128,7 @@ void Connection::disconnect() { tls.early_data_finish = false; } - if (fd != -1) { + if (proto != Proto::HTTP3 && fd != -1) { shutdown(fd, SHUT_WR); close(fd); fd = -1; diff --git a/src/shrpx_http3_upstream.cc b/src/shrpx_http3_upstream.cc index 83b957bd..040f3192 100644 --- a/src/shrpx_http3_upstream.cc +++ b/src/shrpx_http3_upstream.cc @@ -185,6 +185,7 @@ int Http3Upstream::init(const UpstreamAddr *faddr, const Address &remote_addr, ngtcp2_transport_params params; ngtcp2_transport_params_default(¶ms); + params.initial_max_streams_uni = 3; params.initial_max_data = 1_m; params.initial_max_stream_data_bidi_remote = 256_k; params.initial_max_stream_data_uni = 256_k; @@ -217,7 +218,119 @@ int Http3Upstream::init(const UpstreamAddr *faddr, const Address &remote_addr, int Http3Upstream::on_read() { return 0; } -int Http3Upstream::on_write() { return 0; } +int Http3Upstream::on_write() { + std::array buf; + size_t max_pktcnt = + std::min(static_cast(64_k), ngtcp2_conn_get_send_quantum(conn_)) / + SHRPX_MAX_UDP_PAYLOAD_SIZE; + ngtcp2_pkt_info pi; + uint8_t *bufpos = buf.data(); + ngtcp2_path_storage ps, prev_ps; + size_t pktcnt = 0; + auto ts = quic_timestamp(); + + ngtcp2_path_storage_zero(&ps); + ngtcp2_path_storage_zero(&prev_ps); + + for (;;) { + int64_t stream_id = -1; + int fin = 0; + + ngtcp2_ssize ndatalen; + + uint32_t flags = NGTCP2_WRITE_STREAM_FLAG_MORE; + if (fin) { + flags |= NGTCP2_WRITE_STREAM_FLAG_FIN; + } + + auto nwrite = ngtcp2_conn_writev_stream( + conn_, &ps.path, &pi, bufpos, SHRPX_MAX_UDP_PAYLOAD_SIZE, &ndatalen, + flags, stream_id, nullptr, 0, ts); + if (nwrite < 0) { + switch (nwrite) { + case NGTCP2_ERR_STREAM_DATA_BLOCKED: + assert(ndatalen == -1); + continue; + case NGTCP2_ERR_STREAM_SHUT_WR: + assert(ndatalen == -1); + continue; + case NGTCP2_ERR_WRITE_MORE: + assert(ndatalen >= 0); + continue; + } + + assert(ndatalen == -1); + + LOG(ERROR) << "ngtcp2_conn_writev_stream: " << ngtcp2_strerror(nwrite); + + last_error_ = quic::err_transport(nwrite); + + handler_->get_connection()->wlimit.stopw(); + + return handle_error(); + } else if (ndatalen >= 0) { + // TODO do something + } + + if (nwrite == 0) { + if (bufpos - buf.data()) { + quic_send_packet(static_cast(prev_ps.path.user_data), + prev_ps.path.remote.addr, prev_ps.path.remote.addrlen, + prev_ps.path.local.addr, prev_ps.path.local.addrlen, + buf.data(), bufpos - buf.data(), + SHRPX_MAX_UDP_PAYLOAD_SIZE); + + ngtcp2_conn_update_pkt_tx_time(conn_, ts); + // reset_idle_timer here + } + + handler_->get_connection()->wlimit.stopw(); + + return 0; + } + + bufpos += nwrite; + + if (pktcnt == 0) { + ngtcp2_path_copy(&prev_ps.path, &ps.path); + } else if (!ngtcp2_path_eq(&prev_ps.path, &ps.path)) { + quic_send_packet(static_cast(prev_ps.path.user_data), + prev_ps.path.remote.addr, prev_ps.path.remote.addrlen, + prev_ps.path.local.addr, prev_ps.path.local.addrlen, + buf.data(), bufpos - buf.data() - nwrite, + SHRPX_MAX_UDP_PAYLOAD_SIZE); + + quic_send_packet(static_cast(ps.path.user_data), + ps.path.remote.addr, ps.path.remote.addrlen, + ps.path.local.addr, ps.path.local.addrlen, + bufpos - nwrite, nwrite, SHRPX_MAX_UDP_PAYLOAD_SIZE); + + ngtcp2_conn_update_pkt_tx_time(conn_, ts); + // reset_idle_timer here + + handler_->signal_write(); + + return 0; + } + + if (++pktcnt == max_pktcnt || + static_cast(nwrite) < SHRPX_MAX_UDP_PAYLOAD_SIZE) { + quic_send_packet(static_cast(ps.path.user_data), + ps.path.remote.addr, ps.path.remote.addrlen, + ps.path.local.addr, ps.path.local.addrlen, buf.data(), + bufpos - buf.data(), SHRPX_MAX_UDP_PAYLOAD_SIZE); + + ngtcp2_conn_update_pkt_tx_time(conn_, ts); + // reset_idle_timer here + + handler_->signal_write(); + + return 0; + } + } + + return 0; +} int Http3Upstream::on_timeout(Downstream *downstream) { return 0; } @@ -382,6 +495,13 @@ int Http3Upstream::on_tx_secret(ngtcp2_crypto_level level, int Http3Upstream::add_crypto_data(ngtcp2_crypto_level level, const uint8_t *data, size_t datalen) { + int rv = ngtcp2_conn_submit_crypto_data(conn_, level, data, datalen); + + if (rv != 0) { + LOG(ERROR) << "ngtcp2_conn_submit_crypto_data: " << ngtcp2_strerror(rv); + return -1; + } + return 0; } diff --git a/src/shrpx_quic.cc b/src/shrpx_quic.cc index cd989f9f..09f0683f 100644 --- a/src/shrpx_quic.cc +++ b/src/shrpx_quic.cc @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -198,10 +199,86 @@ int create_quic_server_socket(UpstreamAddr &faddr) { return 0; } -int quic_send_packet(const UpstreamAddr *addr, const sockaddr *remote_sa, +int quic_send_packet(const UpstreamAddr *faddr, const sockaddr *remote_sa, size_t remote_salen, const sockaddr *local_sa, size_t local_salen, const uint8_t *data, size_t datalen, size_t gso_size) { + iovec msg_iov = {const_cast(data), datalen}; + msghdr msg{}; + msg.msg_name = const_cast(remote_sa); + msg.msg_namelen = remote_salen; + msg.msg_iov = &msg_iov; + msg.msg_iovlen = 1; + + uint8_t + msg_ctrl[CMSG_SPACE(sizeof(uint16_t)) + CMSG_SPACE(sizeof(in6_pktinfo))]; + + memset(msg_ctrl, 0, sizeof(msg_ctrl)); + + msg.msg_control = msg_ctrl; + msg.msg_controllen = sizeof(msg_ctrl); + + size_t controllen = 0; + + auto cm = CMSG_FIRSTHDR(&msg); + + switch (local_sa->sa_family) { + case AF_INET: { + controllen += CMSG_SPACE(sizeof(in_pktinfo)); + cm->cmsg_level = IPPROTO_IP; + cm->cmsg_type = IP_PKTINFO; + cm->cmsg_len = CMSG_LEN(sizeof(in_pktinfo)); + auto pktinfo = reinterpret_cast(CMSG_DATA(cm)); + memset(pktinfo, 0, sizeof(in_pktinfo)); + auto addrin = + reinterpret_cast(const_cast(local_sa)); + pktinfo->ipi_spec_dst = addrin->sin_addr; + break; + } + case AF_INET6: { + controllen += CMSG_SPACE(sizeof(in6_pktinfo)); + cm->cmsg_level = IPPROTO_IPV6; + cm->cmsg_type = IPV6_PKTINFO; + cm->cmsg_len = CMSG_LEN(sizeof(in6_pktinfo)); + auto pktinfo = reinterpret_cast(CMSG_DATA(cm)); + memset(pktinfo, 0, sizeof(in6_pktinfo)); + auto addrin = + reinterpret_cast(const_cast(local_sa)); + pktinfo->ipi6_addr = addrin->sin6_addr; + break; + } + default: + assert(0); + } + + if (gso_size && datalen > gso_size) { + controllen += CMSG_SPACE(sizeof(uint16_t)); + cm = CMSG_NXTHDR(&msg, cm); + cm->cmsg_level = SOL_UDP; + cm->cmsg_type = UDP_SEGMENT; + cm->cmsg_len = CMSG_LEN(sizeof(uint16_t)); + *(reinterpret_cast(CMSG_DATA(cm))) = gso_size; + } + + msg.msg_controllen = controllen; + + ssize_t nwrite; + + do { + nwrite = sendmsg(faddr->fd, &msg, 0); + } while (nwrite == -1 && errno == EINTR); + + if (nwrite == -1) { + return -1; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "QUIC sent packet: local=" + << util::to_numeric_addr(local_sa, local_salen) + << " remote=" << util::to_numeric_addr(remote_sa, remote_salen) + << " " << nwrite << " bytes"; + } + return 0; } diff --git a/src/shrpx_quic.h b/src/shrpx_quic.h index cf405e61..4fa329e8 100644 --- a/src/shrpx_quic.h +++ b/src/shrpx_quic.h @@ -42,7 +42,7 @@ ngtcp2_tstamp quic_timestamp(); int create_quic_server_socket(UpstreamAddr &addr); -int quic_send_packet(const UpstreamAddr *addr, const sockaddr *remote_sa, +int quic_send_packet(const UpstreamAddr *faddr, const sockaddr *remote_sa, size_t remote_salen, const sockaddr *local_sa, size_t local_salen, const uint8_t *data, size_t datalen, size_t gso_size); diff --git a/src/shrpx_quic_connection_handler.cc b/src/shrpx_quic_connection_handler.cc index 7ff08767..3d6d97ed 100644 --- a/src/shrpx_quic_connection_handler.cc +++ b/src/shrpx_quic_connection_handler.cc @@ -105,8 +105,11 @@ int QUICConnectionHandler::handle_packet(const UpstreamAddr *faddr, if (handler->read_quic(faddr, remote_addr, local_addr, data, datalen) != 0) { delete handler; + return 0; } + handler->signal_write(); + return 0; } @@ -135,6 +138,10 @@ ClientHandler *QUICConnectionHandler::handle_new_connection( return nullptr; } + assert(SSL_is_quic(ssl)); + + SSL_set_accept_state(ssl); + // Disable TLS session ticket if we don't have working ticket // keys. if (!worker_->get_ticket_keys()) { diff --git a/src/shrpx_tls.cc b/src/shrpx_tls.cc index 9ca883e8..e6d7fabf 100644 --- a/src/shrpx_tls.cc +++ b/src/shrpx_tls.cc @@ -1118,6 +1118,10 @@ int quic_send_alert(SSL *ssl, OSSL_ENCRYPTION_LEVEL ossl_level, uint8_t alert) { auto handler = static_cast(conn->data); auto upstream = static_cast(handler->get_upstream()); + if (!upstream) { + return 1; + } + upstream->set_tls_alert(alert); return 1; From 354f46d8c5dde2d27228ed735cc286beb0718705 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Mon, 16 Aug 2021 21:43:39 +0900 Subject: [PATCH 098/124] nghttpx: Add QUIC timeouts --- src/shrpx.cc | 3 + src/shrpx_config.h | 3 + src/shrpx_http3_upstream.cc | 153 ++++++++++++++++++++++++++++++++++-- src/shrpx_http3_upstream.h | 8 ++ 4 files changed, 161 insertions(+), 6 deletions(-) diff --git a/src/shrpx.cc b/src/shrpx.cc index d33291f0..184125f0 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -1548,6 +1548,9 @@ void fill_default_config(Config *config) { downstreamconf.option, downstreamconf.encoder_dynamic_table_size); } + auto &quicconf = config->quic; + { quicconf.timeout.idle = 30_s; } + auto &loggingconf = config->logging; { auto &accessconf = loggingconf.access; diff --git a/src/shrpx_config.h b/src/shrpx_config.h index 6c426685..74fd42fb 100644 --- a/src/shrpx_config.h +++ b/src/shrpx_config.h @@ -708,6 +708,9 @@ struct QUICConfig { struct { std::array secret; } stateless_reset; + struct { + ev_tstamp idle; + } timeout; }; // custom error page diff --git a/src/shrpx_http3_upstream.cc b/src/shrpx_http3_upstream.cc index 040f3192..aafe45e1 100644 --- a/src/shrpx_http3_upstream.cc +++ b/src/shrpx_http3_upstream.cc @@ -38,10 +38,57 @@ namespace shrpx { +namespace { +void idle_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { + auto upstream = static_cast(w->data); + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "QUIC idle timeout"; + } + + // TODO Implement draining period. + + auto handler = upstream->get_client_handler(); + + delete handler; +} +} // namespace + +namespace { +void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { + auto upstream = static_cast(w->data); + + if (upstream->handle_expiry() != 0 || upstream->on_write() != 0) { + goto fail; + } + + return; + +fail: + auto handler = upstream->get_client_handler(); + + delete handler; +} +} // namespace + Http3Upstream::Http3Upstream(ClientHandler *handler) - : handler_{handler}, conn_{nullptr}, tls_alert_{0} {} + : handler_{handler}, conn_{nullptr}, tls_alert_{0} { + ev_timer_init(&timer_, timeoutcb, 0., 0.); + timer_.data = this; + + auto config = get_config(); + auto &quicconf = config->quic; + + ev_timer_init(&idle_timer_, idle_timeoutcb, 0., quicconf.timeout.idle); + idle_timer_.data = this; +} Http3Upstream::~Http3Upstream() { + auto loop = handler_->get_loop(); + + ev_timer_stop(loop, &idle_timer_); + ev_timer_stop(loop, &timer_); + if (conn_) { auto worker = handler_->get_worker(); auto quic_client_handler = worker->get_quic_connection_handler(); @@ -183,13 +230,17 @@ int Http3Upstream::init(const UpstreamAddr *faddr, const Address &remote_addr, settings.max_udp_payload_size = SHRPX_MAX_UDP_PAYLOAD_SIZE; settings.rand_ctx = {&worker->get_randgen()}; + auto config = get_config(); + auto &quicconf = config->quic; + ngtcp2_transport_params params; ngtcp2_transport_params_default(¶ms); params.initial_max_streams_uni = 3; params.initial_max_data = 1_m; params.initial_max_stream_data_bidi_remote = 256_k; params.initial_max_stream_data_uni = 256_k; - params.max_idle_timeout = 30 * NGTCP2_SECONDS; + params.max_idle_timeout = + static_cast(quicconf.timeout.idle * NGTCP2_SECONDS); params.original_dcid = initial_hd.dcid; auto path = ngtcp2_path{ @@ -219,6 +270,16 @@ int Http3Upstream::init(const UpstreamAddr *faddr, const Address &remote_addr, int Http3Upstream::on_read() { return 0; } int Http3Upstream::on_write() { + if (write_streams() != 0) { + return -1; + } + + reset_timer(); + + return 0; +} + +int Http3Upstream::write_streams() { std::array buf; size_t max_pktcnt = std::min(static_cast(64_k), ngtcp2_conn_get_send_quantum(conn_)) / @@ -281,7 +342,7 @@ int Http3Upstream::on_write() { SHRPX_MAX_UDP_PAYLOAD_SIZE); ngtcp2_conn_update_pkt_tx_time(conn_, ts); - // reset_idle_timer here + reset_idle_timer(); } handler_->get_connection()->wlimit.stopw(); @@ -306,7 +367,7 @@ int Http3Upstream::on_write() { bufpos - nwrite, nwrite, SHRPX_MAX_UDP_PAYLOAD_SIZE); ngtcp2_conn_update_pkt_tx_time(conn_, ts); - // reset_idle_timer here + reset_idle_timer(); handler_->signal_write(); @@ -321,7 +382,7 @@ int Http3Upstream::on_write() { bufpos - buf.data(), SHRPX_MAX_UDP_PAYLOAD_SIZE); ngtcp2_conn_update_pkt_tx_time(conn_, ts); - // reset_idle_timer here + reset_idle_timer(); handler_->signal_write(); @@ -466,10 +527,53 @@ int Http3Upstream::on_read(const UpstreamAddr *faddr, return handle_error(); } + reset_idle_timer(); + return 0; } -int Http3Upstream::handle_error() { return -1; } +int Http3Upstream::handle_error() { + if (ngtcp2_conn_is_in_closing_period(conn_)) { + return -1; + } + + ngtcp2_path_storage ps; + ngtcp2_pkt_info pi; + + ngtcp2_path_storage_zero(&ps); + + auto ts = quic_timestamp(); + + std::array buf; + ngtcp2_ssize nwrite; + + if (last_error_.type == quic::ErrorType::Transport) { + nwrite = ngtcp2_conn_write_connection_close( + conn_, &ps.path, &pi, buf.data(), SHRPX_MAX_UDP_PAYLOAD_SIZE, + last_error_.code, ts); + if (nwrite < 0) { + LOG(ERROR) << "ngtcp2_conn_write_connection_close: " + << ngtcp2_strerror(nwrite); + return -1; + } + } else { + nwrite = ngtcp2_conn_write_application_close( + conn_, &ps.path, &pi, buf.data(), SHRPX_MAX_UDP_PAYLOAD_SIZE, + last_error_.code, ts); + if (nwrite < 0) { + LOG(ERROR) << "ngtcp2_conn_write_application_close: " + << ngtcp2_strerror(nwrite); + return -1; + } + } + + quic_send_packet(static_cast(ps.path.user_data), + ps.path.remote.addr, ps.path.remote.addrlen, + ps.path.local.addr, ps.path.local.addrlen, buf.data(), + nwrite, 0); + + return -1; +} int Http3Upstream::on_rx_secret(ngtcp2_crypto_level level, const uint8_t *secret, size_t secretlen) { @@ -507,4 +611,41 @@ int Http3Upstream::add_crypto_data(ngtcp2_crypto_level level, void Http3Upstream::set_tls_alert(uint8_t alert) { tls_alert_ = alert; } +int Http3Upstream::handle_expiry() { + int rv; + + auto ts = quic_timestamp(); + + rv = ngtcp2_conn_handle_expiry(conn_, ts); + if (rv != 0) { + LOG(ERROR) << "ngtcp2_conn_handle_expiry: " << ngtcp2_strerror(rv); + last_error_ = quic::err_transport(rv); + return handle_error(); + } + + return 0; +} + +void Http3Upstream::reset_idle_timer() { + auto ts = quic_timestamp(); + auto idle_ts = ngtcp2_conn_get_idle_expiry(conn_); + + idle_timer_.repeat = + idle_ts > ts ? static_cast(idle_ts - ts) / NGTCP2_SECONDS + : 1e-9; + + ev_timer_again(handler_->get_loop(), &idle_timer_); +} + +void Http3Upstream::reset_timer() { + auto ts = quic_timestamp(); + auto expiry_ts = ngtcp2_conn_get_expiry(conn_); + + timer_.repeat = expiry_ts > ts + ? static_cast(expiry_ts - ts) / NGTCP2_SECONDS + : 1e-9; + + ev_timer_again(handler_->get_loop(), &timer_); +} + } // namespace shrpx diff --git a/src/shrpx_http3_upstream.h b/src/shrpx_http3_upstream.h index f41f6597..5e94bf65 100644 --- a/src/shrpx_http3_upstream.h +++ b/src/shrpx_http3_upstream.h @@ -91,6 +91,8 @@ public: int on_read(const UpstreamAddr *faddr, const Address &remote_addr, const Address &local_addr, const uint8_t *data, size_t datalen); + int write_streams(); + int on_rx_secret(ngtcp2_crypto_level level, const uint8_t *secret, size_t secretlen); int on_tx_secret(ngtcp2_crypto_level level, const uint8_t *secret, @@ -103,8 +105,14 @@ public: int handle_error(); + int handle_expiry(); + void reset_idle_timer(); + void reset_timer(); + private: ClientHandler *handler_; + ev_timer timer_; + ev_timer idle_timer_; ngtcp2_cid initial_client_dcid_; ngtcp2_conn *conn_; quic::Error last_error_; From 3ed2da562b9c970a3098903d27ef4ba5505d8636 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Mon, 16 Aug 2021 22:45:36 +0900 Subject: [PATCH 099/124] nghttpx: Add HTTP3 skeleton and minor SSL_CTX fix --- src/shrpx_connection_handler.cc | 3 +- src/shrpx_http3_upstream.cc | 164 +++++++++++++++++++++++++++++++- src/shrpx_http3_upstream.h | 4 + src/shrpx_tls.cc | 7 +- 4 files changed, 174 insertions(+), 4 deletions(-) diff --git a/src/shrpx_connection_handler.cc b/src/shrpx_connection_handler.cc index ddeb51ec..03f74594 100644 --- a/src/shrpx_connection_handler.cc +++ b/src/shrpx_connection_handler.cc @@ -345,7 +345,8 @@ int ConnectionHandler::create_worker_thread(size_t num) { return -1; } # endif // HAVE_MRUBY - if (worker->setup_quic_server_socket() != 0) { + if ((!apiconf.enabled || i != 0) && + worker->setup_quic_server_socket() != 0) { return -1; } diff --git a/src/shrpx_http3_upstream.cc b/src/shrpx_http3_upstream.cc index aafe45e1..33e62a3e 100644 --- a/src/shrpx_http3_upstream.cc +++ b/src/shrpx_http3_upstream.cc @@ -72,7 +72,7 @@ fail: } // namespace Http3Upstream::Http3Upstream(ClientHandler *handler) - : handler_{handler}, conn_{nullptr}, tls_alert_{0} { + : handler_{handler}, conn_{nullptr}, tls_alert_{0}, httpconn_{nullptr} { ev_timer_init(&timer_, timeoutcb, 0., 0.); timer_.data = this; @@ -89,6 +89,8 @@ Http3Upstream::~Http3Upstream() { ev_timer_stop(loop, &idle_timer_); ev_timer_stop(loop, &timer_); + nghttp3_conn_del(httpconn_); + if (conn_) { auto worker = handler_->get_worker(); auto quic_client_handler = worker->get_quic_connection_handler(); @@ -594,6 +596,10 @@ int Http3Upstream::on_tx_secret(ngtcp2_crypto_level level, return -1; } + if (level == NGTCP2_CRYPTO_LEVEL_APPLICATION && setup_httpconn() != 0) { + return -1; + } + return 0; } @@ -648,4 +654,160 @@ void Http3Upstream::reset_timer() { ev_timer_again(handler_->get_loop(), &timer_); } +namespace { +int http_deferred_consume(nghttp3_conn *conn, int64_t stream_id, + size_t nsoncumed, void *user_data, + void *stream_user_data) { + return 0; +} +} // namespace + +namespace { +int http_acked_stream_data(nghttp3_conn *conn, int64_t stream_id, + size_t datalen, void *user_data, + void *stream_user_data) { + return 0; +} +} // namespace + +namespace { +int http_begin_request_headers(nghttp3_conn *conn, int64_t stream_id, + void *user_data, void *stream_user_data) { + return 0; +} +} // namespace + +namespace { +int http_recv_request_header(nghttp3_conn *conn, int64_t stream_id, + int32_t token, nghttp3_rcbuf *name, + nghttp3_rcbuf *value, uint8_t flags, + void *user_data, void *stream_user_data) { + return 0; +} +} // namespace + +namespace { +int http_end_request_headers(nghttp3_conn *conn, int64_t stream_id, + void *user_data, void *stream_user_data) { + return 0; +} +} // namespace + +namespace { +int http_recv_data(nghttp3_conn *conn, int64_t stream_id, const uint8_t *data, + size_t datalen, void *user_data, void *stream_user_data) { + return 0; +} +} // namespace + +namespace { +int http_end_stream(nghttp3_conn *conn, int64_t stream_id, void *user_data, + void *stream_user_data) { + return 0; +} +} // namespace + +namespace { +int http_stream_close(nghttp3_conn *conn, int64_t stream_id, + uint64_t app_error_code, void *conn_user_data, + void *stream_user_data) { + return 0; +} +} // namespace + +namespace { +int http_send_stop_sending(nghttp3_conn *conn, int64_t stream_id, + uint64_t app_error_code, void *user_data, + void *stream_user_data) { + return 0; +} +} // namespace + +namespace { +int http_reset_stream(nghttp3_conn *conn, int64_t stream_id, + uint64_t app_error_code, void *user_data, + void *stream_user_data) { + return 0; +} +} // namespace + +int Http3Upstream::setup_httpconn() { + int rv; + + if (ngtcp2_conn_get_max_local_streams_uni(conn_) < 3) { + return -1; + } + + nghttp3_callbacks callbacks{ + http_acked_stream_data, + http_stream_close, + http_recv_data, + http_deferred_consume, + http_begin_request_headers, + http_recv_request_header, + http_end_request_headers, + nullptr, // begin_trailers + nullptr, // recv_trailer + nullptr, // end_trailers + http_send_stop_sending, + http_end_stream, + http_reset_stream, + }; + + nghttp3_settings settings; + nghttp3_settings_default(&settings); + settings.qpack_max_table_capacity = 4_k; + + auto mem = nghttp3_mem_default(); + + rv = nghttp3_conn_server_new(&httpconn_, &callbacks, &settings, mem, this); + if (rv != 0) { + LOG(ERROR) << "nghttp3_conn_server_new: " << nghttp3_strerror(rv); + return -1; + } + + ngtcp2_transport_params params; + ngtcp2_conn_get_local_transport_params(conn_, ¶ms); + + nghttp3_conn_set_max_client_streams_bidi(httpconn_, + params.initial_max_streams_bidi); + + int64_t ctrl_stream_id; + + rv = ngtcp2_conn_open_uni_stream(conn_, &ctrl_stream_id, nullptr); + if (rv != 0) { + LOG(ERROR) << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv); + return -1; + } + + rv = nghttp3_conn_bind_control_stream(httpconn_, ctrl_stream_id); + if (rv != 0) { + LOG(ERROR) << "nghttp3_conn_bind_control_stream: " << nghttp3_strerror(rv); + return -1; + } + + int64_t qpack_enc_stream_id, qpack_dec_stream_id; + + rv = ngtcp2_conn_open_uni_stream(conn_, &qpack_enc_stream_id, nullptr); + if (rv != 0) { + LOG(ERROR) << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv); + return -1; + } + + rv = ngtcp2_conn_open_uni_stream(conn_, &qpack_dec_stream_id, nullptr); + if (rv != 0) { + LOG(ERROR) << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv); + return -1; + } + + rv = nghttp3_conn_bind_qpack_streams(httpconn_, qpack_enc_stream_id, + qpack_dec_stream_id); + if (rv != 0) { + LOG(ERROR) << "nghttp3_conn_bind_qpack_streams: " << nghttp3_strerror(rv); + return -1; + } + + return 0; +} + } // namespace shrpx diff --git a/src/shrpx_http3_upstream.h b/src/shrpx_http3_upstream.h index 5e94bf65..cf8fa9b4 100644 --- a/src/shrpx_http3_upstream.h +++ b/src/shrpx_http3_upstream.h @@ -28,6 +28,7 @@ #include "shrpx.h" #include +#include #include "shrpx_upstream.h" #include "quic.h" @@ -109,6 +110,8 @@ public: void reset_idle_timer(); void reset_timer(); + int setup_httpconn(); + private: ClientHandler *handler_; ev_timer timer_; @@ -117,6 +120,7 @@ private: ngtcp2_conn *conn_; quic::Error last_error_; uint8_t tls_alert_; + nghttp3_conn *httpconn_; }; } // namespace shrpx diff --git a/src/shrpx_tls.cc b/src/shrpx_tls.cc index e6d7fabf..79104d54 100644 --- a/src/shrpx_tls.cc +++ b/src/shrpx_tls.cc @@ -185,7 +185,8 @@ int servername_callback(SSL *ssl, int *al, void *arg) { auto hostname = StringRef{std::begin(buf), end_buf}; - auto cert_tree = worker->get_cert_lookup_tree(); + auto cert_tree = SSL_is_quic(ssl) ? worker->get_quic_cert_lookup_tree() + : worker->get_cert_lookup_tree(); auto idx = cert_tree->lookup(hostname); if (idx == -1) { @@ -196,7 +197,9 @@ int servername_callback(SSL *ssl, int *al, void *arg) { auto conn_handler = worker->get_connection_handler(); - const auto &ssl_ctx_list = conn_handler->get_indexed_ssl_ctx(idx); + const auto &ssl_ctx_list = SSL_is_quic(ssl) + ? conn_handler->get_quic_indexed_ssl_ctx(idx) + : conn_handler->get_indexed_ssl_ctx(idx); assert(!ssl_ctx_list.empty()); #if !defined(OPENSSL_IS_BORINGSSL) && !LIBRESSL_IN_USE && \ From 12425556c1eb9cfe0490ca8b64b6baaa002e9107 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Mon, 16 Aug 2021 22:49:00 +0900 Subject: [PATCH 100/124] nghttpx: Extend Downstream stream_id to 64 bits --- src/shrpx_downstream.cc | 14 +++++++------- src/shrpx_downstream.h | 20 ++++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/shrpx_downstream.cc b/src/shrpx_downstream.cc index dc7762d0..d3d40e51 100644 --- a/src/shrpx_downstream.cc +++ b/src/shrpx_downstream.cc @@ -113,7 +113,7 @@ void downstream_wtimeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { // upstream could be nullptr for unittests Downstream::Downstream(Upstream *upstream, MemchunkPool *mcpool, - int32_t stream_id) + int64_t stream_id) : dlnext(nullptr), dlprev(nullptr), response_sent_body_length(0), @@ -605,9 +605,9 @@ void Downstream::reset_upstream(Upstream *upstream) { Upstream *Downstream::get_upstream() const { return upstream_; } -void Downstream::set_stream_id(int32_t stream_id) { stream_id_ = stream_id; } +void Downstream::set_stream_id(int64_t stream_id) { stream_id_ = stream_id; } -int32_t Downstream::get_stream_id() const { return stream_id_; } +int64_t Downstream::get_stream_id() const { return stream_id_; } void Downstream::set_request_state(DownstreamState state) { request_state_ = state; @@ -904,11 +904,11 @@ StringRef Downstream::get_http2_settings() const { return http2_settings->value; } -void Downstream::set_downstream_stream_id(int32_t stream_id) { +void Downstream::set_downstream_stream_id(int64_t stream_id) { downstream_stream_id_ = stream_id; } -int32_t Downstream::get_downstream_stream_id() const { +int64_t Downstream::get_downstream_stream_id() const { return downstream_stream_id_; } @@ -1115,11 +1115,11 @@ DefaultMemchunks Downstream::pop_response_buf() { return std::move(response_buf_); } -void Downstream::set_assoc_stream_id(int32_t stream_id) { +void Downstream::set_assoc_stream_id(int64_t stream_id) { assoc_stream_id_ = stream_id; } -int32_t Downstream::get_assoc_stream_id() const { return assoc_stream_id_; } +int64_t Downstream::get_assoc_stream_id() const { return assoc_stream_id_; } BlockAllocator &Downstream::get_block_allocator() { return balloc_; } diff --git a/src/shrpx_downstream.h b/src/shrpx_downstream.h index fff49a1a..10063b50 100644 --- a/src/shrpx_downstream.h +++ b/src/shrpx_downstream.h @@ -319,20 +319,20 @@ enum class DispatchState { class Downstream { public: - Downstream(Upstream *upstream, MemchunkPool *mcpool, int32_t stream_id); + Downstream(Upstream *upstream, MemchunkPool *mcpool, int64_t stream_id); ~Downstream(); void reset_upstream(Upstream *upstream); Upstream *get_upstream() const; - void set_stream_id(int32_t stream_id); - int32_t get_stream_id() const; - void set_assoc_stream_id(int32_t stream_id); - int32_t get_assoc_stream_id() const; + void set_stream_id(int64_t stream_id); + int64_t get_stream_id() const; + void set_assoc_stream_id(int64_t stream_id); + int64_t get_assoc_stream_id() const; void pause_read(IOCtrlReason reason); int resume_read(IOCtrlReason reason, size_t consumed); void force_resume_read(); // Set stream ID for downstream HTTP2 connection. - void set_downstream_stream_id(int32_t stream_id); - int32_t get_downstream_stream_id() const; + void set_downstream_stream_id(int64_t stream_id); + int64_t get_downstream_stream_id() const; int attach_downstream_connection(std::unique_ptr dconn); void detach_downstream_connection(); @@ -566,12 +566,12 @@ private: // How many times we tried in backend connection size_t num_retry_; // The stream ID in frontend connection - int32_t stream_id_; + int64_t stream_id_; // The associated stream ID in frontend connection if this is pushed // stream. - int32_t assoc_stream_id_; + int64_t assoc_stream_id_; // stream ID in backend connection - int32_t downstream_stream_id_; + int64_t downstream_stream_id_; // RST_STREAM error_code from downstream HTTP2 connection uint32_t response_rst_stream_error_code_; // An affinity cookie value. From b0548b4944d4f4119e6e6d26a08c1911256d83df Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Tue, 17 Aug 2021 18:25:55 +0900 Subject: [PATCH 101/124] nghttpx: Complete HTTP request and response --- src/Makefile.am | 1 + src/http3.cc | 215 ++++++ src/http3.h | 129 ++++ src/memchunk.h | 96 ++- src/shrpx_downstream.cc | 17 +- src/shrpx_downstream.h | 8 + src/shrpx_http3_upstream.cc | 1363 ++++++++++++++++++++++++++++++++++- src/shrpx_http3_upstream.h | 28 + 8 files changed, 1831 insertions(+), 26 deletions(-) create mode 100644 src/http3.cc create mode 100644 src/http3.h diff --git a/src/Makefile.am b/src/Makefile.am index 344cc7cf..9226ed31 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -157,6 +157,7 @@ NGHTTPX_SRCS = \ shrpx_quic_listener.cc shrpx_quic_listener.h \ shrpx_quic_connection_handler.cc shrpx_quic_connection_handler.h \ shrpx_http3_upstream.cc shrpx_http3_upstream.h \ + http3.cc http3.h \ quic.cc quic.h \ buffer.h memchunk.h template.h allocator.h \ xsi_strerror.c xsi_strerror.h diff --git a/src/http3.cc b/src/http3.cc new file mode 100644 index 00000000..9d9185ea --- /dev/null +++ b/src/http3.cc @@ -0,0 +1,215 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "http3.h" + +namespace nghttp2 { + +namespace http3 { + +namespace { +nghttp3_nv make_nv_internal(const std::string &name, const std::string &value, + bool never_index, uint8_t nv_flags) { + uint8_t flags; + + flags = nv_flags | + (never_index ? NGHTTP3_NV_FLAG_NEVER_INDEX : NGHTTP3_NV_FLAG_NONE); + + return {(uint8_t *)name.c_str(), (uint8_t *)value.c_str(), name.size(), + value.size(), flags}; +} +} // namespace + +namespace { +nghttp3_nv make_nv_internal(const StringRef &name, const StringRef &value, + bool never_index, uint8_t nv_flags) { + uint8_t flags; + + flags = nv_flags | + (never_index ? NGHTTP3_NV_FLAG_NEVER_INDEX : NGHTTP3_NV_FLAG_NONE); + + return {(uint8_t *)name.c_str(), (uint8_t *)value.c_str(), name.size(), + value.size(), flags}; +} +} // namespace + +nghttp3_nv make_nv(const std::string &name, const std::string &value, + bool never_index) { + return make_nv_internal(name, value, never_index, NGHTTP3_NV_FLAG_NONE); +} + +nghttp3_nv make_nv(const StringRef &name, const StringRef &value, + bool never_index) { + return make_nv_internal(name, value, never_index, NGHTTP3_NV_FLAG_NONE); +} + +nghttp3_nv make_nv_nocopy(const std::string &name, const std::string &value, + bool never_index) { + return make_nv_internal(name, value, never_index, + NGHTTP3_NV_FLAG_NO_COPY_NAME | + NGHTTP3_NV_FLAG_NO_COPY_VALUE); +} + +nghttp3_nv make_nv_nocopy(const StringRef &name, const StringRef &value, + bool never_index) { + return make_nv_internal(name, value, never_index, + NGHTTP3_NV_FLAG_NO_COPY_NAME | + NGHTTP3_NV_FLAG_NO_COPY_VALUE); +} + +namespace { +void copy_headers_to_nva_internal(std::vector &nva, + const HeaderRefs &headers, uint8_t nv_flags, + uint32_t flags) { + auto it_forwarded = std::end(headers); + auto it_xff = std::end(headers); + auto it_xfp = std::end(headers); + auto it_via = std::end(headers); + + for (auto it = std::begin(headers); it != std::end(headers); ++it) { + auto kv = &(*it); + if (kv->name.empty() || kv->name[0] == ':') { + continue; + } + switch (kv->token) { + case http2::HD_COOKIE: + case http2::HD_CONNECTION: + case http2::HD_HOST: + case http2::HD_HTTP2_SETTINGS: + case http2::HD_KEEP_ALIVE: + case http2::HD_PROXY_CONNECTION: + case http2::HD_SERVER: + case http2::HD_TE: + case http2::HD_TRANSFER_ENCODING: + case http2::HD_UPGRADE: + continue; + case http2::HD_EARLY_DATA: + if (flags & http2::HDOP_STRIP_EARLY_DATA) { + continue; + } + break; + case http2::HD_SEC_WEBSOCKET_ACCEPT: + if (flags & http2::HDOP_STRIP_SEC_WEBSOCKET_ACCEPT) { + continue; + } + break; + case http2::HD_SEC_WEBSOCKET_KEY: + if (flags & http2::HDOP_STRIP_SEC_WEBSOCKET_KEY) { + continue; + } + break; + case http2::HD_FORWARDED: + if (flags & http2::HDOP_STRIP_FORWARDED) { + continue; + } + + if (it_forwarded == std::end(headers)) { + it_forwarded = it; + continue; + } + + kv = &(*it_forwarded); + it_forwarded = it; + break; + case http2::HD_X_FORWARDED_FOR: + if (flags & http2::HDOP_STRIP_X_FORWARDED_FOR) { + continue; + } + + if (it_xff == std::end(headers)) { + it_xff = it; + continue; + } + + kv = &(*it_xff); + it_xff = it; + break; + case http2::HD_X_FORWARDED_PROTO: + if (flags & http2::HDOP_STRIP_X_FORWARDED_PROTO) { + continue; + } + + if (it_xfp == std::end(headers)) { + it_xfp = it; + continue; + } + + kv = &(*it_xfp); + it_xfp = it; + break; + case http2::HD_VIA: + if (flags & http2::HDOP_STRIP_VIA) { + continue; + } + + if (it_via == std::end(headers)) { + it_via = it; + continue; + } + + kv = &(*it_via); + it_via = it; + break; + } + nva.push_back( + make_nv_internal(kv->name, kv->value, kv->no_index, nv_flags)); + } +} +} // namespace + +void copy_headers_to_nva(std::vector &nva, + const HeaderRefs &headers, uint32_t flags) { + copy_headers_to_nva_internal(nva, headers, NGHTTP3_NV_FLAG_NONE, flags); +} + +void copy_headers_to_nva_nocopy(std::vector &nva, + const HeaderRefs &headers, uint32_t flags) { + copy_headers_to_nva_internal( + nva, headers, + NGHTTP3_NV_FLAG_NO_COPY_NAME | NGHTTP3_NV_FLAG_NO_COPY_VALUE, flags); +} + +int check_nv(const uint8_t *name, size_t namelen, const uint8_t *value, + size_t valuelen) { + if (!nghttp3_check_header_name(name, namelen)) { + return 0; + } + if (!nghttp3_check_header_value(value, valuelen)) { + return 0; + } + return 1; +} + +void dump_nv(FILE *out, const nghttp3_nv *nva, size_t nvlen) { + auto end = nva + nvlen; + for (; nva != end; ++nva) { + fprintf(out, "%s: %s\n", nva->name, nva->value); + } + fputc('\n', out); + fflush(out); +} + +} // namespace http3 + +} // namespace nghttp2 diff --git a/src/http3.h b/src/http3.h new file mode 100644 index 00000000..dfabd784 --- /dev/null +++ b/src/http3.h @@ -0,0 +1,129 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef HTTP3_H +#define HTTP3_H + +#include "nghttp2_config.h" + +#include +#include +#include + +#include + +#include "http2.h" +#include "template.h" + +namespace nghttp2 { + +namespace http3 { + +// Creates nghttp3_nv using |name| and |value| and returns it. The +// returned value only references the data pointer to name.c_str() and +// value.c_str(). If |no_index| is true, nghttp3_nv flags member has +// NGHTTP3_NV_FLAG_NEVER_INDEX flag set. +nghttp3_nv make_nv(const std::string &name, const std::string &value, + bool never_index = false); + +nghttp3_nv make_nv(const StringRef &name, const StringRef &value, + bool never_index = false); + +nghttp3_nv make_nv_nocopy(const std::string &name, const std::string &value, + bool never_index = false); + +nghttp3_nv make_nv_nocopy(const StringRef &name, const StringRef &value, + bool never_index = false); + +// Create nghttp3_nv from string literal |name| and |value|. +template +constexpr nghttp3_nv make_nv_ll(const char (&name)[N], const char (&value)[M]) { + return {(uint8_t *)name, (uint8_t *)value, N - 1, M - 1, + NGHTTP3_NV_FLAG_NO_COPY_NAME | NGHTTP3_NV_FLAG_NO_COPY_VALUE}; +} + +// Create nghttp3_nv from string literal |name| and c-string |value|. +template +nghttp3_nv make_nv_lc(const char (&name)[N], const char *value) { + return {(uint8_t *)name, (uint8_t *)value, N - 1, strlen(value), + NGHTTP3_NV_FLAG_NO_COPY_NAME}; +} + +template +nghttp3_nv make_nv_lc_nocopy(const char (&name)[N], const char *value) { + return {(uint8_t *)name, (uint8_t *)value, N - 1, strlen(value), + NGHTTP3_NV_FLAG_NO_COPY_NAME | NGHTTP3_NV_FLAG_NO_COPY_VALUE}; +} + +// Create nghttp3_nv from string literal |name| and std::string +// |value|. +template +nghttp3_nv make_nv_ls(const char (&name)[N], const std::string &value) { + return {(uint8_t *)name, (uint8_t *)value.c_str(), N - 1, value.size(), + NGHTTP3_NV_FLAG_NO_COPY_NAME}; +} + +template +nghttp3_nv make_nv_ls_nocopy(const char (&name)[N], const std::string &value) { + return {(uint8_t *)name, (uint8_t *)value.c_str(), N - 1, value.size(), + NGHTTP3_NV_FLAG_NO_COPY_NAME | NGHTTP3_NV_FLAG_NO_COPY_VALUE}; +} + +template +nghttp3_nv make_nv_ls_nocopy(const char (&name)[N], const StringRef &value) { + return {(uint8_t *)name, (uint8_t *)value.c_str(), N - 1, value.size(), + NGHTTP3_NV_FLAG_NO_COPY_NAME | NGHTTP3_NV_FLAG_NO_COPY_VALUE}; +} + +// Appends headers in |headers| to |nv|. |headers| must be indexed +// before this call (its element's token field is assigned). Certain +// headers, including disallowed headers in HTTP/3 spec and headers +// which require special handling (i.e. via), are not copied. |flags| +// is one or more of HeaderBuildOp flags. They tell function that +// certain header fields should not be added. +void copy_headers_to_nva(std::vector &nva, + const HeaderRefs &headers, uint32_t flags); + +// Just like copy_headers_to_nva(), but this adds +// NGHTTP3_NV_FLAG_NO_COPY_NAME and NGHTTP3_NV_FLAG_NO_COPY_VALUE. +void copy_headers_to_nva_nocopy(std::vector &nva, + const HeaderRefs &headers, uint32_t flags); + +// Dumps name/value pairs in |nva| to |out|. +void dump_nv(FILE *out, const nghttp3_nv *nva, size_t nvlen); + +// Checks the header name/value pair using nghttp3_check_header_name() +// and nghttp3_check_header_value(). If both function returns nonzero, +// this function returns nonzero. +int check_nv(const uint8_t *name, size_t namelen, const uint8_t *value, + size_t valuelen); + +// Dumps name/value pairs in |nva| to |out|. +void dump_nv(FILE *out, const nghttp3_nv *nva, size_t nvlen); + +} // namespace http3 + +} // namespace nghttp2 + +#endif // HTTP3_H diff --git a/src/memchunk.h b/src/memchunk.h index 46116c19..7a7f2e9b 100644 --- a/src/memchunk.h +++ b/src/memchunk.h @@ -113,13 +113,22 @@ template struct Pool { template struct Memchunks { Memchunks(Pool *pool) - : pool(pool), head(nullptr), tail(nullptr), len(0) {} + : pool(pool), + head(nullptr), + tail(nullptr), + len(0), + mark(nullptr), + mark_pos(nullptr), + mark_offset(0) {} Memchunks(const Memchunks &) = delete; Memchunks(Memchunks &&other) noexcept : pool{other.pool}, // keep other.pool head{std::exchange(other.head, nullptr)}, tail{std::exchange(other.tail, nullptr)}, - len{std::exchange(other.len, 0)} {} + len{std::exchange(other.len, 0)}, + mark{std::exchange(other.mark, nullptr)}, + mark_pos{std::exchange(other.mark_pos, nullptr)}, + mark_offset{std::exchange(other.mark_offset, 0)} {} Memchunks &operator=(const Memchunks &) = delete; Memchunks &operator=(Memchunks &&other) noexcept { if (this == &other) { @@ -132,6 +141,9 @@ template struct Memchunks { head = std::exchange(other.head, nullptr); tail = std::exchange(other.tail, nullptr); len = std::exchange(other.len, 0); + mark = std::exchange(other.mark, nullptr); + mark_pos = std::exchange(other.mark_pos, nullptr); + mark_offset = std::exchange(other.mark_offset, 0); return *this; } @@ -200,6 +212,8 @@ template struct Memchunks { return len; } size_t remove(void *dest, size_t count) { + assert(mark == nullptr); + if (!tail || count == 0) { return 0; } @@ -231,6 +245,8 @@ template struct Memchunks { return first - static_cast(dest); } size_t remove(Memchunks &dest, size_t count) { + assert(mark == nullptr); + if (!tail || count == 0) { return 0; } @@ -262,6 +278,7 @@ template struct Memchunks { } size_t remove(Memchunks &dest) { assert(pool == dest.pool); + assert(mark == nullptr); if (head == nullptr) { return 0; @@ -284,6 +301,8 @@ template struct Memchunks { return n; } size_t drain(size_t count) { + assert(mark == nullptr); + auto ndata = count; auto m = head; while (m) { @@ -305,6 +324,38 @@ template struct Memchunks { } return ndata - count; } + size_t drain_mark(size_t count) { + auto ndata = count; + auto m = head; + while (m) { + auto next = m->next; + auto n = std::min(count, m->len()); + m->pos += n; + count -= n; + len -= n; + mark_offset -= n; + + if (m->len() > 0) { + assert(mark != m || m->pos <= mark_pos); + break; + } + if (mark == m) { + assert(m->pos <= mark_pos); + + mark = nullptr; + mark_pos = nullptr; + mark_offset = 0; + } + + pool->recycle(m); + m = next; + } + head = m; + if (head == nullptr) { + tail = nullptr; + } + return ndata - count; + } int riovec(struct iovec *iov, int iovcnt) const { if (!head) { return 0; @@ -317,7 +368,41 @@ template struct Memchunks { } return i; } + int riovec_mark(struct iovec *iov, int iovcnt) { + if (!head || iovcnt == 0) { + return 0; + } + + int i = 0; + Memchunk *m; + if (mark) { + if (mark_pos != mark->last) { + iov[0].iov_base = mark_pos; + iov[0].iov_len = mark->len() - (mark_pos - mark->pos); + + mark_pos = mark->last; + mark_offset += iov[0].iov_len; + i = 1; + } + m = mark->next; + } else { + i = 0; + m = head; + } + + for (; i < iovcnt && m; ++i, m = m->next) { + iov[i].iov_base = m->pos; + iov[i].iov_len = m->len(); + + mark = m; + mark_pos = m->last; + mark_offset += m->len(); + } + + return i; + } size_t rleft() const { return len; } + size_t rleft_mark() const { return len - mark_offset; } void reset() { for (auto m = head; m;) { auto next = m->next; @@ -325,12 +410,17 @@ template struct Memchunks { m = next; } len = 0; - head = tail = nullptr; + head = tail = mark = nullptr; + mark_pos = nullptr; + mark_offset = 0; } Pool *pool; Memchunk *head, *tail; size_t len; + Memchunk *mark; + uint8_t *mark_pos; + size_t mark_offset; }; // Wrapper around Memchunks to offer "peeking" functionality. diff --git a/src/shrpx_downstream.cc b/src/shrpx_downstream.cc index d3d40e51..ca07f2f1 100644 --- a/src/shrpx_downstream.cc +++ b/src/shrpx_downstream.cc @@ -145,7 +145,8 @@ Downstream::Downstream(Upstream *upstream, MemchunkPool *mcpool, accesslog_written_(false), new_affinity_cookie_(false), blocked_request_data_eof_(false), - expect_100_continue_(false) { + expect_100_continue_(false), + stop_reading_(false) { auto &timeoutconf = get_config()->http2.timeout; @@ -164,6 +165,7 @@ Downstream::Downstream(Upstream *upstream, MemchunkPool *mcpool, downstream_wtimer_.data = this; rcbufs_.reserve(32); + rcbufs3_.reserve(32); } Downstream::~Downstream() { @@ -203,6 +205,10 @@ Downstream::~Downstream() { // explicitly. dconn_.reset(); + for (auto rcbuf : rcbufs3_) { + nghttp3_rcbuf_decref(rcbuf); + } + for (auto rcbuf : rcbufs_) { nghttp2_rcbuf_decref(rcbuf); } @@ -1128,6 +1134,11 @@ void Downstream::add_rcbuf(nghttp2_rcbuf *rcbuf) { rcbufs_.push_back(rcbuf); } +void Downstream::add_rcbuf(nghttp3_rcbuf *rcbuf) { + nghttp3_rcbuf_incref(rcbuf); + rcbufs3_.push_back(rcbuf); +} + void Downstream::set_downstream_addr_group( const std::shared_ptr &group) { group_ = group; @@ -1169,4 +1180,8 @@ bool Downstream::get_expect_100_continue() const { return expect_100_continue_; } +bool Downstream::get_stop_reading() const { return stop_reading_; } + +void Downstream::set_stop_reading(bool f) { stop_reading_ = f; } + } // namespace shrpx diff --git a/src/shrpx_downstream.h b/src/shrpx_downstream.h index 10063b50..20ec54c4 100644 --- a/src/shrpx_downstream.h +++ b/src/shrpx_downstream.h @@ -38,6 +38,8 @@ #include +#include + #include "llhttp.h" #include "shrpx_io_control.h" @@ -488,6 +490,7 @@ public: BlockAllocator &get_block_allocator(); void add_rcbuf(nghttp2_rcbuf *rcbuf); + void add_rcbuf(nghttp3_rcbuf *rcbuf); void set_downstream_addr_group(const std::shared_ptr &group); @@ -513,6 +516,9 @@ public: bool get_expect_100_continue() const; + bool get_stop_reading() const; + void set_stop_reading(bool f); + enum { EVENT_ERROR = 0x1, EVENT_TIMEOUT = 0x2, @@ -527,6 +533,7 @@ private: BlockAllocator balloc_; std::vector rcbufs_; + std::vector rcbufs3_; Request req_; Response resp_; @@ -606,6 +613,7 @@ private: bool blocked_request_data_eof_; // true if request contains "expect: 100-continue" header field. bool expect_100_continue_; + bool stop_reading_; }; } // namespace shrpx diff --git a/src/shrpx_http3_upstream.cc b/src/shrpx_http3_upstream.cc index 33e62a3e..7f43ff95 100644 --- a/src/shrpx_http3_upstream.cc +++ b/src/shrpx_http3_upstream.cc @@ -34,6 +34,8 @@ #include "shrpx_log.h" #include "shrpx_quic.h" #include "shrpx_worker.h" +#include "shrpx_http.h" +#include "http3.h" #include "util.h" namespace shrpx { @@ -71,8 +73,25 @@ fail: } } // namespace +namespace { +size_t downstream_queue_size(Worker *worker) { + auto &downstreamconf = *worker->get_downstream_config(); + + if (get_config()->http2_proxy) { + return downstreamconf.connections_per_host; + } + + return downstreamconf.connections_per_frontend; +} +} // namespace + Http3Upstream::Http3Upstream(ClientHandler *handler) - : handler_{handler}, conn_{nullptr}, tls_alert_{0}, httpconn_{nullptr} { + : handler_{handler}, + conn_{nullptr}, + tls_alert_{0}, + httpconn_{nullptr}, + downstream_queue_{downstream_queue_size(handler->get_worker()), + !get_config()->http2_proxy} { ev_timer_init(&timer_, timeoutcb, 0., 0.); timer_.data = this; @@ -150,6 +169,13 @@ int get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, uint8_t *token, return NGTCP2_ERR_CALLBACK_FAILURE; } + auto upstream = static_cast(user_data); + auto handler = upstream->get_client_handler(); + auto worker = handler->get_worker(); + auto quic_connection_handler = worker->get_quic_connection_handler(); + + quic_connection_handler->add_connection_id(cid, handler); + return 0; } } // namespace @@ -168,6 +194,209 @@ int remove_connection_id(ngtcp2_conn *conn, const ngtcp2_cid *cid, } } // namespace +void Http3Upstream::http_begin_request_headers(int64_t stream_id) { + auto downstream = + std::make_unique(this, handler_->get_mcpool(), stream_id); + nghttp3_conn_set_stream_user_data(httpconn_, stream_id, downstream.get()); + + downstream->reset_upstream_rtimer(); + + handler_->repeat_read_timer(); + + auto &req = downstream->request(); + req.http_major = 3; + req.http_minor = 0; + + add_pending_downstream(std::move(downstream)); +} + +void Http3Upstream::add_pending_downstream( + std::unique_ptr downstream) { + downstream_queue_.add_pending(std::move(downstream)); +} + +namespace { +int recv_stream_data(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id, + uint64_t offset, const uint8_t *data, size_t datalen, + void *user_data, void *stream_user_data) { + auto upstream = static_cast(user_data); + + if (upstream->recv_stream_data(flags, stream_id, data, datalen) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Http3Upstream::recv_stream_data(uint32_t flags, int64_t stream_id, + const uint8_t *data, size_t datalen) { + assert(httpconn_); + + auto nconsumed = nghttp3_conn_read_stream( + httpconn_, stream_id, data, datalen, flags & NGTCP2_STREAM_DATA_FLAG_FIN); + if (nconsumed < 0) { + LOG(ERROR) << "nghttp3_conn_read_stream: " << nghttp3_strerror(nconsumed); + last_error_ = quic::err_application(nconsumed); + return -1; + } + + ngtcp2_conn_extend_max_stream_offset(conn_, stream_id, nconsumed); + ngtcp2_conn_extend_max_offset(conn_, nconsumed); + + return 0; +} + +namespace { +int stream_close(ngtcp2_conn *conn, int64_t stream_id, uint64_t app_error_code, + void *user_data, void *stream_user_data) { + auto upstream = static_cast(user_data); + + if (upstream->stream_close(stream_id, app_error_code) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Http3Upstream::stream_close(int64_t stream_id, uint64_t app_error_code) { + if (!httpconn_) { + return 0; + } + + auto rv = nghttp3_conn_close_stream(httpconn_, stream_id, app_error_code); + switch (rv) { + case 0: + break; + case NGHTTP3_ERR_STREAM_NOT_FOUND: + if (ngtcp2_is_bidi_stream(stream_id)) { + ngtcp2_conn_extend_max_streams_bidi(conn_, 1); + } + break; + default: + LOG(ERROR) << "nghttp3_conn_close_stream: " << nghttp3_strerror(rv); + last_error_ = quic::err_application(rv); + return -1; + } + + return 0; +} + +namespace { +int acked_stream_data_offset(ngtcp2_conn *conn, int64_t stream_id, + uint64_t offset, uint64_t datalen, void *user_data, + void *stream_user_data) { + auto upstream = static_cast(user_data); + + if (upstream->acked_stream_data_offset(stream_id, datalen) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Http3Upstream::acked_stream_data_offset(int64_t stream_id, + uint64_t datalen) { + if (!httpconn_) { + return 0; + } + + auto rv = nghttp3_conn_add_ack_offset(httpconn_, stream_id, datalen); + if (rv != 0) { + LOG(ERROR) << "nghttp3_conn_add_ack_offset: " << nghttp3_strerror(rv); + return -1; + } + + return 0; +} + +namespace { +int extend_max_stream_data(ngtcp2_conn *conn, int64_t stream_id, + uint64_t max_data, void *user_data, + void *stream_user_data) { + auto upstream = static_cast(user_data); + + if (upstream->extend_max_stream_data(stream_id) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Http3Upstream::extend_max_stream_data(int64_t stream_id) { + if (!httpconn_) { + return 0; + } + + auto rv = nghttp3_conn_unblock_stream(httpconn_, stream_id); + if (rv != 0) { + LOG(ERROR) << "nghttp3_conn_unblock_stream: " << nghttp3_strerror(rv); + return -1; + } + + return 0; +} + +namespace { +int extend_max_remote_streams_bidi(ngtcp2_conn *conn, uint64_t max_streams, + void *user_data) { + auto upstream = static_cast(user_data); + + upstream->extend_max_remote_streams_bidi(max_streams); + + return 0; +} +} // namespace + +void Http3Upstream::extend_max_remote_streams_bidi(uint64_t max_streams) { + nghttp3_conn_set_max_client_streams_bidi(httpconn_, max_streams); +} + +namespace { +int stream_reset(ngtcp2_conn *conn, int64_t stream_id, uint64_t final_size, + uint64_t app_error_code, void *user_data, + void *stream_user_data) { + auto upstream = static_cast(user_data); + + if (upstream->http_shutdown_stream_read(stream_id) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Http3Upstream::http_shutdown_stream_read(int64_t stream_id) { + if (!httpconn_) { + return 0; + } + + auto rv = nghttp3_conn_shutdown_stream_read(httpconn_, stream_id); + if (rv != 0) { + LOG(ERROR) << "nghttp3_conn_shutdown_stream_read: " << nghttp3_strerror(rv); + return -1; + } + + return 0; +} + +namespace { +int stream_stop_sending(ngtcp2_conn *conn, int64_t stream_id, + uint64_t app_error_code, void *user_data, + void *stream_user_data) { + auto upstream = static_cast(user_data); + + if (upstream->http_shutdown_stream_read(stream_id) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + int Http3Upstream::init(const UpstreamAddr *faddr, const Address &remote_addr, const Address &local_addr, const ngtcp2_pkt_hd &initial_hd) { @@ -184,10 +413,10 @@ int Http3Upstream::init(const UpstreamAddr *faddr, const Address &remote_addr, ngtcp2_crypto_encrypt_cb, ngtcp2_crypto_decrypt_cb, ngtcp2_crypto_hp_mask_cb, - nullptr, // recv_stream_data - nullptr, // acked_stream_data_offset + shrpx::recv_stream_data, + shrpx::acked_stream_data_offset, nullptr, // stream_open - nullptr, // stream_close + shrpx::stream_close, nullptr, // recv_stateless_reset nullptr, // recv_retry nullptr, // extend_max_local_streams_bidi @@ -198,10 +427,10 @@ int Http3Upstream::init(const UpstreamAddr *faddr, const Address &remote_addr, ngtcp2_crypto_update_key_cb, nullptr, // path_validation nullptr, // select_preferred_addr - nullptr, // stream_reset - nullptr, // extend_max_remote_streams_bidi + shrpx::stream_reset, + shrpx::extend_max_remote_streams_bidi, nullptr, // extend_max_remote_streams_uni - nullptr, // extend_max_stream_data + shrpx::extend_max_stream_data, nullptr, // dcid_status nullptr, // handshake_confirmed nullptr, // recv_new_token @@ -211,7 +440,7 @@ int Http3Upstream::init(const UpstreamAddr *faddr, const Address &remote_addr, nullptr, // ack_datagram nullptr, // lost_datagram ngtcp2_crypto_get_path_challenge_data_cb, - nullptr, // stream_stop_sending + shrpx::stream_stop_sending, }; initial_client_dcid_ = initial_hd.dcid; @@ -230,13 +459,14 @@ int Http3Upstream::init(const UpstreamAddr *faddr, const Address &remote_addr, settings.max_window = 6_m; settings.max_stream_window = 6_m; settings.max_udp_payload_size = SHRPX_MAX_UDP_PAYLOAD_SIZE; - settings.rand_ctx = {&worker->get_randgen()}; + settings.rand_ctx.native_handle = &worker->get_randgen(); auto config = get_config(); auto &quicconf = config->quic; ngtcp2_transport_params params; ngtcp2_transport_params_default(¶ms); + params.initial_max_streams_bidi = 100; params.initial_max_streams_uni = 3; params.initial_max_data = 1_m; params.initial_max_stream_data_bidi_remote = 256_k; @@ -282,6 +512,7 @@ int Http3Upstream::on_write() { } int Http3Upstream::write_streams() { + std::array vec; std::array buf; size_t max_pktcnt = std::min(static_cast(64_k), ngtcp2_conn_get_send_quantum(conn_)) / @@ -290,6 +521,7 @@ int Http3Upstream::write_streams() { uint8_t *bufpos = buf.data(); ngtcp2_path_storage ps, prev_ps; size_t pktcnt = 0; + int rv; auto ts = quic_timestamp(); ngtcp2_path_storage_zero(&ps); @@ -298,8 +530,22 @@ int Http3Upstream::write_streams() { for (;;) { int64_t stream_id = -1; int fin = 0; + nghttp3_ssize sveccnt = 0; + + if (httpconn_ && ngtcp2_conn_get_max_data_left(conn_)) { + sveccnt = nghttp3_conn_writev_stream(httpconn_, &stream_id, &fin, + vec.data(), vec.size()); + if (sveccnt < 0) { + LOG(ERROR) << "nghttp3_conn_writev_stream: " + << nghttp3_strerror(sveccnt); + last_error_ = quic::err_application(sveccnt); + return handle_error(); + } + } ngtcp2_ssize ndatalen; + auto v = vec.data(); + auto vcnt = static_cast(sveccnt); uint32_t flags = NGTCP2_WRITE_STREAM_FLAG_MORE; if (fin) { @@ -308,17 +554,37 @@ int Http3Upstream::write_streams() { auto nwrite = ngtcp2_conn_writev_stream( conn_, &ps.path, &pi, bufpos, SHRPX_MAX_UDP_PAYLOAD_SIZE, &ndatalen, - flags, stream_id, nullptr, 0, ts); + flags, stream_id, reinterpret_cast(v), vcnt, ts); if (nwrite < 0) { switch (nwrite) { case NGTCP2_ERR_STREAM_DATA_BLOCKED: assert(ndatalen == -1); + rv = nghttp3_conn_block_stream(httpconn_, stream_id); + if (rv != 0) { + LOG(ERROR) << "nghttp3_conn_block_stream: " << nghttp3_strerror(rv); + last_error_ = quic::err_application(rv); + return handle_error(); + } continue; case NGTCP2_ERR_STREAM_SHUT_WR: assert(ndatalen == -1); + rv = nghttp3_conn_shutdown_stream_write(httpconn_, stream_id); + if (rv != 0) { + LOG(ERROR) << "nghttp3_conn_shutdown_stream_write: " + << nghttp3_strerror(rv); + last_error_ = quic::err_application(rv); + return handle_error(); + } continue; case NGTCP2_ERR_WRITE_MORE: assert(ndatalen >= 0); + rv = nghttp3_conn_add_write_offset(httpconn_, stream_id, ndatalen); + if (rv != 0) { + LOG(ERROR) << "nghttp3_conn_add_write_offset: " + << nghttp3_strerror(rv); + last_error_ = quic::err_application(rv); + return handle_error(); + } continue; } @@ -332,7 +598,12 @@ int Http3Upstream::write_streams() { return handle_error(); } else if (ndatalen >= 0) { - // TODO do something + rv = nghttp3_conn_add_write_offset(httpconn_, stream_id, ndatalen); + if (rv != 0) { + LOG(ERROR) << "nghttp3_conn_add_write_offset: " << nghttp3_strerror(rv); + last_error_ = quic::err_application(rv); + return handle_error(); + } } if (nwrite == 0) { @@ -399,6 +670,16 @@ int Http3Upstream::on_timeout(Downstream *downstream) { return 0; } int Http3Upstream::on_downstream_abort_request(Downstream *downstream, unsigned int status_code) { + int rv; + + rv = error_reply(downstream, status_code); + + if (rv != 0) { + return -1; + } + + handler_->signal_write(); + return 0; } @@ -407,33 +688,458 @@ int Http3Upstream::on_downstream_abort_request_with_https_redirect( return 0; } -int Http3Upstream::downstream_read(DownstreamConnection *dconn) { return 0; } +namespace { +uint64_t +infer_upstream_shutdown_stream_error_code(uint32_t downstream_error_code) { + // NGHTTP2_REFUSED_STREAM is important because it tells upstream + // client to retry. + switch (downstream_error_code) { + case NGHTTP2_NO_ERROR: + return NGHTTP3_H3_NO_ERROR; + case NGHTTP2_REFUSED_STREAM: + return NGHTTP3_H3_REQUEST_REJECTED; + default: + return NGHTTP3_H3_INTERNAL_ERROR; + } +} +} // namespace -int Http3Upstream::downstream_write(DownstreamConnection *dconn) { return 0; } +int Http3Upstream::downstream_read(DownstreamConnection *dconn) { + auto downstream = dconn->get_downstream(); -int Http3Upstream::downstream_eof(DownstreamConnection *dconn) { return 0; } + if (downstream->get_response_state() == DownstreamState::MSG_RESET) { + // The downstream stream was reset (canceled). In this case, + // RST_STREAM to the upstream and delete downstream connection + // here. Deleting downstream will be taken place at + // on_stream_close_callback. + shutdown_stream(downstream, + infer_upstream_shutdown_stream_error_code( + downstream->get_response_rst_stream_error_code())); + downstream->pop_downstream_connection(); + // dconn was deleted + dconn = nullptr; + } else if (downstream->get_response_state() == + DownstreamState::MSG_BAD_HEADER) { + if (error_reply(downstream, 502) != 0) { + return -1; + } + downstream->pop_downstream_connection(); + // dconn was deleted + dconn = nullptr; + } else { + auto rv = downstream->on_read(); + if (rv == SHRPX_ERR_EOF) { + if (downstream->get_request_header_sent()) { + return downstream_eof(dconn); + } + return SHRPX_ERR_RETRY; + } + if (rv == SHRPX_ERR_DCONN_CANCELED) { + downstream->pop_downstream_connection(); + handler_->signal_write(); + return 0; + } + if (rv != 0) { + if (rv != SHRPX_ERR_NETWORK) { + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, dconn) << "HTTP parser failure"; + } + } + return downstream_error(dconn, Downstream::EVENT_ERROR); + } + + if (downstream->can_detach_downstream_connection()) { + // Keep-alive + downstream->detach_downstream_connection(); + } + } + + handler_->signal_write(); + + // At this point, downstream may be deleted. + + return 0; +} + +int Http3Upstream::downstream_write(DownstreamConnection *dconn) { + int rv; + rv = dconn->on_write(); + if (rv == SHRPX_ERR_NETWORK) { + return downstream_error(dconn, Downstream::EVENT_ERROR); + } + if (rv != 0) { + return rv; + } + return 0; +} + +int Http3Upstream::downstream_eof(DownstreamConnection *dconn) { + auto downstream = dconn->get_downstream(); + + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, dconn) << "EOF. stream_id=" << downstream->get_stream_id(); + } + + // Delete downstream connection. If we don't delete it here, it will + // be pooled in on_stream_close_callback. + downstream->pop_downstream_connection(); + // dconn was deleted + dconn = nullptr; + // downstream wil be deleted in on_stream_close_callback. + if (downstream->get_response_state() == DownstreamState::HEADER_COMPLETE) { + // Server may indicate the end of the request by EOF + if (LOG_ENABLED(INFO)) { + ULOG(INFO, this) << "Downstream body was ended by EOF"; + } + downstream->set_response_state(DownstreamState::MSG_COMPLETE); + + // For tunneled connection, MSG_COMPLETE signals + // downstream_read_data_callback to send RST_STREAM after pending + // response body is sent. This is needed to ensure that RST_STREAM + // is sent after all pending data are sent. + on_downstream_body_complete(downstream); + } else if (downstream->get_response_state() != + DownstreamState::MSG_COMPLETE) { + // If stream was not closed, then we set MSG_COMPLETE and let + // on_stream_close_callback delete downstream. + if (error_reply(downstream, 502) != 0) { + return -1; + } + } + handler_->signal_write(); + // At this point, downstream may be deleted. + return 0; +} int Http3Upstream::downstream_error(DownstreamConnection *dconn, int events) { + auto downstream = dconn->get_downstream(); + + if (LOG_ENABLED(INFO)) { + if (events & Downstream::EVENT_ERROR) { + DCLOG(INFO, dconn) << "Downstream network/general error"; + } else { + DCLOG(INFO, dconn) << "Timeout"; + } + if (downstream->get_upgraded()) { + DCLOG(INFO, dconn) << "Note: this is tunnel connection"; + } + } + + // Delete downstream connection. If we don't delete it here, it will + // be pooled in on_stream_close_callback. + downstream->pop_downstream_connection(); + // dconn was deleted + dconn = nullptr; + + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + // For SSL tunneling, we issue RST_STREAM. For other types of + // stream, we don't have to do anything since response was + // complete. + if (downstream->get_upgraded()) { + shutdown_stream(downstream, NGHTTP3_H3_NO_ERROR); + } + } else { + if (downstream->get_response_state() == DownstreamState::HEADER_COMPLETE) { + if (downstream->get_upgraded()) { + on_downstream_body_complete(downstream); + } else { + shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR); + } + } else { + unsigned int status; + if (events & Downstream::EVENT_TIMEOUT) { + if (downstream->get_request_header_sent()) { + status = 504; + } else { + status = 408; + } + } else { + status = 502; + } + if (error_reply(downstream, status) != 0) { + return -1; + } + } + downstream->set_response_state(DownstreamState::MSG_COMPLETE); + } + handler_->signal_write(); + // At this point, downstream may be deleted. return 0; } ClientHandler *Http3Upstream::get_client_handler() const { return handler_; } +namespace { +nghttp3_ssize downstream_read_data_callback(nghttp3_conn *conn, + int64_t stream_id, nghttp3_vec *vec, + size_t veccnt, uint32_t *pflags, + void *conn_user_data, + void *stream_user_data) { + auto downstream = static_cast(stream_user_data); + + assert(downstream); + + auto body = downstream->get_response_buf(); + + assert(body); + + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + *pflags |= NGHTTP3_DATA_FLAG_EOF; + } else if (body->rleft_mark() == 0) { + downstream->disable_upstream_wtimer(); + return NGHTTP3_ERR_WOULDBLOCK; + } + + downstream->reset_upstream_wtimer(); + + veccnt = body->riovec_mark(reinterpret_cast(vec), veccnt); + + assert(veccnt); + + downstream->response_sent_body_length += nghttp3_vec_len(vec, veccnt); + + return veccnt; +} +} // namespace + int Http3Upstream::on_downstream_header_complete(Downstream *downstream) { + int rv; + + const auto &req = downstream->request(); + auto &resp = downstream->response(); + + auto &balloc = downstream->get_block_allocator(); + + if (LOG_ENABLED(INFO)) { + if (downstream->get_non_final_response()) { + DLOG(INFO, downstream) << "HTTP non-final response header"; + } else { + DLOG(INFO, downstream) << "HTTP response header completed"; + } + } + + auto config = get_config(); + auto &httpconf = config->http; + + if (!config->http2_proxy && !httpconf.no_location_rewrite) { + downstream->rewrite_location_response_header(req.scheme); + } + +#ifdef HAVE_MRUBY + if (!downstream->get_non_final_response()) { + auto dconn = downstream->get_downstream_connection(); + const auto &group = dconn->get_downstream_addr_group(); + if (group) { + const auto &dmruby_ctx = group->shared_addr->mruby_ctx; + + if (dmruby_ctx->run_on_response_proc(downstream) != 0) { + if (error_reply(downstream, 500) != 0) { + return -1; + } + // Returning -1 will signal deletion of dconn. + return -1; + } + + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + return -1; + } + } + + auto worker = handler_->get_worker(); + auto mruby_ctx = worker->get_mruby_context(); + + if (mruby_ctx->run_on_response_proc(downstream) != 0) { + if (error_reply(downstream, 500) != 0) { + return -1; + } + // Returning -1 will signal deletion of dconn. + return -1; + } + + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + return -1; + } + } +#endif // HAVE_MRUBY + + auto &http2conf = config->http2; + + auto nva = std::vector(); + // 4 means :status and possible server, via, and set-cookie (for + // affinity cookie) header field. + nva.reserve(resp.fs.headers().size() + 4 + + httpconf.add_response_headers.size()); + + if (downstream->get_non_final_response()) { + auto response_status = http2::stringify_status(balloc, resp.http_status); + + nva.push_back(http3::make_nv_ls_nocopy(":status", response_status)); + + http3::copy_headers_to_nva_nocopy(nva, resp.fs.headers(), + http2::HDOP_STRIP_ALL); + + if (LOG_ENABLED(INFO)) { + log_response_headers(downstream, nva); + } + + rv = nghttp3_conn_submit_info(httpconn_, downstream->get_stream_id(), + nva.data(), nva.size()); + + resp.fs.clear_headers(); + + if (rv != 0) { + ULOG(FATAL, this) << "nghttp3_conn_submit_info() failed"; + return -1; + } + + return 0; + } + + auto striphd_flags = http2::HDOP_STRIP_ALL & ~http2::HDOP_STRIP_VIA; + StringRef response_status; + + if (req.connect_proto == ConnectProto::WEBSOCKET && resp.http_status == 101) { + response_status = http2::stringify_status(balloc, 200); + striphd_flags |= http2::HDOP_STRIP_SEC_WEBSOCKET_ACCEPT; + } else { + response_status = http2::stringify_status(balloc, resp.http_status); + } + + nva.push_back(http3::make_nv_ls_nocopy(":status", response_status)); + + http3::copy_headers_to_nva_nocopy(nva, resp.fs.headers(), striphd_flags); + + if (!config->http2_proxy && !httpconf.no_server_rewrite) { + nva.push_back(http3::make_nv_ls_nocopy("server", httpconf.server_name)); + } else { + auto server = resp.fs.header(http2::HD_SERVER); + if (server) { + nva.push_back(http3::make_nv_ls_nocopy("server", (*server).value)); + } + } + + if (!req.regular_connect_method() || !downstream->get_upgraded()) { + auto affinity_cookie = downstream->get_affinity_cookie_to_send(); + if (affinity_cookie) { + auto dconn = downstream->get_downstream_connection(); + assert(dconn); + auto &group = dconn->get_downstream_addr_group(); + auto &shared_addr = group->shared_addr; + auto &cookieconf = shared_addr->affinity.cookie; + auto secure = + http::require_cookie_secure_attribute(cookieconf.secure, req.scheme); + auto cookie_str = http::create_affinity_cookie( + balloc, cookieconf.name, affinity_cookie, cookieconf.path, secure); + nva.push_back(http3::make_nv_ls_nocopy("set-cookie", cookie_str)); + } + } + + auto via = resp.fs.header(http2::HD_VIA); + if (httpconf.no_via) { + if (via) { + nva.push_back(http3::make_nv_ls_nocopy("via", (*via).value)); + } + } else { + // we don't create more than 16 bytes in + // http::create_via_header_value. + size_t len = 16; + if (via) { + len += via->value.size() + 2; + } + + auto iov = make_byte_ref(balloc, len + 1); + auto p = iov.base; + if (via) { + p = std::copy(std::begin(via->value), std::end(via->value), p); + p = util::copy_lit(p, ", "); + } + p = http::create_via_header_value(p, resp.http_major, resp.http_minor); + *p = '\0'; + + nva.push_back(http3::make_nv_ls_nocopy("via", StringRef{iov.base, p})); + } + + for (auto &p : httpconf.add_response_headers) { + nva.push_back(http3::make_nv_nocopy(p.name, p.value)); + } + + if (LOG_ENABLED(INFO)) { + log_response_headers(downstream, nva); + } + + if (http2conf.upstream.debug.dump.response_header) { + http3::dump_nv(http2conf.upstream.debug.dump.response_header, nva.data(), + nva.size()); + } + + nghttp3_data_reader data_read; + data_read.read_data = downstream_read_data_callback; + + nghttp3_data_reader *data_readptr; + + if (downstream->expect_response_body() || + downstream->expect_response_trailer()) { + data_readptr = &data_read; + } else { + data_readptr = nullptr; + } + + rv = nghttp3_conn_submit_response(httpconn_, downstream->get_stream_id(), + nva.data(), nva.size(), data_readptr); + if (rv != 0) { + ULOG(FATAL, this) << "nghttp3_conn_submit_response() failed"; + return -1; + } + + if (data_readptr) { + downstream->reset_upstream_wtimer(); + } + return 0; } int Http3Upstream::on_downstream_body(Downstream *downstream, const uint8_t *data, size_t len, bool flush) { + auto body = downstream->get_response_buf(); + body->append(data, len); + + if (flush) { + nghttp3_conn_resume_stream(httpconn_, downstream->get_stream_id()); + + downstream->ensure_upstream_wtimer(); + } + return 0; } int Http3Upstream::on_downstream_body_complete(Downstream *downstream) { + if (LOG_ENABLED(INFO)) { + DLOG(INFO, downstream) << "HTTP response completed"; + } + + auto &resp = downstream->response(); + + if (!downstream->validate_response_recv_body_length()) { + shutdown_stream(downstream, NGHTTP3_H3_GENERAL_PROTOCOL_ERROR); + resp.connection_close = true; + return 0; + } + + nghttp3_conn_resume_stream(httpconn_, downstream->get_stream_id()); + downstream->ensure_upstream_wtimer(); + return 0; } -void Http3Upstream::on_handler_delete() {} +void Http3Upstream::on_handler_delete() { + for (auto d = downstream_queue_.get_downstreams(); d; d = d->dlnext) { + if (d->get_dispatch_state() == DispatchState::ACTIVE && + d->accesslog_ready()) { + handler_->write_accesslog(d); + } + } +} int Http3Upstream::on_downstream_reset(Downstream *downstream, bool no_retry) { return 0; @@ -443,11 +1149,85 @@ void Http3Upstream::pause_read(IOCtrlReason reason) {} int Http3Upstream::resume_read(IOCtrlReason reason, Downstream *downstream, size_t consumed) { + consume(downstream->get_stream_id(), consumed); + + auto &req = downstream->request(); + + req.consume(consumed); + + handler_->signal_write(); + return 0; } int Http3Upstream::send_reply(Downstream *downstream, const uint8_t *body, size_t bodylen) { + int rv; + + nghttp3_data_reader data_read, *data_read_ptr = nullptr; + + if (bodylen) { + data_read.read_data = downstream_read_data_callback; + data_read_ptr = &data_read; + } + + const auto &resp = downstream->response(); + auto config = get_config(); + auto &httpconf = config->http; + + auto &balloc = downstream->get_block_allocator(); + + const auto &headers = resp.fs.headers(); + auto nva = std::vector(); + // 2 for :status and server + nva.reserve(2 + headers.size() + httpconf.add_response_headers.size()); + + auto response_status = http2::stringify_status(balloc, resp.http_status); + + nva.push_back(http3::make_nv_ls_nocopy(":status", response_status)); + + for (auto &kv : headers) { + if (kv.name.empty() || kv.name[0] == ':') { + continue; + } + switch (kv.token) { + case http2::HD_CONNECTION: + case http2::HD_KEEP_ALIVE: + case http2::HD_PROXY_CONNECTION: + case http2::HD_TE: + case http2::HD_TRANSFER_ENCODING: + case http2::HD_UPGRADE: + continue; + } + nva.push_back(http3::make_nv_nocopy(kv.name, kv.value, kv.no_index)); + } + + if (!resp.fs.header(http2::HD_SERVER)) { + nva.push_back(http3::make_nv_ls_nocopy("server", config->http.server_name)); + } + + for (auto &p : httpconf.add_response_headers) { + nva.push_back(http3::make_nv_nocopy(p.name, p.value)); + } + + rv = nghttp3_conn_submit_response(httpconn_, downstream->get_stream_id(), + nva.data(), nva.size(), data_read_ptr); + if (nghttp3_err_is_fatal(rv)) { + ULOG(FATAL, this) << "nghttp3_conn_submit_response() failed: " + << nghttp3_strerror(rv); + return -1; + } + + auto buf = downstream->get_response_buf(); + + buf->append(body, bodylen); + + downstream->set_response_state(DownstreamState::MSG_COMPLETE); + + if (data_read_ptr) { + downstream->reset_upstream_wtimer(); + } + return 0; } @@ -656,8 +1436,12 @@ void Http3Upstream::reset_timer() { namespace { int http_deferred_consume(nghttp3_conn *conn, int64_t stream_id, - size_t nsoncumed, void *user_data, + size_t nconsumed, void *user_data, void *stream_user_data) { + auto upstream = static_cast(user_data); + + upstream->consume(stream_id, nconsumed); + return 0; } } // namespace @@ -666,13 +1450,48 @@ namespace { int http_acked_stream_data(nghttp3_conn *conn, int64_t stream_id, size_t datalen, void *user_data, void *stream_user_data) { + auto upstream = static_cast(user_data); + auto downstream = static_cast(stream_user_data); + + assert(downstream); + + if (upstream->http_acked_stream_data(downstream, datalen) != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + return 0; } } // namespace +int Http3Upstream::http_acked_stream_data(Downstream *downstream, + size_t datalen) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Stream " << downstream->get_stream_id() << " " << datalen + << " bytes acknowledged"; + } + + auto body = downstream->get_response_buf(); + auto drained = body->drain_mark(datalen); + + assert(datalen == drained); + + if (downstream->resume_read(SHRPX_NO_BUFFER, datalen) != 0) { + return -1; + } + + return 0; +} + namespace { int http_begin_request_headers(nghttp3_conn *conn, int64_t stream_id, void *user_data, void *stream_user_data) { + if (!ngtcp2_is_bidi_stream(stream_id)) { + return 0; + } + + auto upstream = static_cast(user_data); + upstream->http_begin_request_headers(stream_id); + return 0; } } // namespace @@ -682,17 +1501,300 @@ int http_recv_request_header(nghttp3_conn *conn, int64_t stream_id, int32_t token, nghttp3_rcbuf *name, nghttp3_rcbuf *value, uint8_t flags, void *user_data, void *stream_user_data) { + auto upstream = static_cast(user_data); + auto downstream = static_cast(stream_user_data); + + if (!downstream || downstream->get_stop_reading()) { + return 0; + } + + if (upstream->http_recv_request_header(downstream, token, name, value, + flags) != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + return 0; } } // namespace +int Http3Upstream::http_recv_request_header(Downstream *downstream, + int32_t h3token, + nghttp3_rcbuf *name, + nghttp3_rcbuf *value, + uint8_t flags) { + auto namebuf = nghttp3_rcbuf_get_buf(name); + auto valuebuf = nghttp3_rcbuf_get_buf(value); + auto &req = downstream->request(); + auto config = get_config(); + auto &httpconf = config->http; + + if (req.fs.buffer_size() + namebuf.len + valuebuf.len > + httpconf.request_header_field_buffer || + req.fs.num_fields() >= httpconf.max_request_header_fields) { + downstream->set_stop_reading(true); + + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + return 0; + } + + if (LOG_ENABLED(INFO)) { + ULOG(INFO, this) << "Too large or many header field size=" + << req.fs.buffer_size() + namebuf.len + valuebuf.len + << ", num=" << req.fs.num_fields() + 1; + } + + if (error_reply(downstream, 431) != 0) { + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + + return 0; + } + + auto token = http2::lookup_token(namebuf.base, namebuf.len); + auto no_index = flags & NGHTTP3_NV_FLAG_NEVER_INDEX; + + downstream->add_rcbuf(name); + downstream->add_rcbuf(value); + + req.fs.add_header_token(StringRef{namebuf.base, namebuf.len}, + StringRef{valuebuf.base, valuebuf.len}, no_index, + token); + return 0; +} + namespace { int http_end_request_headers(nghttp3_conn *conn, int64_t stream_id, void *user_data, void *stream_user_data) { + auto upstream = static_cast(user_data); + auto downstream = static_cast(stream_user_data); + + if (!downstream || downstream->get_stop_reading()) { + return 0; + } + + if (upstream->http_end_request_headers(downstream) != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + return 0; } } // namespace +int Http3Upstream::http_end_request_headers(Downstream *downstream) { + auto lgconf = log_config(); + lgconf->update_tstamp(std::chrono::system_clock::now()); + auto &req = downstream->request(); + req.tstamp = lgconf->tstamp; + + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + return 0; + } + + auto &nva = req.fs.headers(); + + if (LOG_ENABLED(INFO)) { + std::stringstream ss; + for (auto &nv : nva) { + if (nv.name == "authorization") { + ss << TTY_HTTP_HD << nv.name << TTY_RST << ": \n"; + continue; + } + ss << TTY_HTTP_HD << nv.name << TTY_RST << ": " << nv.value << "\n"; + } + ULOG(INFO, this) << "HTTP request headers. stream_id=" + << downstream->get_stream_id() << "\n" + << ss.str(); + } + + auto config = get_config(); + auto &dump = config->http2.upstream.debug.dump; + + if (dump.request_header) { + http2::dump_nv(dump.request_header, nva); + } + + auto content_length = req.fs.header(http2::HD_CONTENT_LENGTH); + if (content_length) { + // libnghttp2 guarantees this can be parsed + req.fs.content_length = util::parse_uint(content_length->value); + } + + // presence of mandatory header fields are guaranteed by libnghttp2. + auto authority = req.fs.header(http2::HD__AUTHORITY); + auto path = req.fs.header(http2::HD__PATH); + auto method = req.fs.header(http2::HD__METHOD); + auto scheme = req.fs.header(http2::HD__SCHEME); + + auto method_token = http2::lookup_method_token(method->value); + if (method_token == -1) { + if (error_reply(downstream, 501) != 0) { + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + return 0; + } + + auto faddr = handler_->get_upstream_addr(); + + // For HTTP/2 proxy, we require :authority. + if (method_token != HTTP_CONNECT && config->http2_proxy && + faddr->alt_mode == UpstreamAltMode::NONE && !authority) { + shutdown_stream(downstream, NGHTTP2_PROTOCOL_ERROR); + return 0; + } + + req.method = method_token; + if (scheme) { + req.scheme = scheme->value; + } + + // nghttp2 library guarantees either :authority or host exist + if (!authority) { + req.no_authority = true; + authority = req.fs.header(http2::HD_HOST); + } + + if (authority) { + req.authority = authority->value; + } + + if (path) { + if (method_token == HTTP_OPTIONS && + path->value == StringRef::from_lit("*")) { + // Server-wide OPTIONS request. Path is empty. + } else if (config->http2_proxy && + faddr->alt_mode == UpstreamAltMode::NONE) { + req.path = path->value; + } else { + req.path = http2::rewrite_clean_path(downstream->get_block_allocator(), + path->value); + } + } + + auto connect_proto = req.fs.header(http2::HD__PROTOCOL); + if (connect_proto) { + if (connect_proto->value != "websocket") { + if (error_reply(downstream, 400) != 0) { + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + return 0; + } + req.connect_proto = ConnectProto::WEBSOCKET; + } + + // We are not sure that request has body or not at the moment. + req.http2_expect_body = true; + + downstream->inspect_http2_request(); + + downstream->set_request_state(DownstreamState::HEADER_COMPLETE); + +#ifdef HAVE_MRUBY + auto upstream = downstream->get_upstream(); + auto handler = upstream->get_client_handler(); + auto worker = handler->get_worker(); + auto mruby_ctx = worker->get_mruby_context(); + + if (mruby_ctx->run_on_request_proc(downstream) != 0) { + if (error_reply(downstream, 500) != 0) { + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + return 0; + } +#endif // HAVE_MRUBY + + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + return 0; + } + + start_downstream(downstream); + + return 0; +} + +void Http3Upstream::start_downstream(Downstream *downstream) { + if (downstream_queue_.can_activate(downstream->request().authority)) { + initiate_downstream(downstream); + return; + } + + downstream_queue_.mark_blocked(downstream); +} + +void Http3Upstream::initiate_downstream(Downstream *downstream) { + int rv; + + DownstreamConnection *dconn_ptr; + + for (;;) { + auto dconn = handler_->get_downstream_connection(rv, downstream); + if (!dconn) { + if (rv == SHRPX_ERR_TLS_REQUIRED) { + rv = redirect_to_https(downstream); + } else { + rv = error_reply(downstream, 502); + } + if (rv != 0) { + shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR); + } + + downstream->set_request_state(DownstreamState::CONNECT_FAIL); + downstream_queue_.mark_failure(downstream); + + return; + } + +#ifdef HAVE_MRUBY + dconn_ptr = dconn.get(); +#endif // HAVE_MRUBY + rv = downstream->attach_downstream_connection(std::move(dconn)); + if (rv == 0) { + break; + } + } + +#ifdef HAVE_MRUBY + const auto &group = dconn_ptr->get_downstream_addr_group(); + if (group) { + const auto &mruby_ctx = group->shared_addr->mruby_ctx; + if (mruby_ctx->run_on_request_proc(downstream) != 0) { + if (error_reply(downstream, 500) != 0) { + shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR); + } + + downstream_queue_.mark_failure(downstream); + + return; + } + + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + return; + } + } +#endif // HAVE_MRUBY + + rv = downstream->push_request_headers(); + if (rv != 0) { + + if (error_reply(downstream, 502) != 0) { + shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR); + } + + downstream_queue_.mark_failure(downstream); + + return; + } + + downstream_queue_.mark_active(downstream); + + auto &req = downstream->request(); + if (!req.http2_expect_body) { + rv = downstream->end_upload_data(); + if (rv != 0) { + shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR); + } + } +} + namespace { int http_recv_data(nghttp3_conn *conn, int64_t stream_id, const uint8_t *data, size_t datalen, void *user_data, void *stream_user_data) { @@ -703,18 +1805,101 @@ int http_recv_data(nghttp3_conn *conn, int64_t stream_id, const uint8_t *data, namespace { int http_end_stream(nghttp3_conn *conn, int64_t stream_id, void *user_data, void *stream_user_data) { + auto upstream = static_cast(user_data); + auto downstream = static_cast(stream_user_data); + + if (!downstream || downstream->get_stop_reading()) { + return 0; + } + + if (upstream->http_end_stream(downstream) != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + return 0; } } // namespace +int Http3Upstream::http_end_stream(Downstream *downstream) { + downstream->disable_upstream_rtimer(); + + if (downstream->end_upload_data() != 0) { + if (downstream->get_response_state() != DownstreamState::MSG_COMPLETE) { + shutdown_stream(downstream, NGHTTP2_INTERNAL_ERROR); + } + } + + downstream->set_request_state(DownstreamState::MSG_COMPLETE); + + return 0; +} + namespace { int http_stream_close(nghttp3_conn *conn, int64_t stream_id, uint64_t app_error_code, void *conn_user_data, void *stream_user_data) { + auto upstream = static_cast(conn_user_data); + auto downstream = static_cast(stream_user_data); + + if (!downstream) { + return 0; + } + + if (upstream->http_stream_close(downstream, app_error_code) != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + return 0; } } // namespace +int Http3Upstream::http_stream_close(Downstream *downstream, + uint64_t app_error_code) { + auto stream_id = downstream->get_stream_id(); + + if (LOG_ENABLED(INFO)) { + ULOG(INFO, this) << "Stream stream_id=" << stream_id + << " is being closed with app_error_code=" + << app_error_code; + + auto body = downstream->get_response_buf(); + + LOG(INFO) << "response unacked_left=" << body->rleft() + << " not_sent=" << body->rleft_mark(); + } + + auto &req = downstream->request(); + + consume(stream_id, req.unconsumed_body_length); + + req.unconsumed_body_length = 0; + + ngtcp2_conn_extend_max_streams_bidi(conn_, 1); + + if (downstream->get_request_state() == DownstreamState::CONNECT_FAIL) { + remove_downstream(downstream); + // downstream was deleted + + return 0; + } + + if (downstream->can_detach_downstream_connection()) { + // Keep-alive + downstream->detach_downstream_connection(); + } + + downstream->set_request_state(DownstreamState::STREAM_CLOSED); + + // At this point, downstream read may be paused. + + // If shrpx_downstream::push_request_headers() failed, the + // error is handled here. + remove_downstream(downstream); + // downstream was deleted + + return 0; +} + namespace { int http_send_stop_sending(nghttp3_conn *conn, int64_t stream_id, uint64_t app_error_code, void *user_data, @@ -739,18 +1924,18 @@ int Http3Upstream::setup_httpconn() { } nghttp3_callbacks callbacks{ - http_acked_stream_data, - http_stream_close, + shrpx::http_acked_stream_data, + shrpx::http_stream_close, http_recv_data, http_deferred_consume, - http_begin_request_headers, - http_recv_request_header, - http_end_request_headers, + shrpx::http_begin_request_headers, + shrpx::http_recv_request_header, + shrpx::http_end_request_headers, nullptr, // begin_trailers nullptr, // recv_trailer nullptr, // end_trailers http_send_stop_sending, - http_end_stream, + shrpx::http_end_stream, http_reset_stream, }; @@ -810,4 +1995,138 @@ int Http3Upstream::setup_httpconn() { return 0; } +int Http3Upstream::error_reply(Downstream *downstream, + unsigned int status_code) { + int rv; + auto &resp = downstream->response(); + + auto &balloc = downstream->get_block_allocator(); + + auto html = http::create_error_html(balloc, status_code); + resp.http_status = status_code; + auto body = downstream->get_response_buf(); + body->append(html); + downstream->set_response_state(DownstreamState::MSG_COMPLETE); + + nghttp3_data_reader data_read; + data_read.read_data = downstream_read_data_callback; + + auto lgconf = log_config(); + lgconf->update_tstamp(std::chrono::system_clock::now()); + + auto response_status = http2::stringify_status(balloc, status_code); + auto content_length = util::make_string_ref_uint(balloc, html.size()); + auto date = make_string_ref(balloc, lgconf->tstamp->time_http); + + auto nva = std::array{ + {http3::make_nv_ls_nocopy(":status", response_status), + http3::make_nv_ll("content-type", "text/html; charset=UTF-8"), + http3::make_nv_ls_nocopy("server", get_config()->http.server_name), + http3::make_nv_ls_nocopy("content-length", content_length), + http3::make_nv_ls_nocopy("date", date)}}; + + rv = nghttp3_conn_submit_response(httpconn_, downstream->get_stream_id(), + nva.data(), nva.size(), &data_read); + if (nghttp3_err_is_fatal(rv)) { + ULOG(FATAL, this) << "nghttp3_submit_response() failed: " + << nghttp3_strerror(rv); + return -1; + } + + downstream->reset_upstream_wtimer(); + + // TODO Should we shutdown read here? + + return 0; +} + +int Http3Upstream::shutdown_stream(Downstream *downstream, + uint64_t app_error_code) { + auto stream_id = downstream->get_stream_id(); + + if (LOG_ENABLED(INFO)) { + ULOG(INFO, this) << "Shutdown stream_id=" << stream_id + << " with app_error_code=" << app_error_code; + } + + auto rv = ngtcp2_conn_shutdown_stream(conn_, stream_id, app_error_code); + if (rv != 0) { + ULOG(FATAL, this) << "ngtcp2_conn_shutdown_stream() failed: " + << ngtcp2_strerror(rv); + return -1; + } + + return 0; +} + +int Http3Upstream::redirect_to_https(Downstream *downstream) { + auto &req = downstream->request(); + if (req.regular_connect_method() || req.scheme != "http") { + return error_reply(downstream, 400); + } + + auto authority = util::extract_host(req.authority); + if (authority.empty()) { + return error_reply(downstream, 400); + } + + auto &balloc = downstream->get_block_allocator(); + auto config = get_config(); + auto &httpconf = config->http; + + StringRef loc; + if (httpconf.redirect_https_port == StringRef::from_lit("443")) { + loc = concat_string_ref(balloc, StringRef::from_lit("https://"), authority, + req.path); + } else { + loc = concat_string_ref(balloc, StringRef::from_lit("https://"), authority, + StringRef::from_lit(":"), + httpconf.redirect_https_port, req.path); + } + + auto &resp = downstream->response(); + resp.http_status = 308; + resp.fs.add_header_token(StringRef::from_lit("location"), loc, false, + http2::HD_LOCATION); + + return send_reply(downstream, nullptr, 0); +} + +void Http3Upstream::consume(int64_t stream_id, size_t nconsumed) { + ngtcp2_conn_extend_max_stream_offset(conn_, stream_id, nconsumed); + ngtcp2_conn_extend_max_offset(conn_, nconsumed); +} + +void Http3Upstream::remove_downstream(Downstream *downstream) { + if (downstream->accesslog_ready()) { + handler_->write_accesslog(downstream); + } + + nghttp3_conn_set_stream_user_data(httpconn_, downstream->get_stream_id(), + nullptr); + + auto next_downstream = downstream_queue_.remove_and_get_blocked(downstream); + + if (next_downstream) { + initiate_downstream(next_downstream); + } + + if (downstream_queue_.get_downstreams() == nullptr) { + // There is no downstream at the moment. Start idle timer now. + handler_->repeat_read_timer(); + } +} + +void Http3Upstream::log_response_headers( + Downstream *downstream, const std::vector &nva) const { + std::stringstream ss; + for (auto &nv : nva) { + ss << TTY_HTTP_HD << StringRef{nv.name, nv.namelen} << TTY_RST << ": " + << StringRef{nv.value, nv.valuelen} << "\n"; + } + ULOG(INFO, this) << "HTTP response headers. stream_id=" + << downstream->get_stream_id() << "\n" + << ss.str(); +} + } // namespace shrpx diff --git a/src/shrpx_http3_upstream.h b/src/shrpx_http3_upstream.h index cf8fa9b4..fd052ec6 100644 --- a/src/shrpx_http3_upstream.h +++ b/src/shrpx_http3_upstream.h @@ -31,6 +31,8 @@ #include #include "shrpx_upstream.h" +#include "shrpx_downstream_queue.h" +#include "shrpx_mruby.h" #include "quic.h" #include "network.h" @@ -111,6 +113,31 @@ public: void reset_timer(); int setup_httpconn(); + void add_pending_downstream(std::unique_ptr downstream); + int recv_stream_data(uint32_t flags, int64_t stream_id, const uint8_t *data, + size_t datalen); + int acked_stream_data_offset(int64_t stream_id, uint64_t datalen); + int extend_max_stream_data(int64_t stream_id); + void extend_max_remote_streams_bidi(uint64_t max_streams); + int error_reply(Downstream *downstream, unsigned int status_code); + void http_begin_request_headers(int64_t stream_id); + int http_recv_request_header(Downstream *downstream, int32_t token, + nghttp3_rcbuf *name, nghttp3_rcbuf *value, + uint8_t flags); + int http_end_request_headers(Downstream *downstream); + int http_end_stream(Downstream *downstream); + void start_downstream(Downstream *downstream); + void initiate_downstream(Downstream *downstream); + int shutdown_stream(Downstream *downstream, uint64_t app_error_code); + int redirect_to_https(Downstream *downstream); + int http_stream_close(Downstream *downstream, uint64_t app_error_code); + void consume(int64_t stream_id, size_t nconsumed); + void remove_downstream(Downstream *downstream); + int stream_close(int64_t stream_id, uint64_t app_error_code); + void log_response_headers(Downstream *downstream, + const std::vector &nva) const; + int http_acked_stream_data(Downstream *downstream, size_t datalen); + int http_shutdown_stream_read(int64_t stream_id); private: ClientHandler *handler_; @@ -121,6 +148,7 @@ private: quic::Error last_error_; uint8_t tls_alert_; nghttp3_conn *httpconn_; + DownstreamQueue downstream_queue_; }; } // namespace shrpx From 37bd9ffc48bc23a358b410ead127a2ce55b3ea4f Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Tue, 17 Aug 2021 19:05:53 +0900 Subject: [PATCH 102/124] nghttpx: Implement http_reset_stream and http_send_stop_sending --- src/shrpx_http3_upstream.cc | 38 +++++++++++++++++++++++++++++++++++-- src/shrpx_http3_upstream.h | 2 ++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/shrpx_http3_upstream.cc b/src/shrpx_http3_upstream.cc index 7f43ff95..5c05fe0d 100644 --- a/src/shrpx_http3_upstream.cc +++ b/src/shrpx_http3_upstream.cc @@ -1904,18 +1904,52 @@ namespace { int http_send_stop_sending(nghttp3_conn *conn, int64_t stream_id, uint64_t app_error_code, void *user_data, void *stream_user_data) { + auto upstream = static_cast(user_data); + + if (upstream->http_send_stop_sending(stream_id, app_error_code) != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + return 0; } } // namespace +int Http3Upstream::http_send_stop_sending(int64_t stream_id, + uint64_t app_error_code) { + auto rv = ngtcp2_conn_shutdown_stream_read(conn_, stream_id, app_error_code); + if (ngtcp2_err_is_fatal(rv)) { + LOG(ERROR) << "ngtcp2_conn_shutdown_stream_read: " << ngtcp2_strerror(rv); + return -1; + } + + return 0; +} + namespace { int http_reset_stream(nghttp3_conn *conn, int64_t stream_id, uint64_t app_error_code, void *user_data, void *stream_user_data) { + auto upstream = static_cast(user_data); + + if (upstream->http_reset_stream(stream_id, app_error_code) != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + return 0; } } // namespace +int Http3Upstream::http_reset_stream(int64_t stream_id, + uint64_t app_error_code) { + auto rv = ngtcp2_conn_shutdown_stream_write(conn_, stream_id, app_error_code); + if (ngtcp2_err_is_fatal(rv)) { + LOG(ERROR) << "ngtcp2_conn_shutdown_stream_write: " << ngtcp2_strerror(rv); + return -1; + } + + return 0; +} + int Http3Upstream::setup_httpconn() { int rv; @@ -1934,9 +1968,9 @@ int Http3Upstream::setup_httpconn() { nullptr, // begin_trailers nullptr, // recv_trailer nullptr, // end_trailers - http_send_stop_sending, + shrpx::http_send_stop_sending, shrpx::http_end_stream, - http_reset_stream, + shrpx::http_reset_stream, }; nghttp3_settings settings; diff --git a/src/shrpx_http3_upstream.h b/src/shrpx_http3_upstream.h index fd052ec6..24e1064d 100644 --- a/src/shrpx_http3_upstream.h +++ b/src/shrpx_http3_upstream.h @@ -138,6 +138,8 @@ public: const std::vector &nva) const; int http_acked_stream_data(Downstream *downstream, size_t datalen); int http_shutdown_stream_read(int64_t stream_id); + int http_reset_stream(int64_t stream_id, uint64_t app_error_code); + int http_send_stop_sending(int64_t stream_id, uint64_t app_error_code); private: ClientHandler *handler_; From fe4c6e4c562e0f7a2664361152ac9fda666261ab Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Tue, 17 Aug 2021 19:15:55 +0900 Subject: [PATCH 103/124] nghttpx: Generate stateless reset secret --- src/shrpx.cc | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/shrpx.cc b/src/shrpx.cc index 184125f0..62c0abc0 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -1549,7 +1549,17 @@ void fill_default_config(Config *config) { } auto &quicconf = config->quic; - { quicconf.timeout.idle = 30_s; } + { + quicconf.timeout.idle = 30_s; + + auto &stateless_resetconf = quicconf.stateless_reset; + // TODO Find better place to do this and error handling. + if (RAND_bytes(stateless_resetconf.secret.data(), + stateless_resetconf.secret.size()) != 1) { + LOG(FATAL) << "Unable to generate stateless reset secret"; + exit(EXIT_FAILURE); + } + } auto &loggingconf = config->logging; { From 48bb1ebe018002ee8b7f846bae5eefbb7303fafd Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Tue, 17 Aug 2021 19:16:11 +0900 Subject: [PATCH 104/124] nghttpx: Add configuration to enable ngtcp2 logging (no cmd-line opt yet) --- src/shrpx_config.h | 3 +++ src/shrpx_http3_upstream.cc | 10 ++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/shrpx_config.h b/src/shrpx_config.h index 74fd42fb..291e25e6 100644 --- a/src/shrpx_config.h +++ b/src/shrpx_config.h @@ -711,6 +711,9 @@ struct QUICConfig { struct { ev_tstamp idle; } timeout; + struct { + bool log; + } debug; }; // custom error page diff --git a/src/shrpx_http3_upstream.cc b/src/shrpx_http3_upstream.cc index 5c05fe0d..e4b945ae 100644 --- a/src/shrpx_http3_upstream.cc +++ b/src/shrpx_http3_upstream.cc @@ -451,9 +451,14 @@ int Http3Upstream::init(const UpstreamAddr *faddr, const Address &remote_addr, return -1; } + auto config = get_config(); + auto &quicconf = config->quic; + ngtcp2_settings settings; ngtcp2_settings_default(&settings); - settings.log_printf = log_printf; + if (quicconf.debug.log) { + settings.log_printf = log_printf; + } settings.initial_ts = quic_timestamp(); settings.cc_algo = NGTCP2_CC_ALGO_BBR; settings.max_window = 6_m; @@ -461,9 +466,6 @@ int Http3Upstream::init(const UpstreamAddr *faddr, const Address &remote_addr, settings.max_udp_payload_size = SHRPX_MAX_UDP_PAYLOAD_SIZE; settings.rand_ctx.native_handle = &worker->get_randgen(); - auto config = get_config(); - auto &quicconf = config->quic; - ngtcp2_transport_params params; ngtcp2_transport_params_default(¶ms); params.initial_max_streams_bidi = 100; From 9b2982510e5da8af2e44dc5ee26df14392b79c44 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Tue, 17 Aug 2021 21:48:11 +0900 Subject: [PATCH 105/124] nghttpx: Send stateless reset --- src/shrpx_quic_connection_handler.cc | 52 ++++++++++++++++++++++++++++ src/shrpx_quic_connection_handler.h | 3 ++ 2 files changed, 55 insertions(+) diff --git a/src/shrpx_quic_connection_handler.cc b/src/shrpx_quic_connection_handler.cc index 3d6d97ed..7fc1965a 100644 --- a/src/shrpx_quic_connection_handler.cc +++ b/src/shrpx_quic_connection_handler.cc @@ -92,6 +92,8 @@ int QUICConnectionHandler::handle_packet(const UpstreamAddr *faddr, remote_addr, local_addr); return 0; default: + // TODO Must be rate limited + send_stateless_reset(faddr, dcid, dcidlen, remote_addr, local_addr); return 0; } @@ -217,6 +219,56 @@ int QUICConnectionHandler::send_version_negotiation( 0); } +int QUICConnectionHandler::send_stateless_reset(const UpstreamAddr *faddr, + const uint8_t *dcid, + size_t dcidlen, + const Address &remote_addr, + const Address &local_addr) { + int rv; + std::array token; + ngtcp2_cid cid; + + ngtcp2_cid_init(&cid, dcid, dcidlen); + + auto config = get_config(); + auto &quicconf = config->quic; + auto &stateless_resetconf = quicconf.stateless_reset; + + rv = generate_quic_stateless_reset_token(token.data(), &cid, + stateless_resetconf.secret.data(), + stateless_resetconf.secret.size()); + if (rv != 0) { + return -1; + } + + std::array rand_bytes; + + if (RAND_bytes(rand_bytes.data(), rand_bytes.size()) != 1) { + return -1; + } + + std::array buf; + + auto nwrite = + ngtcp2_pkt_write_stateless_reset(buf.data(), buf.size(), token.data(), + rand_bytes.data(), rand_bytes.size()); + if (nwrite < 0) { + LOG(ERROR) << "ngtcp2_pkt_write_stateless_reset: " + << ngtcp2_strerror(nwrite); + return -1; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Send stateless_reset to remote=" + << util::to_numeric_addr(&remote_addr) + << " dcid=" << util::format_hex(dcid, dcidlen); + } + + return quic_send_packet(faddr, &remote_addr.su.sa, remote_addr.len, + &local_addr.su.sa, local_addr.len, buf.data(), nwrite, + 0); +} + void QUICConnectionHandler::add_connection_id(const ngtcp2_cid *cid, ClientHandler *handler) { auto key = make_cid_key(cid); diff --git a/src/shrpx_quic_connection_handler.h b/src/shrpx_quic_connection_handler.h index 38b2f217..d4941ffe 100644 --- a/src/shrpx_quic_connection_handler.h +++ b/src/shrpx_quic_connection_handler.h @@ -55,6 +55,9 @@ public: const uint8_t *scid, size_t scidlen, const Address &remote_addr, const Address &local_addr); + int send_stateless_reset(const UpstreamAddr *faddr, const uint8_t *dcid, + size_t dcidlen, const Address &remote_addr, + const Address &local_addr); ClientHandler *handle_new_connection(const UpstreamAddr *faddr, const Address &remote_addr, const Address &local_addr, From 3abf62b41a56b0e30f80e3fa1560397fd2cbe103 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Tue, 17 Aug 2021 22:18:18 +0900 Subject: [PATCH 106/124] nghttpx: Send stateless reset token in TP --- src/shrpx_http3_upstream.cc | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/shrpx_http3_upstream.cc b/src/shrpx_http3_upstream.cc index e4b945ae..f312e231 100644 --- a/src/shrpx_http3_upstream.cc +++ b/src/shrpx_http3_upstream.cc @@ -477,6 +477,17 @@ int Http3Upstream::init(const UpstreamAddr *faddr, const Address &remote_addr, static_cast(quicconf.timeout.idle * NGTCP2_SECONDS); params.original_dcid = initial_hd.dcid; + auto &stateless_resetconf = quicconf.stateless_reset; + + rv = generate_quic_stateless_reset_token(params.stateless_reset_token, &scid, + stateless_resetconf.secret.data(), + stateless_resetconf.secret.size()); + if (rv != 0) { + LOG(ERROR) << "generate_quic_stateless_reset_token failed"; + return -1; + } + params.stateless_reset_token_present = 1; + auto path = ngtcp2_path{ {local_addr.len, const_cast(&local_addr.su.sa)}, {remote_addr.len, const_cast(&remote_addr.su.sa)}, From c45f2085d5b4a8ae9cb32dabf3ea49f515773524 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Tue, 17 Aug 2021 22:40:41 +0900 Subject: [PATCH 107/124] nghttpx: Fix veccnt assertion --- src/shrpx_http3_upstream.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shrpx_http3_upstream.cc b/src/shrpx_http3_upstream.cc index f312e231..fa78d180 100644 --- a/src/shrpx_http3_upstream.cc +++ b/src/shrpx_http3_upstream.cc @@ -907,7 +907,7 @@ nghttp3_ssize downstream_read_data_callback(nghttp3_conn *conn, veccnt = body->riovec_mark(reinterpret_cast(vec), veccnt); - assert(veccnt); + assert((*pflags & NGHTTP3_DATA_FLAG_EOF) || veccnt); downstream->response_sent_body_length += nghttp3_vec_len(vec, veccnt); From 446124f378e229e62d4b397ea0f222d172b91c25 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 18 Aug 2021 09:24:59 +0900 Subject: [PATCH 108/124] nghttpx: Process request body --- src/shrpx_http3_upstream.cc | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/shrpx_http3_upstream.cc b/src/shrpx_http3_upstream.cc index fa78d180..7ea0b91b 100644 --- a/src/shrpx_http3_upstream.cc +++ b/src/shrpx_http3_upstream.cc @@ -1811,10 +1811,34 @@ void Http3Upstream::initiate_downstream(Downstream *downstream) { namespace { int http_recv_data(nghttp3_conn *conn, int64_t stream_id, const uint8_t *data, size_t datalen, void *user_data, void *stream_user_data) { + auto upstream = static_cast(user_data); + auto downstream = static_cast(stream_user_data); + + if (upstream->http_recv_data(downstream, data, datalen) != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + return 0; } } // namespace +int Http3Upstream::http_recv_data(Downstream *downstream, const uint8_t *data, + size_t datalen) { + downstream->reset_upstream_rtimer(); + + if (downstream->push_upload_data_chunk(data, datalen) != 0) { + if (downstream->get_response_state() != DownstreamState::MSG_COMPLETE) { + shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR); + } + + consume(downstream->get_stream_id(), datalen); + + return 0; + } + + return 0; +} + namespace { int http_end_stream(nghttp3_conn *conn, int64_t stream_id, void *user_data, void *stream_user_data) { @@ -1973,7 +1997,7 @@ int Http3Upstream::setup_httpconn() { nghttp3_callbacks callbacks{ shrpx::http_acked_stream_data, shrpx::http_stream_close, - http_recv_data, + shrpx::http_recv_data, http_deferred_consume, shrpx::http_begin_request_headers, shrpx::http_recv_request_header, From 44663a7e6e3c1a2e58540e2d24c009843a0dc0c0 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 18 Aug 2021 09:55:14 +0900 Subject: [PATCH 109/124] nghttpx: Handle backend reset and early response --- src/shrpx_http3_upstream.cc | 117 +++++++++++++++++++++++++++++++++++- src/shrpx_http3_upstream.h | 3 + 2 files changed, 118 insertions(+), 2 deletions(-) diff --git a/src/shrpx_http3_upstream.cc b/src/shrpx_http3_upstream.cc index 7ea0b91b..da080c0a 100644 --- a/src/shrpx_http3_upstream.cc +++ b/src/shrpx_http3_upstream.cc @@ -698,6 +698,14 @@ int Http3Upstream::on_downstream_abort_request(Downstream *downstream, int Http3Upstream::on_downstream_abort_request_with_https_redirect( Downstream *downstream) { + int rv; + + rv = redirect_to_https(downstream); + if (rv != 0) { + return -1; + } + + handler_->signal_write(); return 0; } @@ -888,6 +896,7 @@ nghttp3_ssize downstream_read_data_callback(nghttp3_conn *conn, size_t veccnt, uint32_t *pflags, void *conn_user_data, void *stream_user_data) { + auto upstream = static_cast(conn_user_data); auto downstream = static_cast(stream_user_data); assert(downstream); @@ -911,6 +920,11 @@ nghttp3_ssize downstream_read_data_callback(nghttp3_conn *conn, downstream->response_sent_body_length += nghttp3_vec_len(vec, veccnt); + if ((*pflags & NGHTTP3_DATA_FLAG_EOF) && + upstream->shutdown_stream_read(stream_id, NGHTTP3_H3_NO_ERROR) != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + return veccnt; } } // namespace @@ -1106,6 +1120,9 @@ int Http3Upstream::on_downstream_header_complete(Downstream *downstream) { if (data_readptr) { downstream->reset_upstream_wtimer(); + } else if (shutdown_stream_read(downstream->get_stream_id(), + NGHTTP3_H3_NO_ERROR) != 0) { + return -1; } return 0; @@ -1155,6 +1172,81 @@ void Http3Upstream::on_handler_delete() { } int Http3Upstream::on_downstream_reset(Downstream *downstream, bool no_retry) { + int rv; + + if (downstream->get_dispatch_state() != DispatchState::ACTIVE) { + // This is error condition when we failed push_request_headers() + // in initiate_downstream(). Otherwise, we have + // DispatchState::ACTIVE state, or we did not set + // DownstreamConnection. + downstream->pop_downstream_connection(); + handler_->signal_write(); + + return 0; + } + + if (!downstream->request_submission_ready()) { + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + // We have got all response body already. Send it off. + downstream->pop_downstream_connection(); + return 0; + } + // pushed stream is handled here + shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR); + downstream->pop_downstream_connection(); + + handler_->signal_write(); + + return 0; + } + + downstream->pop_downstream_connection(); + + downstream->add_retry(); + + std::unique_ptr dconn; + + rv = 0; + + if (no_retry || downstream->no_more_retry()) { + goto fail; + } + + // downstream connection is clean; we can retry with new + // downstream connection. + + for (;;) { + auto dconn = handler_->get_downstream_connection(rv, downstream); + if (!dconn) { + goto fail; + } + + rv = downstream->attach_downstream_connection(std::move(dconn)); + if (rv == 0) { + break; + } + } + + rv = downstream->push_request_headers(); + if (rv != 0) { + goto fail; + } + + return 0; + +fail: + if (rv == SHRPX_ERR_TLS_REQUIRED) { + rv = on_downstream_abort_request_with_https_redirect(downstream); + } else { + rv = on_downstream_abort_request(downstream, 502); + } + if (rv != 0) { + shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR); + } + downstream->pop_downstream_connection(); + + handler_->signal_write(); + return 0; } @@ -1241,6 +1333,11 @@ int Http3Upstream::send_reply(Downstream *downstream, const uint8_t *body, downstream->reset_upstream_wtimer(); } + if (shutdown_stream_read(downstream->get_stream_id(), NGHTTP3_H3_NO_ERROR) != + 0) { + return -1; + } + return 0; } @@ -2099,14 +2196,17 @@ int Http3Upstream::error_reply(Downstream *downstream, rv = nghttp3_conn_submit_response(httpconn_, downstream->get_stream_id(), nva.data(), nva.size(), &data_read); if (nghttp3_err_is_fatal(rv)) { - ULOG(FATAL, this) << "nghttp3_submit_response() failed: " + ULOG(FATAL, this) << "nghttp3_conn_submit_response() failed: " << nghttp3_strerror(rv); return -1; } downstream->reset_upstream_wtimer(); - // TODO Should we shutdown read here? + if (shutdown_stream_read(downstream->get_stream_id(), NGHTTP3_H3_NO_ERROR) != + 0) { + return -1; + } return 0; } @@ -2130,6 +2230,19 @@ int Http3Upstream::shutdown_stream(Downstream *downstream, return 0; } +int Http3Upstream::shutdown_stream_read(int64_t stream_id, + uint64_t app_error_code) { + auto rv = + ngtcp2_conn_shutdown_stream_read(conn_, stream_id, NGHTTP3_H3_NO_ERROR); + if (ngtcp2_err_is_fatal(rv)) { + ULOG(FATAL, this) << "ngtcp2_conn_shutdown_stream_read: " + << ngtcp2_strerror(rv); + return -1; + } + + return 0; +} + int Http3Upstream::redirect_to_https(Downstream *downstream) { auto &req = downstream->request(); if (req.regular_connect_method() || req.scheme != "http") { diff --git a/src/shrpx_http3_upstream.h b/src/shrpx_http3_upstream.h index 24e1064d..05933a14 100644 --- a/src/shrpx_http3_upstream.h +++ b/src/shrpx_http3_upstream.h @@ -129,6 +129,7 @@ public: void start_downstream(Downstream *downstream); void initiate_downstream(Downstream *downstream); int shutdown_stream(Downstream *downstream, uint64_t app_error_code); + int shutdown_stream_read(int64_t stream_id, uint64_t app_error_code); int redirect_to_https(Downstream *downstream); int http_stream_close(Downstream *downstream, uint64_t app_error_code); void consume(int64_t stream_id, size_t nconsumed); @@ -140,6 +141,8 @@ public: int http_shutdown_stream_read(int64_t stream_id); int http_reset_stream(int64_t stream_id, uint64_t app_error_code); int http_send_stop_sending(int64_t stream_id, uint64_t app_error_code); + int http_recv_data(Downstream *downstream, const uint8_t *data, + size_t datalen); private: ClientHandler *handler_; From f46984d218d7279d50fe26e0432e4de5c0c0d383 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 18 Aug 2021 11:00:34 +0900 Subject: [PATCH 110/124] nghttpx: Enable QUIC 0RTT --- src/shrpx_quic_connection_handler.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/shrpx_quic_connection_handler.cc b/src/shrpx_quic_connection_handler.cc index 7fc1965a..c09a4650 100644 --- a/src/shrpx_quic_connection_handler.cc +++ b/src/shrpx_quic_connection_handler.cc @@ -143,6 +143,7 @@ ClientHandler *QUICConnectionHandler::handle_new_connection( assert(SSL_is_quic(ssl)); SSL_set_accept_state(ssl); + SSL_set_quic_early_data_enabled(ssl, 1); // Disable TLS session ticket if we don't have working ticket // keys. From 4eced8a393d4a648f441633e614c09e812ca6d55 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 21 Aug 2021 19:00:43 +0900 Subject: [PATCH 111/124] Build without HTTP/3 support --- CMakeLists.txt | 4 ++ configure.ac | 92 ++++++++++++++++++++++---- src/Makefile.am | 23 +++++-- src/h2load.cc | 48 ++++++++++++-- src/h2load.h | 14 +++- src/shrpx.cc | 2 + src/shrpx_client_handler.cc | 8 ++- src/shrpx_client_handler.h | 9 ++- src/shrpx_config.cc | 8 +++ src/shrpx_config.h | 11 +++- src/shrpx_connection_handler.cc | 60 +++++++++++++---- src/shrpx_connection_handler.h | 6 ++ src/shrpx_downstream.cc | 6 ++ src/shrpx_downstream.h | 8 ++- src/shrpx_tls.cc | 111 +++++++++++++++++++------------- src/shrpx_tls.h | 12 ++-- src/shrpx_worker.cc | 24 +++++-- src/shrpx_worker.h | 26 ++++++-- 18 files changed, 365 insertions(+), 107 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index eab2a177..579d0365 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -448,6 +448,10 @@ foreach(name configure_file("${name}.in" "${name}" @ONLY) endforeach() +if(APPLE) + add_definitions(-D__APPLE_USE_RFC_3542) +endif() + include_directories( "${CMAKE_CURRENT_BINARY_DIR}" # for config.h ) diff --git a/configure.ac b/configure.ac index def91ae8..37256518 100644 --- a/configure.ac +++ b/configure.ac @@ -107,6 +107,11 @@ AC_ARG_ENABLE([lib-only], [Build libnghttp2 only. This is a short hand for --disable-app --disable-examples --disable-hpack-tools --disable-python-bindings])], [request_lib_only=$enableval], [request_lib_only=no]) +AC_ARG_ENABLE([http3], + [AS_HELP_STRING([--enable-http3], + [(EXPERIMENTAL) Enable HTTP/3. This requires ngtcp2, nghttp3, and a custom OpenSSL.])], + [request_http3=$enableval], [request_http3=no]) + AC_ARG_WITH([libxml2], [AS_HELP_STRING([--with-libxml2], [Use libxml2 [default=check]])], @@ -172,6 +177,16 @@ AC_ARG_WITH([cython], [Use cython in given PATH])], [cython_path=$withval], []) +AC_ARG_WITH([libngtcp2], + [AS_HELP_STRING([--with-libngtcp2], + [Use libngtcp2 [default=check]])], + [request_libngtcp2=$withval], [request_libngtcp2=check]) + +AC_ARG_WITH([libnghttp3], + [AS_HELP_STRING([--with-libnghttp3], + [Use libnghttp3 [default=check]])], + [request_libnghttp3=$withval], [request_libnghttp3=check]) + dnl Define variables AC_ARG_VAR([CYTHON], [the Cython executable]) @@ -334,6 +349,13 @@ case "$host_os" in ;; esac +case "${build}" in + *-apple-darwin*) + EXTRA_DEFS="-D__APPLE_USE_RFC_3542" + AC_SUBST([EXTRA_DEFS]) + ;; +esac + # zlib have_zlib=no if test "x${request_zlib}" != "xno"; then @@ -455,26 +477,50 @@ if test "x${request_libcares}" = "xyes" && fi # ngtcp2 (for src) -PKG_CHECK_MODULES([LIBNGTCP2], [libngtcp2 >= 0.0.0], [have_libngtcp2=yes], - [have_libngtcp2=no]) -if test "x${have_libngtcp2}" = "xno"; then - AC_MSG_NOTICE($LIBNGTCP2_PKG_ERRORS) +have_libngtcp2=no +if test "x${request_libngtcp2}" != "xno"; then + PKG_CHECK_MODULES([LIBNGTCP2], [libngtcp2 >= 0.0.0], [have_libngtcp2=yes], + [have_libngtcp2=no]) + if test "x${have_libngtcp2}" = "xno"; then + AC_MSG_NOTICE($LIBNGTCP2_PKG_ERRORS) + fi +fi + +if test "x${request_libngtcp2}" = "xyes" && + test "x${have_libngtcp2}" != "xyes"; then + AC_MSG_ERROR([libngtcp2 was requested (--with-libngtcp2) but not found]) fi # ngtcp2_crypto_openssl (for src) -PKG_CHECK_MODULES([LIBNGTCP2_CRYPTO_OPENSSL], - [libngtcp2_crypto_openssl >= 0.0.0], - [have_libngtcp2_crypto_openssl=yes], - [have_libngtcp2_crypto_openssl=no]) -if test "x${have_libngtcp2_crypto_openssl}" = "xno"; then - AC_MSG_NOTICE($LIBNGTCP2_CRYPTO_OPENSSL_PKG_ERRORS) +have_libngtcp2_crypto_openssl=no +if test "x${request_libngtcp2}" != "xno"; then + PKG_CHECK_MODULES([LIBNGTCP2_CRYPTO_OPENSSL], + [libngtcp2_crypto_openssl >= 0.0.0], + [have_libngtcp2_crypto_openssl=yes], + [have_libngtcp2_crypto_openssl=no]) + if test "x${have_libngtcp2_crypto_openssl}" = "xno"; then + AC_MSG_NOTICE($LIBNGTCP2_CRYPTO_OPENSSL_PKG_ERRORS) + fi +fi + +if test "x${request_libngtcp2}" = "xyes" && + test "x${have_libngtcp2_crypto_openssl}" != "xyes"; then + AC_MSG_ERROR([libngtcp2_crypto_openssl was requested (--with-libngtcp2) but not found]) fi # nghttp3 (for src) -PKG_CHECK_MODULES([LIBNGHTTP3], [libnghttp3 >= 0.0.0], [have_libnghttp3=yes], - [have_libnghttp3=no]) -if test "x${have_libnghttp3}" = "xno"; then - AC_MSG_NOTICE($LIBNGHTTP3_PKT_ERRORS) +have_libnghttp3=no +if test "x${request_libnghttp3}" != "xno"; then + PKG_CHECK_MODULES([LIBNGHTTP3], [libnghttp3 >= 0.0.0], [have_libnghttp3=yes], + [have_libnghttp3=no]) + if test "x${have_libnghttp3}" = "xno"; then + AC_MSG_NOTICE($LIBNGHTTP3_PKG_ERRORS) + fi +fi + +if test "x${request_libnghttp3}" = "xyes" && + test "x${have_libnghttp3}" != "xyes"; then + AC_MSG_ERROR([libnghttp3 was requested (--with-libnghttp3) but not found]) fi # libevent_openssl (for examples) @@ -621,6 +667,23 @@ fi AM_CONDITIONAL([ENABLE_APP], [ test "x${enable_app}" = "xyes" ]) +# Check HTTP/3 support +enable_http3=no +if test "x${request_http3}" != "xno" && + test "x${have_libngtcp2}" = "xyes" && + test "x${have_libngtcp2_crypto_openssl}" = "xyes" && + test "x${have_libnghttp3}" = "xyes"; then + enable_http3=yes + AC_DEFINE([ENABLE_HTTP3], [1], [Define to 1 if HTTP/3 is enabled.]) +fi + +if test "x${request_http3}" = "xyes" && + test "x${enable_http3}" != "xyes"; then + AC_MSG_ERROR([HTTP/3 was requested (--enable-http3) but dependencies are not met.]) +fi + +AM_CONDITIONAL([ENABLE_HTTP3], [ test "x${enable_http3}" = "xyes" ]) + enable_hpack_tools=no # HPACK tools requires jansson if test "x${request_hpack_tools}" != "xno" && @@ -1058,4 +1121,5 @@ AC_MSG_NOTICE([summary of build options: Examples: ${enable_examples} Python bindings:${enable_python_bindings} Threading: ${enable_threads} + HTTP/3 (EXPERIMENTAL): ${enable_http3} ]) diff --git a/src/Makefile.am b/src/Makefile.am index 9226ed31..eb763fb6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -51,6 +51,7 @@ AM_CPPFLAGS = \ @LIBNGTCP2_CFLAGS@ \ @JANSSON_CFLAGS@ \ @ZLIB_CFLAGS@ \ + @EXTRA_DEFS@ \ @DEFS@ AM_LDFLAGS = @LIBTOOL_LDFLAGS@ @@ -103,10 +104,14 @@ h2load_SOURCES = util.cc util.h \ tls.cc tls.h \ h2load_session.h \ h2load_http2_session.cc h2load_http2_session.h \ - h2load_http1_session.cc h2load_http1_session.h \ + h2load_http1_session.cc h2load_http1_session.h + +if ENABLE_HTTP3 +h2load_SOURCES += \ h2load_http3_session.cc h2load_http3_session.h \ h2load_quic.cc h2load_quic.h \ quic.cc quic.h +endif # ENABLE_HTTP3 NGHTTPX_SRCS = \ util.cc util.h http2.cc http2.h timegm.c timegm.h base64.h \ @@ -153,12 +158,6 @@ NGHTTPX_SRCS = \ shrpx_dns_resolver.cc shrpx_dns_resolver.h \ shrpx_dual_dns_resolver.cc shrpx_dual_dns_resolver.h \ shrpx_dns_tracker.cc shrpx_dns_tracker.h \ - shrpx_quic.cc shrpx_quic.h \ - shrpx_quic_listener.cc shrpx_quic_listener.h \ - shrpx_quic_connection_handler.cc shrpx_quic_connection_handler.h \ - shrpx_http3_upstream.cc shrpx_http3_upstream.h \ - http3.cc http3.h \ - quic.cc quic.h \ buffer.h memchunk.h template.h allocator.h \ xsi_strerror.c xsi_strerror.h @@ -171,6 +170,16 @@ NGHTTPX_SRCS += \ shrpx_mruby_module_response.cc shrpx_mruby_module_response.h endif # HAVE_MRUBY +if ENABLE_HTTP3 +NGHTTPX_SRCS += \ + shrpx_quic.cc shrpx_quic.h \ + shrpx_quic_listener.cc shrpx_quic_listener.h \ + shrpx_quic_connection_handler.cc shrpx_quic_connection_handler.h \ + shrpx_http3_upstream.cc shrpx_http3_upstream.h \ + http3.cc http3.h \ + quic.cc quic.h +endif # ENABLE_HTTP3 + noinst_LIBRARIES = libnghttpx.a libnghttpx_a_SOURCES = ${NGHTTPX_SRCS} libnghttpx_a_CPPFLAGS = ${AM_CPPFLAGS} diff --git a/src/h2load.cc b/src/h2load.cc index d08cff61..2b3f529b 100644 --- a/src/h2load.cc +++ b/src/h2load.cc @@ -50,14 +50,18 @@ #include -#include +#ifdef ENABLE_HTTP3 +# include +#endif // ENABLE_HTTP3 #include "url-parser/url_parser.h" #include "h2load_http1_session.h" #include "h2load_http2_session.h" -#include "h2load_http3_session.h" -#include "h2load_quic.h" +#ifdef ENABLE_HTTP3 +# include "h2load_http3_session.h" +# include "h2load_quic.h" +#endif // ENABLE_HTTP3 #include "tls.h" #include "http2.h" #include "util.h" @@ -141,8 +145,12 @@ bool Config::is_timing_based_mode() const { return (this->duration > 0); } bool Config::has_base_uri() const { return (!this->base_uri.empty()); } bool Config::rps_enabled() const { return this->rps > 0.0; } bool Config::is_quic() const { +#ifdef ENABLE_HTTP3 return !npn_list.empty() && (npn_list[0] == NGHTTP3_ALPN_H3 || npn_list[0] == "\x5h3-29"); +#else // !ENABLE_HTTP3 + return false; +#endif // !ENABLE_HTTP3 } Config config; @@ -435,7 +443,9 @@ Client::Client(uint32_t id, Worker *worker, size_t req_todo) cstat{}, worker(worker), ssl(nullptr), +#ifdef ENABLE_HTTP3 quic{}, +#endif // ENABLE_HTTP3 next_addr(config.addrs), current_addr(nullptr), reqidx(0), @@ -478,16 +488,20 @@ Client::Client(uint32_t id, Worker *worker, size_t req_todo) ev_timer_init(&rps_watcher, rps_cb, 0., 0.); rps_watcher.data = this; +#ifdef ENABLE_HTTP3 ev_timer_init(&quic.pkt_timer, quic_pkt_timeout_cb, 0., 0.); quic.pkt_timer.data = this; +#endif // ENABLE_HTTP3 } Client::~Client() { disconnect(); +#ifdef ENABLE_HTTP3 if (config.is_quic()) { quic_free(); } +#endif // ENABLE_HTTP3 if (ssl) { SSL_free(ssl); @@ -504,6 +518,7 @@ int Client::make_socket(addrinfo *addr) { int rv; if (config.is_quic()) { +#ifdef ENABLE_HTTP3 fd = util::create_nonblock_udp_socket(addr->ai_family); if (fd == -1) { return -1; @@ -528,6 +543,7 @@ int Client::make_socket(addrinfo *addr) { std::cerr << "quic_init failed" << std::endl; return -1; } +#endif // ENABLE_HTTP3 } else { fd = util::create_nonblock_socket(addr->ai_family); if (fd == -1) { @@ -614,10 +630,12 @@ int Client::connect() { ev_io_start(worker->loop, &wev); if (config.is_quic()) { +#ifdef ENABLE_HTTP3 ev_io_start(worker->loop, &rev); readfn = &Client::read_quic; writefn = &Client::write_quic; +#endif // ENABLE_HTTP3 } else { writefn = &Client::connected; } @@ -676,11 +694,15 @@ void Client::fail() { void Client::disconnect() { record_client_end_time(); +#ifdef ENABLE_HTTP3 if (config.is_quic()) { quic_close_connection(); } +#endif // ENABLE_HTTP3 +#ifdef ENABLE_HTTP3 ev_timer_stop(worker->loop, &quic.pkt_timer); +#endif // ENABLE_HTTP3 ev_timer_stop(worker->loop, &conn_inactivity_watcher); ev_timer_stop(worker->loop, &conn_active_watcher); ev_timer_stop(worker->loop, &rps_watcher); @@ -843,9 +865,11 @@ void Client::report_app_info() { } void Client::terminate_session() { +#ifdef ENABLE_HTTP3 if (config.is_quic()) { quic.close_requested = true; } +#endif // ENABLE_HTTP3 if (session) { session->terminate(); } @@ -1047,11 +1071,13 @@ int Client::connection_made() { if (next_proto) { auto proto = StringRef{next_proto, next_proto_len}; if (config.is_quic()) { +#ifdef ENABLE_HTTP3 assert(session); if (!util::streq(StringRef{&NGHTTP3_ALPN_H3[1]}, proto) && !util::streq_l("h3-29", proto)) { return -1; } +#endif // ENABLE_HTTP3 } else if (util::check_h2_is_selected(proto)) { session = std::make_unique(this); } else if (util::streq(NGHTTP2_H1_1, proto)) { @@ -1377,6 +1403,7 @@ int Client::write_tls() { return 0; } +#ifdef ENABLE_HTTP3 int Client::write_udp(const sockaddr *addr, socklen_t addrlen, const uint8_t *data, size_t datalen, size_t gso_size) { iovec msg_iov; @@ -1389,7 +1416,7 @@ int Client::write_udp(const sockaddr *addr, socklen_t addrlen, msg.msg_iov = &msg_iov; msg.msg_iovlen = 1; -#ifdef UDP_SEGMENT +# ifdef UDP_SEGMENT std::array msg_ctrl{}; if (gso_size && datalen > gso_size) { msg.msg_control = msg_ctrl.data(); @@ -1401,7 +1428,7 @@ int Client::write_udp(const sockaddr *addr, socklen_t addrlen, cm->cmsg_len = CMSG_LEN(sizeof(uint16_t)); *(reinterpret_cast(CMSG_DATA(cm))) = gso_size; } -#endif // UDP_SEGMENT +# endif // UDP_SEGMENT auto nwrite = sendmsg(fd, &msg, 0); if (nwrite < 0) { @@ -1414,6 +1441,7 @@ int Client::write_udp(const sockaddr *addr, socklen_t addrlen, return 0; } +#endif // ENABLE_HTTP3 void Client::record_request_time(RequestStat *req_stat) { req_stat->request_time = std::chrono::steady_clock::now(); @@ -2740,7 +2768,9 @@ int main(int argc, char **argv) { << "Warning: --qlog-file-base: only effective in quic, ignoring." << std::endl; } else { +#ifdef ENABLE_HTTP3 config.qlog_file_base = qlog_base; +#endif // ENABLE_HTTP3 } } @@ -2764,8 +2794,10 @@ int main(int argc, char **argv) { SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS); if (config.is_quic()) { +#ifdef ENABLE_HTTP3 SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_3_VERSION); SSL_CTX_set_max_proto_version(ssl_ctx, TLS1_3_VERSION); +#endif // ENABLE_HTTP3 } else if (nghttp2::tls::ssl_ctx_set_proto_versions( ssl_ctx, nghttp2::tls::NGHTTP2_TLS_MIN_VERSION, nghttp2::tls::NGHTTP2_TLS_MAX_VERSION) != 0) { @@ -2780,12 +2812,14 @@ int main(int argc, char **argv) { exit(EXIT_FAILURE); } +#if OPENSSL_1_1_1_API if (SSL_CTX_set_ciphersuites(ssl_ctx, config.tls13_ciphers.c_str()) == 0) { std::cerr << "SSL_CTX_set_ciphersuites with " << config.tls13_ciphers << " failed: " << ERR_error_string(ERR_get_error(), nullptr) << std::endl; exit(EXIT_FAILURE); } +#endif // OPENSSL_1_1_1_API if (SSL_CTX_set1_groups_list(ssl_ctx, config.groups.c_str()) != 1) { std::cerr << "SSL_CTX_set1_groups_list failed" << std::endl; @@ -2806,6 +2840,7 @@ int main(int argc, char **argv) { SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size()); #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L +#if OPENSSL_1_1_1_API auto keylog_filename = getenv("SSLKEYLOGFILE"); if (keylog_filename) { keylog_file.open(keylog_filename, std::ios_base::app); @@ -2813,6 +2848,7 @@ int main(int argc, char **argv) { SSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback); } } +#endif // OPENSSL_1_1_1_API std::string user_agent = "h2load nghttp2/" NGHTTP2_VERSION; Headers shared_nva; @@ -3093,10 +3129,12 @@ traffic: )" << util::utos_funit(stats.bytes_total) << ") headers (space savings " << header_space_savings * 100 << "%), " << util::utos_funit(stats.bytes_body) << "B (" << stats.bytes_body << R"() data)" << std::endl; +#ifdef ENABLE_HTTP3 if (config.is_quic()) { std::cout << "UDP datagram: " << stats.udp_dgram_sent << " sent, " << stats.udp_dgram_recv << " received" << std::endl; } +#endif // ENABLE_HTTP3 std::cout << R"( min max mean sd +/- sd time for request: )" diff --git a/src/h2load.h b/src/h2load.h index 061c776f..92c0e2ac 100644 --- a/src/h2load.h +++ b/src/h2load.h @@ -45,15 +45,19 @@ #include -#include -#include +#ifdef ENABLE_HTTP3 +# include +# include +#endif // ENABLE_HTTP3 #include #include #include "http2.h" -#include "quic.h" +#ifdef ENABLE_HTTP3 +# include "quic.h" +#endif // ENABLE_HTTP3 #include "memchunk.h" #include "template.h" @@ -327,6 +331,7 @@ struct Client { std::function readfn, writefn; Worker *worker; SSL *ssl; +#ifdef ENABLE_HTTP3 struct { ev_timer pkt_timer; ngtcp2_conn *conn; @@ -335,6 +340,7 @@ struct Client { bool close_requested; FILE *qlog_file; } quic; +#endif // ENABLE_HTTP3 ev_timer request_timeout_watcher; addrinfo *next_addr; // Address for the current address. When try_new_connection() is @@ -447,6 +453,7 @@ struct Client { void signal_write(); +#ifdef ENABLE_HTTP3 // QUIC int quic_init(const sockaddr *local_addr, socklen_t local_addrlen, const sockaddr *remote_addr, socklen_t remote_addrlen); @@ -475,6 +482,7 @@ struct Client { int quic_pkt_timeout(); void quic_restart_pkt_timer(); void quic_write_qlog(const void *data, size_t datalen); +#endif // ENABLE_HTTP3 }; } // namespace h2load diff --git a/src/shrpx.cc b/src/shrpx.cc index 62c0abc0..a619fe77 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -1548,6 +1548,7 @@ void fill_default_config(Config *config) { downstreamconf.option, downstreamconf.encoder_dynamic_table_size); } +#ifdef ENABLE_HTTP3 auto &quicconf = config->quic; { quicconf.timeout.idle = 30_s; @@ -1560,6 +1561,7 @@ void fill_default_config(Config *config) { exit(EXIT_FAILURE); } } +#endif // ENABLE_HTTP3 auto &loggingconf = config->logging; { diff --git a/src/shrpx_client_handler.cc b/src/shrpx_client_handler.cc index 341566b6..d58c9a5f 100644 --- a/src/shrpx_client_handler.cc +++ b/src/shrpx_client_handler.cc @@ -51,7 +51,9 @@ #include "shrpx_api_downstream_connection.h" #include "shrpx_health_monitor_downstream_connection.h" #include "shrpx_null_downstream_connection.h" -#include "shrpx_http3_upstream.h" +#ifdef ENABLE_HTTP3 +# include "shrpx_http3_upstream.h" +#endif // ENABLE_HTTP3 #include "shrpx_log.h" #include "util.h" #include "template.h" @@ -287,6 +289,7 @@ int ClientHandler::write_tls() { } } +#ifdef ENABLE_HTTP3 int ClientHandler::read_quic(const UpstreamAddr *faddr, const Address &remote_addr, const Address &local_addr, const uint8_t *data, @@ -297,6 +300,7 @@ int ClientHandler::read_quic(const UpstreamAddr *faddr, } int ClientHandler::write_quic() { return upstream_->on_write(); } +#endif // ENABLE_HTTP3 int ClientHandler::upstream_noop() { return 0; } @@ -509,12 +513,14 @@ void ClientHandler::setup_upstream_io_callback() { } } +#ifdef ENABLE_HTTP3 void ClientHandler::setup_http3_upstream( std::unique_ptr &&upstream) { upstream_ = std::move(upstream); alpn_ = StringRef::from_lit("h3"); write_ = &ClientHandler::write_quic; } +#endif // ENABLE_HTTP3 ClientHandler::~ClientHandler() { if (LOG_ENABLED(INFO)) { diff --git a/src/shrpx_client_handler.h b/src/shrpx_client_handler.h index ef467810..0c013973 100644 --- a/src/shrpx_client_handler.h +++ b/src/shrpx_client_handler.h @@ -53,7 +53,9 @@ class Downstream; struct WorkerStat; struct DownstreamAddrGroup; struct DownstreamAddr; +#ifdef ENABLE_HTTP3 class Http3Upstream; +#endif // ENABLE_HTTP3 class ClientHandler { public: @@ -71,9 +73,6 @@ public: int read_tls(); int write_tls(); - int read_quic(const UpstreamAddr *faddr, const Address &remote_addr, - const Address &local_addr, const uint8_t *data, size_t datalen); - int upstream_noop(); int upstream_read(); int upstream_http2_connhd_read(); @@ -147,8 +146,12 @@ public: void setup_upstream_io_callback(); +#ifdef ENABLE_HTTP3 void setup_http3_upstream(std::unique_ptr &&upstream); + int read_quic(const UpstreamAddr *faddr, const Address &remote_addr, + const Address &local_addr, const uint8_t *data, size_t datalen); int write_quic(); +#endif // ENABLE_HTTP3 // Returns string suitable for use in "by" parameter of Forwarded // header field. diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc index 8742ad6a..c3eb218a 100644 --- a/src/shrpx_config.cc +++ b/src/shrpx_config.cc @@ -821,7 +821,11 @@ int parse_upstream_params(UpstreamParams &out, const StringRef &src_params) { } else if (util::strieq_l("proxyproto", param)) { out.proxyproto = true; } else if (util::strieq_l("quic", param)) { +#ifdef ENABLE_HTTP3 out.quic = true; +#else // !ENABLE_HTTP3 + LOG(ERROR) << "quic: QUIC is disabled at compile time"; +#endif // !ENABLE_HTTP3 } else if (!param.empty()) { LOG(ERROR) << "frontend: " << param << ": unknown keyword"; return -1; @@ -2674,8 +2678,12 @@ int parse_config(Config *config, int optid, const StringRef &opt, apiconf.enabled = true; } +#ifdef ENABLE_HTTP3 auto &addrs = params.quic ? config->conn.quic_listener.addrs : config->conn.listener.addrs; +#else // !ENABLE_HTTP3 + auto &addrs = config->conn.listener.addrs; +#endif // !ENABLE_HTTP3 if (util::istarts_with(optarg, SHRPX_UNIX_PATH_PREFIX)) { auto path = std::begin(optarg) + SHRPX_UNIX_PATH_PREFIX.size(); diff --git a/src/shrpx_config.h b/src/shrpx_config.h index 291e25e6..d3a22baa 100644 --- a/src/shrpx_config.h +++ b/src/shrpx_config.h @@ -704,6 +704,7 @@ struct TLSConfig { bool no_postpone_early_data; }; +#ifdef ENABLE_HTTP3 struct QUICConfig { struct { std::array secret; @@ -715,6 +716,7 @@ struct QUICConfig { bool log; } debug; }; +#endif // ENABLE_HTTP3 // custom error page struct ErrorPage { @@ -921,9 +923,11 @@ struct ConnectionConfig { int fastopen; } listener; +#ifdef ENABLE_HTTP3 struct { std::vector addrs; } quic_listener; +#endif // ENABLE_HTTP3 struct { struct { @@ -968,7 +972,9 @@ struct Config { http{}, http2{}, tls{}, +#ifdef ENABLE_HTTP3 quic{}, +#endif // ENABLE_HTTP3 logging{}, conn{}, api{}, @@ -986,7 +992,8 @@ struct Config { single_process{false}, single_thread{false}, ignore_per_pattern_mruby_error{false}, - ev_loop_flags{0} {} + ev_loop_flags{0} { + } ~Config(); Config(Config &&) = delete; @@ -1002,7 +1009,9 @@ struct Config { HttpConfig http; Http2Config http2; TLSConfig tls; +#ifdef ENABLE_HTTP3 QUICConfig quic; +#endif // ENABLE_HTTP3 LoggingConfig logging; ConnectionConfig conn; APIConfig api; diff --git a/src/shrpx_connection_handler.cc b/src/shrpx_connection_handler.cc index 03f74594..39ea08cd 100644 --- a/src/shrpx_connection_handler.cc +++ b/src/shrpx_connection_handler.cc @@ -156,6 +156,7 @@ ConnectionHandler::~ConnectionHandler() { ev_timer_stop(loop_, &ocsp_timer_); ev_timer_stop(loop_, &disable_acceptor_timer_); +#ifdef ENABLE_HTTP3 for (auto ssl_ctx : quic_all_ssl_ctx_) { if (ssl_ctx == nullptr) { continue; @@ -166,6 +167,7 @@ ConnectionHandler::~ConnectionHandler() { delete tls_ctx_data; SSL_CTX_free(ssl_ctx); } +#endif // ENABLE_HTTP3 for (auto ssl_ctx : all_ssl_ctx_) { auto tls_ctx_data = @@ -221,14 +223,16 @@ int ConnectionHandler::create_single_worker() { #endif // HAVE_NEVERBLEED ); +#ifdef ENABLE_HTTP3 quic_cert_tree_ = tls::create_cert_lookup_tree(); auto quic_sv_ssl_ctx = tls::setup_quic_server_ssl_context( quic_all_ssl_ctx_, quic_indexed_ssl_ctx_, quic_cert_tree_.get() -#ifdef HAVE_NEVERBLEED +# ifdef HAVE_NEVERBLEED , nb_ -#endif // HAVE_NEVERBLEED +# endif // HAVE_NEVERBLEED ); +#endif // ENABLE_HTTP3 auto cl_ssl_ctx = tls::setup_downstream_client_ssl_context( #ifdef HAVE_NEVERBLEED @@ -238,7 +242,9 @@ int ConnectionHandler::create_single_worker() { if (cl_ssl_ctx) { all_ssl_ctx_.push_back(cl_ssl_ctx); +#ifdef ENABLE_HTTP3 quic_all_ssl_ctx_.push_back(nullptr); +#endif // ENABLE_HTTP3 } auto config = get_config(); @@ -255,22 +261,29 @@ int ConnectionHandler::create_single_worker() { tlsconf.cacert, memcachedconf.cert_file, memcachedconf.private_key_file, nullptr); all_ssl_ctx_.push_back(session_cache_ssl_ctx); +#ifdef ENABLE_HTTP3 quic_all_ssl_ctx_.push_back(nullptr); +#endif // ENABLE_HTTP3 } } single_worker_ = std::make_unique( loop_, sv_ssl_ctx, cl_ssl_ctx, session_cache_ssl_ctx, cert_tree_.get(), - quic_sv_ssl_ctx, quic_cert_tree_.get(), ticket_keys_, this, - config->conn.downstream); +#ifdef ENABLE_HTTP3 + quic_sv_ssl_ctx, quic_cert_tree_.get(), +#endif // ENABLE_HTTP3 + ticket_keys_, this, config->conn.downstream); #ifdef HAVE_MRUBY if (single_worker_->create_mruby_context() != 0) { return -1; } #endif // HAVE_MRUBY + +#ifdef ENABLE_HTTP3 if (single_worker_->setup_quic_server_socket() != 0) { return -1; } +#endif // ENABLE_HTTP3 return 0; } @@ -288,14 +301,16 @@ int ConnectionHandler::create_worker_thread(size_t num) { # endif // HAVE_NEVERBLEED ); +# ifdef ENABLE_HTTP3 quic_cert_tree_ = tls::create_cert_lookup_tree(); auto quic_sv_ssl_ctx = tls::setup_quic_server_ssl_context( quic_all_ssl_ctx_, quic_indexed_ssl_ctx_, quic_cert_tree_.get() -# ifdef HAVE_NEVERBLEED +# ifdef HAVE_NEVERBLEED , nb_ -# endif // HAVE_NEVERBLEED +# endif // HAVE_NEVERBLEED ); +# endif // ENABLE_HTTP3 auto cl_ssl_ctx = tls::setup_downstream_client_ssl_context( # ifdef HAVE_NEVERBLEED @@ -305,7 +320,9 @@ int ConnectionHandler::create_worker_thread(size_t num) { if (cl_ssl_ctx) { all_ssl_ctx_.push_back(cl_ssl_ctx); +# ifdef ENABLE_HTTP3 quic_all_ssl_ctx_.push_back(nullptr); +# endif // ENABLE_HTTP3 } auto config = get_config(); @@ -329,7 +346,9 @@ int ConnectionHandler::create_worker_thread(size_t num) { tlsconf.cacert, memcachedconf.cert_file, memcachedconf.private_key_file, nullptr); all_ssl_ctx_.push_back(session_cache_ssl_ctx); +# ifdef ENABLE_HTTP3 quic_all_ssl_ctx_.push_back(nullptr); +# endif // ENABLE_HTTP3 } } @@ -338,17 +357,22 @@ int ConnectionHandler::create_worker_thread(size_t num) { auto worker = std::make_unique( loop, sv_ssl_ctx, cl_ssl_ctx, session_cache_ssl_ctx, cert_tree_.get(), - quic_sv_ssl_ctx, quic_cert_tree_.get(), ticket_keys_, this, - config->conn.downstream); +# ifdef ENABLE_HTTP3 + quic_sv_ssl_ctx, quic_cert_tree_.get(), +# endif // ENABLE_HTTP3 + ticket_keys_, this, config->conn.downstream); # ifdef HAVE_MRUBY if (worker->create_mruby_context() != 0) { return -1; } # endif // HAVE_MRUBY + +# ifdef ENABLE_HTTP3 if ((!apiconf.enabled || i != 0) && worker->setup_quic_server_socket() != 0) { return -1; } +# endif // ENABLE_HTTP3 workers_.push_back(std::move(worker)); worker_loops_.push_back(loop); @@ -646,7 +670,9 @@ void ConnectionHandler::handle_ocsp_complete() { ev_child_stop(loop_, &ocsp_.chldev); assert(ocsp_.next < all_ssl_ctx_.size()); +#ifdef ENABLE_HTTP3 assert(all_ssl_ctx_.size() == quic_all_ssl_ctx_.size()); +#endif // ENABLE_HTTP3 auto ssl_ctx = all_ssl_ctx_[ocsp_.next]; auto tls_ctx_data = @@ -674,6 +700,7 @@ void ConnectionHandler::handle_ocsp_complete() { if (tlsconf.ocsp.no_verify || tls::verify_ocsp_response(ssl_ctx, ocsp_.resp.data(), ocsp_.resp.size()) == 0) { +#ifdef ENABLE_HTTP3 // We have list of SSL_CTX with the same certificate in // quic_all_ssl_ctx_ as well. Some SSL_CTXs are missing there in // that case we get nullptr. @@ -681,21 +708,22 @@ void ConnectionHandler::handle_ocsp_complete() { if (quic_ssl_ctx) { auto quic_tls_ctx_data = static_cast( SSL_CTX_get_app_data(quic_ssl_ctx)); -#ifndef OPENSSL_IS_BORINGSSL -# ifdef HAVE_ATOMIC_STD_SHARED_PTR +# ifndef OPENSSL_IS_BORINGSSL +# ifdef HAVE_ATOMIC_STD_SHARED_PTR std::atomic_store_explicit( &quic_tls_ctx_data->ocsp_data, std::make_shared>(ocsp_.resp), std::memory_order_release); -# else // !HAVE_ATOMIC_STD_SHARED_PTR +# else // !HAVE_ATOMIC_STD_SHARED_PTR std::lock_guard g(quic_tls_ctx_data->mu); quic_tls_ctx_data->ocsp_data = std::make_shared>(ocsp_.resp); -# endif // !HAVE_ATOMIC_STD_SHARED_PTR -#else // OPENSSL_IS_BORINGSSL +# endif // !HAVE_ATOMIC_STD_SHARED_PTR +# else // OPENSSL_IS_BORINGSSL SSL_CTX_set_ocsp_response(ssl_ctx, ocsp_.resp.data(), ocsp_.resp.size()); -#endif // OPENSSL_IS_BORINGSSL +# endif // OPENSSL_IS_BORINGSSL } +#endif // ENABLE_HTTP3 #ifndef OPENSSL_IS_BORINGSSL # ifdef HAVE_ATOMIC_STD_SHARED_PTR @@ -877,7 +905,9 @@ SSL_CTX *ConnectionHandler::create_tls_ticket_key_memcached_ssl_ctx() { nullptr); all_ssl_ctx_.push_back(ssl_ctx); +#ifdef ENABLE_HTTP3 quic_all_ssl_ctx_.push_back(nullptr); +#endif // ENABLE_HTTP3 return ssl_ctx; } @@ -940,10 +970,12 @@ ConnectionHandler::get_indexed_ssl_ctx(size_t idx) const { return indexed_ssl_ctx_[idx]; } +#ifdef ENABLE_HTTP3 const std::vector & ConnectionHandler::get_quic_indexed_ssl_ctx(size_t idx) const { return quic_indexed_ssl_ctx_[idx]; } +#endif // ENABLE_HTTP3 void ConnectionHandler::set_enable_acceptor_on_ocsp_completion(bool f) { enable_acceptor_on_ocsp_completion_ = f; diff --git a/src/shrpx_connection_handler.h b/src/shrpx_connection_handler.h index eb86b855..3e8a5406 100644 --- a/src/shrpx_connection_handler.h +++ b/src/shrpx_connection_handler.h @@ -159,7 +159,9 @@ public: SSL_CTX *get_ssl_ctx(size_t idx) const; const std::vector &get_indexed_ssl_ctx(size_t idx) const; +#ifdef ENABLE_HTTP3 const std::vector &get_quic_indexed_ssl_ctx(size_t idx) const; +#endif // ENABLE_HTTP3 #ifdef HAVE_NEVERBLEED void set_neverbleed(neverbleed_t *nb); @@ -188,8 +190,10 @@ private: // selection among them are performed by hostname presented by SNI, // and signature algorithm presented by client. std::vector> indexed_ssl_ctx_; +#ifdef ENABLE_HTTP3 std::vector quic_all_ssl_ctx_; std::vector> quic_indexed_ssl_ctx_; +#endif // ENABLE_HTTP3 OCSPUpdateContext ocsp_; std::mt19937 &gen_; // ev_loop for each worker @@ -206,7 +210,9 @@ private: // Otherwise, nullptr and workers_ has instances of Worker instead. std::unique_ptr single_worker_; std::unique_ptr cert_tree_; +#ifdef ENABLE_HTTP3 std::unique_ptr quic_cert_tree_; +#endif // ENABLE_HTTP3 std::unique_ptr tls_ticket_key_memcached_dispatcher_; // Current TLS session ticket keys. Note that TLS connection does // not refer to this field directly. They use TicketKeys object in diff --git a/src/shrpx_downstream.cc b/src/shrpx_downstream.cc index ca07f2f1..7c822ba8 100644 --- a/src/shrpx_downstream.cc +++ b/src/shrpx_downstream.cc @@ -165,7 +165,9 @@ Downstream::Downstream(Upstream *upstream, MemchunkPool *mcpool, downstream_wtimer_.data = this; rcbufs_.reserve(32); +#ifdef ENABLE_HTTP3 rcbufs3_.reserve(32); +#endif // ENABLE_HTTP3 } Downstream::~Downstream() { @@ -205,9 +207,11 @@ Downstream::~Downstream() { // explicitly. dconn_.reset(); +#ifdef ENABLE_HTTP3 for (auto rcbuf : rcbufs3_) { nghttp3_rcbuf_decref(rcbuf); } +#endif // ENABLE_HTTP3 for (auto rcbuf : rcbufs_) { nghttp2_rcbuf_decref(rcbuf); @@ -1134,10 +1138,12 @@ void Downstream::add_rcbuf(nghttp2_rcbuf *rcbuf) { rcbufs_.push_back(rcbuf); } +#ifdef ENABLE_HTTP3 void Downstream::add_rcbuf(nghttp3_rcbuf *rcbuf) { nghttp3_rcbuf_incref(rcbuf); rcbufs3_.push_back(rcbuf); } +#endif // ENABLE_HTTP3 void Downstream::set_downstream_addr_group( const std::shared_ptr &group) { diff --git a/src/shrpx_downstream.h b/src/shrpx_downstream.h index 20ec54c4..2a50a974 100644 --- a/src/shrpx_downstream.h +++ b/src/shrpx_downstream.h @@ -38,7 +38,9 @@ #include -#include +#ifdef ENABLE_HTTP3 +# include +#endif // ENABLE_HTTP3 #include "llhttp.h" @@ -490,7 +492,9 @@ public: BlockAllocator &get_block_allocator(); void add_rcbuf(nghttp2_rcbuf *rcbuf); +#ifdef ENABLE_HTTP3 void add_rcbuf(nghttp3_rcbuf *rcbuf); +#endif // ENABLE_HTTP3 void set_downstream_addr_group(const std::shared_ptr &group); @@ -533,7 +537,9 @@ private: BlockAllocator balloc_; std::vector rcbufs_; +#ifdef ENABLE_HTTP3 std::vector rcbufs3_; +#endif // ENABLE_HTTP3 Request req_; Response resp_; diff --git a/src/shrpx_tls.cc b/src/shrpx_tls.cc index 79104d54..a86dc1a5 100644 --- a/src/shrpx_tls.cc +++ b/src/shrpx_tls.cc @@ -51,9 +51,11 @@ #include -#include -#include -#include +#ifdef ENABLE_HTTP3 +# include +# include +# include +#endif // ENABLE_HTTP3 #include "shrpx_log.h" #include "shrpx_client_handler.h" @@ -64,7 +66,9 @@ #include "shrpx_memcached_request.h" #include "shrpx_memcached_dispatcher.h" #include "shrpx_connection_handler.h" -#include "shrpx_http3_upstream.h" +#ifdef ENABLE_HTTP3 +# include "shrpx_http3_upstream.h" +#endif // ENABLE_HTTP3 #include "util.h" #include "tls.h" #include "template.h" @@ -185,8 +189,12 @@ int servername_callback(SSL *ssl, int *al, void *arg) { auto hostname = StringRef{std::begin(buf), end_buf}; +#ifdef ENABLE_HTTP3 auto cert_tree = SSL_is_quic(ssl) ? worker->get_quic_cert_lookup_tree() : worker->get_cert_lookup_tree(); +#else // !ENABLE_HTTP3 + auto cert_tree = worker->get_cert_lookup_tree(); +#endif // !ENABLE_HTTP3 auto idx = cert_tree->lookup(hostname); if (idx == -1) { @@ -197,9 +205,14 @@ int servername_callback(SSL *ssl, int *al, void *arg) { auto conn_handler = worker->get_connection_handler(); +#ifdef ENABLE_HTTP3 const auto &ssl_ctx_list = SSL_is_quic(ssl) ? conn_handler->get_quic_indexed_ssl_ctx(idx) : conn_handler->get_indexed_ssl_ctx(idx); +#else // !ENABLE_HTTP3 + const auto &ssl_ctx_list = conn_handler->get_indexed_ssl_ctx(idx); +#endif // !ENABLE_HTTP3 + assert(!ssl_ctx_list.empty()); #if !defined(OPENSSL_IS_BORINGSSL) && !LIBRESSL_IN_USE && \ @@ -608,7 +621,8 @@ int alpn_select_proto_cb(SSL *ssl, const unsigned char **out, } // namespace #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L -#if OPENSSL_VERSION_NUMBER >= 0x10002000L +#ifdef ENABLE_HTTP3 +# if OPENSSL_VERSION_NUMBER >= 0x10002000L namespace { int quic_alpn_select_proto_cb(SSL *ssl, const unsigned char **out, unsigned char *outlen, const unsigned char *in, @@ -632,7 +646,8 @@ int quic_alpn_select_proto_cb(SSL *ssl, const unsigned char **out, return SSL_TLSEXT_ERR_NOACK; } } // namespace -#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L +# endif // OPENSSL_VERSION_NUMBER >= 0x10002000L +#endif // ENABLE_HTTP3 #if !LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L @@ -1076,6 +1091,7 @@ SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file, return ssl_ctx; } +#ifdef ENABLE_HTTP3 namespace { int quic_set_encryption_secrets(SSL *ssl, OSSL_ENCRYPTION_LEVEL ossl_level, const uint8_t *rx_secret, @@ -1143,10 +1159,10 @@ auto quic_method = SSL_QUIC_METHOD{ SSL_CTX *create_quic_ssl_context(const char *private_key_file, const char *cert_file, const std::vector &sct_data -#ifdef HAVE_NEVERBLEED +# ifdef HAVE_NEVERBLEED , neverbleed_t *nb -#endif // HAVE_NEVERBLEED +# endif // HAVE_NEVERBLEED ) { auto ssl_ctx = SSL_CTX_new(TLS_server_method()); if (!ssl_ctx) { @@ -1159,14 +1175,14 @@ SSL_CTX *create_quic_ssl_context(const char *private_key_file, SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION | SSL_OP_SINGLE_ECDH_USE | SSL_OP_SINGLE_DH_USE | SSL_OP_CIPHER_SERVER_PREFERENCE -#if OPENSSL_1_1_1_API +# if OPENSSL_1_1_1_API // The reason for disabling built-in anti-replay in OpenSSL is // that it only works if client gets back to the same server. // The freshness check described in // https://tools.ietf.org/html/rfc8446#section-8.3 is still // performed. | SSL_OP_NO_ANTI_REPLAY -#endif // OPENSSL_1_1_1_API +# endif // OPENSSL_1_1_1_API ; auto config = mod_config(); @@ -1194,27 +1210,27 @@ SSL_CTX *create_quic_ssl_context(const char *private_key_file, DIE(); } -#if OPENSSL_1_1_1_API +# if OPENSSL_1_1_1_API if (SSL_CTX_set_ciphersuites(ssl_ctx, tlsconf.tls13_ciphers.c_str()) == 0) { LOG(FATAL) << "SSL_CTX_set_ciphersuites " << tlsconf.tls13_ciphers << " failed: " << ERR_error_string(ERR_get_error(), nullptr); DIE(); } -#endif // OPENSSL_1_1_1_API +# endif // OPENSSL_1_1_1_API -#ifndef OPENSSL_NO_EC -# if !LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L +# ifndef OPENSSL_NO_EC +# if !LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L if (SSL_CTX_set1_curves_list(ssl_ctx, tlsconf.ecdh_curves.c_str()) != 1) { LOG(FATAL) << "SSL_CTX_set1_curves_list " << tlsconf.ecdh_curves << " failed"; DIE(); } -# if !defined(OPENSSL_IS_BORINGSSL) && !OPENSSL_1_1_API +# if !defined(OPENSSL_IS_BORINGSSL) && !OPENSSL_1_1_API // It looks like we need this function call for OpenSSL 1.0.2. This // function was deprecated in OpenSSL 1.1.0 and BoringSSL. SSL_CTX_set_ecdh_auto(ssl_ctx, 1); -# endif // !defined(OPENSSL_IS_BORINGSSL) && !OPENSSL_1_1_API -# else // LIBRESSL_LEGACY_API || OPENSSL_VERSION_NUBMER < 0x10002000L +# endif // !defined(OPENSSL_IS_BORINGSSL) && !OPENSSL_1_1_API +# else // LIBRESSL_LEGACY_API || OPENSSL_VERSION_NUBMER < 0x10002000L // Use P-256, which is sufficiently secure at the time of this // writing. auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); @@ -1225,8 +1241,8 @@ SSL_CTX *create_quic_ssl_context(const char *private_key_file, } SSL_CTX_set_tmp_ecdh(ssl_ctx, ecdh); EC_KEY_free(ecdh); -# endif // LIBRESSL_LEGACY_API || OPENSSL_VERSION_NUBMER < 0x10002000L -#endif // OPENSSL_NO_EC +# endif // LIBRESSL_LEGACY_API || OPENSSL_VERSION_NUBMER < 0x10002000L +# endif // OPENSSL_NO_EC if (!tlsconf.dh_param_file.empty()) { // Read DH parameters from file @@ -1269,20 +1285,20 @@ SSL_CTX *create_quic_ssl_context(const char *private_key_file, SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx, config); } -#ifndef HAVE_NEVERBLEED +# ifndef HAVE_NEVERBLEED if (SSL_CTX_use_PrivateKey_file(ssl_ctx, private_key_file, SSL_FILETYPE_PEM) != 1) { LOG(FATAL) << "SSL_CTX_use_PrivateKey_file failed: " << ERR_error_string(ERR_get_error(), nullptr); } -#else // HAVE_NEVERBLEED +# else // HAVE_NEVERBLEED std::array errbuf; if (neverbleed_load_private_key_file(nb, ssl_ctx, private_key_file, errbuf.data()) != 1) { LOG(FATAL) << "neverbleed_load_private_key_file failed: " << errbuf.data(); DIE(); } -#endif // HAVE_NEVERBLEED +# endif // HAVE_NEVERBLEED if (SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_file) != 1) { LOG(FATAL) << "SSL_CTX_use_certificate_file failed: " @@ -1324,27 +1340,27 @@ SSL_CTX *create_quic_ssl_context(const char *private_key_file, } SSL_CTX_set_tlsext_servername_callback(ssl_ctx, servername_callback); SSL_CTX_set_tlsext_ticket_key_cb(ssl_ctx, ticket_key_cb); -#ifndef OPENSSL_IS_BORINGSSL +# ifndef OPENSSL_IS_BORINGSSL SSL_CTX_set_tlsext_status_cb(ssl_ctx, ocsp_resp_cb); -#endif // OPENSSL_IS_BORINGSSL +# endif // OPENSSL_IS_BORINGSSL -#ifdef OPENSSL_IS_BORINGSSL +# ifdef OPENSSL_IS_BORINGSSL SSL_CTX_set_early_data_enabled(ssl_ctx, 1); -#endif // OPENSSL_IS_BORINGSSL +# endif // OPENSSL_IS_BORINGSSL -#if OPENSSL_VERSION_NUMBER >= 0x10002000L +# if OPENSSL_VERSION_NUMBER >= 0x10002000L // ALPN selection callback SSL_CTX_set_alpn_select_cb(ssl_ctx, quic_alpn_select_proto_cb, nullptr); -#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L +# endif // OPENSSL_VERSION_NUMBER >= 0x10002000L -#if !LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L && \ - !defined(OPENSSL_IS_BORINGSSL) +# if !LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L && \ + !defined(OPENSSL_IS_BORINGSSL) // SSL_extension_supported(TLSEXT_TYPE_signed_certificate_timestamp) // returns 1, which means OpenSSL internally handles it. But // OpenSSL handles signed_certificate_timestamp extension specially, // and it lets custom handler to process the extension. if (!sct_data.empty()) { -# if OPENSSL_1_1_1_API +# if OPENSSL_1_1_1_API // It is not entirely clear to me that SSL_EXT_CLIENT_HELLO is // required here. sct_parse_cb is called without // SSL_EXT_CLIENT_HELLO being set. But the passed context value @@ -1358,7 +1374,7 @@ SSL_CTX *create_quic_ssl_context(const char *private_key_file, << ERR_error_string(ERR_get_error(), nullptr); DIE(); } -# else // !OPENSSL_1_1_1_API +# else // !OPENSSL_1_1_1_API if (SSL_CTX_add_server_custom_ext( ssl_ctx, TLSEXT_TYPE_signed_certificate_timestamp, legacy_sct_add_cb, legacy_sct_free_cb, nullptr, legacy_sct_parse_cb, @@ -1367,23 +1383,23 @@ SSL_CTX *create_quic_ssl_context(const char *private_key_file, << ERR_error_string(ERR_get_error(), nullptr); DIE(); } -# endif // !OPENSSL_1_1_1_API +# endif // !OPENSSL_1_1_1_API } -#endif // !LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L && - // !defined(OPENSSL_IS_BORINGSSL) +# endif // !LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L && + // !defined(OPENSSL_IS_BORINGSSL) -#if OPENSSL_1_1_1_API +# if OPENSSL_1_1_1_API if (SSL_CTX_set_max_early_data(ssl_ctx, std::numeric_limits::max()) != 1) { LOG(FATAL) << "SSL_CTX_set_max_early_data failed: " << ERR_error_string(ERR_get_error(), nullptr); DIE(); } -#endif // OPENSSL_1_1_1_API +# endif // OPENSSL_1_1_1_API -#ifndef OPENSSL_NO_PSK +# ifndef OPENSSL_NO_PSK SSL_CTX_set_psk_server_callback(ssl_ctx, psk_server_cb); -#endif // !LIBRESSL_NO_PSK +# endif // !LIBRESSL_NO_PSK SSL_CTX_set_quic_method(ssl_ctx, &quic_method); @@ -1395,6 +1411,7 @@ SSL_CTX *create_quic_ssl_context(const char *private_key_file, return ssl_ctx; } +#endif // ENABLE_HTTP3 namespace { int select_h2_next_proto_cb(SSL *ssl, unsigned char **out, @@ -2101,9 +2118,11 @@ bool in_proto_list(const std::vector &protos, } bool upstream_tls_enabled(const ConnectionConfig &connconf) { +#ifdef ENABLE_HTTP3 if (connconf.quic_listener.addrs.size()) { return true; } +#endif // ENABLE_HTTP3 const auto &faddrs = connconf.listener.addrs; return std::any_of(std::begin(faddrs), std::end(faddrs), @@ -2184,14 +2203,15 @@ setup_server_ssl_context(std::vector &all_ssl_ctx, return ssl_ctx; } +#ifdef ENABLE_HTTP3 SSL_CTX *setup_quic_server_ssl_context( std::vector &all_ssl_ctx, std::vector> &indexed_ssl_ctx, CertLookupTree *cert_tree -#ifdef HAVE_NEVERBLEED +# ifdef HAVE_NEVERBLEED , neverbleed_t *nb -#endif // HAVE_NEVERBLEED +# endif // HAVE_NEVERBLEED ) { auto config = get_config(); @@ -2204,10 +2224,10 @@ SSL_CTX *setup_quic_server_ssl_context( auto ssl_ctx = create_quic_ssl_context(tlsconf.private_key_file.c_str(), tlsconf.cert_file.c_str(), tlsconf.sct_data -#ifdef HAVE_NEVERBLEED +# ifdef HAVE_NEVERBLEED , nb -#endif // HAVE_NEVERBLEED +# endif // HAVE_NEVERBLEED ); all_ssl_ctx.push_back(ssl_ctx); @@ -2222,10 +2242,10 @@ SSL_CTX *setup_quic_server_ssl_context( for (auto &c : tlsconf.subcerts) { auto ssl_ctx = create_quic_ssl_context(c.private_key_file.c_str(), c.cert_file.c_str(), c.sct_data -#ifdef HAVE_NEVERBLEED +# ifdef HAVE_NEVERBLEED , nb -#endif // HAVE_NEVERBLEED +# endif // HAVE_NEVERBLEED ); all_ssl_ctx.push_back(ssl_ctx); @@ -2238,6 +2258,7 @@ SSL_CTX *setup_quic_server_ssl_context( return ssl_ctx; } +#endif // ENABLE_HTTP3 SSL_CTX *setup_downstream_client_ssl_context( #ifdef HAVE_NEVERBLEED diff --git a/src/shrpx_tls.h b/src/shrpx_tls.h index ebc4aa27..5d751c0f 100644 --- a/src/shrpx_tls.h +++ b/src/shrpx_tls.h @@ -100,15 +100,17 @@ SSL_CTX *create_ssl_client_context( unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg)); +#ifdef ENABLE_HTTP3 SSL_CTX *create_quic_ssl_client_context( -#ifdef HAVE_NEVERBLEED +# ifdef HAVE_NEVERBLEED neverbleed_t *nb, -#endif // HAVE_NEVERBLEED +# endif // HAVE_NEVERBLEED const StringRef &cacert, const StringRef &cert_file, const StringRef &private_key_file, int (*next_proto_select_cb)(SSL *s, unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg)); +#endif // ENABLE_HTTP3 ClientHandler *accept_connection(Worker *worker, int fd, sockaddr *addr, int addrlen, const UpstreamAddr *faddr); @@ -227,15 +229,17 @@ setup_server_ssl_context(std::vector &all_ssl_ctx, #endif // HAVE_NEVERBLEED ); +#ifdef ENABLE_HTTP3 SSL_CTX *setup_quic_server_ssl_context( std::vector &all_ssl_ctx, std::vector> &indexed_ssl_ctx, CertLookupTree *cert_tree -#ifdef HAVE_NEVERBLEED +# ifdef HAVE_NEVERBLEED , neverbleed_t *nb -#endif // HAVE_NEVERBLEED +# endif // HAVE_NEVERBLEED ); +#endif // ENABLE_HTTP3 // Setups client side SSL_CTX. SSL_CTX *setup_downstream_client_ssl_context( diff --git a/src/shrpx_worker.cc b/src/shrpx_worker.cc index 0a4ed3c7..b153b0e6 100644 --- a/src/shrpx_worker.cc +++ b/src/shrpx_worker.cc @@ -39,8 +39,10 @@ #ifdef HAVE_MRUBY # include "shrpx_mruby.h" #endif // HAVE_MRUBY -#include "shrpx_quic.h" -#include "shrpx_quic_listener.h" +#ifdef ENABLE_HTTP3 +# include "shrpx_quic.h" +# include "shrpx_quic_listener.h" +#endif // ENABLE_HTTP3 #include "util.h" #include "template.h" @@ -132,23 +134,29 @@ create_downstream_key(const std::shared_ptr &shared_addr, Worker::Worker(struct ev_loop *loop, SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx, SSL_CTX *tls_session_cache_memcached_ssl_ctx, - tls::CertLookupTree *cert_tree, SSL_CTX *quic_sv_ssl_ctx, - tls::CertLookupTree *quic_cert_tree, + tls::CertLookupTree *cert_tree, +#ifdef ENABLE_HTTP3 + SSL_CTX *quic_sv_ssl_ctx, tls::CertLookupTree *quic_cert_tree, +#endif // ENABLE_HTTP3 const std::shared_ptr &ticket_keys, ConnectionHandler *conn_handler, std::shared_ptr downstreamconf) : randgen_(util::make_mt19937()), worker_stat_{}, dns_tracker_(loop), +#ifdef ENABLE_HTTP3 quic_upstream_addrs_{get_config()->conn.quic_listener.addrs}, +#endif // ENABLE_HTTP3 loop_(loop), sv_ssl_ctx_(sv_ssl_ctx), cl_ssl_ctx_(cl_ssl_ctx), cert_tree_(cert_tree), conn_handler_(conn_handler), +#ifdef ENABLE_HTTP3 quic_sv_ssl_ctx_{quic_sv_ssl_ctx}, quic_cert_tree_{quic_cert_tree}, quic_conn_handler_{this}, +#endif // ENABLE_HTTP3 ticket_keys_(ticket_keys), connect_blocker_( std::make_unique(randgen_, loop_, nullptr, nullptr)), @@ -511,9 +519,11 @@ void Worker::process_events() { tls::CertLookupTree *Worker::get_cert_lookup_tree() const { return cert_tree_; } +#ifdef ENABLE_HTTP3 tls::CertLookupTree *Worker::get_quic_cert_lookup_tree() const { return quic_cert_tree_; } +#endif // ENABLE_HTTP3 std::shared_ptr Worker::get_ticket_keys() { #ifdef HAVE_ATOMIC_STD_SHARED_PTR @@ -545,7 +555,9 @@ SSL_CTX *Worker::get_sv_ssl_ctx() const { return sv_ssl_ctx_; } SSL_CTX *Worker::get_cl_ssl_ctx() const { return cl_ssl_ctx_; } +#ifdef ENABLE_HTTP3 SSL_CTX *Worker::get_quic_sv_ssl_ctx() const { return quic_sv_ssl_ctx_; } +#endif // ENABLE_HTTP3 void Worker::set_graceful_shutdown(bool f) { graceful_shutdown_ = f; } @@ -591,12 +603,15 @@ ConnectionHandler *Worker::get_connection_handler() const { return conn_handler_; } +#ifdef ENABLE_HTTP3 QUICConnectionHandler *Worker::get_quic_connection_handler() { return &quic_conn_handler_; } +#endif // ENABLE_HTTP3 DNSTracker *Worker::get_dns_tracker() { return &dns_tracker_; } +#ifdef ENABLE_HTTP3 int Worker::setup_quic_server_socket() { for (auto &addr : quic_upstream_addrs_) { assert(!addr.host_unix); @@ -609,6 +624,7 @@ int Worker::setup_quic_server_socket() { return 0; } +#endif // ENABLE_HTTP3 namespace { size_t match_downstream_addr_group_host( diff --git a/src/shrpx_worker.h b/src/shrpx_worker.h index 02ece027..e4e83e6e 100644 --- a/src/shrpx_worker.h +++ b/src/shrpx_worker.h @@ -50,7 +50,9 @@ #include "shrpx_live_check.h" #include "shrpx_connect_blocker.h" #include "shrpx_dns_tracker.h" -#include "shrpx_quic_connection_handler.h" +#ifdef ENABLE_HTTP3 +# include "shrpx_quic_connection_handler.h" +#endif // ENABLE_HTTP3 #include "allocator.h" using namespace nghttp2; @@ -62,7 +64,9 @@ class ConnectBlocker; class MemcachedDispatcher; struct UpstreamAddr; class ConnectionHandler; +#ifdef ENABLE_HTTP3 class QUICListener; +#endif // ENABLE_HTTP3 #ifdef HAVE_MRUBY namespace mruby { @@ -271,8 +275,10 @@ class Worker { public: Worker(struct ev_loop *loop, SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx, SSL_CTX *tls_session_cache_memcached_ssl_ctx, - tls::CertLookupTree *cert_tree, SSL_CTX *quic_sv_ssl_ctx, - tls::CertLookupTree *quic_cert_tree, + tls::CertLookupTree *cert_tree, +#ifdef ENABLE_HTTP3 + SSL_CTX *quic_sv_ssl_ctx, tls::CertLookupTree *quic_cert_tree, +#endif // ENABLE_HTTP3 const std::shared_ptr &ticket_keys, ConnectionHandler *conn_handler, std::shared_ptr downstreamconf); @@ -283,7 +289,9 @@ public: void send(const WorkerEvent &event); tls::CertLookupTree *get_cert_lookup_tree() const; +#ifdef ENABLE_HTTP3 tls::CertLookupTree *get_quic_cert_lookup_tree() const; +#endif // ENABLE_HTTP3 // These 2 functions make a lock m_ to get/set ticket keys // atomically. @@ -294,7 +302,9 @@ public: struct ev_loop *get_loop() const; SSL_CTX *get_sv_ssl_ctx() const; SSL_CTX *get_cl_ssl_ctx() const; +#ifdef ENABLE_HTTP3 SSL_CTX *get_quic_sv_ssl_ctx() const; +#endif // ENABLE_HTTP3 void set_graceful_shutdown(bool f); bool get_graceful_shutdown() const; @@ -324,11 +334,13 @@ public: ConnectionHandler *get_connection_handler() const; +#ifdef ENABLE_HTTP3 QUICConnectionHandler *get_quic_connection_handler(); - DNSTracker *get_dns_tracker(); - int setup_quic_server_socket(); +#endif // ENABLE_HTTP3 + + DNSTracker *get_dns_tracker(); private: #ifndef NOTHREADS @@ -344,8 +356,10 @@ private: WorkerStat worker_stat_; DNSTracker dns_tracker_; +#ifdef ENABLE_HTTP3 std::vector quic_upstream_addrs_; std::vector> quic_listeners_; +#endif // ENABLE_HTTP3 std::shared_ptr downstreamconf_; std::unique_ptr session_cache_memcached_dispatcher_; @@ -360,10 +374,12 @@ private: SSL_CTX *cl_ssl_ctx_; tls::CertLookupTree *cert_tree_; ConnectionHandler *conn_handler_; +#ifdef ENABLE_HTTP3 SSL_CTX *quic_sv_ssl_ctx_; tls::CertLookupTree *quic_cert_tree_; QUICConnectionHandler quic_conn_handler_; +#endif // ENABLE_HTTP3 #ifndef HAVE_ATOMIC_STD_SHARED_PTR std::mutex ticket_keys_m_; From 7de71b29a0530b0db81bea3d7e8909612e2d0899 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 22 Aug 2021 18:07:52 +0900 Subject: [PATCH 112/124] Update doc --- README.rst | 45 ++++++++++++++++----------------------------- 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/README.rst b/README.rst index 2063d404..745f4c12 100644 --- a/README.rst +++ b/README.rst @@ -16,35 +16,6 @@ An experimental high level C++ library is also available. We have Python bindings of this library, but we do not have full code coverage yet. -Running h2load against HTTP/3 server ------------------------------------- - -In order to build h2load with HTTP/3 support, you have to build -ngtcp2, nghttp3 and my patched OpenSSL. -https://github.com/ngtcp2/ngtcp2#build-from-git describes how to build -these three software. - -To run h2load against HTTP/3 server, specify h3 ALPN with -``--npn-list`` option like so: - -.. code-block:: text - - $ h2load --npn-list h3 https://127.0.0.1:4433 - -You can use Dockerfile to skip the tedious build steps to manually -pull and build dependencies. In order to build Docker image, do this: - -.. code-block:: text - - $ cd docker - $ docker build -t nghttp2-quic . - -Run h2load: - -.. code-block:: text - - $ docker run --rm -it --network=host nghttp2-quic /usr/local/bin/h2load --npn-list h3 https://127.0.0.1:4433 - Development Status ------------------ @@ -174,6 +145,14 @@ minimizes the risk of private key leakage when serious bug like Heartbleed is exploited. The neverbleed is disabled by default. To enable it, use ``--with-neverbleed`` configure option. +To enable the experimental HTTP/3 support for h2load and nghttpx, the +following libraries are required: + +* `OpenSSL with QUIC support + `_ +* `ngtcp2 `_ +* `nghttp3 `_ + Compiling libnghttp2 C source code requires a C99 compiler. gcc 4.8 is known to be adequate. In order to compile the C++ source code, gcc >= 6.0 or clang >= 6.0 is required. C++ source code requires C++14 @@ -904,6 +883,14 @@ threads to avoid saturating a single core on client side. considered a DOS attack. Please only use it against your private servers. +If the experimental HTTP/3 is enabled, h2load can send requests to +HTTP/3 server. To do this, specify ``h3`` to ``--npn-list`` option +like so: + +.. code-block:: text + + $ h2load --npn-list h3 https://127.0.0.1:4433 + HPACK tools ----------- From 102d960106ffcd7d3980039f9e12e9a87a492998 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 22 Aug 2021 19:01:30 +0900 Subject: [PATCH 113/124] nghttpx: Compile without UDP_SEGMENT --- src/shrpx_http3_upstream.cc | 18 ++++++++++++++++++ src/shrpx_quic.cc | 9 +++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/shrpx_http3_upstream.cc b/src/shrpx_http3_upstream.cc index da080c0a..92a8aad6 100644 --- a/src/shrpx_http3_upstream.cc +++ b/src/shrpx_http3_upstream.cc @@ -638,6 +638,7 @@ int Http3Upstream::write_streams() { bufpos += nwrite; +#ifdef UDP_SEGMENT if (pktcnt == 0) { ngtcp2_path_copy(&prev_ps.path, &ps.path); } else if (!ngtcp2_path_eq(&prev_ps.path, &ps.path)) { @@ -674,6 +675,23 @@ int Http3Upstream::write_streams() { return 0; } +#else // !UDP_SEGMENT + quic_send_packet(static_cast(ps.path.user_data), + ps.path.remote.addr, ps.path.remote.addrlen, + ps.path.local.addr, ps.path.local.addrlen, buf.data(), + bufpos - buf.data(), 0); + + if (++pktcnt == max_pktcnt) { + ngtcp2_conn_update_pkt_tx_time(conn_, ts); + reset_idle_timer(); + + handler_->signal_write(); + + return 0; + } + + bufpos = buf.data(); +#endif // !UDP_SEGMENT } return 0; diff --git a/src/shrpx_quic.cc b/src/shrpx_quic.cc index 09f0683f..a1ea2a32 100644 --- a/src/shrpx_quic.cc +++ b/src/shrpx_quic.cc @@ -210,8 +210,11 @@ int quic_send_packet(const UpstreamAddr *faddr, const sockaddr *remote_sa, msg.msg_iov = &msg_iov; msg.msg_iovlen = 1; - uint8_t - msg_ctrl[CMSG_SPACE(sizeof(uint16_t)) + CMSG_SPACE(sizeof(in6_pktinfo))]; + uint8_t msg_ctrl[ +#ifdef UDP_SEGMENT + CMSG_SPACE(sizeof(uint16_t)) + +#endif // UDP_SEGMENT + CMSG_SPACE(sizeof(in6_pktinfo))]; memset(msg_ctrl, 0, sizeof(msg_ctrl)); @@ -251,6 +254,7 @@ int quic_send_packet(const UpstreamAddr *faddr, const sockaddr *remote_sa, assert(0); } +#ifdef UDP_SEGMENT if (gso_size && datalen > gso_size) { controllen += CMSG_SPACE(sizeof(uint16_t)); cm = CMSG_NXTHDR(&msg, cm); @@ -259,6 +263,7 @@ int quic_send_packet(const UpstreamAddr *faddr, const sockaddr *remote_sa, cm->cmsg_len = CMSG_LEN(sizeof(uint16_t)); *(reinterpret_cast(CMSG_DATA(cm))) = gso_size; } +#endif // UDP_SEGMENT msg.msg_controllen = controllen; From a619e7a88c6b6be4d3862a936f27cecffacf9f2b Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 22 Aug 2021 19:49:55 +0900 Subject: [PATCH 114/124] Define UDP_SEGMENT if linux/udp.h has it --- configure.ac | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/configure.ac b/configure.ac index 37256518..cc328e15 100644 --- a/configure.ac +++ b/configure.ac @@ -910,6 +910,31 @@ AC_CHECK_DECLS([initgroups], [], [], [[ #include ]]) +have_netinet_udp_h_udp_segment=no +AC_CHECK_DECL([UDP_SEGMENT], [have_netinet_udp_h_udp_segment=yes], + [have_netinet_udp_h_udp_segment=no], [[ + #include +]]) + +if test "x$have_netinet_udp_h_udp_segment" = "xno"; then + have_linux_udp_h_udp_segment=no + AC_COMPILE_IFELSE([AC_LANG_PROGRAM( + [[ + #include + ]], + [[ + #if UDP_SEGMENT != 103 + exit(1) + #endif + ]])], + [have_linux_udp_h_udp_segment=yes], + [have_linux_udp_h_udp_segment=no]) + + if test "x$have_linux_udp_h_udp_segment" = "xyes"; then + EXTRA_DEFS="$EXTRA_DEFS -DUDP_SEGMENT=103" + fi +fi + save_CFLAGS=$CFLAGS save_CXXFLAGS=$CXXFLAGS @@ -1077,6 +1102,8 @@ AC_MSG_NOTICE([summary of build options: CXX1XCXXFLAGS: ${CXX1XCXXFLAGS} EXTRACFLAG: ${EXTRACFLAG} LIBS: ${LIBS} + DEFS: ${DEFS} + EXTRA_DEFS: ${EXTRA_DEFS} Library: Shared: ${enable_shared} Static: ${enable_static} From 4f4dce82c6510040794d381cde1d155c5e3542bb Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 22 Aug 2021 21:16:23 +0900 Subject: [PATCH 115/124] Update h2load Dockerfile --- docker/Dockerfile | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 3f5cf706..00040724 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -5,7 +5,7 @@ RUN apt-get update && \ git g++ make binutils autoconf automake autotools-dev libtool \ pkg-config \ zlib1g-dev libev-dev libjemalloc-dev ruby-dev libc-ares-dev bison && \ - git clone --depth 1 -b OpenSSL_1_1_1g-quic-draft-33 https://github.com/tatsuhiro-t/openssl && \ + git clone --depth 1 -b OpenSSL_1_1_1k+quic https://github.com/quictls/openssl && \ cd openssl && ./config enable-tls1_3 --openssldir=/etc/ssl && make -j$(nproc) && make install_sw && cd .. && rm -rf openssl && \ git clone --depth 1 https://github.com/ngtcp2/nghttp3 && \ cd nghttp3 && autoreconf -i && \ @@ -13,13 +13,16 @@ RUN apt-get update && \ make -j$(nproc) && make install-strip && cd .. && rm -rf nghttp3 && \ git clone --depth 1 https://github.com/ngtcp2/ngtcp2 && \ cd ngtcp2 && autoreconf -i && \ - ./configure && \ + ./configure --enable-lib-only \ + LIBTOOL_LDFLAGS="-static-libtool-libs" \ + OPENSSL_LIBS="-l:libssl.a -l:libcrypto.a -ldl -lpthread" && \ make -j$(nproc) && make install-strip && cd .. && rm -rf ngtcp2 && \ - git clone --depth 1 -b quic https://github.com/nghttp2/nghttp2.git && \ + git clone --depth 1 https://github.com/nghttp2/nghttp2.git && \ cd nghttp2 && \ git submodule update --init && autoreconf -i && \ ./configure --disable-examples --disable-hpack-tools \ --disable-python-bindings --with-mruby --with-neverbleed \ + --enable-http3 \ LIBTOOL_LDFLAGS="-static-libtool-libs" \ LIBS="-ldl -pthread" \ OPENSSL_LIBS="-l:libssl.a -l:libcrypto.a" \ From e7ef2bec8b6f984b5d814f4dfe38ee8a500f1fd9 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 22 Aug 2021 21:16:44 +0900 Subject: [PATCH 116/124] Rename h2load Dockerfile to Dockerfile-h2load-http3 --- docker/{Dockerfile => Dockerfile-h2load-http3} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docker/{Dockerfile => Dockerfile-h2load-http3} (100%) diff --git a/docker/Dockerfile b/docker/Dockerfile-h2load-http3 similarity index 100% rename from docker/Dockerfile rename to docker/Dockerfile-h2load-http3 From 0dcdf7ae21d9f60460a87483a85366900777837b Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 22 Aug 2021 21:31:16 +0900 Subject: [PATCH 117/124] Run http3 build on CI --- .github/workflows/build.yml | 43 +++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a568c8f1..231172ad 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,6 +11,10 @@ jobs: os: [ubuntu-20.04, macos-10.15] compiler: [gcc, clang] buildtool: [autotools, cmake] + http3: [http3, no-http3] + exclude: + - buildtool: cmake + http3: http3 steps: - uses: actions/checkout@v2 @@ -74,6 +78,41 @@ jobs: run: | echo 'CC=gcc' >> $GITHUB_ENV echo 'CXX=g++' >> $GITHUB_ENV + - name: Build quictls/openssl + if: matrix.http3 == 'http3' + run: | + git clone --depth 1 -b OpenSSL_1_1_1k+quic https://github.com/quictls/openssl + cd openssl + ./config enable-tls1_3 --prefix=$PWD/build + make -j$(nproc) + make install_sw + - name: Build nghttp3 + if: matrix.http3 == 'http3' + run: | + git clone https://github.com/ngtcp2/nghttp3 + cd nghttp3 + autoreconf -i + ./configure --prefix=$PWD/build --enable-lib-only + make -j$(nproc) check + make install + - name: Build ngtcp2 + if: matrix.http3 == 'http3' + run: | + git clone https://github.com/ngtcp2/ngtcp2 + cd ngtcp2 + autoreconf -i + ./configure --prefix=$PWD/build --enable-lib-only PKG_CONFIG_PATH="../openssl/build/lib/pkgconfig" + make -j$(nproc) check + make install + - name: Setup extra environment variables for HTTP/3 + if: matrix.http3 == 'http3' + run: | + PKG_CONFIG_PATH="$PWD/openssl/build/lib/pkgconfig:$PWD/nghttp3/build/lib/pkgconfig:$PWD/ngtcp2/build/lib/pkgconfig:$PKG_CONFIG_PATH" + LDFLAGS="$LDFLAGS -Wl,-rpath,$PWD/openssl/build/lib" + + echo 'PKG_CONFIG_PATH='"$PKG_CONFIG_PATH" >> $GITHUB_ENV + echo 'LDFLAGS='"$LDFLAGS" >> $GITHUB_ENV + echo 'EXTRA_AUTOTOOLS_OPTS=--enable-http3' >> $GITHUB_ENV - name: Setup git submodules run: | git submodule update --init @@ -81,7 +120,7 @@ jobs: if: matrix.buildtool == 'autotools' run: | autoreconf -i - ./configure --enable-werror --with-mruby + ./configure --enable-werror --with-mruby $EXTRA_AUTOTOOLS_OPTS - name: Configure cmake if: matrix.buildtool == 'cmake' run: | @@ -90,7 +129,7 @@ jobs: if: matrix.buildtool == 'autotools' run: | make distcheck \ - DISTCHECK_CONFIGURE_FLAGS="--with-mruby --with-neverbleed --enable-werror CPPFLAGS=\"$CPPFLAGS\" LDFLAGS=\"$LDFLAGS\"" + DISTCHECK_CONFIGURE_FLAGS="--with-mruby --with-neverbleed --enable-werror $EXTRA_AUTOTOOLS_OPTS CPPFLAGS=\"$CPPFLAGS\" LDFLAGS=\"$LDFLAGS\"" - name: Build nghttp2 with cmake if: matrix.buildtool == 'cmake' run: | From 6f243108e917c5d43ef9e5ac6d887fe774f39f4d Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 22 Aug 2021 22:17:19 +0900 Subject: [PATCH 118/124] nghttpx: Fix CI build error --- src/shrpx_http3_upstream.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/shrpx_http3_upstream.cc b/src/shrpx_http3_upstream.cc index 92a8aad6..adc71a7d 100644 --- a/src/shrpx_http3_upstream.cc +++ b/src/shrpx_http3_upstream.cc @@ -136,13 +136,14 @@ void log_printf(void *user_data, const char *fmt, ...) { auto nwrite = vsnprintf(buf.data(), buf.size(), fmt, ap); va_end(ap); - if (nwrite >= buf.size()) { + if (static_cast(nwrite) >= buf.size()) { nwrite = buf.size() - 1; } buf[nwrite++] = '\n'; - write(fileno(stderr), buf.data(), nwrite); + while (write(fileno(stderr), buf.data(), nwrite) == -1 && errno == EINTR) + ; } } // namespace From cbd45478e057d96e6a15f097568350d63100ebbe Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 22 Aug 2021 23:09:48 +0900 Subject: [PATCH 119/124] Cleanup flags/libs order --- src/Makefile.am | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index eb763fb6..a818634b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -44,11 +44,11 @@ AM_CPPFLAGS = \ @JEMALLOC_CFLAGS@ \ @LIBXML2_CFLAGS@ \ @LIBEV_CFLAGS@ \ - @OPENSSL_CFLAGS@ \ - @LIBCARES_CFLAGS@ \ @LIBNGHTTP3_CFLAGS@ \ @LIBNGTCP2_CRYPTO_OPENSSL_CFLAGS@ \ @LIBNGTCP2_CFLAGS@ \ + @OPENSSL_CFLAGS@ \ + @LIBCARES_CFLAGS@ \ @JANSSON_CFLAGS@ \ @ZLIB_CFLAGS@ \ @EXTRA_DEFS@ \ @@ -61,11 +61,11 @@ LDADD = $(top_builddir)/lib/libnghttp2.la \ @JEMALLOC_LIBS@ \ @LIBXML2_LIBS@ \ @LIBEV_LIBS@ \ - @OPENSSL_LIBS@ \ - @LIBCARES_LIBS@ \ @LIBNGHTTP3_LIBS@ \ @LIBNGTCP2_CRYPTO_OPENSSL_LIBS@ \ @LIBNGTCP2_LIBS@ \ + @OPENSSL_LIBS@ \ + @LIBCARES_LIBS@ \ @SYSTEMD_LIBS@ \ @JANSSON_LIBS@ \ @ZLIB_LIBS@ \ From c07a0d90051e2d6b87511a05c523984a98633dc3 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 22 Aug 2021 23:10:36 +0900 Subject: [PATCH 120/124] Allow HTTP/3 in cmake build --- CMakeLists.txt | 22 ++++++++++++ CMakeOptions.txt | 1 + cmake/FindLibnghttp3.cmake | 41 ++++++++++++++++++++++ cmake/FindLibngtcp2.cmake | 41 ++++++++++++++++++++++ cmake/FindLibngtcp2_crypto_openssl.cmake | 43 ++++++++++++++++++++++++ cmakeconfig.h.in | 3 ++ src/CMakeLists.txt | 24 ++++++++++++- 7 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 cmake/FindLibnghttp3.cmake create mode 100644 cmake/FindLibngtcp2.cmake create mode 100644 cmake/FindLibngtcp2_crypto_openssl.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 579d0365..3f9f7614 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,6 +61,9 @@ find_package(OpenSSL 1.0.1) find_package(Libev 4.11) find_package(Libcares 1.7.5) find_package(ZLIB 1.2.3) +find_package(Libngtcp2 0.0.0) +find_package(Libngtcp2_crypto_openssl 0.0.0) +find_package(Libnghttp3 0.0.0) if(OPENSSL_FOUND AND LIBEV_FOUND AND ZLIB_FOUND) set(ENABLE_APP_DEFAULT ON) else() @@ -182,9 +185,18 @@ if(HAVE_CUNIT) endif() # openssl (for src) +include(CheckSymbolExists) set(HAVE_OPENSSL ${OPENSSL_FOUND}) if(OPENSSL_FOUND) set(OPENSSL_INCLUDE_DIRS ${OPENSSL_INCLUDE_DIR}) + cmake_push_check_state() + set(CMAKE_REQUIRED_INCLUDES "${OPENSSL_INCLUDE_DIR}") + set(CMAKE_REQUIRED_LIBRARIES "${OPENSSL_LIBRARIES}") + check_symbol_exists(SSL_is_quic "openssl/ssl.h" HAVE_SSL_IS_QUIC) + if(NOT HAVE_SSL_IS_QUIC) + message(WARNING "OpenSSL in ${OPENSSL_LIBRARIES} dose not have SSL_is_quic. HTTP/3 support cannot be enabled") + endif() + cmake_pop_check_state() else() set(OPENSSL_INCLUDE_DIRS "") set(OPENSSL_LIBRARIES "") @@ -228,6 +240,12 @@ if(ENABLE_APP AND NOT (ZLIB_FOUND AND OPENSSL_FOUND AND LIBEV_FOUND)) message(FATAL_ERROR "Applications were requested (ENABLE_APP=1) but dependencies are not met.") endif() +# HTTP/3 requires quictls/openssl, libngtcp2, libngtcp2_crypto_openssl +# and libnghttp3. +if(ENABLE_HTTP3 AND NOT (HAVE_SSL_IS_QUIC AND LIBNGTCP2_FOUND AND LIBNGTCP2_CRYPTO_OPENSSL_FOUND AND LIBNGHTTP3_FOUND)) + message(FATAL_ERROR "HTTP/3 was requested (ENABLE_HTTP3=1) but dependencies are not met.") +endif() + # HPACK tools requires jansson if(ENABLE_HPACK_TOOLS AND NOT HAVE_JANSSON) message(FATAL_ERROR "HPACK tools were requested (ENABLE_HPACK_TOOLS=1) but dependencies are not met.") @@ -503,6 +521,9 @@ message(STATUS "summary of build options: Libxml2: ${HAVE_LIBXML2} (LIBS='${LIBXML2_LIBRARIES}') Libev: ${HAVE_LIBEV} (LIBS='${LIBEV_LIBRARIES}') Libc-ares: ${HAVE_LIBCARES} (LIBS='${LIBCARES_LIBRARIES}') + Libngtcp2: ${HAVE_LIBNGTCP2} (LIBS='${LIBNGTCP2_LIBRARIES}') + Libngtcp2_crypto_openssl: ${HAVE_LIBNGTCP2_CRYPTO_OPENSSL} (LIBS='${LIBNGTCP2_CRYPTO_OPENSSL_LIBRARIES}') + Libnghttp3: ${HAVE_LIBNGHTTP3} (LIBS='${LIBNGHTTP3_LIBRARIES}') Libevent(SSL): ${HAVE_LIBEVENT_OPENSSL} (LIBS='${LIBEVENT_OPENSSL_LIBRARIES}') Jansson: ${HAVE_JANSSON} (LIBS='${JANSSON_LIBRARIES}') Jemalloc: ${HAVE_JEMALLOC} (LIBS='${JEMALLOC_LIBRARIES}') @@ -521,6 +542,7 @@ message(STATUS "summary of build options: Examples: ${ENABLE_EXAMPLES} Python bindings:${ENABLE_PYTHON_BINDINGS} Threading: ${ENABLE_THREADS} + HTTP/3(EXPERIMENTAL): ${ENABLE_HTTP3} ") if(ENABLE_LIB_ONLY_DISABLED_OTHERS) message("Only the library will be built. To build other components " diff --git a/CMakeOptions.txt b/CMakeOptions.txt index a8332bf0..754428a2 100644 --- a/CMakeOptions.txt +++ b/CMakeOptions.txt @@ -17,6 +17,7 @@ option(ENABLE_LIB_ONLY "Build libnghttp2 only. This is a short hand for -DENAB option(ENABLE_STATIC_LIB "Build libnghttp2 in static mode also") option(ENABLE_SHARED_LIB "Build libnghttp2 as a shared library" ON) option(ENABLE_STATIC_CRT "Build libnghttp2 against the MS LIBCMT[d]") +option(ENABLE_HTTP3 "Enable HTTP/3 support" OFF) option(WITH_LIBXML2 "Use libxml2" ${WITH_LIBXML2_DEFAULT}) diff --git a/cmake/FindLibnghttp3.cmake b/cmake/FindLibnghttp3.cmake new file mode 100644 index 00000000..ecd01f6c --- /dev/null +++ b/cmake/FindLibnghttp3.cmake @@ -0,0 +1,41 @@ +# - Try to find libnghttp3 +# Once done this will define +# LIBNGHTTP3_FOUND - System has libnghttp3 +# LIBNGHTTP3_INCLUDE_DIRS - The libnghttp3 include directories +# LIBNGHTTP3_LIBRARIES - The libraries needed to use libnghttp3 + +find_package(PkgConfig QUIET) +pkg_check_modules(PC_LIBNGHTTP3 QUIET libnghttp3) + +find_path(LIBNGHTTP3_INCLUDE_DIR + NAMES nghttp3/nghttp3.h + HINTS ${PC_LIBNGHTTP3_INCLUDE_DIRS} +) +find_library(LIBNGHTTP3_LIBRARY + NAMES nghttp3 + HINTS ${PC_LIBNGHTTP3_LIBRARY_DIRS} +) + +if(LIBNGHTTP3_INCLUDE_DIR) + set(_version_regex "^#define[ \t]+NGHTTP3_VERSION[ \t]+\"([^\"]+)\".*") + file(STRINGS "${LIBNGHTTP3_INCLUDE_DIR}/nghttp3/version.h" + LIBNGHTTP3_VERSION REGEX "${_version_regex}") + string(REGEX REPLACE "${_version_regex}" "\\1" + LIBNGHTTP3_VERSION "${LIBNGHTTP3_VERSION}") + unset(_version_regex) +endif() + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set LIBNGHTTP3_FOUND +# to TRUE if all listed variables are TRUE and the requested version +# matches. +find_package_handle_standard_args(Libnghttp3 REQUIRED_VARS + LIBNGHTTP3_LIBRARY LIBNGHTTP3_INCLUDE_DIR + VERSION_VAR LIBNGHTTP3_VERSION) + +if(LIBNGHTTP3_FOUND) + set(LIBNGHTTP3_LIBRARIES ${LIBNGHTTP3_LIBRARY}) + set(LIBNGHTTP3_INCLUDE_DIRS ${LIBNGHTTP3_INCLUDE_DIR}) +endif() + +mark_as_advanced(LIBNGHTTP3_INCLUDE_DIR LIBNGHTTP3_LIBRARY) diff --git a/cmake/FindLibngtcp2.cmake b/cmake/FindLibngtcp2.cmake new file mode 100644 index 00000000..c6701149 --- /dev/null +++ b/cmake/FindLibngtcp2.cmake @@ -0,0 +1,41 @@ +# - Try to find libngtcp2 +# Once done this will define +# LIBNGTCP2_FOUND - System has libngtcp2 +# LIBNGTCP2_INCLUDE_DIRS - The libngtcp2 include directories +# LIBNGTCP2_LIBRARIES - The libraries needed to use libngtcp2 + +find_package(PkgConfig QUIET) +pkg_check_modules(PC_LIBNGTCP2 QUIET libngtcp2) + +find_path(LIBNGTCP2_INCLUDE_DIR + NAMES ngtcp2/ngtcp2.h + HINTS ${PC_LIBNGTCP2_INCLUDE_DIRS} +) +find_library(LIBNGTCP2_LIBRARY + NAMES ngtcp2 + HINTS ${PC_LIBNGTCP2_LIBRARY_DIRS} +) + +if(LIBNGTCP2_INCLUDE_DIR) + set(_version_regex "^#define[ \t]+NGTCP2_VERSION[ \t]+\"([^\"]+)\".*") + file(STRINGS "${LIBNGTCP2_INCLUDE_DIR}/ngtcp2/version.h" + LIBNGTCP2_VERSION REGEX "${_version_regex}") + string(REGEX REPLACE "${_version_regex}" "\\1" + LIBNGTCP2_VERSION "${LIBNGTCP2_VERSION}") + unset(_version_regex) +endif() + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set LIBNGTCP2_FOUND +# to TRUE if all listed variables are TRUE and the requested version +# matches. +find_package_handle_standard_args(Libngtcp2 REQUIRED_VARS + LIBNGTCP2_LIBRARY LIBNGTCP2_INCLUDE_DIR + VERSION_VAR LIBNGTCP2_VERSION) + +if(LIBNGTCP2_FOUND) + set(LIBNGTCP2_LIBRARIES ${LIBNGTCP2_LIBRARY}) + set(LIBNGTCP2_INCLUDE_DIRS ${LIBNGTCP2_INCLUDE_DIR}) +endif() + +mark_as_advanced(LIBNGTCP2_INCLUDE_DIR LIBNGTCP2_LIBRARY) diff --git a/cmake/FindLibngtcp2_crypto_openssl.cmake b/cmake/FindLibngtcp2_crypto_openssl.cmake new file mode 100644 index 00000000..8df78595 --- /dev/null +++ b/cmake/FindLibngtcp2_crypto_openssl.cmake @@ -0,0 +1,43 @@ +# - Try to find libngtcp2_crypto_openssl +# Once done this will define +# LIBNGTCP2_CRYPTO_OPENSSL_FOUND - System has libngtcp2_crypto_openssl +# LIBNGTCP2_CRYPTO_OPENSSL_INCLUDE_DIRS - The libngtcp2_crypto_openssl include directories +# LIBNGTCP2_CRYPTO_OPENSSL_LIBRARIES - The libraries needed to use libngtcp2_crypto_openssl + +find_package(PkgConfig QUIET) +pkg_check_modules(PC_LIBNGTCP2_CRYPTO_OPENSSL QUIET libngtcp2_crypto_openssl) + +find_path(LIBNGTCP2_CRYPTO_OPENSSL_INCLUDE_DIR + NAMES ngtcp2/ngtcp2_crypto_openssl.h + HINTS ${PC_LIBNGTCP2_CRYPTO_OPENSSL_INCLUDE_DIRS} +) +find_library(LIBNGTCP2_CRYPTO_OPENSSL_LIBRARY + NAMES ngtcp2_crypto_openssl + HINTS ${PC_LIBNGTCP2_CRYPTO_OPENSSL_LIBRARY_DIRS} +) + +if(LIBNGTCP2_CRYPTO_OPENSSL_INCLUDE_DIR) + set(_version_regex "^#define[ \t]+NGTCP2_VERSION[ \t]+\"([^\"]+)\".*") + file(STRINGS "${LIBNGTCP2_CRYPTO_OPENSSL_INCLUDE_DIR}/ngtcp2/version.h" + LIBNGTCP2_CRYPTO_OPENSSL_VERSION REGEX "${_version_regex}") + string(REGEX REPLACE "${_version_regex}" "\\1" + LIBNGTCP2_CRYPTO_OPENSSL_VERSION "${LIBNGTCP2_CRYPTO_OPENSSL_VERSION}") + unset(_version_regex) +endif() + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set +# LIBNGTCP2_CRYPTO_OPENSSL_FOUND to TRUE if all listed variables are +# TRUE and the requested version matches. +find_package_handle_standard_args(Libngtcp2_crypto_openssl REQUIRED_VARS + LIBNGTCP2_CRYPTO_OPENSSL_LIBRARY + LIBNGTCP2_CRYPTO_OPENSSL_INCLUDE_DIR + VERSION_VAR LIBNGTCP2_CRYPTO_OPENSSL_VERSION) + +if(LIBNGTCP2_CRYPTO_OPENSSL_FOUND) + set(LIBNGTCP2_CRYPTO_OPENSSL_LIBRARIES ${LIBNGTCP2_CRYPTO_OPENSSL_LIBRARY}) + set(LIBNGTCP2_CRYPTO_OPENSSL_INCLUDE_DIRS ${LIBNGTCP2_CRYPTO_OPENSSL_INCLUDE_DIR}) +endif() + +mark_as_advanced(LIBNGTCP2_CRYPTO_OPENSSL_INCLUDE_DIR + LIBNGTCP2_CRYPTO_OPENSSL_LIBRARY) diff --git a/cmakeconfig.h.in b/cmakeconfig.h.in index 6c96f70a..290b99e5 100644 --- a/cmakeconfig.h.in +++ b/cmakeconfig.h.in @@ -78,3 +78,6 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_UNISTD_H 1 + +/* Define to 1 if HTTP/3 is enabled. */ +#cmakedefine ENABLE_HTTP3 1 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8ec64d0e..f65a2925 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -15,6 +15,9 @@ include_directories( ${JEMALLOC_INCLUDE_DIRS} ${LIBXML2_INCLUDE_DIRS} ${LIBEV_INCLUDE_DIRS} + ${LIBNGHTTP3_INCLUDE_DIRS} + ${LIBNGTCP2_INCLUDE_DIRS} + ${LIBNGTCP2_CRYPTO_OPENSSL_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIRS} ${LIBCARES_INCLUDE_DIRS} ${JANSSON_INCLUDE_DIRS} @@ -27,6 +30,9 @@ link_libraries( ${JEMALLOC_LIBRARIES} ${LIBXML2_LIBRARIES} ${LIBEV_LIBRARIES} + ${LIBNGHTTP3_LIBRARIES} + ${LIBNGTCP2_LIBRARIES} + ${LIBNGTCP2_CRYPTO_OPENSSL_LIBRARIES} ${OPENSSL_LIBRARIES} ${LIBCARES_LIBRARIES} ${JANSSON_LIBRARIES} @@ -67,7 +73,13 @@ if(ENABLE_APP) h2load_http2_session.cc h2load_http1_session.cc ) - + if(ENABLE_HTTP3) + list(APPEND H2LOAD_SOURCES + h2load_http3_session.cc + h2load_quic.cc + quic.cc + ) + endif() # Common libnhttpx sources (used for nghttpx and unit tests) set(NGHTTPX_SRCS @@ -120,6 +132,16 @@ if(ENABLE_APP) shrpx_mruby_module_response.cc ) endif() + if(ENABLE_HTTP3) + list(APPEND NGHTTPX_SRCS + shrpx_quic.cc + shrpx_quic_listener.cc + shrpx_quic_connection_handler.cc + shrpx_http3_upstream.cc + http3.cc + quic.cc + ) + endif() add_library(nghttpx_static STATIC ${NGHTTPX_SRCS}) set_target_properties(nghttpx_static PROPERTIES ARCHIVE_OUTPUT_NAME nghttpx) From 9fe08d391302cb0ed68b8c56811ef2daa6793df5 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 22 Aug 2021 23:24:18 +0900 Subject: [PATCH 121/124] nghttpx: Fix build without mruby --- src/shrpx_http3_upstream.cc | 3 +++ src/shrpx_http3_upstream.h | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/shrpx_http3_upstream.cc b/src/shrpx_http3_upstream.cc index adc71a7d..c4dbbd86 100644 --- a/src/shrpx_http3_upstream.cc +++ b/src/shrpx_http3_upstream.cc @@ -35,6 +35,9 @@ #include "shrpx_quic.h" #include "shrpx_worker.h" #include "shrpx_http.h" +#ifdef HAVE_MRUBY +# include "shrpx_mruby.h" +#endif // HAVE_MRUBY #include "http3.h" #include "util.h" diff --git a/src/shrpx_http3_upstream.h b/src/shrpx_http3_upstream.h index 05933a14..72816f9a 100644 --- a/src/shrpx_http3_upstream.h +++ b/src/shrpx_http3_upstream.h @@ -32,7 +32,6 @@ #include "shrpx_upstream.h" #include "shrpx_downstream_queue.h" -#include "shrpx_mruby.h" #include "quic.h" #include "network.h" From 29694e2945debb4dac2bf9f98f69bb677284db5d Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 22 Aug 2021 23:28:10 +0900 Subject: [PATCH 122/124] nghttpx: Fix build error regarding RAND_bytes --- src/shrpx.cc | 1 + src/shrpx_quic_connection_handler.cc | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/shrpx.cc b/src/shrpx.cc index a619fe77..f9fc4739 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -71,6 +71,7 @@ #include #include +#include #include #include diff --git a/src/shrpx_quic_connection_handler.cc b/src/shrpx_quic_connection_handler.cc index c09a4650..d83f0822 100644 --- a/src/shrpx_quic_connection_handler.cc +++ b/src/shrpx_quic_connection_handler.cc @@ -24,6 +24,8 @@ */ #include "shrpx_quic_connection_handler.h" +#include + #include #include "shrpx_worker.h" From cdb6d199898c54a512d86c4a5406ed8a4277caab Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 22 Aug 2021 23:31:43 +0900 Subject: [PATCH 123/124] Enable HTTP/3 build for cmake on CI --- .github/workflows/build.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 231172ad..d17fe82c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,9 +12,6 @@ jobs: compiler: [gcc, clang] buildtool: [autotools, cmake] http3: [http3, no-http3] - exclude: - - buildtool: cmake - http3: http3 steps: - uses: actions/checkout@v2 @@ -113,6 +110,7 @@ jobs: echo 'PKG_CONFIG_PATH='"$PKG_CONFIG_PATH" >> $GITHUB_ENV echo 'LDFLAGS='"$LDFLAGS" >> $GITHUB_ENV echo 'EXTRA_AUTOTOOLS_OPTS=--enable-http3' >> $GITHUB_ENV + echo 'EXTRA_CMAKE_OPTS=-DENABLE_HTTP3=ON' >> $GITHUB_ENV - name: Setup git submodules run: | git submodule update --init @@ -124,7 +122,7 @@ jobs: - name: Configure cmake if: matrix.buildtool == 'cmake' run: | - cmake -DENABLE_WERROR=1 -DWITH_MRUBY=1 -DWITH_NEVERBLEED=1 -DCPPFLAGS="$CPPFLAGS" -DLDFLAGS="$LDFLAGS" . + cmake -DENABLE_WERROR=1 -DWITH_MRUBY=1 -DWITH_NEVERBLEED=1 $EXTRA_CMAKE_OPTS -DCPPFLAGS="$CPPFLAGS" -DLDFLAGS="$LDFLAGS" . - name: Build nghttp2 with autotools if: matrix.buildtool == 'autotools' run: | From 50fe8e7852bf0e8409dcf7ffbdcc6b8b2fd5ed3b Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 22 Aug 2021 23:59:09 +0900 Subject: [PATCH 124/124] Check the availability of SSL_is_quic --- configure.ac | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/configure.ac b/configure.ac index cc328e15..a204c710 100644 --- a/configure.ac +++ b/configure.ac @@ -453,6 +453,25 @@ if test "x${request_openssl}" != "xno"; then [have_openssl=yes], [have_openssl=no]) if test "x${have_openssl}" = "xno"; then AC_MSG_NOTICE($OPENSSL_PKG_ERRORS) + else + save_CFLAGS="$CFLAGS" + save_LIBS="$LIBS" + CFLAGS="$OPENSSL_CFLAGS $CFLAGS" + LIBS="$OPENSSL_LIBS $LIBS" + + have_ssl_is_quic=no + AC_MSG_CHECKING([for SSL_is_quic]) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ + #include + ]], [[ + SSL *ssl = NULL; + SSL_is_quic(ssl); + ]])], + [AC_MSG_RESULT([yes]); have_ssl_is_quic=yes], + [AC_MSG_RESULT([no]); have_ssl_is_quic=no]) + + CFLAGS="$save_CFLAGS" + LIBS="$save_LIBS" fi fi @@ -670,6 +689,7 @@ AM_CONDITIONAL([ENABLE_APP], [ test "x${enable_app}" = "xyes" ]) # Check HTTP/3 support enable_http3=no if test "x${request_http3}" != "xno" && + test "x${have_ssl_is_quic}" = "xyes" && test "x${have_libngtcp2}" = "xyes" && test "x${have_libngtcp2_crypto_openssl}" = "xyes" && test "x${have_libnghttp3}" = "xyes"; then