1106 lines
31 KiB
C++
1106 lines
31 KiB
C++
/*
|
|
* 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 <iostream>
|
|
|
|
#include <openssl/err.h>
|
|
#include <openssl/bio.h>
|
|
|
|
#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<Client *>(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<Client *>(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<Client *>(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<Client *>(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<Client *>(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<Client *>(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<Client *>(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<Client *>(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<Client *>(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<Client *>(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<Http3Session *>(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<Client *>(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<Http3Session *>(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<Client *>(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<Http3Session *>(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<Client *>(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<Http3Session *>(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<uint8_t>(
|
|
0, std::numeric_limits<uint8_t>::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<uint8_t>(
|
|
0, std::numeric_limits<uint8_t>::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<Client *>(BIO_get_data(b));
|
|
|
|
len = c->quic_read_server_handshake(reinterpret_cast<uint8_t *>(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<Client *>(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<Client *>(arg);
|
|
auto msg = reinterpret_cast<const uint8_t *>(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<const uint8_t *>(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);
|
|
|
|
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 = (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 path = ngtcp2_path{
|
|
{local_addrlen,
|
|
const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(local_addr))},
|
|
{remote_addrlen,
|
|
const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(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<uint8_t, 1500> 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<sockaddr *>(ps.path.remote.addr),
|
|
ps.path.remote.addrlen, buf.data(), nwrite);
|
|
}
|
|
|
|
int Client::quic_setup_initial_crypto() {
|
|
int rv;
|
|
|
|
std::array<uint8_t, 32> 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<const uint8_t *>(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<uint8_t, 16> 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<uint8_t, 64> 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<uint8_t, 4096> 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<Client *>(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<ev_tstamp>(expiry - now) / NGTCP2_SECONDS;
|
|
quic.pkt_timer.repeat = t;
|
|
ev_timer_again(worker->loop, &quic.pkt_timer);
|
|
}
|
|
|
|
int Client::read_quic() {
|
|
std::array<uint8_t, 1500> 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<uint8_t *>(&local_addr.su.sa)},
|
|
{addrlen, reinterpret_cast<uint8_t *>(&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;
|
|
}
|
|
|
|
std::array<nghttp3_vec, 16> vec;
|
|
std::array<uint8_t, 1500> 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<sockaddr *>(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<Http3Session *>(session.get());
|
|
|
|
for (;;) {
|
|
int64_t stream_id;
|
|
int fin;
|
|
ssize_t sveccnt = 0;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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<sockaddr *>(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<size_t>(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<const ngtcp2_vec *>(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<sockaddr *>(ps.path.remote.addr),
|
|
ps.path.remote.addrlen, buf.data(), nwrite);
|
|
|
|
ev_io_start(worker->loop, &wev);
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace h2load
|