blob: 6767e2ef595ba2b22956c04560b3b5a74c0782e6 [file] [log] [blame]
/*
* ngtcp2
*
* Copyright (c) 2017 ngtcp2 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 <chrono>
#include <cstdlib>
#include <cassert>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <memory>
#include <fstream>
#include <future>
#include <thread>
#include <unistd.h>
#include <getopt.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <netinet/udp.h>
#include <signal.h>
#include <linux/bpf.h>
enum bpf_stats_type {};
#include <bpf/libbpf.h>
#include <bpf/bpf.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <http-parser/http_parser.h>
#include "server.h"
#include "network.h"
#include "debug.h"
#include "util.h"
#include "shared.h"
#include "http.h"
#include "keylog.h"
#include "template.h"
using namespace ngtcp2;
using namespace std::literals;
#ifndef NGTCP2_ENABLE_UDP_GSO
# ifdef UDP_SEGMENT
# define NGTCP2_ENABLE_UDP_GSO 1
# else // !UDP_SEGMENT
# define NGTCP2_ENABLE_UDP_GSO 0
# endif // !UDP_SEGMENT
#endif // NGTCP2_ENABLE_UDP_GSO
namespace {
constexpr size_t NGTCP2_SV_SCIDLEN = 18;
} // namespace
namespace {
constexpr size_t TOKEN_RAND_DATALEN = 16;
} // namespace
namespace {
constexpr size_t MAX_DYNBUFLEN = 10 * 1024 * 1024;
} // namespace
namespace {
auto randgen = util::make_mt19937();
} // namespace
namespace {
// RETRY_TOKEN_MAGIC is the magic byte of Retry token. Sent in
// plaintext.
constexpr uint8_t RETRY_TOKEN_MAGIC = 0xb6;
constexpr size_t MAX_RETRY_TOKENLEN =
/* magic */ 1 + sizeof(uint64_t) + NGTCP2_MAX_CIDLEN +
/* aead tag */ 16 + TOKEN_RAND_DATALEN;
// TOKEN_MAGIC is the magic byte of token which is sent in NEW_TOKEN
// frame. Sent in plaintext.
constexpr uint8_t TOKEN_MAGIC = 0x36;
constexpr size_t MAX_TOKENLEN =
/* magic */ 1 + sizeof(uint64_t) + /* aead tag */ 16 + TOKEN_RAND_DATALEN;
} // namespace
namespace {
Config config{};
} // namespace
namespace {
int prog_fd;
int reuseport_array;
} // namespace
Buffer::Buffer(const uint8_t *data, size_t datalen)
: buf{data, data + datalen}, begin(buf.data()), tail(begin + datalen) {}
Buffer::Buffer(size_t datalen) : buf(datalen), begin(buf.data()), tail(begin) {}
namespace {
void sha256(uint8_t *dest, const uint8_t *kpad, const uint8_t *b, size_t blen) {
auto ctx = EVP_MD_CTX_new();
assert(ctx);
auto ctx_deleter = defer(EVP_MD_CTX_free, ctx);
unsigned int mdlen = 32;
if (!EVP_DigestInit_ex(ctx, EVP_sha256(), nullptr) ||
!EVP_DigestUpdate(ctx, kpad, 64) || !EVP_DigestUpdate(ctx, b, blen) ||
!EVP_DigestFinal_ex(ctx, dest, &mdlen)) {
assert(0);
}
}
} // namespace
namespace {
constexpr size_t HMACLEN = 4;
std::array<uint8_t, 64> kopad, kipad;
} // namespace
namespace {
void hmac256_32(uint32_t *dest, const uint8_t *src, size_t srclen) {
uint8_t h[32];
sha256(h, kipad.data(), src, srclen);
sha256(h, kopad.data(), h, sizeof(h));
memcpy(dest, h, HMACLEN);
}
} // namespace
namespace {
void generate_authenticated_cid(uint8_t *dest, size_t destlen,
uint32_t svindex) {
// - Connection ID is NGTCP2_SV_SCIDLEN bytes in total.
// Connection
//
// - ID is authenticated with HMAC-SHA256-32 to reliably embed
// svindex in it.
//
// - The last 4 bytes are digest. Thus 14 bytes are available.
//
// - For now, encode svindex in the first (most significant) byte.
assert(destlen == NGTCP2_SV_SCIDLEN);
assert(svindex < 256);
auto dis = std::uniform_int_distribution<uint8_t>(0, 255);
auto f = [&dis]() { return dis(randgen); };
auto len = NGTCP2_SV_SCIDLEN - HMACLEN;
std::generate_n(dest, len, f);
// TODO Encode svindex elsewhere other than first byte.
dest[0] = static_cast<uint8_t>(svindex);
uint32_t hmac;
hmac256_32(&hmac, dest, len);
memcpy(dest + len, &hmac, sizeof(hmac));
}
} // namespace
int Handler::on_key(ngtcp2_crypto_level level, const uint8_t *rx_secret,
const uint8_t *tx_secret, size_t secretlen) {
std::array<uint8_t, 64> rx_key, rx_iv, rx_hp_key, tx_key, tx_iv, tx_hp_key;
if (ngtcp2_crypto_derive_and_install_rx_key(
conn_, rx_key.data(), rx_iv.data(), rx_hp_key.data(), level,
rx_secret, secretlen) != 0) {
return -1;
}
if (ngtcp2_crypto_derive_and_install_tx_key(
conn_, tx_key.data(), tx_iv.data(), tx_hp_key.data(), level,
tx_secret, secretlen) != 0) {
return -1;
}
auto crypto_ctx = ngtcp2_conn_get_crypto_ctx(conn_);
auto aead = &crypto_ctx->aead;
auto keylen = ngtcp2_crypto_aead_keylen(aead);
auto ivlen = ngtcp2_crypto_packet_protection_ivlen(aead);
const char *title = nullptr;
switch (level) {
case NGTCP2_CRYPTO_LEVEL_EARLY:
title = "early_traffic";
keylog::log_secret(ssl_, keylog::QUIC_CLIENT_EARLY_TRAFFIC_SECRET,
rx_secret, secretlen);
break;
case NGTCP2_CRYPTO_LEVEL_HANDSHAKE:
title = "handshake_traffic";
keylog::log_secret(ssl_, keylog::QUIC_CLIENT_HANDSHAKE_TRAFFIC_SECRET,
rx_secret, secretlen);
keylog::log_secret(ssl_, keylog::QUIC_SERVER_HANDSHAKE_TRAFFIC_SECRET,
tx_secret, secretlen);
break;
case NGTCP2_CRYPTO_LEVEL_APPLICATION:
title = "application_traffic";
keylog::log_secret(ssl_, keylog::QUIC_CLIENT_TRAFFIC_SECRET_0, rx_secret,
secretlen);
keylog::log_secret(ssl_, keylog::QUIC_SERVER_TRAFFIC_SECRET_0, tx_secret,
secretlen);
break;
default:
assert(0);
}
if (!config.quiet && config.show_secret) {
std::cerr << title << " rx secret" << std::endl;
debug::print_secrets(rx_secret, secretlen, rx_key.data(), keylen,
rx_iv.data(), ivlen, rx_hp_key.data(), keylen);
if (tx_secret) {
std::cerr << title << " tx secret" << std::endl;
debug::print_secrets(tx_secret, secretlen, tx_key.data(), keylen,
tx_iv.data(), ivlen, tx_hp_key.data(), keylen);
}
}
if (level == NGTCP2_CRYPTO_LEVEL_APPLICATION && setup_httpconn() != 0) {
return -1;
}
return 0;
}
Stream::Stream(int64_t stream_id, Handler *handler)
: stream_id(stream_id),
handler(handler),
data(nullptr),
datalen(0),
dynresp(false),
dyndataleft(0),
dynbuflen(0) {}
namespace {
constexpr char NGTCP2_SERVER[] = "nghttp3/ngtcp2 server";
} // namespace
namespace {
std::string make_status_body(unsigned int status_code) {
auto status_string = std::to_string(status_code);
auto reason_phrase = http::get_reason_phrase(status_code);
std::string body;
body = "<html><head><title>";
body += status_string;
body += ' ';
body += reason_phrase;
body += "</title></head><body><h1>";
body += status_string;
body += ' ';
body += reason_phrase;
body += "</h1><hr><address>";
body += NGTCP2_SERVER;
body += " at port ";
body += std::to_string(config.port);
body += "</address>";
body += "</body></html>";
return body;
}
} // namespace
struct Request {
std::string path;
std::vector<std::string> pushes;
struct {
int32_t urgency;
int inc;
} pri;
};
namespace {
Request request_path(const std::string_view &uri, bool is_connect) {
http_parser_url u;
Request req;
req.pri.urgency = -1;
req.pri.inc = -1;
http_parser_url_init(&u);
if (auto rv = http_parser_parse_url(uri.data(), uri.size(), is_connect, &u);
rv != 0) {
return req;
}
if (u.field_set & (1 << UF_PATH)) {
req.path = std::string(uri.data() + u.field_data[UF_PATH].off,
u.field_data[UF_PATH].len);
if (req.path.find('%') != std::string::npos) {
req.path = util::percent_decode(std::begin(req.path), std::end(req.path));
}
if (!req.path.empty() && req.path.back() == '/') {
req.path += "index.html";
}
} else {
req.path = "/index.html";
}
req.path = util::normalize_path(req.path);
if (req.path == "/") {
req.path = "/index.html";
}
if (u.field_set & (1 << UF_QUERY)) {
static constexpr char push_prefix[] = "push=";
static constexpr char urgency_prefix[] = "u=";
static constexpr char inc_prefix[] = "i=";
auto q = std::string(uri.data() + u.field_data[UF_QUERY].off,
u.field_data[UF_QUERY].len);
for (auto p = std::begin(q); p != std::end(q);) {
if (util::istarts_with(p, std::end(q), std::begin(push_prefix),
std::end(push_prefix) - 1)) {
auto path_start = p + sizeof(push_prefix) - 1;
auto path_end = std::find(path_start, std::end(q), '&');
if (path_start != path_end && *path_start == '/') {
req.pushes.emplace_back(path_start, path_end);
}
if (path_end == std::end(q)) {
break;
}
p = path_end + 1;
continue;
}
if (util::istarts_with(p, std::end(q), std::begin(urgency_prefix),
std::end(urgency_prefix) - 1)) {
auto urgency_start = p + sizeof(urgency_prefix) - 1;
auto urgency_end = std::find(urgency_start, std::end(q), '&');
if (urgency_start + 1 == urgency_end && '0' <= *urgency_start &&
*urgency_start <= '7') {
req.pri.urgency = *urgency_start - '0';
}
if (urgency_end == std::end(q)) {
break;
}
p = urgency_end + 1;
continue;
}
if (util::istarts_with(p, std::end(q), std::begin(inc_prefix),
std::end(inc_prefix) - 1)) {
auto inc_start = p + sizeof(inc_prefix) - 1;
auto inc_end = std::find(inc_start, std::end(q), '&');
if (inc_start + 1 == inc_end &&
(*inc_start == '0' || *inc_start == '1')) {
req.pri.inc = *inc_start - '0';
}
if (inc_end == std::end(q)) {
break;
}
p = inc_end + 1;
continue;
}
p = std::find(p, std::end(q), '&');
if (p == std::end(q)) {
break;
}
++p;
}
}
return req;
}
} // namespace
enum FileEntryFlag {
FILE_ENTRY_TYPE_DIR = 0x1,
};
struct FileEntry {
uint64_t len;
void *map;
int fd;
uint8_t flags;
};
namespace {
std::unordered_map<std::string, FileEntry> file_cache;
} // namespace
std::pair<FileEntry, int> Stream::open_file(const std::string &path) {
auto it = file_cache.find(path);
if (it != std::end(file_cache)) {
return {(*it).second, 0};
}
auto fd = open(path.c_str(), O_RDONLY);
if (fd == -1) {
return {{}, -1};
}
struct stat st {};
if (fstat(fd, &st) != 0) {
close(fd);
return {{}, -1};
}
FileEntry fe{};
if (st.st_mode & S_IFDIR) {
fe.flags |= FILE_ENTRY_TYPE_DIR;
fe.fd = -1;
close(fd);
} else {
fe.fd = fd;
fe.len = st.st_size;
fe.map = mmap(nullptr, fe.len, PROT_READ, MAP_SHARED, fd, 0);
if (fe.map == MAP_FAILED) {
std::cerr << "mmap: " << strerror(errno) << std::endl;
close(fd);
return {{}, -1};
}
}
file_cache.emplace(path, fe);
return {std::move(fe), 0};
}
void Stream::map_file(const FileEntry &fe) {
data = static_cast<uint8_t *>(fe.map);
datalen = fe.len;
}
int64_t Stream::find_dyn_length(const std::string_view &path) {
assert(path[0] == '/');
if (path.size() == 1) {
return -1;
}
uint64_t n = 0;
for (auto it = std::begin(path) + 1; it != std::end(path); ++it) {
if (*it < '0' || '9' < *it) {
return -1;
}
auto d = *it - '0';
if (n > (((1ull << 62) - 1) - d) / 10) {
return -1;
}
n = n * 10 + d;
if (n > config.max_dyn_length) {
return -1;
}
}
return static_cast<int64_t>(n);
}
namespace {
nghttp3_ssize read_data(nghttp3_conn *conn, int64_t stream_id, nghttp3_vec *vec,
size_t veccnt, uint32_t *pflags, void *user_data,
void *stream_user_data) {
auto stream = static_cast<Stream *>(stream_user_data);
vec[0].base = stream->data;
vec[0].len = stream->datalen;
*pflags |= NGHTTP3_DATA_FLAG_EOF;
if (config.send_trailers) {
*pflags |= NGHTTP3_DATA_FLAG_NO_END_STREAM;
}
return 1;
}
} // namespace
auto dyn_buf = std::make_unique<std::array<uint8_t, 16_k>>();
namespace {
nghttp3_ssize dyn_read_data(nghttp3_conn *conn, int64_t stream_id,
nghttp3_vec *vec, size_t veccnt, uint32_t *pflags,
void *user_data, void *stream_user_data) {
auto stream = static_cast<Stream *>(stream_user_data);
if (stream->dynbuflen > MAX_DYNBUFLEN) {
return NGHTTP3_ERR_WOULDBLOCK;
}
auto len =
std::min(dyn_buf->size(), static_cast<size_t>(stream->dyndataleft));
vec[0].base = dyn_buf->data();
vec[0].len = len;
stream->dynbuflen += len;
stream->dyndataleft -= len;
if (stream->dyndataleft == 0) {
*pflags |= NGHTTP3_DATA_FLAG_EOF;
if (config.send_trailers) {
*pflags |= NGHTTP3_DATA_FLAG_NO_END_STREAM;
auto stream_id_str = std::to_string(stream_id);
std::array<nghttp3_nv, 1> trailers{
util::make_nv("x-ngtcp2-stream-id", stream_id_str),
};
if (auto rv = nghttp3_conn_submit_trailers(
conn, stream_id, trailers.data(), trailers.size());
rv != 0) {
std::cerr << "nghttp3_conn_submit_trailers: " << nghttp3_strerror(rv)
<< std::endl;
return NGHTTP3_ERR_CALLBACK_FAILURE;
}
}
}
return 1;
}
} // namespace
void Stream::http_acked_stream_data(size_t datalen) {
if (!dynresp) {
return;
}
assert(dynbuflen >= datalen);
dynbuflen -= datalen;
}
int Stream::send_status_response(nghttp3_conn *httpconn,
unsigned int status_code,
const std::vector<HTTPHeader> &extra_headers) {
status_resp_body = make_status_body(status_code);
auto status_code_str = std::to_string(status_code);
auto content_length_str = std::to_string(status_resp_body.size());
std::vector<nghttp3_nv> nva(4 + extra_headers.size());
nva[0] = util::make_nv(":status", status_code_str);
nva[1] = util::make_nv("server", NGTCP2_SERVER);
nva[2] = util::make_nv("content-type", "text/html; charset=utf-8");
nva[3] = util::make_nv("content-length", content_length_str);
for (size_t i = 0; i < extra_headers.size(); ++i) {
auto &hdr = extra_headers[i];
auto &nv = nva[4 + i];
nv = util::make_nv(hdr.name, hdr.value);
}
data = (uint8_t *)status_resp_body.data();
datalen = status_resp_body.size();
nghttp3_data_reader dr{};
dr.read_data = read_data;
if (auto rv = nghttp3_conn_submit_response(httpconn, stream_id, nva.data(),
nva.size(), &dr);
rv != 0) {
std::cerr << "nghttp3_conn_submit_response: " << nghttp3_strerror(rv)
<< std::endl;
return -1;
}
if (config.send_trailers) {
auto stream_id_str = std::to_string(stream_id);
std::array<nghttp3_nv, 1> trailers{
util::make_nv("x-ngtcp2-stream-id", stream_id_str),
};
if (auto rv = nghttp3_conn_submit_trailers(
httpconn, stream_id, trailers.data(), trailers.size());
rv != 0) {
std::cerr << "nghttp3_conn_submit_trailers: " << nghttp3_strerror(rv)
<< std::endl;
return -1;
}
}
handler->shutdown_read(stream_id, NGHTTP3_H3_NO_ERROR);
return 0;
}
int Stream::send_redirect_response(nghttp3_conn *httpconn,
unsigned int status_code,
const std::string_view &path) {
return send_status_response(httpconn, status_code, {{"location", path}});
}
int Stream::start_response(nghttp3_conn *httpconn) {
// TODO This should be handled by nghttp3
if (uri.empty() || method.empty()) {
return send_status_response(httpconn, 400);
}
auto req = request_path(uri, method == "CONNECT");
if (req.path.empty()) {
return send_status_response(httpconn, 400);
}
auto dyn_len = find_dyn_length(req.path);
int64_t content_length = -1;
nghttp3_data_reader dr{};
std::string content_type = "text/plain";
if (dyn_len == -1) {
auto path = config.htdocs + req.path;
auto [fe, rv] = open_file(path);
if (rv != 0) {
send_status_response(httpconn, 404);
return 0;
}
if (fe.flags & FILE_ENTRY_TYPE_DIR) {
send_redirect_response(httpconn, 308,
path.substr(config.htdocs.size() - 1) + '/');
return 0;
}
content_length = fe.len;
if (method != "HEAD") {
map_file(fe);
}
dr.read_data = read_data;
auto ext = std::end(req.path) - 1;
for (; ext != std::begin(req.path) && *ext != '.' && *ext != '/'; --ext)
;
if (*ext == '.') {
++ext;
auto it = config.mime_types.find(std::string{ext, std::end(req.path)});
if (it != std::end(config.mime_types)) {
content_type = (*it).second;
}
}
} else {
content_length = dyn_len;
datalen = dyn_len;
dynresp = true;
dyndataleft = dyn_len;
dr.read_data = dyn_read_data;
content_type = "application/octet-stream";
}
if ((stream_id & 0x3) == 0 && !authority.empty()) {
for (const auto &push : req.pushes) {
if (handler->push_content(stream_id, authority, push) != 0) {
return -1;
}
}
}
auto content_length_str = std::to_string(content_length);
std::array<nghttp3_nv, 5> nva{
util::make_nv(":status", "200"),
util::make_nv("server", NGTCP2_SERVER),
util::make_nv("content-type", content_type),
util::make_nv("content-length", content_length_str),
};
size_t nvlen = 4;
std::string prival;
if (req.pri.urgency != -1 || req.pri.inc != -1) {
nghttp3_pri pri;
if (auto rv = nghttp3_conn_get_stream_priority(httpconn, &pri, stream_id);
rv != 0) {
std::cerr << "nghttp3_conn_get_stream_priority: " << nghttp3_strerror(rv)
<< std::endl;
return -1;
}
if (req.pri.urgency != -1) {
pri.urgency = req.pri.urgency;
}
if (req.pri.inc != -1) {
pri.inc = req.pri.inc;
}
if (auto rv = nghttp3_conn_set_stream_priority(httpconn, stream_id, &pri);
rv != 0) {
std::cerr << "nghttp3_conn_set_stream_priority: " << nghttp3_strerror(rv)
<< std::endl;
return -1;
}
prival = "u=";
prival += pri.urgency + '0';
prival += ",i";
if (!pri.inc) {
prival += "=?0";
}
nva[nvlen++] = util::make_nv("priority", prival);
}
if (!config.quiet) {
debug::print_http_response_headers(stream_id, nva.data(), nvlen);
}
if (auto rv = nghttp3_conn_submit_response(httpconn, stream_id, nva.data(),
nvlen, &dr);
rv != 0) {
std::cerr << "nghttp3_conn_submit_response: " << nghttp3_strerror(rv)
<< std::endl;
return -1;
}
if (config.send_trailers && dyn_len == -1) {
auto stream_id_str = std::to_string(stream_id);
std::array<nghttp3_nv, 1> trailers{
util::make_nv("x-ngtcp2-stream-id", stream_id_str),
};
if (auto rv = nghttp3_conn_submit_trailers(
httpconn, stream_id, trailers.data(), trailers.size());
rv != 0) {
std::cerr << "nghttp3_conn_submit_trailers: " << nghttp3_strerror(rv)
<< std::endl;
return -1;
}
handler->shutdown_read(stream_id, NGHTTP3_H3_NO_ERROR);
}
return 0;
}
namespace {
void writecb(struct ev_loop *loop, ev_io *w, int revents) {
ev_io_stop(loop, w);
auto h = static_cast<Handler *>(w->data);
auto s = h->server();
switch (h->on_write()) {
case 0:
case NETWORK_ERR_CLOSE_WAIT:
return;
default:
s->remove(h);
}
}
} // namespace
namespace {
void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
auto h = static_cast<Handler *>(w->data);
auto s = h->server();
if (ngtcp2_conn_is_in_closing_period(h->conn())) {
if (!config.quiet) {
std::cerr << "Closing Period is over" << std::endl;
}
s->remove(h);
return;
}
if (h->draining()) {
if (!config.quiet) {
std::cerr << "Draining Period is over" << std::endl;
}
s->remove(h);
return;
}
if (!config.quiet) {
std::cerr << "Timeout" << std::endl;
}
h->start_draining_period();
}
} // namespace
namespace {
void retransmitcb(struct ev_loop *loop, ev_timer *w, int revents) {
int rv;
auto h = static_cast<Handler *>(w->data);
auto s = h->server();
if (!config.quiet) {
std::cerr << "Timer expired" << std::endl;
}
rv = h->handle_expiry();
if (rv != 0) {
goto fail;
}
rv = h->on_write();
if (rv != 0) {
goto fail;
}
return;
fail:
switch (rv) {
case NETWORK_ERR_CLOSE_WAIT:
ev_timer_stop(loop, w);
return;
default:
s->remove(h);
return;
}
}
} // namespace
Handler::Handler(struct ev_loop *loop, SSL_CTX *ssl_ctx, Server *server,
const ngtcp2_cid *rcid)
: endpoint_{nullptr},
remote_addr_{},
max_pktlen_(0),
loop_(loop),
ssl_ctx_(ssl_ctx),
ssl_(nullptr),
server_(server),
qlog_(nullptr),
crypto_{},
conn_(nullptr),
scid_{},
pscid_{},
rcid_(*rcid),
httpconn_{nullptr},
last_error_{QUICErrorType::Transport, 0},
nkey_update_(0),
draining_(false) {
ev_io_init(&wev_, writecb, 0, EV_WRITE);
wev_.data = this;
ev_timer_init(&timer_, timeoutcb, 0.,
static_cast<double>(config.timeout) / NGTCP2_SECONDS);
timer_.data = this;
ev_timer_init(&rttimer_, retransmitcb, 0., 0.);
rttimer_.data = this;
}
Handler::~Handler() {
if (!config.quiet) {
std::cerr << scid_ << " Closing QUIC connection " << std::endl;
}
ev_timer_stop(loop_, &rttimer_);
ev_timer_stop(loop_, &timer_);
ev_io_stop(loop_, &wev_);
if (httpconn_) {
nghttp3_conn_del(httpconn_);
}
if (conn_) {
ngtcp2_conn_del(conn_);
}
if (ssl_) {
SSL_free(ssl_);
}
if (qlog_) {
fclose(qlog_);
}
}
namespace {
int handshake_completed(ngtcp2_conn *conn, void *user_data) {
auto h = static_cast<Handler *>(user_data);
if (!config.quiet) {
debug::handshake_completed(conn, user_data);
}
if (h->handshake_completed() != 0) {
return NGTCP2_ERR_CALLBACK_FAILURE;
}
return 0;
}
} // namespace
int Handler::handshake_completed() {
if (!config.quiet) {
std::cerr << "Negotiated cipher suite is " << SSL_get_cipher_name(ssl_)
<< std::endl;
const unsigned char *alpn = nullptr;
unsigned int alpnlen;
SSL_get0_alpn_selected(ssl_, &alpn, &alpnlen);
if (alpn) {
std::cerr << "Negotiated ALPN is ";
std::cerr.write(reinterpret_cast<const char *>(alpn), alpnlen);
std::cerr << std::endl;
}
}
std::array<uint8_t, MAX_TOKENLEN> token;
size_t tokenlen = token.size();
if (server_->generate_token(token.data(), tokenlen, &remote_addr_.su.sa) !=
0) {
if (!config.quiet) {
std::cerr << "Unable to generate token" << std::endl;
}
return 0;
}
if (auto rv = ngtcp2_conn_submit_new_token(conn_, token.data(), tokenlen);
rv != 0) {
if (!config.quiet) {
std::cerr << "ngtcp2_conn_submit_new_token: " << ngtcp2_strerror(rv)
<< std::endl;
}
return -1;
}
return 0;
}
namespace {
int do_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp,
const ngtcp2_crypto_cipher_ctx *hp_ctx, const uint8_t *sample) {
if (ngtcp2_crypto_hp_mask(dest, hp, hp_ctx, sample) != 0) {
return NGTCP2_ERR_CALLBACK_FAILURE;
}
if (!config.quiet && config.show_secret) {
debug::print_hp_mask(dest, NGTCP2_HP_MASKLEN, sample, NGTCP2_HP_SAMPLELEN);
}
return 0;
}
} // namespace
namespace {
int recv_crypto_data(ngtcp2_conn *conn, ngtcp2_crypto_level crypto_level,
uint64_t offset, const uint8_t *data, size_t datalen,
void *user_data) {
if (!config.quiet && !config.no_quic_dump) {
debug::print_crypto_data(crypto_level, data, datalen);
}
auto h = static_cast<Handler *>(user_data);
if (h->recv_crypto_data(crypto_level, data, datalen) != 0) {
if (auto err = ngtcp2_conn_get_tls_error(conn); err) {
return err;
}
return NGTCP2_ERR_CRYPTO;
}
return 0;
}
} // namespace
namespace {
int recv_stream_data(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id,
uint64_t offset, const uint8_t *data, size_t datalen,
void *user_data, void *stream_user_data) {
auto h = static_cast<Handler *>(user_data);
if (h->recv_stream_data(flags, stream_id, data, datalen) != 0) {
return NGTCP2_ERR_CALLBACK_FAILURE;
}
return 0;
}
} // namespace
namespace {
int acked_crypto_offset(ngtcp2_conn *conn, ngtcp2_crypto_level crypto_level,
uint64_t offset, uint64_t datalen, void *user_data) {
auto h = static_cast<Handler *>(user_data);
h->remove_tx_crypto_data(crypto_level, offset, datalen);
return 0;
}
} // namespace
namespace {
int acked_stream_data_offset(ngtcp2_conn *conn, int64_t stream_id,
uint64_t offset, uint64_t datalen, void *user_data,
void *stream_user_data) {
auto h = static_cast<Handler *>(user_data);
if (h->acked_stream_data_offset(stream_id, datalen) != 0) {
return NGTCP2_ERR_CALLBACK_FAILURE;
}
return 0;
}
} // namespace
int Handler::acked_stream_data_offset(int64_t stream_id, uint64_t datalen) {
if (!httpconn_) {
return 0;
}
if (auto rv = nghttp3_conn_add_ack_offset(httpconn_, stream_id, datalen);
rv != 0) {
std::cerr << "nghttp3_conn_add_ack_offset: " << nghttp3_strerror(rv)
<< std::endl;
return -1;
}
return 0;
}
namespace {
int stream_open(ngtcp2_conn *conn, int64_t stream_id, void *user_data) {
auto h = static_cast<Handler *>(user_data);
h->on_stream_open(stream_id);
return 0;
}
} // namespace
void Handler::on_stream_open(int64_t stream_id) {
if (!ngtcp2_is_bidi_stream(stream_id)) {
return;
}
auto it = streams_.find(stream_id);
assert(it == std::end(streams_));
streams_.emplace(stream_id, std::make_unique<Stream>(stream_id, this));
}
int Handler::push_content(int64_t stream_id, const std::string_view &authority,
const std::string_view &path) {
auto nva = std::array<nghttp3_nv, 4>{
util::make_nv(":method", "GET"),
util::make_nv(":scheme", "https"),
util::make_nv(":authority", authority),
util::make_nv(":path", path),
};
int64_t push_id;
if (auto rv = nghttp3_conn_submit_push_promise(httpconn_, &push_id, stream_id,
nva.data(), nva.size());
rv != 0) {
std::cerr << "nghttp3_conn_submit_push_promise: " << nghttp3_strerror(rv)
<< std::endl;
if (rv != NGHTTP3_ERR_PUSH_ID_BLOCKED) {
return -1;
}
return 0;
}
if (!config.quiet) {
debug::print_http_push_promise(stream_id, push_id, nva.data(), nva.size());
}
int64_t push_stream_id;
if (auto rv = ngtcp2_conn_open_uni_stream(conn_, &push_stream_id, nullptr);
rv != 0) {
std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv)
<< std::endl;
if (rv != NGTCP2_ERR_STREAM_ID_BLOCKED) {
return -1;
}
return 0;
}
if (!config.quiet) {
debug::push_stream(push_id, push_stream_id);
}
Stream *stream;
{
auto p = std::make_unique<Stream>(push_stream_id, this);
stream = p.get();
streams_.emplace(push_stream_id, std::move(p));
}
if (auto rv =
nghttp3_conn_bind_push_stream(httpconn_, push_id, push_stream_id);
rv != 0) {
std::cerr << "nghttp3_conn_bind_push_stream: " << nghttp3_strerror(rv)
<< std::endl;
return -1;
}
stream->uri = path;
stream->method = "GET";
stream->authority = authority;
nghttp3_conn_set_stream_user_data(httpconn_, push_stream_id, stream);
stream->start_response(httpconn_);
return 0;
}
namespace {
int stream_close(ngtcp2_conn *conn, int64_t stream_id, uint64_t app_error_code,
void *user_data, void *stream_user_data) {
auto h = static_cast<Handler *>(user_data);
if (h->on_stream_close(stream_id, app_error_code) != 0) {
return NGTCP2_ERR_CALLBACK_FAILURE;
}
return 0;
}
} // namespace
namespace {
int stream_reset(ngtcp2_conn *conn, int64_t stream_id, uint64_t final_size,
uint64_t app_error_code, void *user_data,
void *stream_user_data) {
auto h = static_cast<Handler *>(user_data);
if (h->on_stream_reset(stream_id) != 0) {
return NGTCP2_ERR_CALLBACK_FAILURE;
}
return 0;
}
} // namespace
int Handler::on_stream_reset(int64_t stream_id) {
if (httpconn_) {
if (auto rv = nghttp3_conn_reset_stream(httpconn_, stream_id); rv != 0) {
std::cerr << "nghttp3_conn_reset_stream: " << nghttp3_strerror(rv)
<< std::endl;
return -1;
}
}
return 0;
}
namespace {
int rand(uint8_t *dest, size_t destlen, const ngtcp2_rand_ctx *rand_ctx,
ngtcp2_rand_usage usage) {
auto dis = std::uniform_int_distribution<uint8_t>(0, 255);
std::generate(dest, dest + destlen, [&dis]() { return dis(randgen); });
return 0;
}
} // namespace
namespace {
int get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, uint8_t *token,
size_t cidlen, void *user_data) {
auto h = static_cast<Handler *>(user_data);
auto server = h->server();
cid->datalen = cidlen;
generate_authenticated_cid(cid->data, cidlen, server->get_svindex());
auto md = ngtcp2_crypto_md{const_cast<EVP_MD *>(EVP_sha256())};
if (ngtcp2_crypto_generate_stateless_reset_token(
token, &md, config.static_secret.data(), config.static_secret.size(),
cid) != 0) {
return NGTCP2_ERR_CALLBACK_FAILURE;
}
server->associate_cid(cid, h);
return 0;
}
} // namespace
namespace {
int remove_connection_id(ngtcp2_conn *conn, const ngtcp2_cid *cid,
void *user_data) {
auto h = static_cast<Handler *>(user_data);
h->server()->dissociate_cid(cid);
return 0;
}
} // namespace
namespace {
int update_key(ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret,
ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
const uint8_t *current_rx_secret,
const uint8_t *current_tx_secret, size_t secretlen,
void *user_data) {
auto h = static_cast<Handler *>(user_data);
if (h->update_key(rx_secret, tx_secret, rx_aead_ctx, rx_iv, tx_aead_ctx,
tx_iv, current_rx_secret, current_tx_secret,
secretlen) != 0) {
return NGTCP2_ERR_CALLBACK_FAILURE;
}
return 0;
}
} // namespace
namespace {
int path_validation(ngtcp2_conn *conn, const ngtcp2_path *path,
ngtcp2_path_validation_result res, void *user_data) {
if (!config.quiet) {
debug::path_validation(path, res);
}
return 0;
}
} // namespace
namespace {
int extend_max_remote_streams_bidi(ngtcp2_conn *conn, uint64_t max_streams,
void *user_data) {
auto h = static_cast<Handler *>(user_data);
h->extend_max_remote_streams_bidi(max_streams);
return 0;
}
} // namespace
void Handler::extend_max_remote_streams_bidi(uint64_t max_streams) {
if (!httpconn_) {
return;
}
nghttp3_conn_set_max_client_streams_bidi(httpconn_, max_streams);
}
namespace {
int http_recv_data(nghttp3_conn *conn, int64_t stream_id, const uint8_t *data,
size_t datalen, void *user_data, void *stream_user_data) {
if (!config.quiet && !config.no_http_dump) {
debug::print_http_data(stream_id, data, datalen);
}
auto h = static_cast<Handler *>(user_data);
h->http_consume(stream_id, datalen);
return 0;
}
} // namespace
namespace {
int http_deferred_consume(nghttp3_conn *conn, int64_t stream_id,
size_t nconsumed, void *user_data,
void *stream_user_data) {
auto h = static_cast<Handler *>(user_data);
h->http_consume(stream_id, nconsumed);
return 0;
}
} // namespace
void Handler::http_consume(int64_t stream_id, size_t nconsumed) {
ngtcp2_conn_extend_max_stream_offset(conn_, stream_id, nconsumed);
ngtcp2_conn_extend_max_offset(conn_, nconsumed);
}
namespace {
int http_begin_request_headers(nghttp3_conn *conn, int64_t stream_id,
void *user_data, void *stream_user_data) {
if (!config.quiet) {
debug::print_http_begin_request_headers(stream_id);
}
auto h = static_cast<Handler *>(user_data);
h->http_begin_request_headers(stream_id);
return 0;
}
} // namespace
void Handler::http_begin_request_headers(int64_t stream_id) {
auto it = streams_.find(stream_id);
assert(it != std::end(streams_));
auto &stream = (*it).second;
nghttp3_conn_set_stream_user_data(httpconn_, stream_id, stream.get());
}
namespace {
int http_recv_request_header(nghttp3_conn *conn, int64_t stream_id,
int32_t token, nghttp3_rcbuf *name,
nghttp3_rcbuf *value, uint8_t flags,
void *user_data, void *stream_user_data) {
if (!config.quiet) {
debug::print_http_header(stream_id, name, value, flags);
}
auto h = static_cast<Handler *>(user_data);
auto stream = static_cast<Stream *>(stream_user_data);
h->http_recv_request_header(stream, token, name, value);
return 0;
}
} // namespace
void Handler::http_recv_request_header(Stream *stream, int32_t token,
nghttp3_rcbuf *name,
nghttp3_rcbuf *value) {
auto v = nghttp3_rcbuf_get_buf(value);
switch (token) {
case NGHTTP3_QPACK_TOKEN__PATH:
stream->uri = std::string{v.base, v.base + v.len};
break;
case NGHTTP3_QPACK_TOKEN__METHOD:
stream->method = std::string{v.base, v.base + v.len};
break;
case NGHTTP3_QPACK_TOKEN__AUTHORITY:
stream->authority = std::string{v.base, v.base + v.len};
break;
}
}
namespace {
int http_end_request_headers(nghttp3_conn *conn, int64_t stream_id,
void *user_data, void *stream_user_data) {
if (!config.quiet) {
debug::print_http_end_headers(stream_id);
}
auto h = static_cast<Handler *>(user_data);
auto stream = static_cast<Stream *>(stream_user_data);
if (h->http_end_request_headers(stream) != 0) {
return NGHTTP3_ERR_CALLBACK_FAILURE;
}
return 0;
}
} // namespace
int Handler::http_end_request_headers(Stream *stream) {
if (config.early_response) {
return start_response(stream);
}
return 0;
}
namespace {
int http_end_stream(nghttp3_conn *conn, int64_t stream_id, void *user_data,
void *stream_user_data) {
auto h = static_cast<Handler *>(user_data);
auto stream = static_cast<Stream *>(stream_user_data);
if (h->http_end_stream(stream) != 0) {
return NGHTTP3_ERR_CALLBACK_FAILURE;
}
return 0;
}
} // namespace
int Handler::http_end_stream(Stream *stream) {
if (!config.early_response) {
return start_response(stream);
}
return 0;
}
int Handler::start_response(Stream *stream) {
return stream->start_response(httpconn_);
}
namespace {
int http_acked_stream_data(nghttp3_conn *conn, int64_t stream_id,
size_t datalen, void *user_data,
void *stream_user_data) {
auto h = static_cast<Handler *>(user_data);
auto stream = static_cast<Stream *>(stream_user_data);
h->http_acked_stream_data(stream, datalen);
return 0;
}
} // namespace
void Handler::http_acked_stream_data(Stream *stream, size_t datalen) {
stream->http_acked_stream_data(datalen);
if (stream->dynresp && stream->dynbuflen < MAX_DYNBUFLEN - 16384) {
if (auto rv = nghttp3_conn_resume_stream(httpconn_, stream->stream_id);
rv != 0) {
// TODO Handle error
std::cerr << "nghttp3_conn_resume_stream: " << nghttp3_strerror(rv)
<< std::endl;
}
}
}
namespace {
int http_stream_close(nghttp3_conn *conn, int64_t stream_id,
uint64_t app_error_code, void *conn_user_data,
void *stream_user_data) {
auto h = static_cast<Handler *>(conn_user_data);
h->http_stream_close(stream_id, app_error_code);
return 0;
}
} // namespace
void Handler::http_stream_close(int64_t stream_id, uint64_t app_error_code) {
auto it = streams_.find(stream_id);
if (it == std::end(streams_)) {
return;
}
if (!config.quiet) {
std::cerr << "HTTP stream " << stream_id << " closed with error code "
<< app_error_code << std::endl;
}
streams_.erase(it);
if (ngtcp2_is_bidi_stream(stream_id)) {
assert(!ngtcp2_conn_is_local_stream(conn_, stream_id));
ngtcp2_conn_extend_max_streams_bidi(conn_, 1);
}
}
namespace {
int http_send_stop_sending(nghttp3_conn *conn, int64_t stream_id,
uint64_t app_error_code, void *user_data,
void *stream_user_data) {
auto h = static_cast<Handler *>(user_data);
if (h->http_send_stop_sending(stream_id, app_error_code) != 0) {
return NGHTTP3_ERR_CALLBACK_FAILURE;
}
return 0;
}
} // namespace
int Handler::http_send_stop_sending(int64_t stream_id,
uint64_t app_error_code) {
if (auto rv =
ngtcp2_conn_shutdown_stream_read(conn_, stream_id, app_error_code);
rv != 0) {
std::cerr << "ngtcp2_conn_shutdown_stream_read: " << ngtcp2_strerror(rv)
<< std::endl;
return -1;
if (rv == NGTCP2_ERR_STREAM_NOT_FOUND) {
return 0;
}
return -1;
}
return 0;
}
namespace {
int http_reset_stream(nghttp3_conn *conn, int64_t stream_id,
uint64_t app_error_code, void *user_data,
void *stream_user_data) {
auto h = static_cast<Handler *>(user_data);
if (h->http_reset_stream(stream_id, app_error_code) != 0) {
return NGHTTP3_ERR_CALLBACK_FAILURE;
}
return 0;
}
} // namespace
int Handler::http_reset_stream(int64_t stream_id, uint64_t app_error_code) {
if (auto rv =
ngtcp2_conn_shutdown_stream_write(conn_, stream_id, app_error_code);
rv != 0) {
std::cerr << "ngtcp2_conn_shutdown_stream_write: " << ngtcp2_strerror(rv)
<< std::endl;
return -1;
if (rv == NGTCP2_ERR_STREAM_NOT_FOUND) {
return 0;
}
return -1;
}
return 0;
}
int Handler::setup_httpconn() {
if (httpconn_) {
return 0;
}
if (ngtcp2_conn_get_max_local_streams_uni(conn_) < 3) {
std::cerr << "peer does not allow at least 3 unidirectional streams."
<< std::endl;
return -1;
}
nghttp3_conn_callbacks callbacks{
::http_acked_stream_data, // acked_stream_data
::http_stream_close,
::http_recv_data,
::http_deferred_consume,
::http_begin_request_headers,
::http_recv_request_header,
::http_end_request_headers,
nullptr, // begin_trailers
nullptr, // recv_trailer
nullptr, // end_trailers
nullptr, // begin_push_promise
nullptr, // recv_push_promise
nullptr, // end_push_promise
nullptr, // cancel_push
::http_send_stop_sending,
nullptr, // push_stream
::http_end_stream,
::http_reset_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();
if (auto rv =
nghttp3_conn_server_new(&httpconn_, &callbacks, &settings, mem, this);
rv != 0) {
std::cerr << "nghttp3_conn_server_new: " << nghttp3_strerror(rv)
<< std::endl;
return -1;
}
ngtcp2_transport_params params;
ngtcp2_conn_get_local_transport_params(conn_, &params);
nghttp3_conn_set_max_client_streams_bidi(httpconn_,
params.initial_max_streams_bidi);
int64_t ctrl_stream_id;
if (auto rv = ngtcp2_conn_open_uni_stream(conn_, &ctrl_stream_id, nullptr);
rv != 0) {
std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv)
<< std::endl;
return -1;
}
if (auto rv = nghttp3_conn_bind_control_stream(httpconn_, ctrl_stream_id);
rv != 0) {
std::cerr << "nghttp3_conn_bind_control_stream: " << nghttp3_strerror(rv)
<< std::endl;
return -1;
}
if (!config.quiet) {
fprintf(stderr, "http: control stream=%" PRIx64 "\n", ctrl_stream_id);
}
int64_t qpack_enc_stream_id, qpack_dec_stream_id;
if (auto rv =
ngtcp2_conn_open_uni_stream(conn_, &qpack_enc_stream_id, nullptr);
rv != 0) {
std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv)
<< std::endl;
return -1;
}
if (auto rv =
ngtcp2_conn_open_uni_stream(conn_, &qpack_dec_stream_id, nullptr);
rv != 0) {
std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv)
<< std::endl;
return -1;
}
if (auto rv = nghttp3_conn_bind_qpack_streams(httpconn_, qpack_enc_stream_id,
qpack_dec_stream_id);
rv != 0) {
std::cerr << "nghttp3_conn_bind_qpack_streams: " << nghttp3_strerror(rv)
<< std::endl;
return -1;
}
if (!config.quiet) {
fprintf(stderr,
"http: QPACK streams encoder=%" PRIx64 " decoder=%" PRIx64 "\n",
qpack_enc_stream_id, qpack_dec_stream_id);
}
return 0;
}
namespace {
int extend_max_stream_data(ngtcp2_conn *conn, int64_t stream_id,
uint64_t max_data, void *user_data,
void *stream_user_data) {
auto h = static_cast<Handler *>(user_data);
if (h->extend_max_stream_data(stream_id, max_data) != 0) {
return NGTCP2_ERR_CALLBACK_FAILURE;
}
return 0;
}
} // namespace
int Handler::extend_max_stream_data(int64_t stream_id, uint64_t max_data) {
if (auto rv = nghttp3_conn_unblock_stream(httpconn_, stream_id); rv != 0) {
std::cerr << "nghttp3_conn_unblock_stream: " << nghttp3_strerror(rv)
<< std::endl;
return -1;
}
return 0;
}
namespace {
void write_qlog(void *user_data, uint32_t flags, const void *data,
size_t datalen) {
auto h = static_cast<Handler *>(user_data);
h->write_qlog(data, datalen);
}
} // namespace
void Handler::write_qlog(const void *data, size_t datalen) {
assert(qlog_);
fwrite(data, 1, datalen, qlog_);
}
int Handler::init(const Endpoint &ep, const sockaddr *sa, socklen_t salen,
const ngtcp2_cid *dcid, const ngtcp2_cid *scid,
const ngtcp2_cid *ocid, const uint8_t *token, size_t tokenlen,
uint32_t version) {
endpoint_ = const_cast<Endpoint *>(&ep);
remote_addr_.len = salen;
memcpy(&remote_addr_.su.sa, sa, salen);
if (config.max_udp_payload_size) {
max_pktlen_ = config.max_udp_payload_size;
} else {
switch (remote_addr_.su.storage.ss_family) {
case AF_INET:
max_pktlen_ = NGTCP2_MAX_PKTLEN_IPV4;
break;
case AF_INET6:
max_pktlen_ = NGTCP2_MAX_PKTLEN_IPV6;
break;
default:
return -1;
}
}
ssl_ = SSL_new(ssl_ctx_);
SSL_set_app_data(ssl_, this);
SSL_set_accept_state(ssl_);
SSL_set_quic_early_data_enabled(ssl_, 1);
auto callbacks = ngtcp2_conn_callbacks{
nullptr, // client_initial
ngtcp2_crypto_recv_client_initial_cb,
::recv_crypto_data,
::handshake_completed,
nullptr, // recv_version_negotiation
ngtcp2_crypto_encrypt_cb,
ngtcp2_crypto_decrypt_cb,
do_hp_mask,
::recv_stream_data,
acked_crypto_offset,
::acked_stream_data_offset,
stream_open,
stream_close,
nullptr, // recv_stateless_reset
nullptr, // recv_retry
nullptr, // extend_max_streams_bidi
nullptr, // extend_max_streams_uni
rand,
get_new_connection_id,
remove_connection_id,
::update_key,
path_validation,
nullptr, // select_preferred_addr
::stream_reset,
::extend_max_remote_streams_bidi,
nullptr, // extend_max_remote_streams_uni
::extend_max_stream_data,
nullptr, // dcid_status
nullptr, // handshake_confirmed
nullptr, // recv_new_token
ngtcp2_crypto_delete_crypto_aead_ctx_cb,
ngtcp2_crypto_delete_crypto_cipher_ctx_cb,
};
auto dis = std::uniform_int_distribution<uint8_t>(0, 255);
scid_.datalen = NGTCP2_SV_SCIDLEN;
generate_authenticated_cid(scid_.data, NGTCP2_SV_SCIDLEN,
server_->get_svindex());
ngtcp2_settings settings;
ngtcp2_settings_default(&settings);
settings.log_printf = config.quiet ? nullptr : debug::log_printf;
settings.initial_ts = util::timestamp(loop_);
settings.token = ngtcp2_vec{const_cast<uint8_t *>(token), tokenlen};
settings.max_udp_payload_size = max_pktlen_;
settings.cc_algo =
config.cc == "cubic" ? NGTCP2_CC_ALGO_CUBIC : NGTCP2_CC_ALGO_RENO;
settings.initial_rtt = config.initial_rtt;
settings.max_window = config.max_window;
settings.max_stream_window = config.max_stream_window;
if (!config.qlog_dir.empty()) {
auto path = std::string{config.qlog_dir};
path += '/';
path += util::format_hex(scid_.data, scid_.datalen);
path += ".qlog";
qlog_ = fopen(path.c_str(), "w");
if (qlog_ == nullptr) {
std::cerr << "Could not open qlog file " << path << ": "
<< strerror(errno) << std::endl;
return -1;
}
settings.qlog.write = ::write_qlog;
settings.qlog.odcid = *scid;
}
auto &params = settings.transport_params;
params.initial_max_stream_data_bidi_local = config.max_stream_data_bidi_local;
params.initial_max_stream_data_bidi_remote =
config.max_stream_data_bidi_remote;
params.initial_max_stream_data_uni = config.max_stream_data_uni;
params.initial_max_data = config.max_data;
params.initial_max_streams_bidi = config.max_streams_bidi;
params.initial_max_streams_uni = config.max_streams_uni;
params.max_idle_timeout = config.timeout;
params.stateless_reset_token_present = 1;
params.active_connection_id_limit = 7;
if (ocid) {
params.original_dcid = *ocid;
params.retry_scid = *scid;
params.retry_scid_present = 1;
} else {
params.original_dcid = *scid;
}
std::generate(std::begin(params.stateless_reset_token),
std::end(params.stateless_reset_token),
[&dis]() { return dis(randgen); });
if (config.preferred_ipv4_addr.len || config.preferred_ipv6_addr.len) {
params.preferred_address_present = 1;
if (config.preferred_ipv4_addr.len) {
auto &dest = params.preferred_address.ipv4_addr;
const auto &addr = config.preferred_ipv4_addr;
assert(sizeof(dest) == sizeof(addr.su.in.sin_addr));
memcpy(&dest, &addr.su.in.sin_addr, sizeof(dest));
params.preferred_address.ipv4_port = htons(addr.su.in.sin_port);
}
if (config.preferred_ipv6_addr.len) {
auto &dest = params.preferred_address.ipv6_addr;
const auto &addr = config.preferred_ipv6_addr;
assert(sizeof(dest) == sizeof(addr.su.in6.sin6_addr));
memcpy(&dest, &addr.su.in6.sin6_addr, sizeof(dest));
params.preferred_address.ipv6_port = htons(addr.su.in6.sin6_port);
}
auto &token = params.preferred_address.stateless_reset_token;
std::generate(std::begin(token), std::end(token),
[&dis]() { return dis(randgen); });
pscid_.datalen = NGTCP2_SV_SCIDLEN;
std::generate(pscid_.data, pscid_.data + pscid_.datalen,
[&dis]() { return dis(randgen); });
params.preferred_address.cid = pscid_;
}
auto path = ngtcp2_path{{ep.addr.len, const_cast<sockaddr *>(&ep.addr.su.sa),
const_cast<Endpoint *>(&ep)},
{salen, const_cast<sockaddr *>(sa)}};
if (auto rv = ngtcp2_conn_server_new(&conn_, dcid, &scid_, &path, version,
&callbacks, &settings, nullptr, this);
rv != 0) {
std::cerr << "ngtcp2_conn_server_new: " << ngtcp2_strerror(rv) << std::endl;
return -1;
}
ngtcp2_conn_set_tls_native_handle(conn_, ssl_);
ev_io_set(&wev_, endpoint_->fd, EV_WRITE);
ev_timer_again(loop_, &timer_);
return 0;
}
void Handler::write_server_handshake(ngtcp2_crypto_level level,
const uint8_t *data, size_t datalen) {
auto &crypto = crypto_[level];
crypto.data.emplace_back(data, datalen);
auto &buf = crypto.data.back();
ngtcp2_conn_submit_crypto_data(conn_, level, buf.rpos(), buf.size());
}
int Handler::recv_crypto_data(ngtcp2_crypto_level crypto_level,
const uint8_t *data, size_t datalen) {
return ngtcp2_crypto_read_write_crypto_data(conn_, crypto_level, data,
datalen);
}
void Handler::update_endpoint(const ngtcp2_addr *addr) {
endpoint_ = static_cast<Endpoint *>(addr->user_data);
assert(endpoint_);
}
void Handler::update_remote_addr(const ngtcp2_addr *addr,
const ngtcp2_pkt_info *pi) {
remote_addr_.len = addr->addrlen;
memcpy(&remote_addr_.su, addr->addr, addr->addrlen);
if (pi) {
ecn_ = pi->ecn;
} else {
ecn_ = 0;
}
}
int Handler::feed_data(const Endpoint &ep, const sockaddr *sa, socklen_t salen,
const ngtcp2_pkt_info *pi, uint8_t *data,
size_t datalen) {
auto path = ngtcp2_path{{ep.addr.len, const_cast<sockaddr *>(&ep.addr.su.sa),
const_cast<Endpoint *>(&ep)},
{salen, const_cast<sockaddr *>(sa)}};
if (auto rv = ngtcp2_conn_read_pkt(conn_, &path, pi, data, datalen,
util::timestamp(loop_));
rv != 0) {
std::cerr << "ngtcp2_conn_read_pkt: " << ngtcp2_strerror(rv) << std::endl;
switch (rv) {
case NGTCP2_ERR_DRAINING:
start_draining_period();
return NETWORK_ERR_CLOSE_WAIT;
case NGTCP2_ERR_RETRY:
return NETWORK_ERR_RETRY;
case NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM:
case NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM:
case NGTCP2_ERR_TRANSPORT_PARAM:
// If rv indicates transport_parameters related error, we should
// send TRANSPORT_PARAMETER_ERROR even if last_error_.code is
// already set. This is because OpenSSL might set Alert.
last_error_ = quic_err_transport(rv);
break;
case NGTCP2_ERR_DROP_CONN:
return NETWORK_ERR_DROP_CONN;
default:
if (!last_error_.code) {
last_error_ = quic_err_transport(rv);
}
}
return handle_error();
}
return 0;
}
int Handler::on_read(const Endpoint &ep, const sockaddr *sa, socklen_t salen,
const ngtcp2_pkt_info *pi, uint8_t *data, size_t datalen) {
if (auto rv = feed_data(ep, sa, salen, pi, data, datalen); rv != 0) {
return rv;
}
reset_idle_timer();
return 0;
}
void Handler::reset_idle_timer() {
auto now = util::timestamp(loop_);
auto idle_expiry = ngtcp2_conn_get_idle_expiry(conn_);
timer_.repeat =
idle_expiry > now
? static_cast<ev_tstamp>(idle_expiry - now) / NGTCP2_SECONDS
: 1e-9;
if (!config.quiet) {
std::cerr << "Set idle timer=" << std::fixed << timer_.repeat << "s"
<< std::defaultfloat << std::endl;
}
ev_timer_again(loop_, &timer_);
}
int Handler::handle_expiry() {
auto now = util::timestamp(loop_);
if (auto rv = ngtcp2_conn_handle_expiry(conn_, now); rv != 0) {
std::cerr << "ngtcp2_conn_handle_expiry: " << ngtcp2_strerror(rv)
<< std::endl;
last_error_ = quic_err_transport(rv);
return handle_error();
}
return 0;
}
int Handler::on_write() {
if (ngtcp2_conn_is_in_closing_period(conn_) ||
ngtcp2_conn_is_in_draining_period(conn_)) {
return 0;
}
if (auto rv = write_streams(); rv != 0) {
return rv;
}
schedule_retransmit();
return 0;
}
int Handler::write_streams() {
std::array<nghttp3_vec, 16> vec;
PathStorage path;
size_t pktcnt = 0;
size_t max_pktcnt = std::min(static_cast<size_t>(10),
static_cast<size_t>(64_k / max_pktlen_));
std::array<uint8_t, 64_k> buf;
uint8_t *bufpos = buf.data();
ngtcp2_pkt_info pi;
for (;;) {
int64_t stream_id = -1;
int fin = 0;
nghttp3_ssize sveccnt = 0;
if (httpconn_ && ngtcp2_conn_get_max_data_left(conn_)) {
sveccnt = nghttp3_conn_writev_stream(httpconn_, &stream_id, &fin,
vec.data(), vec.size());
if (sveccnt < 0) {
std::cerr << "nghttp3_conn_writev_stream: " << nghttp3_strerror(sveccnt)
<< std::endl;
last_error_ = quic_err_app(sveccnt);
return handle_error();
}
}
ngtcp2_ssize ndatalen;
auto v = vec.data();
auto vcnt = static_cast<size_t>(sveccnt);
uint32_t flags = NGTCP2_WRITE_STREAM_FLAG_MORE;
if (fin) {
flags |= NGTCP2_WRITE_STREAM_FLAG_FIN;
}
auto nwrite = ngtcp2_conn_writev_stream(
conn_, &path.path, &pi, bufpos, max_pktlen_, &ndatalen, flags,
stream_id, reinterpret_cast<const ngtcp2_vec *>(v), vcnt,
util::timestamp(loop_));
if (nwrite < 0) {
switch (nwrite) {
case NGTCP2_ERR_STREAM_DATA_BLOCKED:
case NGTCP2_ERR_STREAM_SHUT_WR:
assert(ndatalen == -1);
if (auto rv = nghttp3_conn_block_stream(httpconn_, stream_id);
rv != 0) {
std::cerr << "nghttp3_conn_block_stream: " << nghttp3_strerror(rv)
<< std::endl;
last_error_ = quic_err_app(rv);
return handle_error();
}
continue;
case NGTCP2_ERR_WRITE_MORE:
assert(ndatalen > 0);
if (auto rv =
nghttp3_conn_add_write_offset(httpconn_, stream_id, ndatalen);
rv != 0) {
std::cerr << "nghttp3_conn_add_write_offset: " << nghttp3_strerror(rv)
<< std::endl;
last_error_ = quic_err_app(rv);
return handle_error();
}
continue;
}
assert(ndatalen == -1);
std::cerr << "ngtcp2_conn_writev_stream: " << ngtcp2_strerror(nwrite)
<< std::endl;
last_error_ = quic_err_transport(nwrite);
return handle_error();
}
assert(ndatalen == -1);
if (nwrite == 0) {
if (bufpos - buf.data()) {
server_->send_packet(*endpoint_, remote_addr_, ecn_, buf.data(),
bufpos - buf.data(), max_pktlen_);
reset_idle_timer();
}
// We are congestion limited.
return 0;
}
bufpos += nwrite;
#if NGTCP2_ENABLE_UDP_GSO
if (pktcnt == 0) {
update_endpoint(&path.path.local);
update_remote_addr(&path.path.remote, &pi);
} else if (remote_addr_.len != path.path.remote.addrlen ||
0 != memcmp(&remote_addr_.su, path.path.remote.addr,
path.path.remote.addrlen) ||
endpoint_ != path.path.local.user_data || ecn_ != pi.ecn) {
server_->send_packet(*endpoint_, remote_addr_, ecn_, buf.data(),
bufpos - buf.data() - nwrite, max_pktlen_);
update_endpoint(&path.path.local);
update_remote_addr(&path.path.remote, &pi);
server_->send_packet(*endpoint_, remote_addr_, ecn_, bufpos - nwrite,
nwrite, max_pktlen_);
reset_idle_timer();
ev_io_start(loop_, &wev_);
return 0;
}
if (++pktcnt == max_pktcnt || static_cast<size_t>(nwrite) < max_pktlen_) {
server_->send_packet(*endpoint_, remote_addr_, ecn_, buf.data(),
bufpos - buf.data(), max_pktlen_);
reset_idle_timer();
ev_io_start(loop_, &wev_);
return 0;
}
#else // !NGTCP2_ENABLE_UDP_GSO
update_endpoint(&path.path.local);
update_remote_addr(&path.path.remote, &pi);
reset_idle_timer();
server_->send_packet(*endpoint_, remote_addr_, ecn_, buf.data(),
bufpos - buf.data(), 0);
if (++pktcnt == max_pktcnt) {
ev_io_start(loop_, &wev_);
return 0;
}
bufpos = buf.data();
#endif // !NGTCP2_ENABLE_UDP_GSO
}
}
void Handler::signal_write() { ev_io_start(loop_, &wev_); }
bool Handler::draining() const { return draining_; }
void Handler::start_draining_period() {
draining_ = true;
ev_timer_stop(loop_, &rttimer_);
timer_.repeat =
static_cast<ev_tstamp>(ngtcp2_conn_get_pto(conn_)) / NGTCP2_SECONDS * 3;
ev_timer_again(loop_, &timer_);
if (!config.quiet) {
std::cerr << "Draining period has started (" << timer_.repeat << " seconds)"
<< std::endl;
}
}
int Handler::start_closing_period() {
if (!conn_ || ngtcp2_conn_is_in_closing_period(conn_)) {
return 0;
}
ev_timer_stop(loop_, &rttimer_);
timer_.repeat =
static_cast<ev_tstamp>(ngtcp2_conn_get_pto(conn_)) / NGTCP2_SECONDS * 3;
ev_timer_again(loop_, &timer_);
if (!config.quiet) {
std::cerr << "Closing period has started (" << timer_.repeat << " seconds)"
<< std::endl;
}
conn_closebuf_ = std::make_unique<Buffer>(max_pktlen_);
PathStorage path;
ngtcp2_pkt_info pi;
if (last_error_.type == QUICErrorType::Transport) {
auto n = ngtcp2_conn_write_connection_close(
conn_, &path.path, &pi, conn_closebuf_->wpos(), max_pktlen_,
last_error_.code, util::timestamp(loop_));
if (n < 0) {
std::cerr << "ngtcp2_conn_write_connection_close: " << ngtcp2_strerror(n)
<< std::endl;
return -1;
}
conn_closebuf_->push(n);
} else {
auto n = ngtcp2_conn_write_application_close(
conn_, &path.path, &pi, conn_closebuf_->wpos(), max_pktlen_,
last_error_.code, util::timestamp(loop_));
if (n < 0) {
std::cerr << "ngtcp2_conn_write_application_close: " << ngtcp2_strerror(n)
<< std::endl;
return -1;
}
conn_closebuf_->push(n);
}
update_endpoint(&path.path.local);
update_remote_addr(&path.path.remote, &pi);
return 0;
}
int Handler::handle_error() {
if (start_closing_period() != 0) {
return -1;
}
if (auto rv = send_conn_close(); rv != NETWORK_ERR_OK) {
return rv;
}
return NETWORK_ERR_CLOSE_WAIT;
}
int Handler::send_conn_close() {
if (!config.quiet) {
std::cerr << "Closing Period: TX CONNECTION_CLOSE" << std::endl;
}
assert(conn_closebuf_ && conn_closebuf_->size());
return server_->send_packet(*endpoint_, remote_addr_, 0,
conn_closebuf_->rpos(), conn_closebuf_->size(),
0);
}
void Handler::schedule_retransmit() {
auto expiry = ngtcp2_conn_get_expiry(conn_);
auto now = util::timestamp(loop_);
auto t = expiry < now ? 1e-9
: static_cast<ev_tstamp>(expiry - now) / NGTCP2_SECONDS;
if (!config.quiet) {
std::cerr << "Set timer=" << std::fixed << t << "s" << std::defaultfloat
<< std::endl;
}
rttimer_.repeat = t;
ev_timer_again(loop_, &rttimer_);
}
int Handler::recv_stream_data(uint32_t flags, int64_t stream_id,
const uint8_t *data, size_t datalen) {
if (!config.quiet && !config.no_quic_dump) {
debug::print_stream_data(stream_id, data, datalen);
}
if (!httpconn_) {
return 0;
}
auto nconsumed = nghttp3_conn_read_stream(
httpconn_, stream_id, data, datalen, flags & NGTCP2_STREAM_DATA_FLAG_FIN);
if (nconsumed < 0) {
std::cerr << "nghttp3_conn_read_stream: " << nghttp3_strerror(nconsumed)
<< std::endl;
last_error_ = quic_err_app(nconsumed);
return -1;
}
ngtcp2_conn_extend_max_stream_offset(conn_, stream_id, nconsumed);
ngtcp2_conn_extend_max_offset(conn_, nconsumed);
return 0;
}
int Handler::update_key(uint8_t *rx_secret, uint8_t *tx_secret,
ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
const uint8_t *current_rx_secret,
const uint8_t *current_tx_secret, size_t secretlen) {
auto crypto_ctx = ngtcp2_conn_get_crypto_ctx(conn_);
auto aead = &crypto_ctx->aead;
auto keylen = ngtcp2_crypto_aead_keylen(aead);
auto ivlen = ngtcp2_crypto_packet_protection_ivlen(aead);
++nkey_update_;
std::array<uint8_t, 64> rx_key, tx_key;
if (ngtcp2_crypto_update_key(conn_, rx_secret, tx_secret, rx_aead_ctx,
rx_key.data(), rx_iv, tx_aead_ctx, tx_key.data(),
tx_iv, current_rx_secret, current_tx_secret,
secretlen) != 0) {
return -1;
}
if (!config.quiet && config.show_secret) {
std::cerr << "application_traffic rx secret " << nkey_update_ << std::endl;
debug::print_secrets(rx_secret, secretlen, rx_key.data(), keylen, rx_iv,
ivlen);
std::cerr << "application_traffic tx secret " << nkey_update_ << std::endl;
debug::print_secrets(tx_secret, secretlen, tx_key.data(), keylen, tx_iv,
ivlen);
}
return 0;
}
const ngtcp2_cid *Handler::scid() const { return &scid_; }
const ngtcp2_cid *Handler::pscid() const { return &pscid_; }
const ngtcp2_cid *Handler::rcid() const { return &rcid_; }
Server *Handler::server() const { return server_; }
const Address &Handler::remote_addr() const { return remote_addr_; }
ngtcp2_conn *Handler::conn() const { return conn_; }
namespace {
void remove_tx_stream_data(std::deque<Buffer> &d, uint64_t &tx_offset,
uint64_t offset) {
for (; !d.empty() && tx_offset + d.front().size() <= offset;) {
auto &v = d.front();
tx_offset += v.size();
d.pop_front();
}
}
} // namespace
void Handler::remove_tx_crypto_data(ngtcp2_crypto_level crypto_level,
uint64_t offset, uint64_t datalen) {
auto &crypto = crypto_[crypto_level];
::remove_tx_stream_data(crypto.data, crypto.acked_offset, offset + datalen);
}
int Handler::on_stream_close(int64_t stream_id, uint64_t app_error_code) {
if (!config.quiet) {
std::cerr << "QUIC stream " << stream_id << " closed" << std::endl;
}
if (httpconn_) {
if (app_error_code == 0) {
app_error_code = NGHTTP3_H3_NO_ERROR;
}
auto rv = nghttp3_conn_close_stream(httpconn_, stream_id, app_error_code);
switch (rv) {
case 0:
break;
case NGHTTP3_ERR_STREAM_NOT_FOUND:
if (ngtcp2_is_bidi_stream(stream_id)) {
assert(!ngtcp2_conn_is_local_stream(conn_, stream_id));
ngtcp2_conn_extend_max_streams_bidi(conn_, 1);
}
break;
default:
std::cerr << "nghttp3_conn_close_stream: " << nghttp3_strerror(rv)
<< std::endl;
last_error_ = quic_err_app(rv);
return -1;
}
}
return 0;
}
void Handler::shutdown_read(int64_t stream_id, int app_error_code) {
ngtcp2_conn_shutdown_stream_read(conn_, stream_id, app_error_code);
}
void Handler::set_tls_alert(uint8_t alert) {
last_error_ = quic_err_tls(alert);
}
namespace {
void sreadcb(struct ev_loop *loop, ev_io *w, int revents) {
auto ep = static_cast<Endpoint *>(w->data);
ep->server->on_read(*ep);
}
} // namespace
namespace {
std::vector<std::unique_ptr<Server>> servers;
} // namespace
namespace {
void siginthandler(struct ev_loop *loop, ev_signal *watcher, int revents) {
std::cerr << "siginthandler" << std::endl;
for (auto &sv : servers) {
sv->request_stop();
}
std::cerr << "Stopping default loop" << std::endl;
ev_break(loop, EVBREAK_ALL);
}
} // namespace
namespace {
void stopcb(struct ev_loop *loop, ev_async *w, int revents) {
auto sv = static_cast<Server *>(w->data);
sv->stop();
}
} // namespace
Server::Server(uint32_t svindex, struct ev_loop *loop, SSL_CTX *ssl_ctx)
: svindex_(svindex), loop_(loop), ssl_ctx_(ssl_ctx) {
ev_async_init(&stopev_, stopcb);
stopev_.data = this;
token_aead_.native_handle = const_cast<EVP_CIPHER *>(EVP_aes_128_gcm());
token_md_.native_handle = const_cast<EVP_MD *>(EVP_sha256());
}
Server::~Server() {
disconnect();
close();
}
void Server::disconnect() {
config.tx_loss_prob = 0;
for (auto &ep : endpoints_) {
ev_io_stop(loop_, &ep.rev);
}
ev_async_stop(loop_, &stopev_);
while (!handlers_.empty()) {
auto it = std::begin(handlers_);
auto &h = (*it).second;
h->handle_error();
remove(h.get());
}
}
void Server::close() {
for (auto &ep : endpoints_) {
::close(ep.fd);
}
endpoints_.clear();
}
void Server::start() { ev_run(loop_); }
void Server::stop() { ev_break(loop_, EVBREAK_ALL); }
void Server::request_stop() { ev_async_send(loop_, &stopev_); }
namespace {
int create_sock(Address &local_addr, const char *addr, const char *port,
int family, uint32_t svindex) {
addrinfo hints{};
addrinfo *res, *rp;
int val = 1;
hints.ai_family = family;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_flags = AI_PASSIVE;
if (strcmp(addr, "*") == 0) {
addr = nullptr;
}
if (auto rv = getaddrinfo(addr, port, &hints, &res); rv != 0) {
std::cerr << "getaddrinfo: " << gai_strerror(rv) << std::endl;
return -1;
}
auto res_d = defer(freeaddrinfo, res);
int fd = -1;
for (rp = res; rp; rp = rp->ai_next) {
fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (fd == -1) {
continue;
}
if (rp->ai_family == AF_INET6) {
if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val,
static_cast<socklen_t>(sizeof(val))) == -1) {
close(fd);
continue;
}
}
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val,
static_cast<socklen_t>(sizeof(val))) == -1) {
close(fd);
continue;
}
if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &val,
static_cast<socklen_t>(sizeof(val))) == -1) {
close(fd);
continue;
}
if (svindex == 0 &&
setsockopt(fd, SOL_SOCKET, SO_ATTACH_REUSEPORT_EBPF, &prog_fd,
static_cast<socklen_t>(sizeof(prog_fd))) == -1) {
std::cerr << "Unable to attach bpf prog: " << strerror(errno)
<< std::endl;
close(fd);
continue;
}
fd_set_recv_ecn(fd, rp->ai_family);
if (bind(fd, rp->ai_addr, rp->ai_addrlen) != -1) {
if (bpf_map_update_elem(reuseport_array, &svindex, &fd, BPF_NOEXIST) !=
0) {
std::cerr << "bpf_map_update_elem: " << strerror(errno) << std::endl;
close(fd);
continue;
}
break;
}
close(fd);
}
if (!rp) {
std::cerr << "Could not bind" << std::endl;
return -1;
}
socklen_t len = sizeof(local_addr.su.storage);
if (getsockname(fd, &local_addr.su.sa, &len) == -1) {
std::cerr << "getsockname: " << strerror(errno) << std::endl;
close(fd);
return -1;
}
local_addr.len = len;
return fd;
}
} // namespace
namespace {
int add_endpoint(std::vector<Endpoint> &endpoints, const char *addr,
const char *port, int af, uint32_t svindex) {
Address dest;
auto fd = create_sock(dest, addr, port, af, svindex);
if (fd == -1) {
return -1;
}
endpoints.emplace_back();
auto &ep = endpoints.back();
ep.addr = dest;
ep.fd = fd;
ev_io_init(&ep.rev, sreadcb, 0, EV_READ);
return 0;
}
} // namespace
namespace {
int add_endpoint(std::vector<Endpoint> &endpoints, const Address &addr) {
auto fd = socket(addr.su.sa.sa_family, SOCK_DGRAM, 0);
if (fd == -1) {
std::cerr << "socket: " << strerror(errno) << std::endl;
return -1;
}
int val = 1;
if (addr.su.sa.sa_family == AF_INET6 &&
setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val,
static_cast<socklen_t>(sizeof(val)))) {
std::cerr << "setsockopt: " << strerror(errno) << std::endl;
close(fd);
return -1;
}
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val,
static_cast<socklen_t>(sizeof(val))) == -1) {
close(fd);
return -1;
}
fd_set_recv_ecn(fd, addr.su.sa.sa_family);
if (bind(fd, &addr.su.sa, addr.len) == -1) {
std::cerr << "bind: " << strerror(errno) << std::endl;
close(fd);
return -1;
}
endpoints.emplace_back(Endpoint{});
auto &ep = endpoints.back();
ep.addr = addr;
ep.fd = fd;
ev_io_init(&ep.rev, sreadcb, 0, EV_READ);
return 0;
}
} // namespace
int Server::init(const char *addr, const char *port) {
endpoints_.reserve(4);
auto ready = false;
if (!util::numeric_host(addr, AF_INET6) &&
add_endpoint(endpoints_, addr, port, AF_INET, svindex_) == 0) {
ready = true;
}
if (!util::numeric_host(addr, AF_INET) &&
add_endpoint(endpoints_, addr, port, AF_INET6, svindex_) == 0) {
ready = true;
}
if (!ready) {
return -1;
}
if (config.preferred_ipv4_addr.len &&
add_endpoint(endpoints_, config.preferred_ipv4_addr) != 0) {
return -1;
}
if (config.preferred_ipv6_addr.len &&
add_endpoint(endpoints_, config.preferred_ipv6_addr) != 0) {
return -1;
}
for (auto &ep : endpoints_) {
ep.server = this;
ep.rev.data = &ep;
ev_io_set(&ep.rev, ep.fd, EV_READ);
ev_io_start(loop_, &ep.rev);
}
ev_async_start(loop_, &stopev_);
return 0;
}
int Server::on_read(Endpoint &ep) {
sockaddr_union su;
std::array<uint8_t, 64_k> buf;
ngtcp2_pkt_hd hd;
size_t pktcnt = 0;
ngtcp2_pkt_info pi;
iovec msg_iov;
msg_iov.iov_base = buf.data();
msg_iov.iov_len = buf.size();
msghdr msg{};
msg.msg_name = &su;
msg.msg_iov = &msg_iov;
msg.msg_iovlen = 1;
uint8_t msg_ctrl[CMSG_SPACE(sizeof(uint8_t))];
msg.msg_control = msg_ctrl;
for (; pktcnt < 10;) {
msg.msg_namelen = sizeof(su);
msg.msg_controllen = sizeof(msg_ctrl);
auto nread = recvmsg(ep.fd, &msg, MSG_DONTWAIT);
if (nread == -1) {
if (!(errno == EAGAIN || errno == ENOTCONN)) {
std::cerr << "recvmsg: " << strerror(errno) << std::endl;
}
return 0;
}
++pktcnt;
pi.ecn = msghdr_get_ecn(&msg, su.storage.ss_family);
if (!config.quiet) {
std::cerr << "Received packet: local="
<< util::straddr(&ep.addr.su.sa, ep.addr.len)
<< " remote=" << util::straddr(&su.sa, msg.msg_namelen)
<< " ecn=0x" << std::hex << pi.ecn << std::dec << " " << nread
<< " bytes" << std::endl;
}
if (debug::packet_lost(config.rx_loss_prob)) {
if (!config.quiet) {
std::cerr << "** Simulated incoming packet loss **" << std::endl;
}
continue;
}
if (nread == 0) {
continue;
}
uint32_t version;
const uint8_t *dcid, *scid;
size_t dcidlen, scidlen;
if (auto rv = ngtcp2_pkt_decode_version_cid(&version, &dcid, &dcidlen,
&scid, &scidlen, buf.data(),
nread, NGTCP2_SV_SCIDLEN);
rv != 0) {
if (rv == 1) {
send_version_negotiation(version, scid, scidlen, dcid, dcidlen, ep,
&su.sa, msg.msg_namelen);
continue;
}
std::cerr << "Could not decode version and CID from QUIC packet header: "
<< ngtcp2_strerror(rv) << std::endl;
continue;
}
auto dcid_key = util::make_cid_key(dcid, dcidlen);
auto handler_it = handlers_.find(dcid_key);
if (handler_it == std::end(handlers_)) {
auto ctos_it = ctos_.find(dcid_key);
if (ctos_it == std::end(ctos_)) {
if (auto rv = ngtcp2_accept(&hd, buf.data(), nread); rv == -1) {
if (!config.quiet) {
std::cerr << "Unexpected packet received: length=" << nread
<< std::endl;
}
continue;
} else if (rv == 1) {
if (!config.quiet) {
std::cerr << "Unsupported version: Send Version Negotiation"
<< std::endl;
}
send_version_negotiation(hd.version, hd.scid.data, hd.scid.datalen,
hd.dcid.data, hd.dcid.datalen, ep, &su.sa,
msg.msg_namelen);
continue;
}
ngtcp2_cid ocid;
ngtcp2_cid *pocid = nullptr;
switch (hd.type) {
case NGTCP2_PKT_INITIAL:
if (config.validate_addr || hd.token.len) {
std::cerr << "Perform stateless address validation" << std::endl;
if (hd.token.len == 0) {
send_retry(&hd, ep, &su.sa, msg.msg_namelen);
continue;
}
if (hd.token.base[0] != RETRY_TOKEN_MAGIC &&
hd.dcid.datalen < NGTCP2_MIN_INITIAL_DCIDLEN) {
send_stateless_connection_close(&hd, ep, &su.sa, msg.msg_namelen);
continue;
}
switch (hd.token.base[0]) {
case RETRY_TOKEN_MAGIC:
if (verify_retry_token(&ocid, &hd, &su.sa, msg.msg_namelen) !=
0) {
send_stateless_connection_close(&hd, ep, &su.sa,
msg.msg_namelen);
continue;
}
pocid = &ocid;
break;
case TOKEN_MAGIC:
if (verify_token(&hd, &su.sa, msg.msg_namelen) != 0) {
if (config.validate_addr) {
send_retry(&hd, ep, &su.sa, msg.msg_namelen);
continue;
}
hd.token.base = nullptr;
hd.token.len = 0;
}
break;
default:
if (!config.quiet) {
std::cerr << "Ignore unrecognized token" << std::endl;
}
if (config.validate_addr) {
send_retry(&hd, ep, &su.sa, msg.msg_namelen);
continue;
}
hd.token.base = nullptr;
hd.token.len = 0;
break;
}
}
break;
case NGTCP2_PKT_0RTT:
send_retry(&hd, ep, &su.sa, msg.msg_namelen);
continue;
}
auto h = std::make_unique<Handler>(loop_, ssl_ctx_, this, &hd.dcid);
if (h->init(ep, &su.sa, msg.msg_namelen, &hd.scid, &hd.dcid, pocid,
hd.token.base, hd.token.len, hd.version) != 0) {
continue;
}
switch (
h->on_read(ep, &su.sa, msg.msg_namelen, &pi, buf.data(), nread)) {
case 0:
break;
case NETWORK_ERR_RETRY:
send_retry(&hd, ep, &su.sa, msg.msg_namelen);
continue;
default:
continue;
}
switch (h->on_write()) {
case 0:
break;
default:
continue;
}
auto scid = h->scid();
auto scid_key = util::make_cid_key(scid);
ctos_.emplace(dcid_key, scid_key);
auto pscid = h->pscid();
if (pscid->datalen) {
auto pscid_key = util::make_cid_key(pscid);
ctos_.emplace(pscid_key, scid_key);
}
handlers_.emplace(scid_key, std::move(h));
continue;
}
if (!config.quiet) {
std::cerr << "Forward CID=" << util::format_hex((*ctos_it).first)
<< " to CID=" << util::format_hex((*ctos_it).second)
<< std::endl;
}
handler_it = handlers_.find((*ctos_it).second);
assert(handler_it != std::end(handlers_));
}
auto h = (*handler_it).second.get();
if (ngtcp2_conn_is_in_closing_period(h->conn())) {
// TODO do exponential backoff.
switch (h->send_conn_close()) {
case 0:
break;
default:
remove(h);
}
continue;
}
if (h->draining()) {
continue;
}
if (auto rv =
h->on_read(ep, &su.sa, msg.msg_namelen, &pi, buf.data(), nread);
rv != 0) {
if (rv != NETWORK_ERR_CLOSE_WAIT) {
remove(h);
}
continue;
}
h->signal_write();
}
return 0;
}
namespace {
uint32_t generate_reserved_version(const sockaddr *sa, socklen_t salen,
uint32_t version) {
uint32_t h = 0x811C9DC5u;
const uint8_t *p = (const uint8_t *)sa;
const uint8_t *ep = p + salen;
for (; p != ep; ++p) {
h ^= *p;
h *= 0x01000193u;
}
version = htonl(version);
p = (const uint8_t *)&version;
ep = p + sizeof(version);
for (; p != ep; ++p) {
h ^= *p;
h *= 0x01000193u;
}
h &= 0xf0f0f0f0u;
h |= 0x0a0a0a0au;
return h;
}
} // namespace
int Server::send_version_negotiation(uint32_t version, const uint8_t *dcid,
size_t dcidlen, const uint8_t *scid,
size_t scidlen, Endpoint &ep,
const sockaddr *sa, socklen_t salen) {
Buffer buf{NGTCP2_MAX_PKTLEN_IPV4};
std::array<uint32_t, 16> sv;
static_assert(sv.size() >=
1 + (NGTCP2_PROTO_VER_MAX - NGTCP2_PROTO_VER_MIN