[WIP] Add QUIC to h2load

This commit is contained in:
Tatsuhiro Tsujikawa 2019-06-08 22:05:25 +09:00
parent 80c9c705b8
commit 9c748d20d5
12 changed files with 2435 additions and 14 deletions

View File

@ -454,6 +454,20 @@ if test "x${request_libcares}" = "xyes" &&
AC_MSG_ERROR([libcares was requested (--with-libcares) but not found]) AC_MSG_ERROR([libcares was requested (--with-libcares) but not found])
fi 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) # libevent_openssl (for examples)
# 2.0.8 is required because we use evconnlistener_set_error_cb() # 2.0.8 is required because we use evconnlistener_set_error_cb()
have_libevent_openssl=no have_libevent_openssl=no
@ -1011,6 +1025,8 @@ AC_MSG_NOTICE([summary of build options:
Libxml2: ${have_libxml2} (CFLAGS='${LIBXML2_CFLAGS}' LIBS='${LIBXML2_LIBS}') Libxml2: ${have_libxml2} (CFLAGS='${LIBXML2_CFLAGS}' LIBS='${LIBXML2_LIBS}')
Libev: ${have_libev} (CFLAGS='${LIBEV_CFLAGS}' LIBS='${LIBEV_LIBS}') Libev: ${have_libev} (CFLAGS='${LIBEV_CFLAGS}' LIBS='${LIBEV_LIBS}')
Libc-ares: ${have_libcares} (CFLAGS='${LIBCARES_CFLAGS}' LIBS='${LIBCARES_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}') Libevent(SSL): ${have_libevent_openssl} (CFLAGS='${LIBEVENT_OPENSSL_CFLAGS}' LIBS='${LIBEVENT_OPENSSL_LIBS}')
Jansson: ${have_jansson} (CFLAGS='${JANSSON_CFLAGS}' LIBS='${JANSSON_LIBS}') Jansson: ${have_jansson} (CFLAGS='${JANSSON_CFLAGS}' LIBS='${JANSSON_LIBS}')
Jemalloc: ${have_jemalloc} (CFLAGS='${JEMALLOC_CFLAGS}' LIBS='${JEMALLOC_LIBS}') Jemalloc: ${have_jemalloc} (CFLAGS='${JEMALLOC_CFLAGS}' LIBS='${JEMALLOC_LIBS}')

View File

@ -46,6 +46,8 @@ AM_CPPFLAGS = \
@LIBEV_CFLAGS@ \ @LIBEV_CFLAGS@ \
@OPENSSL_CFLAGS@ \ @OPENSSL_CFLAGS@ \
@LIBCARES_CFLAGS@ \ @LIBCARES_CFLAGS@ \
@LIBNGHTTP3_CFLAGS@ \
@LIBNGTCP2_CFLAGS@ \
@JANSSON_CFLAGS@ \ @JANSSON_CFLAGS@ \
@ZLIB_CFLAGS@ \ @ZLIB_CFLAGS@ \
@DEFS@ @DEFS@
@ -59,6 +61,8 @@ LDADD = $(top_builddir)/lib/libnghttp2.la \
@LIBEV_LIBS@ \ @LIBEV_LIBS@ \
@OPENSSL_LIBS@ \ @OPENSSL_LIBS@ \
@LIBCARES_LIBS@ \ @LIBCARES_LIBS@ \
@LIBNGHTTP3_LIBS@ \
@LIBNGTCP2_LIBS@ \
@SYSTEMD_LIBS@ \ @SYSTEMD_LIBS@ \
@JANSSON_LIBS@ \ @JANSSON_LIBS@ \
@ZLIB_LIBS@ \ @ZLIB_LIBS@ \
@ -97,7 +101,10 @@ h2load_SOURCES = util.cc util.h \
tls.cc tls.h \ tls.cc tls.h \
h2load_session.h \ h2load_session.h \
h2load_http2_session.cc h2load_http2_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 = \ NGHTTPX_SRCS = \
util.cc util.h http2.cc http2.h timegm.c timegm.h base64.h \ util.cc util.h http2.cc http2.h timegm.c timegm.h base64.h \

View File

@ -48,10 +48,14 @@
#include <openssl/err.h> #include <openssl/err.h>
#include <ngtcp2/ngtcp2.h>
#include "url-parser/url_parser.h" #include "url-parser/url_parser.h"
#include "h2load_http1_session.h" #include "h2load_http1_session.h"
#include "h2load_http2_session.h" #include "h2load_http2_session.h"
#include "h2load_http3_session.h"
#include "h2load_quic.h"
#include "tls.h" #include "tls.h"
#include "http2.h" #include "http2.h"
#include "util.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::is_timing_based_mode() const { return (this->duration > 0); }
bool Config::has_base_uri() const { return (!this->base_uri.empty()); } bool Config::has_base_uri() const { return (!this->base_uri.empty()); }
bool Config::rps_enabled() const { return this->rps > 0.0; } 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; Config config;
namespace { namespace {
@ -409,6 +416,7 @@ Client::Client(uint32_t id, Worker *worker, size_t req_todo)
cstat{}, cstat{},
worker(worker), worker(worker),
ssl(nullptr), ssl(nullptr),
quic{},
next_addr(config.addrs), next_addr(config.addrs),
current_addr(nullptr), current_addr(nullptr),
reqidx(0), reqidx(0),
@ -420,6 +428,7 @@ Client::Client(uint32_t id, Worker *worker, size_t req_todo)
req_done(0), req_done(0),
id(id), id(id),
fd(-1), fd(-1),
local_addr{},
new_connection_requested(false), new_connection_requested(false),
final(false), final(false),
rps_duration_started(0), 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.); ev_timer_init(&rps_watcher, rps_cb, 0., 0.);
rps_watcher.data = this; rps_watcher.data = this;
ev_timer_init(&quic.pkt_timer, quic_pkt_timeout_cb, 0., 0.);
quic.pkt_timer.data = this;
} }
Client::~Client() { Client::~Client() {
disconnect(); disconnect();
if (config.is_quic()) {
quic_free();
}
if (ssl) { if (ssl) {
SSL_free(ssl); SSL_free(ssl);
} }
@ -466,6 +482,37 @@ int Client::do_read() { return readfn(*this); }
int Client::do_write() { return writefn(*this); } int Client::do_write() { return writefn(*this); }
int Client::make_socket(addrinfo *addr) { 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); fd = util::create_nonblock_socket(addr->ai_family);
if (fd == -1) { if (fd == -1) {
return -1; return -1;
@ -475,17 +522,15 @@ int Client::make_socket(addrinfo *addr) {
ssl = SSL_new(worker->ssl_ctx); 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_fd(ssl, fd);
SSL_set_connect_state(ssl); 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 (rv != 0 && errno != EINPROGRESS) {
if (ssl) { if (ssl) {
SSL_free(ssl); SSL_free(ssl);
@ -542,13 +587,20 @@ int Client::connect() {
current_addr = addr; current_addr = addr;
} }
writefn = &Client::connected;
ev_io_set(&rev, fd, EV_READ); ev_io_set(&rev, fd, EV_READ);
ev_io_set(&wev, fd, EV_WRITE); ev_io_set(&wev, fd, EV_WRITE);
ev_io_start(worker->loop, &wev); 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; return 0;
} }
@ -603,6 +655,11 @@ void Client::fail() {
void Client::disconnect() { void Client::disconnect() {
record_client_end_time(); 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_inactivity_watcher);
ev_timer_stop(worker->loop, &conn_active_watcher); ev_timer_stop(worker->loop, &conn_active_watcher);
ev_timer_stop(worker->loop, &rps_watcher); ev_timer_stop(worker->loop, &rps_watcher);
@ -765,6 +822,9 @@ void Client::report_app_info() {
} }
void Client::terminate_session() { void Client::terminate_session() {
if (config.is_quic()) {
quic.close_requested = true;
}
session->terminate(); session->terminate();
// http1 session needs writecb to tear down session. // http1 session needs writecb to tear down session.
signal_write(); signal_write();
@ -963,7 +1023,15 @@ int Client::connection_made() {
if (next_proto) { if (next_proto) {
auto proto = StringRef{next_proto, next_proto_len}; 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<Http3Session>(this);
if (s->init_conn() == -1) {
return -1;
}
session = std::move(s);
}
} else if (util::check_h2_is_selected(proto)) {
session = std::make_unique<Http2Session>(this); session = std::make_unique<Http2Session>(this);
} else if (util::streq(NGHTTP2_H1_1, proto)) { } else if (util::streq(NGHTTP2_H1_1, proto)) {
session = std::make_unique<Http1Session>(this); session = std::make_unique<Http1Session>(this);
@ -1285,6 +1353,15 @@ int Client::write_tls() {
return 0; 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) { void Client::record_request_time(RequestStat *req_stat) {
req_stat->request_time = std::chrono::steady_clock::now(); req_stat->request_time = std::chrono::steady_clock::now();
req_stat->request_wall_time = std::chrono::system_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 } // namespace
#endif // !OPENSSL_NO_NEXTPROTONEG #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<Client *>(SSL_get_app_data(ssl));
auto conn = c->quic.conn;
ngtcp2_transport_params params;
ngtcp2_conn_get_local_transport_params(conn, &params);
constexpr size_t bufsize = 128;
auto buf = std::make_unique<uint8_t[]>(bufsize);
auto nwrite = ngtcp2_encode_transport_params(
buf.get(), bufsize, NGTCP2_TRANSPORT_PARAMS_TYPE_CLIENT_HELLO, &params);
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<size_t>(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<unsigned char *>(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<Client *>(SSL_get_app_data(ssl));
auto conn = c->quic.conn;
int rv;
ngtcp2_transport_params params;
rv = ngtcp2_decode_transport_params(
&params, 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, &params);
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 { namespace {
constexpr char UNIX_PATH_PREFIX[] = "unix:"; constexpr char UNIX_PATH_PREFIX[] = "unix:";
} // namespace } // namespace
@ -2576,7 +2726,24 @@ int main(int argc, char **argv) {
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY); SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS); SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
if (nghttp2::tls::ssl_ctx_set_proto_versions( 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, ssl_ctx, nghttp2::tls::NGHTTP2_TLS_MIN_VERSION,
nghttp2::tls::NGHTTP2_TLS_MAX_VERSION) != 0) { nghttp2::tls::NGHTTP2_TLS_MAX_VERSION) != 0) {
std::cerr << "Could not set TLS versions" << std::endl; std::cerr << "Could not set TLS versions" << std::endl;
@ -2590,6 +2757,9 @@ int main(int argc, char **argv) {
exit(EXIT_FAILURE); 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 #ifndef OPENSSL_NO_NEXTPROTONEG
SSL_CTX_set_next_proto_select_cb(ssl_ctx, client_select_next_proto_cb, SSL_CTX_set_next_proto_select_cb(ssl_ctx, client_select_next_proto_cb,
nullptr); nullptr);

View File

@ -45,11 +45,14 @@
#include <nghttp2/nghttp2.h> #include <nghttp2/nghttp2.h>
#include <ngtcp2/ngtcp2.h>
#include <ev.h> #include <ev.h>
#include <openssl/ssl.h> #include <openssl/ssl.h>
#include "http2.h" #include "http2.h"
#include "quic.h"
#include "memchunk.h" #include "memchunk.h"
#include "template.h" #include "template.h"
@ -124,6 +127,7 @@ struct Config {
bool is_timing_based_mode() const; bool is_timing_based_mode() const;
bool has_base_uri() const; bool has_base_uri() const;
bool rps_enabled() const; bool rps_enabled() const;
bool is_quic() const;
}; };
struct RequestStat { struct RequestStat {
@ -299,6 +303,13 @@ struct Stream {
Stream(); Stream();
}; };
struct Crypto {
Crypto() : datalen(0), acked_offset(0) {}
std::array<uint8_t, 1024> data;
size_t datalen;
size_t acked_offset;
};
struct Client { struct Client {
DefaultMemchunks wb; DefaultMemchunks wb;
std::unordered_map<int32_t, Stream> streams; std::unordered_map<int32_t, Stream> streams;
@ -309,6 +320,19 @@ struct Client {
std::function<int(Client &)> readfn, writefn; std::function<int(Client &)> readfn, writefn;
Worker *worker; Worker *worker;
SSL *ssl; 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<uint8_t> server_handshake;
size_t server_handshake_nread;
// Client never send CRYPTO in Short packet.
std::array<Crypto, 2> crypto;
size_t max_pktlen;
bool close_requested;
} quic;
ev_timer request_timeout_watcher; ev_timer request_timeout_watcher;
addrinfo *next_addr; addrinfo *next_addr;
// Address for the current address. When try_new_connection() is // Address for the current address. When try_new_connection() is
@ -332,6 +356,7 @@ struct Client {
// The client id per worker // The client id per worker
uint32_t id; uint32_t id;
int fd; int fd;
Address local_addr;
ev_timer conn_active_watcher; ev_timer conn_active_watcher;
ev_timer conn_inactivity_watcher; ev_timer conn_inactivity_watcher;
std::string selected_proto; std::string selected_proto;
@ -419,6 +444,62 @@ struct Client {
void record_client_end_time(); void record_client_end_time();
void signal_write(); 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 } // namespace h2load

375
src/h2load_http3_session.cc Normal file
View File

@ -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 <iostream>
#include <ngtcp2/ngtcp2.h>
#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<nghttp3_nv *>(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<Http3Session *>(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<Http3Session *>(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<Http3Session *>(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<Http3Session *>(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<Http3Session *>(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<Http3Session *>(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

View File

@ -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 <nghttp3/nghttp3.h>
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

1078
src/h2load_quic.cc Normal file

File diff suppressed because it is too large Load Diff

38
src/h2load_quic.h Normal file
View File

@ -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 <ev.h>
#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

422
src/quic.cc Normal file
View File

@ -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 <cassert>
#include <openssl/kdf.h>
#include <ngtcp2/ngtcp2.h>
#include <nghttp3/nghttp3.h>
#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<uint8_t, 256> 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<size_t>(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<uint8_t *>(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

104
src/quic.h Normal file
View File

@ -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 <openssl/ssl.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,
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

View File

@ -945,6 +945,56 @@ int create_nonblock_socket(int family) {
return fd; 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) { bool check_socket_connected(int fd) {
int error; int error;
socklen_t len = sizeof(error); socklen_t len = sizeof(error);

View File

@ -626,6 +626,9 @@ int make_socket_nonblocking(int fd);
int make_socket_nodelay(int fd); int make_socket_nodelay(int fd);
int create_nonblock_socket(int family); 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); bool check_socket_connected(int fd);