/
githubmirror
/
nghttp2
Обзор
Документация
Войти
/
githubmirror
/
nghttp2
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/shrpx_connection.cc
935 строк
26 KB
Tatsuhiro Tsujikawa
src: Adopt size_t literal suffix
03 май 2026, 10:10
03 май 2026, 10:10
9bdb319
Код
Авторство
О чём код?
/* * nghttp2 - HTTP/2 C Library * * Copyright (c) 2015 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_connection.h" #ifdef HAVE_UNISTD_H # include <unistd.h> #endif // defined(HAVE_UNISTD_H) #include <netinet/tcp.h> #include <limits> #include "ssl_compat.h" #ifdef NGHTTP2_OPENSSL_IS_WOLFSSL # include <wolfssl/options.h> # include <wolfssl/openssl/err.h> #else // !defined(NGHTTP2_OPENSSL_IS_WOLFSSL) # include <openssl/err.h> #endif // !defined(NGHTTP2_OPENSSL_IS_WOLFSSL) #include "shrpx_tls.h" #include "shrpx_log.h" #include "memchunk.h" #include "util.h" using namespace nghttp2; using namespace std::chrono_literals; namespace shrpx { Connection::Connection(struct ev_loop *loop, int fd, SSL *ssl, MemchunkPool *mcpool, ev_tstamp write_timeout, ev_tstamp read_timeout, const RateLimitConfig &write_limit, const RateLimitConfig &read_limit, IOCb writecb, IOCb readcb, TimerCb timeoutcb, void *data, size_t tls_dyn_rec_warmup_threshold, ev_tstamp tls_dyn_rec_idle_timeout, Proto proto) : #ifdef ENABLE_HTTP3 conn_ref{nullptr, this}, #endif // defined(ENABLE_HTTP3) tls{DefaultMemchunks(mcpool)}, wlimit(loop, &wev, write_limit.rate, write_limit.burst), rlimit(loop, &rev, read_limit.rate, read_limit.burst, this), loop(loop), data(data), fd(fd), tls_dyn_rec_warmup_threshold(tls_dyn_rec_warmup_threshold), tls_dyn_rec_idle_timeout(util::duration_from(tls_dyn_rec_idle_timeout)), proto(proto), read_timeout(read_timeout) { ev_io_init(&wev, writecb, fd, EV_WRITE); ev_io_init(&rev, readcb, proto == Proto::HTTP3 ? 0 : fd, EV_READ); wev.data = this; rev.data = this; ev_timer_init(&wt, timeoutcb, 0., write_timeout); ev_timer_init(&rt, timeoutcb, 0., read_timeout); wt.data = this; rt.data = this; if (ssl) { set_ssl(ssl); } } Connection::~Connection() { disconnect(); } void Connection::disconnect() { if (tls.ssl) { if (proto != Proto::HTTP3) { SSL_set_shutdown(tls.ssl, SSL_get_shutdown(tls.ssl) | SSL_RECEIVED_SHUTDOWN); ERR_clear_error(); SSL_shutdown(tls.ssl); } // Unset app data here, so that ngtcp2_conn never be used by // libngtcp2_crypto_ossl that may be called by SSL_free. SSL_set_app_data(tls.ssl, nullptr); SSL_free(tls.ssl); tls.ssl = nullptr; tls.last_write_idle = {}; tls.warmup_writelen = 0; tls.last_writelen = 0; tls.last_readlen = 0; tls.initial_handshake_done = false; tls.reneg_started = false; tls.sct_requested = false; tls.early_data_finish = false; } if (proto != Proto::HTTP3 && fd != -1) { shutdown(fd, SHUT_WR); close(fd); fd = -1; } // Stop watchers here because they could be activated in // SSL_shutdown(). ev_timer_stop(loop, &rt); ev_timer_stop(loop, &wt); rlimit.stopw(); wlimit.stopw(); } void Connection::prepare_client_handshake() { SSL_set_connect_state(tls.ssl); // This prevents SSL_read_early_data from being called. tls.early_data_finish = true; } void Connection::prepare_server_handshake() { SSL_set_accept_state(tls.ssl); tls.server_handshake = true; } void Connection::set_ssl(SSL *ssl) { tls.ssl = ssl; SSL_set_app_data(tls.ssl, this); } std::expected<void, Error> Connection::tls_handshake() { wlimit.stopw(); ev_timer_stop(loop, &wt); if (tls.initial_handshake_done) { return write_tls_pending_handshake(); } if (SSL_get_fd(tls.ssl) == -1) { SSL_set_fd(tls.ssl, fd); } int rv; #if defined(NGHTTP2_GENUINE_OPENSSL) || \ defined(NGHTTP2_OPENSSL_IS_BORINGSSL) || \ (defined(NGHTTP2_OPENSSL_IS_WOLFSSL) && defined(WOLFSSL_EARLY_DATA)) auto &tlsconf = get_config()->tls; std::array<uint8_t, 16_k> buf; #endif // defined(NGHTTP2_GENUINE_OPENSSL) || // defined(NGHTTP2_OPENSSL_IS_BORINGSSL) || // (defined(NGHTTP2_OPENSSL_IS_WOLFSSL) && // defined(WOLFSSL_EARLY_DATA)) ERR_clear_error(); #ifdef NGHTTP2_GENUINE_OPENSSL if (!tls.server_handshake || tls.early_data_finish) { rv = SSL_do_handshake(tls.ssl); } else { for (;;) { size_t nread; rv = SSL_read_early_data(tls.ssl, buf.data(), buf.size(), &nread); if (rv == SSL_READ_EARLY_DATA_ERROR) { // If we have early data, and server sends ServerHello, assume // that handshake is completed in server side, and start // processing request. If we don't exit handshake code here, // server waits for EndOfEarlyData and Finished message from // client, which voids the purpose of 0-RTT data. The left // over of handshake is done through write_tls or read_tls. if (tlsconf.no_postpone_early_data && tls.earlybuf.rleft()) { rv = 1; } break; } if (log_enabled(INFO)) { Log{INFO} << "tls: read early data " << nread << " bytes"; } tls.earlybuf.append(buf.data(), nread); if (rv == SSL_READ_EARLY_DATA_FINISH) { if (log_enabled(INFO)) { Log{INFO} << "tls: read all early data; total " << tls.earlybuf.rleft() << " bytes"; } tls.early_data_finish = true; // The same reason stated above. if (tlsconf.no_postpone_early_data && tls.earlybuf.rleft()) { rv = 1; } else { ERR_clear_error(); rv = SSL_do_handshake(tls.ssl); } break; } } } #elif defined(NGHTTP2_OPENSSL_IS_WOLFSSL) && defined(WOLFSSL_EARLY_DATA) if (!tls.server_handshake || tls.early_data_finish) { rv = SSL_do_handshake(tls.ssl); } else { for (;;) { size_t nread = 0; rv = SSL_read_early_data(tls.ssl, buf.data(), buf.size(), &nread); if (rv < 0) { if (SSL_get_error(tls.ssl, rv) == SSL_ERROR_WANT_READ) { if (tlsconf.no_postpone_early_data && tls.earlybuf.rleft()) { rv = 1; } break; } /* It looks like we are here if there is no early data. */ tls.early_data_finish = true; ERR_clear_error(); rv = SSL_do_handshake(tls.ssl); break; } if (log_enabled(INFO)) { Log{INFO} << "tls: read early data " << nread << " bytes"; } tls.earlybuf.append(buf.data(), nread); if (rv == 0) { if (log_enabled(INFO)) { Log{INFO} << "tls: read all early data; total " << tls.earlybuf.rleft() << " bytes"; } tls.early_data_finish = true; // The same reason stated above. if (tlsconf.no_postpone_early_data && tls.earlybuf.rleft()) { rv = 1; } else { ERR_clear_error(); rv = SSL_do_handshake(tls.ssl); } break; } } } #else // !defined(NGHTTP2_GENUINE_OPENSSL) && // (!defined(NGHTTP2_OPENSSL_IS_WOLFSSL) || // !defined(WOLFSSL_EARLY_DATA)) rv = SSL_do_handshake(tls.ssl); #endif // !defined(NGHTTP2_GENUINE_OPENSSL) && // (!defined(NGHTTP2_OPENSSL_IS_WOLFSSL) || // !defined(WOLFSSL_EARLY_DATA)) if (rv <= 0) { auto err = SSL_get_error(tls.ssl, rv); switch (err) { case SSL_ERROR_WANT_READ: break; case SSL_ERROR_WANT_WRITE: wlimit.startw(); ev_timer_again(loop, &wt); break; case SSL_ERROR_SSL: { if (log_enabled(INFO)) { Log{INFO} << "tls: handshake libssl error: " << ERR_error_string(ERR_get_error(), nullptr); } return std::unexpected{Error::NETWORK}; } default: if (log_enabled(INFO)) { Log{INFO} << "tls: handshake libssl error " << err; } return std::unexpected{Error::NETWORK}; } } if (rv != 1) { if (log_enabled(INFO)) { Log{INFO} << "tls: handshake is still in progress"; } return std::unexpected{Error::TLS_HANDSHAKE_INPROGRESS}; } #ifdef NGHTTP2_OPENSSL_IS_BORINGSSL if (!tlsconf.no_postpone_early_data && SSL_in_early_data(tls.ssl) && SSL_in_init(tls.ssl)) { auto nread = SSL_read(tls.ssl, buf.data(), buf.size()); if (nread <= 0) { auto err = SSL_get_error(tls.ssl, nread); switch (err) { case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: break; case SSL_ERROR_ZERO_RETURN: return std::unexpected{Error::RECV_EOF}; case SSL_ERROR_SSL: if (log_enabled(INFO)) { Log{INFO} << "SSL_read: " << ERR_error_string(ERR_get_error(), nullptr); } return std::unexpected{Error::NETWORK}; default: if (log_enabled(INFO)) { Log{INFO} << "SSL_read: SSL_get_error returned " << err; } return std::unexpected{Error::NETWORK}; } } else { tls.earlybuf.append(buf.data(), static_cast<size_t>(nread)); } if (SSL_in_init(tls.ssl)) { return std::unexpected{Error::TLS_HANDSHAKE_INPROGRESS}; } } #endif // defined(NGHTTP2_OPENSSL_IS_BORINGSSL) // Handshake was done if (auto rv = check_http2_requirement(); !rv) { return rv; } tls.initial_handshake_done = true; return write_tls_pending_handshake(); } std::expected<void, Error> Connection::write_tls_pending_handshake() { #ifdef NGHTTP2_OPENSSL_IS_BORINGSSL if (!SSL_in_init(tls.ssl)) { // This will send a session ticket. auto nwrite = SSL_write(tls.ssl, "", 0); if (nwrite < 0) { auto err = SSL_get_error(tls.ssl, nwrite); switch (err) { case SSL_ERROR_WANT_READ: if (log_enabled(INFO)) { Log{INFO} << "Close connection due to TLS renegotiation"; } return std::unexpected{Error::NETWORK}; case SSL_ERROR_WANT_WRITE: break; case SSL_ERROR_SSL: if (log_enabled(INFO)) { Log{INFO} << "SSL_write: " << ERR_error_string(ERR_get_error(), nullptr); } return std::unexpected{Error::NETWORK}; default: if (log_enabled(INFO)) { Log{INFO} << "SSL_write: SSL_get_error returned " << err; } return std::unexpected{Error::NETWORK}; } } } #endif // defined(NGHTTP2_OPENSSL_IS_BORINGSSL) // We have to start read watcher, since later stage of code expects // this. rlimit.startw(); // We may have whole request in tls.rbuf. This means that we don't // get notified further read event. This is especially true for // HTTP/1.1. handle_tls_pending_read(); if (log_enabled(INFO)) { Log{INFO} << "SSL/TLS handshake completed"; nghttp2::tls::TLSSessionInfo tls_info{}; if (nghttp2::tls::get_tls_session_info(&tls_info, tls.ssl)) { Log{INFO} << "cipher=" << tls_info.cipher << " protocol=" << tls_info.protocol << " resumption=" << (tls_info.session_reused ? "yes" : "no") << " session_id=" << util::format_hex(std::span{tls_info.session_id, tls_info.session_id_length}); } } return {}; } std::expected<void, Error> Connection::check_http2_requirement() { const unsigned char *next_proto = nullptr; unsigned int next_proto_len; SSL_get0_alpn_selected(tls.ssl, &next_proto, &next_proto_len); if (next_proto == nullptr || !util::check_h2_is_selected(as_string_view(next_proto, next_proto_len))) { return {}; } if (!nghttp2::tls::check_http2_tls_version(tls.ssl)) { if (log_enabled(INFO)) { Log{INFO} << "TLSv1.2 was not negotiated. HTTP/2 must not be used."; } return std::unexpected{Error::CRYPTO}; } auto check_block_list = false; if (tls.server_handshake) { check_block_list = !get_config()->tls.no_http2_cipher_block_list; } else { check_block_list = !get_config()->tls.client.no_http2_cipher_block_list; } if (check_block_list && nghttp2::tls::check_http2_cipher_block_list(tls.ssl)) { if (log_enabled(INFO)) { Log{INFO} << "The negotiated cipher suite is in HTTP/2 cipher suite " "block list. HTTP/2 must not be used."; } return std::unexpected{Error::CRYPTO}; } return {}; } constexpr size_t SHRPX_SMALL_WRITE_LIMIT = 1300; size_t Connection::get_tls_write_limit() { if (tls_dyn_rec_warmup_threshold == 0) { return std::numeric_limits<ssize_t>::max(); } auto t = std::chrono::steady_clock::now(); if (tls.last_write_idle.time_since_epoch().count() >= 0 && t - tls.last_write_idle > tls_dyn_rec_idle_timeout) { // Time out, use small record size tls.warmup_writelen = 0; return SHRPX_SMALL_WRITE_LIMIT; } if (tls.warmup_writelen >= tls_dyn_rec_warmup_threshold) { return std::numeric_limits<ssize_t>::max(); } return SHRPX_SMALL_WRITE_LIMIT; } void Connection::update_tls_warmup_writelen(size_t n) { if (tls.warmup_writelen < tls_dyn_rec_warmup_threshold) { tls.warmup_writelen += n; } } void Connection::start_tls_write_idle() { if (tls.last_write_idle.time_since_epoch().count() < 0) { tls.last_write_idle = std::chrono::steady_clock::now(); } } std::expected<size_t, Error> Connection::write_tls(std::span<const uint8_t> data) { // SSL_write requires the same arguments (buf pointer and its // length) on SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE. // get_write_limit() may return smaller length than previously // passed to SSL_write, which violates OpenSSL assumption. To avoid // this, we keep last length passed to SSL_write to // tls.last_writelen if SSL_write indicated I/O blocking. if (tls.last_writelen == 0) { data = data.first( std::ranges::min({data.size(), wlimit.avail(), get_tls_write_limit()})); if (data.empty()) { return 0; } } else { data = data.first(tls.last_writelen); tls.last_writelen = 0; } tls.last_write_idle = std::chrono::steady_clock::time_point(-1s); ERR_clear_error(); #ifdef NGHTTP2_GENUINE_OPENSSL int rv; if (SSL_is_init_finished(tls.ssl)) { rv = SSL_write(tls.ssl, data.data(), static_cast<int>(data.size())); } else { size_t nwrite; rv = SSL_write_early_data(tls.ssl, data.data(), data.size(), &nwrite); // Use the same semantics with SSL_write. if (rv == 1) { rv = static_cast<int>(nwrite); } } #else // !defined(NGHTTP2_GENUINE_OPENSSL) auto rv = SSL_write(tls.ssl, data.data(), static_cast<int>(data.size())); #endif // !defined(NGHTTP2_GENUINE_OPENSSL) if (rv <= 0) { auto err = SSL_get_error(tls.ssl, rv); switch (err) { case SSL_ERROR_WANT_READ: if (log_enabled(INFO)) { Log{INFO} << "Close connection due to TLS renegotiation"; } return std::unexpected{Error::NETWORK}; case SSL_ERROR_WANT_WRITE: tls.last_writelen = data.size(); wlimit.startw(); ev_timer_again(loop, &wt); return 0; case SSL_ERROR_SSL: if (log_enabled(INFO)) { Log{INFO} << "SSL_write: " << ERR_error_string(ERR_get_error(), nullptr); } return std::unexpected{Error::NETWORK}; default: if (log_enabled(INFO)) { Log{INFO} << "SSL_write: SSL_get_error returned " << err; } return std::unexpected{Error::NETWORK}; } } auto nwrite = static_cast<size_t>(rv); wlimit.drain(nwrite); if (ev_is_active(&wt)) { ev_timer_again(loop, &wt); } update_tls_warmup_writelen(nwrite); return nwrite; } std::expected<std::span<uint8_t>, Error> Connection::read_tls(std::span<uint8_t> data) { ERR_clear_error(); #if defined(NGHTTP2_GENUINE_OPENSSL) || \ defined(NGHTTP2_OPENSSL_IS_BORINGSSL) || defined(NGHTTP2_OPENSSL_IS_WOLFSSL) if (tls.earlybuf.rleft()) { return data.first(tls.earlybuf.remove(data)); } #endif // defined(NGHTTP2_GENUINE_OPENSSL) || // defined(NGHTTP2_OPENSSL_IS_BORINGSSL) || // defined(NGHTTP2_OPENSSL_IS_WOLFSSL) // SSL_read requires the same arguments (buf pointer and its // length) on SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE. // rlimit_.avail() or rlimit_.avail() may return different length // than the length previously passed to SSL_read, which violates // OpenSSL assumption. To avoid this, we keep last length passed // to SSL_read to tls_last_readlen_ if SSL_read indicated I/O // blocking. if (tls.last_readlen == 0) { data = data.first(std::min(data.size(), rlimit.avail())); if (data.empty()) { return {}; } } else { data = data.first(tls.last_readlen); tls.last_readlen = 0; } #ifdef NGHTTP2_GENUINE_OPENSSL if (!tls.early_data_finish) { // TLSv1.3 handshake is still going on. size_t nread; auto rv = SSL_read_early_data(tls.ssl, data.data(), data.size(), &nread); if (rv == SSL_READ_EARLY_DATA_ERROR) { auto err = SSL_get_error(tls.ssl, rv); switch (err) { case SSL_ERROR_WANT_READ: tls.last_readlen = data.size(); return {}; case SSL_ERROR_SSL: if (log_enabled(INFO)) { Log{INFO} << "SSL_read: " << ERR_error_string(ERR_get_error(), nullptr); } return std::unexpected{Error::NETWORK}; default: if (log_enabled(INFO)) { Log{INFO} << "SSL_read: SSL_get_error returned " << err; } return std::unexpected{Error::NETWORK}; } } if (log_enabled(INFO)) { Log{INFO} << "tls: read early data " << nread << " bytes"; } if (rv == SSL_READ_EARLY_DATA_FINISH) { if (log_enabled(INFO)) { Log{INFO} << "tls: read all early data"; } tls.early_data_finish = true; // We may have stopped write watcher in write_tls. wlimit.startw(); } rlimit.drain(nread); return data.first(nread); } #endif // defined(NGHTTP2_GENUINE_OPENSSL) #if defined(NGHTTP2_OPENSSL_IS_WOLFSSL) && defined(WOLFSSL_EARLY_DATA) if (!tls.early_data_finish) { // TLSv1.3 handshake is still going on. size_t nread = 0; auto rv = SSL_read_early_data(tls.ssl, data.data(), data.size(), &nread); if (rv < 0) { auto err = SSL_get_error(tls.ssl, rv); switch (err) { case SSL_ERROR_WANT_READ: tls.last_readlen = data.size(); return {}; case SSL_ERROR_SSL: if (log_enabled(INFO)) { Log{INFO} << "SSL_read: " << ERR_error_string(ERR_get_error(), nullptr); } return std::unexpected{Error::NETWORK}; default: if (log_enabled(INFO)) { Log{INFO} << "SSL_read: SSL_get_error returned " << err; } return std::unexpected{Error::NETWORK}; } } if (log_enabled(INFO)) { Log{INFO} << "tls: read early data " << nread << " bytes"; } if (rv == 0) { if (log_enabled(INFO)) { Log{INFO} << "tls: read all early data"; } tls.early_data_finish = true; // We may have stopped write watcher in write_tls. wlimit.startw(); } rlimit.drain(nread); return data.first(nread); } #endif // defined(NGHTTP2_OPENSSL_IS_WOLFSSL) && // defined(WOLFSSL_EARLY_DATA) auto rv = SSL_read(tls.ssl, data.data(), static_cast<int>(data.size())); if (rv <= 0) { auto err = SSL_get_error(tls.ssl, rv); switch (err) { case SSL_ERROR_WANT_READ: tls.last_readlen = data.size(); return {}; case SSL_ERROR_WANT_WRITE: if (log_enabled(INFO)) { Log{INFO} << "Close connection due to TLS renegotiation"; } return std::unexpected{Error::NETWORK}; case SSL_ERROR_ZERO_RETURN: return std::unexpected{Error::RECV_EOF}; case SSL_ERROR_SSL: if (log_enabled(INFO)) { Log{INFO} << "SSL_read: " << ERR_error_string(ERR_get_error(), nullptr); } return std::unexpected{Error::NETWORK}; default: if (log_enabled(INFO)) { Log{INFO} << "SSL_read: SSL_get_error returned " << err; } return std::unexpected{Error::NETWORK}; } } auto nread = static_cast<size_t>(rv); rlimit.drain(nread); return data.first(nread); } std::expected<size_t, Error> Connection::write_clear(std::span<const uint8_t> data) { data = data.first(std::min(data.size(), wlimit.avail())); if (data.empty()) { return 0; } ssize_t nwrite; while ((nwrite = write(fd, data.data(), data.size())) == -1 && errno == EINTR) ; if (nwrite == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK) { wlimit.startw(); ev_timer_again(loop, &wt); return 0; } return std::unexpected{Error::NETWORK}; } wlimit.drain(as_unsigned(nwrite)); if (ev_is_active(&wt)) { ev_timer_again(loop, &wt); } return as_unsigned(nwrite); } std::expected<size_t, Error> Connection::writev_clear(std::span<struct iovec> iov) { iov = limit_iovec(iov, wlimit.avail()); if (iov.empty()) { return 0; } ssize_t nwrite; while ((nwrite = writev(fd, iov.data(), static_cast<int>(iov.size()))) == -1 && errno == EINTR) ; if (nwrite == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK) { wlimit.startw(); ev_timer_again(loop, &wt); return 0; } return std::unexpected{Error::NETWORK}; } wlimit.drain(as_unsigned(nwrite)); if (ev_is_active(&wt)) { ev_timer_again(loop, &wt); } return as_unsigned(nwrite); } std::expected<std::span<uint8_t>, Error> Connection::read_clear(std::span<uint8_t> data) { data = data.first(std::min(data.size(), rlimit.avail())); if (data.empty()) { return {}; } ssize_t nread; while ((nread = read(fd, data.data(), data.size())) == -1 && errno == EINTR) ; if (nread == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK) { return {}; } return std::unexpected{Error::NETWORK}; } if (nread == 0) { return std::unexpected{Error::RECV_EOF}; } rlimit.drain(as_unsigned(nread)); return data.first(as_unsigned(nread)); } std::expected<std::span<uint8_t>, Error> Connection::read_nolim_clear(std::span<uint8_t> data) { ssize_t nread; while ((nread = read(fd, data.data(), data.size())) == -1 && errno == EINTR) ; if (nread == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK) { return {}; } return std::unexpected{Error::NETWORK}; } if (nread == 0) { return std::unexpected{Error::RECV_EOF}; } return data.first(as_unsigned(nread)); } std::expected<std::span<uint8_t>, Error> Connection::peek_clear(std::span<uint8_t> data) { ssize_t nread; while ((nread = recv(fd, data.data(), data.size(), MSG_PEEK)) == -1 && errno == EINTR) ; if (nread == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK) { return {}; } return std::unexpected{Error::NETWORK}; } if (nread == 0) { return std::unexpected{Error::RECV_EOF}; } return data.first(as_unsigned(nread)); } void Connection::handle_tls_pending_read() { if (!ev_is_active(&rev)) { return; } rlimit.handle_tls_pending_read(); } std::expected<TCPHint, Error> Connection::get_tcp_hint() const { #if defined(TCP_INFO) && defined(TCP_NOTSENT_LOWAT) struct tcp_info tcp_info; socklen_t tcp_info_len = sizeof(tcp_info); int rv; rv = getsockopt(fd, IPPROTO_TCP, TCP_INFO, &tcp_info, &tcp_info_len); if (rv != 0) { return std::unexpected{Error::SYSCALL}; } auto avail_packets = tcp_info.tcpi_snd_cwnd > tcp_info.tcpi_unacked ? tcp_info.tcpi_snd_cwnd - tcp_info.tcpi_unacked : 0; // http://www.slideshare.net/kazuho/programming-tcp-for-responsiveness // TODO 29 (5 (header) + 8 (explicit nonce) + 16 (tag)) is TLS // overhead for AES-GCM. For CHACHA20_POLY1305, it is 21 since it // does not need 8 bytes explicit nonce. // // For TLSv1.3, AES-GCM and CHACHA20_POLY1305 overhead are now 22 // bytes (5 (header) + 1 (ContentType) + 16 (tag)). size_t tls_overhead; # ifdef TLS1_3_VERSION if (SSL_version(tls.ssl) == TLS1_3_VERSION) { tls_overhead = 22; } else # endif // defined(TLS1_3_VERSION) { tls_overhead = 29; } auto writable_size = (avail_packets + 2) * (tcp_info.tcpi_snd_mss - tls_overhead); if (writable_size > 16_k) { writable_size = writable_size & ~(16_k - 1); } else { if (writable_size < 536) { Log{INFO} << "writable_size is too small: " << writable_size; } // TODO is this required? writable_size = std::max(writable_size, 536UZ * 2UZ); } // if (log_enabled(INFO)) { // Log{INFO} << "snd_cwnd=" << tcp_info.tcpi_snd_cwnd // << ", unacked=" << tcp_info.tcpi_unacked // << ", snd_mss=" << tcp_info.tcpi_snd_mss // << ", rtt=" << tcp_info.tcpi_rtt << "us" // << ", rcv_space=" << tcp_info.tcpi_rcv_space // << ", writable=" << writable_size; // } return TCPHint{ .write_buffer_size = writable_size, // TODO tcpi_rcv_space is considered as rwin, is that correct? .rwin = tcp_info.tcpi_rcv_space, }; #else // !defined(TCP_INFO) || !defined(TCP_NOTSENT_LOWAT) return std::unexpected{Error::NOT_IMPLEMENTED}; #endif // !defined(TCP_INFO) || !defined(TCP_NOTSENT_LOWAT) } void Connection::again_rt(ev_tstamp t) { read_timeout = t; rt.repeat = t; ev_timer_again(loop, &rt); last_read = std::chrono::steady_clock::now(); } void Connection::again_rt() { rt.repeat = read_timeout; ev_timer_again(loop, &rt); last_read = std::chrono::steady_clock::now(); } bool Connection::expired_rt() { auto delta = read_timeout - util::ev_tstamp_from( std::chrono::steady_clock::now() - last_read); if (delta < 1e-9) { return true; } rt.repeat = delta; ev_timer_again(loop, &rt); return false; } } // namespace shrpx