summaryrefslogtreecommitdiffstats
path: root/examples/h09server.cc
diff options
context:
space:
mode:
Diffstat (limited to 'examples/h09server.cc')
-rw-r--r--examples/h09server.cc3034
1 files changed, 3034 insertions, 0 deletions
diff --git a/examples/h09server.cc b/examples/h09server.cc
new file mode 100644
index 0000000..4af67da
--- /dev/null
+++ b/examples/h09server.cc
@@ -0,0 +1,3034 @@
+/*
+ * 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 <iomanip>
+
+#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 <net/if.h>
+
+#include <http-parser/http_parser.h>
+
+#include "h09server.h"
+#include "network.h"
+#include "debug.h"
+#include "util.h"
+#include "shared.h"
+#include "http.h"
+#include "template.h"
+
+using namespace ngtcp2;
+using namespace std::literals;
+
+namespace {
+constexpr size_t NGTCP2_SV_SCIDLEN = 18;
+} // namespace
+
+namespace {
+constexpr size_t max_preferred_versionslen = 4;
+} // namespace
+
+namespace {
+auto randgen = util::make_mt19937();
+} // namespace
+
+Config config{};
+
+Stream::Stream(int64_t stream_id, Handler *handler)
+ : stream_id(stream_id), handler(handler), eos(false) {
+ nghttp3_buf_init(&respbuf);
+ htp.data = this;
+ http_parser_init(&htp, HTTP_REQUEST);
+}
+
+namespace {
+constexpr auto NGTCP2_SERVER = "ngtcp2 server"sv;
+} // namespace
+
+namespace {
+std::string make_status_body(unsigned int status_code) {
+ auto status_string = util::format_uint(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 += util::format_uint(config.port);
+ body += "</address>";
+ body += "</body></html>";
+ return body;
+}
+} // namespace
+
+struct Request {
+ std::string path;
+};
+
+namespace {
+Request request_path(const std::string_view &uri) {
+ http_parser_url u;
+ Request req;
+
+ http_parser_url_init(&u);
+
+ if (auto rv = http_parser_parse_url(uri.data(), uri.size(),
+ /* is_connect = */ 0, &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";
+ }
+
+ 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) {
+ respbuf.begin = respbuf.pos = static_cast<uint8_t *>(fe.map);
+ respbuf.end = respbuf.last = respbuf.begin + fe.len;
+}
+
+int Stream::send_status_response(unsigned int status_code) {
+ status_resp_body = make_status_body(status_code);
+
+ respbuf.begin = respbuf.pos =
+ reinterpret_cast<uint8_t *>(status_resp_body.data());
+ respbuf.end = respbuf.last = respbuf.begin + status_resp_body.size();
+
+ handler->add_sendq(this);
+ handler->shutdown_read(stream_id, 0);
+
+ return 0;
+}
+
+int Stream::start_response() {
+ if (uri.empty()) {
+ return send_status_response(400);
+ }
+
+ auto req = request_path(uri);
+ if (req.path.empty()) {
+ return send_status_response(400);
+ }
+
+ auto path = config.htdocs + req.path;
+ auto [fe, rv] = open_file(path);
+ if (rv != 0) {
+ send_status_response(404);
+ return 0;
+ }
+
+ if (fe.flags & FILE_ENTRY_TYPE_DIR) {
+ send_status_response(308);
+ return 0;
+ }
+
+ map_file(fe);
+
+ if (!config.quiet) {
+ std::array<nghttp3_nv, 1> nva{
+ util::make_nv_nn(":status", "200"),
+ };
+
+ debug::print_http_response_headers(stream_id, nva.data(), nva.size());
+ }
+
+ handler->add_sendq(this);
+
+ return 0;
+}
+
+namespace {
+void writecb(struct ev_loop *loop, ev_io *w, int revents) {
+ 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 close_waitcb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto h = static_cast<Handler *>(w->data);
+ auto s = h->server();
+ auto conn = h->conn();
+
+ if (ngtcp2_conn_is_in_closing_period(conn)) {
+ if (!config.quiet) {
+ std::cerr << "Closing Period is over" << std::endl;
+ }
+
+ s->remove(h);
+ return;
+ }
+ if (ngtcp2_conn_is_in_draining_period(conn)) {
+ if (!config.quiet) {
+ std::cerr << "Draining Period is over" << std::endl;
+ }
+
+ s->remove(h);
+ return;
+ }
+
+ assert(0);
+}
+} // namespace
+
+namespace {
+void timeoutcb(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, Server *server)
+ : loop_(loop),
+ server_(server),
+ qlog_(nullptr),
+ scid_{},
+ nkey_update_(0),
+ no_gso_{
+#ifdef UDP_SEGMENT
+ false
+#else // !UDP_SEGMENT
+ true
+#endif // !UDP_SEGMENT
+ },
+ tx_{
+ .data = std::unique_ptr<uint8_t[]>(new uint8_t[64_k]),
+ } {
+ ev_io_init(&wev_, writecb, 0, EV_WRITE);
+ wev_.data = this;
+ ev_timer_init(&timer_, timeoutcb, 0., 0.);
+ timer_.data = this;
+}
+
+Handler::~Handler() {
+ if (!config.quiet) {
+ std::cerr << scid_ << " Closing QUIC connection " << std::endl;
+ }
+
+ ev_timer_stop(loop_, &timer_);
+ ev_io_stop(loop_, &wev_);
+
+ 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 " << tls_session_.get_cipher_name()
+ << std::endl;
+ std::cerr << "Negotiated ALPN is " << tls_session_.get_selected_alpn()
+ << std::endl;
+ }
+
+ std::array<uint8_t, NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN> token;
+
+ auto path = ngtcp2_conn_get_path(conn_);
+ auto t = std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count();
+
+ auto tokenlen = ngtcp2_crypto_generate_regular_token(
+ token.data(), config.static_secret.data(), config.static_secret.size(),
+ path->remote.addr, path->remote.addrlen, t);
+ if (tokenlen < 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);
+ }
+
+ return ngtcp2_crypto_recv_crypto_data_cb(conn, crypto_level, offset, data,
+ datalen, user_data);
+}
+} // 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_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, offset, datalen) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+int Handler::acked_stream_data_offset(int64_t stream_id, uint64_t offset,
+ uint64_t datalen) {
+ auto it = streams_.find(stream_id);
+ assert(it != std::end(streams_));
+ auto &stream = (*it).second;
+ (void)stream;
+
+ assert(static_cast<uint64_t>(stream->respbuf.end - stream->respbuf.begin) >=
+ offset + datalen);
+
+ 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);
+ (void)it;
+ assert(it == std::end(streams_));
+ streams_.emplace(stream_id, std::make_unique<Stream>(stream_id, this));
+}
+
+namespace {
+int stream_close(ngtcp2_conn *conn, uint32_t flags, 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 {
+void rand(uint8_t *dest, size_t destlen, const ngtcp2_rand_ctx *rand_ctx) {
+ auto dis = std::uniform_int_distribution<uint8_t>(0, 255);
+ std::generate(dest, dest + destlen, [&dis]() { return dis(randgen); });
+}
+} // namespace
+
+namespace {
+int get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, uint8_t *token,
+ size_t cidlen, void *user_data) {
+ if (util::generate_secure_random(cid->data, cidlen) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ cid->datalen = cidlen;
+ if (ngtcp2_crypto_generate_stateless_reset_token(
+ token, config.static_secret.data(), config.static_secret.size(),
+ cid) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ auto h = static_cast<Handler *>(user_data);
+ h->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, uint32_t flags, 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_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) {
+ auto it = streams_.find(stream_id);
+ assert(it != std::end(streams_));
+ auto &stream = (*it).second;
+
+ if (nghttp3_buf_len(&stream->respbuf)) {
+ sendq_.emplace(stream.get());
+ }
+
+ 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 Address &local_addr,
+ 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,
+ TLSServerContext &tls_ctx) {
+ auto callbacks = ngtcp2_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_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
+ nullptr, // stream_reset
+ nullptr, // 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,
+ nullptr, // recv_datagram
+ nullptr, // ack_datagram
+ nullptr, // lost_datagram
+ ngtcp2_crypto_get_path_challenge_data_cb,
+ nullptr, // stream_stop_sending
+ ngtcp2_crypto_version_negotiation_cb,
+ nullptr, // recv_rx_key
+ nullptr, // recv_tx_key
+ };
+
+ scid_.datalen = NGTCP2_SV_SCIDLEN;
+ if (util::generate_secure_random(scid_.data, scid_.datalen) != 0) {
+ std::cerr << "Could not generate connection ID" << std::endl;
+ return -1;
+ }
+
+ 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.cc_algo = config.cc_algo;
+ settings.initial_rtt = config.initial_rtt;
+ settings.max_window = config.max_window;
+ settings.max_stream_window = config.max_stream_window;
+ settings.handshake_timeout = config.handshake_timeout;
+ settings.no_pmtud = config.no_pmtud;
+ settings.ack_thresh = config.ack_thresh;
+ if (config.max_udp_payload_size) {
+ settings.max_tx_udp_payload_size = config.max_udp_payload_size;
+ settings.no_tx_udp_payload_size_shaping = 1;
+ }
+ if (!config.qlog_dir.empty()) {
+ auto path = std::string{config.qlog_dir};
+ path += '/';
+ path += util::format_hex(scid_.data, scid_.datalen);
+ path += ".sqlog";
+ qlog_ = fopen(path.c_str(), "w");
+ if (qlog_ == nullptr) {
+ std::cerr << "Could not open qlog file " << std::quoted(path) << ": "
+ << strerror(errno) << std::endl;
+ return -1;
+ }
+ settings.qlog.write = ::write_qlog;
+ settings.qlog.odcid = *scid;
+ }
+ if (!config.preferred_versions.empty()) {
+ settings.preferred_versions = config.preferred_versions.data();
+ settings.preferred_versionslen = config.preferred_versions.size();
+ }
+ if (!config.other_versions.empty()) {
+ settings.other_versions = config.other_versions.data();
+ settings.other_versionslen = config.other_versions.size();
+ }
+
+ ngtcp2_transport_params params;
+ ngtcp2_transport_params_default(&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 = 0;
+ 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;
+ }
+
+ if (util::generate_secure_random(params.stateless_reset_token,
+ sizeof(params.stateless_reset_token)) != 0) {
+ std::cerr << "Could not generate stateless reset token" << std::endl;
+ return -1;
+ }
+
+ if (config.preferred_ipv4_addr.len || config.preferred_ipv6_addr.len) {
+ params.preferred_address_present = 1;
+
+ if (config.preferred_ipv4_addr.len) {
+ params.preferred_address.ipv4 = config.preferred_ipv4_addr.su.in;
+ params.preferred_address.ipv4_present = 1;
+ }
+
+ if (config.preferred_ipv6_addr.len) {
+ params.preferred_address.ipv6 = config.preferred_ipv6_addr.su.in6;
+ params.preferred_address.ipv6_present = 1;
+ }
+
+ auto &token = params.preferred_address.stateless_reset_token;
+ if (util::generate_secure_random(token, sizeof(token)) != 0) {
+ std::cerr << "Could not generate preferred address stateless reset token"
+ << std::endl;
+ return -1;
+ }
+
+ params.preferred_address.cid.datalen = NGTCP2_SV_SCIDLEN;
+ if (util::generate_secure_random(params.preferred_address.cid.data,
+ params.preferred_address.cid.datalen) !=
+ 0) {
+ std::cerr << "Could not generate preferred address connection ID"
+ << std::endl;
+ return -1;
+ }
+ }
+
+ auto path = ngtcp2_path{
+ {
+ const_cast<sockaddr *>(&local_addr.su.sa),
+ local_addr.len,
+ },
+ {
+ const_cast<sockaddr *>(sa),
+ salen,
+ },
+ const_cast<Endpoint *>(&ep),
+ };
+ if (auto rv =
+ ngtcp2_conn_server_new(&conn_, dcid, &scid_, &path, version,
+ &callbacks, &settings, &params, nullptr, this);
+ rv != 0) {
+ std::cerr << "ngtcp2_conn_server_new: " << ngtcp2_strerror(rv) << std::endl;
+ return -1;
+ }
+
+ if (tls_session_.init(tls_ctx, this) != 0) {
+ return -1;
+ }
+
+ tls_session_.enable_keylog();
+
+ ngtcp2_conn_set_tls_native_handle(conn_, tls_session_.get_native_handle());
+
+ ev_io_set(&wev_, ep.fd, EV_WRITE);
+
+ return 0;
+}
+
+int Handler::feed_data(const Endpoint &ep, const Address &local_addr,
+ const sockaddr *sa, socklen_t salen,
+ const ngtcp2_pkt_info *pi, uint8_t *data,
+ size_t datalen) {
+ auto path = ngtcp2_path{
+ {
+ const_cast<sockaddr *>(&local_addr.su.sa),
+ local_addr.len,
+ },
+ {
+ const_cast<sockaddr *>(sa),
+ salen,
+ },
+ const_cast<Endpoint *>(&ep),
+ };
+
+ 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_DROP_CONN:
+ return NETWORK_ERR_DROP_CONN;
+ case NGTCP2_ERR_CRYPTO:
+ if (!last_error_.error_code) {
+ ngtcp2_connection_close_error_set_transport_error_tls_alert(
+ &last_error_, ngtcp2_conn_get_tls_alert(conn_), nullptr, 0);
+ }
+ break;
+ default:
+ if (!last_error_.error_code) {
+ ngtcp2_connection_close_error_set_transport_error_liberr(
+ &last_error_, rv, nullptr, 0);
+ }
+ }
+ return handle_error();
+ }
+
+ return 0;
+}
+
+int Handler::on_read(const Endpoint &ep, const Address &local_addr,
+ const sockaddr *sa, socklen_t salen,
+ const ngtcp2_pkt_info *pi, uint8_t *data, size_t datalen) {
+ if (auto rv = feed_data(ep, local_addr, sa, salen, pi, data, datalen);
+ rv != 0) {
+ return rv;
+ }
+
+ update_timer();
+
+ return 0;
+}
+
+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;
+ ngtcp2_connection_close_error_set_transport_error_liberr(&last_error_, rv,
+ nullptr, 0);
+ 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 (tx_.send_blocked) {
+ if (auto rv = send_blocked_packet(); rv != 0) {
+ return rv;
+ }
+
+ if (tx_.send_blocked) {
+ return 0;
+ }
+ }
+
+ if (auto rv = write_streams(); rv != 0) {
+ return rv;
+ }
+
+ update_timer();
+
+ return 0;
+}
+
+int Handler::write_streams() {
+ ngtcp2_vec vec;
+ ngtcp2_path_storage ps, prev_ps;
+ uint32_t prev_ecn = 0;
+ size_t pktcnt = 0;
+ auto max_udp_payload_size = ngtcp2_conn_get_max_tx_udp_payload_size(conn_);
+ auto path_max_udp_payload_size =
+ ngtcp2_conn_get_path_max_tx_udp_payload_size(conn_);
+ auto max_pktcnt = ngtcp2_conn_get_send_quantum(conn_) / max_udp_payload_size;
+ uint8_t *bufpos = tx_.data.get();
+ ngtcp2_pkt_info pi;
+ size_t gso_size = 0;
+ auto ts = util::timestamp(loop_);
+
+ ngtcp2_path_storage_zero(&ps);
+ ngtcp2_path_storage_zero(&prev_ps);
+
+ max_pktcnt = std::min(max_pktcnt, static_cast<size_t>(config.max_gso_dgrams));
+
+ for (;;) {
+ int64_t stream_id = -1;
+ size_t vcnt = 0;
+ uint32_t flags = NGTCP2_WRITE_STREAM_FLAG_MORE;
+ Stream *stream = nullptr;
+
+ if (!sendq_.empty() && ngtcp2_conn_get_max_data_left(conn_)) {
+ stream = *std::begin(sendq_);
+
+ stream_id = stream->stream_id;
+ vec.base = stream->respbuf.pos;
+ vec.len = nghttp3_buf_len(&stream->respbuf);
+ vcnt = 1;
+ flags |= NGTCP2_WRITE_STREAM_FLAG_FIN;
+ }
+
+ ngtcp2_ssize ndatalen;
+
+ auto nwrite = ngtcp2_conn_writev_stream(conn_, &ps.path, &pi, bufpos,
+ max_udp_payload_size, &ndatalen,
+ flags, stream_id, &vec, vcnt, ts);
+ if (nwrite < 0) {
+ switch (nwrite) {
+ case NGTCP2_ERR_STREAM_DATA_BLOCKED:
+ case NGTCP2_ERR_STREAM_SHUT_WR:
+ assert(ndatalen == -1);
+ sendq_.erase(std::begin(sendq_));
+ continue;
+ case NGTCP2_ERR_WRITE_MORE:
+ assert(ndatalen >= 0);
+ stream->respbuf.pos += ndatalen;
+ if (nghttp3_buf_len(&stream->respbuf) == 0) {
+ sendq_.erase(std::begin(sendq_));
+ }
+ continue;
+ }
+
+ assert(ndatalen == -1);
+
+ std::cerr << "ngtcp2_conn_writev_stream: " << ngtcp2_strerror(nwrite)
+ << std::endl;
+ ngtcp2_connection_close_error_set_transport_error_liberr(
+ &last_error_, nwrite, nullptr, 0);
+ return handle_error();
+ } else if (ndatalen >= 0) {
+ stream->respbuf.pos += ndatalen;
+ if (nghttp3_buf_len(&stream->respbuf) == 0) {
+ sendq_.erase(std::begin(sendq_));
+ }
+ }
+
+ if (nwrite == 0) {
+ if (bufpos - tx_.data.get()) {
+ auto &ep = *static_cast<Endpoint *>(prev_ps.path.user_data);
+ auto data = tx_.data.get();
+ auto datalen = bufpos - data;
+
+ if (auto [nsent, rv] = server_->send_packet(
+ ep, no_gso_, prev_ps.path.local, prev_ps.path.remote, prev_ecn,
+ data, datalen, gso_size);
+ rv != NETWORK_ERR_OK) {
+ assert(NETWORK_ERR_SEND_BLOCKED == rv);
+
+ on_send_blocked(ep, prev_ps.path.local, prev_ps.path.remote, prev_ecn,
+ data + nsent, datalen - nsent, gso_size);
+
+ start_wev_endpoint(ep);
+ ngtcp2_conn_update_pkt_tx_time(conn_, ts);
+ return 0;
+ }
+ }
+
+ ev_io_stop(loop_, &wev_);
+
+ // We are congestion limited.
+ ngtcp2_conn_update_pkt_tx_time(conn_, ts);
+ return 0;
+ }
+
+ bufpos += nwrite;
+
+ if (pktcnt == 0) {
+ ngtcp2_path_copy(&prev_ps.path, &ps.path);
+ prev_ecn = pi.ecn;
+ gso_size = nwrite;
+ } else if (!ngtcp2_path_eq(&prev_ps.path, &ps.path) || prev_ecn != pi.ecn ||
+ static_cast<size_t>(nwrite) > gso_size ||
+ (gso_size > path_max_udp_payload_size &&
+ static_cast<size_t>(nwrite) != gso_size)) {
+ auto &ep = *static_cast<Endpoint *>(prev_ps.path.user_data);
+ auto data = tx_.data.get();
+ auto datalen = bufpos - data - nwrite;
+
+ if (auto [nsent, rv] = server_->send_packet(
+ ep, no_gso_, prev_ps.path.local, prev_ps.path.remote, prev_ecn,
+ data, datalen, gso_size);
+ rv != 0) {
+ assert(NETWORK_ERR_SEND_BLOCKED == rv);
+
+ on_send_blocked(ep, prev_ps.path.local, prev_ps.path.remote, prev_ecn,
+ data + nsent, datalen - nsent, gso_size);
+
+ on_send_blocked(*static_cast<Endpoint *>(ps.path.user_data),
+ ps.path.local, ps.path.remote, pi.ecn, bufpos - nwrite,
+ nwrite, 0);
+
+ start_wev_endpoint(ep);
+ } else {
+ auto &ep = *static_cast<Endpoint *>(ps.path.user_data);
+ auto data = bufpos - nwrite;
+
+ if (auto [nsent, rv] =
+ server_->send_packet(ep, no_gso_, ps.path.local, ps.path.remote,
+ pi.ecn, data, nwrite, nwrite);
+ rv != 0) {
+ assert(nsent == 0);
+ assert(NETWORK_ERR_SEND_BLOCKED == rv);
+
+ on_send_blocked(ep, ps.path.local, ps.path.remote, pi.ecn, data,
+ nwrite, 0);
+ }
+
+ start_wev_endpoint(ep);
+ }
+
+ ngtcp2_conn_update_pkt_tx_time(conn_, ts);
+ return 0;
+ }
+
+ if (++pktcnt == max_pktcnt || static_cast<size_t>(nwrite) < gso_size) {
+ auto &ep = *static_cast<Endpoint *>(ps.path.user_data);
+ auto data = tx_.data.get();
+ auto datalen = bufpos - data;
+
+ if (auto [nsent, rv] =
+ server_->send_packet(ep, no_gso_, ps.path.local, ps.path.remote,
+ pi.ecn, data, datalen, gso_size);
+ rv != 0) {
+ assert(NETWORK_ERR_SEND_BLOCKED == rv);
+
+ on_send_blocked(ep, ps.path.local, ps.path.remote, pi.ecn, data + nsent,
+ datalen - nsent, gso_size);
+ }
+
+ start_wev_endpoint(ep);
+ ngtcp2_conn_update_pkt_tx_time(conn_, ts);
+ return 0;
+ }
+ }
+}
+
+void Handler::on_send_blocked(Endpoint &ep, const ngtcp2_addr &local_addr,
+ const ngtcp2_addr &remote_addr, unsigned int ecn,
+ const uint8_t *data, size_t datalen,
+ size_t gso_size) {
+ assert(tx_.num_blocked || !tx_.send_blocked);
+ assert(tx_.num_blocked < 2);
+
+ tx_.send_blocked = true;
+
+ auto &p = tx_.blocked[tx_.num_blocked++];
+
+ memcpy(&p.local_addr.su, local_addr.addr, local_addr.addrlen);
+ memcpy(&p.remote_addr.su, remote_addr.addr, remote_addr.addrlen);
+
+ p.local_addr.len = local_addr.addrlen;
+ p.remote_addr.len = remote_addr.addrlen;
+ p.endpoint = &ep;
+ p.ecn = ecn;
+ p.data = data;
+ p.datalen = datalen;
+ p.gso_size = gso_size;
+}
+
+void Handler::start_wev_endpoint(const Endpoint &ep) {
+ // We do not close ep.fd, so we can expect that each Endpoint has
+ // unique fd.
+ if (ep.fd != wev_.fd) {
+ if (ev_is_active(&wev_)) {
+ ev_io_stop(loop_, &wev_);
+ }
+
+ ev_io_set(&wev_, ep.fd, EV_WRITE);
+ }
+
+ ev_io_start(loop_, &wev_);
+}
+
+int Handler::send_blocked_packet() {
+ assert(tx_.send_blocked);
+
+ for (; tx_.num_blocked_sent < tx_.num_blocked; ++tx_.num_blocked_sent) {
+ auto &p = tx_.blocked[tx_.num_blocked_sent];
+
+ ngtcp2_addr local_addr{
+ .addr = &p.local_addr.su.sa,
+ .addrlen = p.local_addr.len,
+ };
+ ngtcp2_addr remote_addr{
+ .addr = &p.remote_addr.su.sa,
+ .addrlen = p.remote_addr.len,
+ };
+
+ auto [nsent, rv] =
+ server_->send_packet(*p.endpoint, no_gso_, local_addr, remote_addr,
+ p.ecn, p.data, p.datalen, p.gso_size);
+ if (rv != 0) {
+ assert(NETWORK_ERR_SEND_BLOCKED == rv);
+
+ p.data += nsent;
+ p.datalen -= nsent;
+
+ start_wev_endpoint(*p.endpoint);
+
+ return 0;
+ }
+ }
+
+ tx_.send_blocked = false;
+ tx_.num_blocked = 0;
+ tx_.num_blocked_sent = 0;
+
+ return 0;
+}
+
+void Handler::signal_write() { ev_io_start(loop_, &wev_); }
+
+void Handler::start_draining_period() {
+ ev_io_stop(loop_, &wev_);
+
+ ev_set_cb(&timer_, close_waitcb);
+ 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_) ||
+ ngtcp2_conn_is_in_draining_period(conn_)) {
+ return 0;
+ }
+
+ ev_io_stop(loop_, &wev_);
+
+ ev_set_cb(&timer_, close_waitcb);
+ 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>(NGTCP2_MAX_UDP_PAYLOAD_SIZE);
+
+ ngtcp2_path_storage ps;
+
+ ngtcp2_path_storage_zero(&ps);
+
+ ngtcp2_pkt_info pi;
+ auto n = ngtcp2_conn_write_connection_close(
+ conn_, &ps.path, &pi, conn_closebuf_->wpos(), conn_closebuf_->left(),
+ &last_error_, util::timestamp(loop_));
+ if (n < 0) {
+ std::cerr << "ngtcp2_conn_write_connection_close: " << ngtcp2_strerror(n)
+ << std::endl;
+ return -1;
+ }
+
+ if (n == 0) {
+ return 0;
+ }
+
+ conn_closebuf_->push(n);
+
+ return 0;
+}
+
+int Handler::handle_error() {
+ if (last_error_.type ==
+ NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT_IDLE_CLOSE) {
+ return -1;
+ }
+
+ if (start_closing_period() != 0) {
+ return -1;
+ }
+
+ if (ngtcp2_conn_is_in_draining_period(conn_)) {
+ return NETWORK_ERR_CLOSE_WAIT;
+ }
+
+ 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());
+ assert(conn_);
+ assert(!ngtcp2_conn_is_in_draining_period(conn_));
+
+ auto path = ngtcp2_conn_get_path(conn_);
+
+ return server_->send_packet(
+ *static_cast<Endpoint *>(path->user_data), path->local, path->remote,
+ /* ecn = */ 0, conn_closebuf_->rpos(), conn_closebuf_->size());
+}
+
+void Handler::update_timer() {
+ auto expiry = ngtcp2_conn_get_expiry(conn_);
+ auto now = util::timestamp(loop_);
+
+ if (expiry <= now) {
+ if (!config.quiet) {
+ auto t = static_cast<ev_tstamp>(now - expiry) / NGTCP2_SECONDS;
+ std::cerr << "Timer has already expired: " << t << "s" << std::endl;
+ }
+
+ ev_feed_event(loop_, &timer_, EV_TIMER);
+
+ return;
+ }
+
+ auto t = static_cast<ev_tstamp>(expiry - now) / NGTCP2_SECONDS;
+ if (!config.quiet) {
+ std::cerr << "Set timer=" << std::fixed << t << "s" << std::defaultfloat
+ << std::endl;
+ }
+ timer_.repeat = t;
+ ev_timer_again(loop_, &timer_);
+}
+
+namespace {
+int on_msg_begin(http_parser *htp) {
+ auto s = static_cast<Stream *>(htp->data);
+ if (s->eos) {
+ return -1;
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+int on_url_cb(http_parser *htp, const char *data, size_t datalen) {
+ auto s = static_cast<Stream *>(htp->data);
+ s->uri.append(data, datalen);
+ return 0;
+}
+} // namespace
+
+namespace {
+int on_msg_complete(http_parser *htp) {
+ auto s = static_cast<Stream *>(htp->data);
+ s->eos = true;
+ if (s->start_response() != 0) {
+ return -1;
+ }
+ return 0;
+}
+} // namespace
+
+auto htp_settings = http_parser_settings{
+ on_msg_begin, // on_message_begin
+ on_url_cb, // on_url
+ nullptr, // on_status
+ nullptr, // on_header_field
+ nullptr, // on_header_value
+ nullptr, // on_headers_complete
+ nullptr, // on_body
+ on_msg_complete, // on_message_complete
+ nullptr, // on_chunk_header,
+ nullptr, // on_chunk_complete
+};
+
+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);
+ }
+
+ auto it = streams_.find(stream_id);
+ assert(it != std::end(streams_));
+ auto &stream = (*it).second;
+
+ if (!stream->eos) {
+ auto nread =
+ http_parser_execute(&stream->htp, &htp_settings,
+ reinterpret_cast<const char *>(data), datalen);
+ if (nread != datalen) {
+ if (auto rv = ngtcp2_conn_shutdown_stream(conn_, stream_id,
+ /* app error code */ 1);
+ rv != 0) {
+ std::cerr << "ngtcp2_conn_shutdown_stream: " << ngtcp2_strerror(rv)
+ << std::endl;
+ ngtcp2_connection_close_error_set_transport_error_liberr(
+ &last_error_, NGTCP2_ERR_INTERNAL, nullptr, 0);
+ return -1;
+ }
+ }
+ }
+
+ ngtcp2_conn_extend_max_stream_offset(conn_, stream_id, datalen);
+ ngtcp2_conn_extend_max_offset(conn_, datalen);
+
+ 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;
+}
+
+Server *Handler::server() const { return server_; }
+
+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;
+ }
+
+ auto it = streams_.find(stream_id);
+ assert(it != std::end(streams_));
+ auto &stream = (*it).second;
+
+ sendq_.erase(stream.get());
+
+ 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);
+ }
+
+ 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::add_sendq(Stream *stream) { sendq_.emplace(stream); }
+
+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 {
+void siginthandler(struct ev_loop *loop, ev_signal *watcher, int revents) {
+ ev_break(loop, EVBREAK_ALL);
+}
+} // namespace
+
+Server::Server(struct ev_loop *loop, TLSServerContext &tls_ctx)
+ : loop_(loop), tls_ctx_(tls_ctx) {
+ ev_signal_init(&sigintev_, siginthandler, SIGINT);
+}
+
+Server::~Server() {
+ disconnect();
+ close();
+}
+
+void Server::disconnect() {
+ config.tx_loss_prob = 0;
+
+ for (auto &ep : endpoints_) {
+ ev_io_stop(loop_, &ep.rev);
+ }
+
+ ev_signal_stop(loop_, &sigintev_);
+
+ while (!handlers_.empty()) {
+ auto it = std::begin(handlers_);
+ auto &h = (*it).second;
+
+ h->handle_error();
+
+ remove(h);
+ }
+}
+
+void Server::close() {
+ for (auto &ep : endpoints_) {
+ ::close(ep.fd);
+ }
+
+ endpoints_.clear();
+}
+
+namespace {
+int create_sock(Address &local_addr, const char *addr, const char *port,
+ int family) {
+ 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 = util::create_nonblock_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, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ close(fd);
+ continue;
+ }
+ } else if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ close(fd);
+ continue;
+ }
+
+ if (bind(fd, rp->ai_addr, rp->ai_addrlen) != -1) {
+ break;
+ }
+
+ close(fd);
+ }
+
+ if (!rp) {
+ std::cerr << "Could not bind" << std::endl;
+ 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, rp->ai_family);
+ fd_set_ip_mtu_discover(fd, rp->ai_family);
+ fd_set_ip_dontfrag(fd, family);
+
+ 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;
+ local_addr.ifindex = 0;
+
+ return fd;
+}
+
+} // namespace
+
+namespace {
+int add_endpoint(std::vector<Endpoint> &endpoints, const char *addr,
+ const char *port, int af) {
+ Address dest;
+ auto fd = create_sock(dest, addr, port, af);
+ 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 = util::create_nonblock_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) {
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ std::cerr << "setsockopt: " << strerror(errno) << std::endl;
+ close(fd);
+ return -1;
+ }
+
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ std::cerr << "setsockopt: " << strerror(errno) << std::endl;
+ close(fd);
+ return -1;
+ }
+ } else if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ std::cerr << "setsockopt: " << strerror(errno) << std::endl;
+ close(fd);
+ return -1;
+ }
+
+ if (bind(fd, &addr.su.sa, addr.len) == -1) {
+ std::cerr << "bind: " << 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);
+ fd_set_ip_mtu_discover(fd, addr.su.sa.sa_family);
+ fd_set_ip_dontfrag(fd, addr.su.sa.sa_family);
+
+ 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) == 0) {
+ ready = true;
+ }
+ if (!util::numeric_host(addr, AF_INET) &&
+ add_endpoint(endpoints_, addr, port, AF_INET6) == 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_signal_start(loop_, &sigintev_);
+
+ 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)) + CMSG_SPACE(sizeof(in6_pktinfo))];
+ msg.msg_control = msg_ctrl;
+
+ for (; pktcnt < 10;) {
+ msg.msg_namelen = sizeof(su);
+ msg.msg_controllen = sizeof(msg_ctrl);
+
+ auto nread = recvmsg(ep.fd, &msg, 0);
+ 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);
+ auto local_addr = msghdr_get_local_addr(&msg, su.storage.ss_family);
+ if (!local_addr) {
+ std::cerr << "Unable to obtain local address" << std::endl;
+ continue;
+ }
+
+ set_port(*local_addr, ep.addr);
+
+ if (!config.quiet) {
+ std::array<char, IF_NAMESIZE> ifname;
+ std::cerr << "Received packet: local="
+ << util::straddr(&local_addr->su.sa, local_addr->len)
+ << " remote=" << util::straddr(&su.sa, msg.msg_namelen)
+ << " if=" << if_indextoname(local_addr->ifindex, ifname.data())
+ << " 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;
+ }
+
+ ngtcp2_version_cid vc;
+
+ switch (auto rv = ngtcp2_pkt_decode_version_cid(&vc, buf.data(), nread,
+ NGTCP2_SV_SCIDLEN);
+ rv) {
+ case 0:
+ break;
+ case NGTCP2_ERR_VERSION_NEGOTIATION:
+ send_version_negotiation(vc.version, vc.scid, vc.scidlen, vc.dcid,
+ vc.dcidlen, ep, *local_addr, &su.sa,
+ msg.msg_namelen);
+ continue;
+ default:
+ 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(vc.dcid, vc.dcidlen);
+
+ auto handler_it = handlers_.find(dcid_key);
+ if (handler_it == std::end(handlers_)) {
+ switch (auto rv = ngtcp2_accept(&hd, buf.data(), nread); rv) {
+ case 0:
+ break;
+ case NGTCP2_ERR_RETRY:
+ send_retry(&hd, ep, *local_addr, &su.sa, msg.msg_namelen, nread * 3);
+ continue;
+ default:
+ if (!config.quiet) {
+ std::cerr << "Unexpected packet received: length=" << nread
+ << std::endl;
+ }
+ continue;
+ }
+
+ ngtcp2_cid ocid;
+ ngtcp2_cid *pocid = nullptr;
+
+ assert(hd.type == 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, *local_addr, &su.sa, msg.msg_namelen, nread * 3);
+ continue;
+ }
+
+ if (hd.token.base[0] != NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY &&
+ hd.dcid.datalen < NGTCP2_MIN_INITIAL_DCIDLEN) {
+ send_stateless_connection_close(&hd, ep, *local_addr, &su.sa,
+ msg.msg_namelen);
+ continue;
+ }
+
+ switch (hd.token.base[0]) {
+ case NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY:
+ if (verify_retry_token(&ocid, &hd, &su.sa, msg.msg_namelen) != 0) {
+ send_stateless_connection_close(&hd, ep, *local_addr, &su.sa,
+ msg.msg_namelen);
+ continue;
+ }
+ pocid = &ocid;
+ break;
+ case NGTCP2_CRYPTO_TOKEN_MAGIC_REGULAR:
+ if (verify_token(&hd, &su.sa, msg.msg_namelen) != 0) {
+ if (config.validate_addr) {
+ send_retry(&hd, ep, *local_addr, &su.sa, msg.msg_namelen,
+ nread * 3);
+ 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, *local_addr, &su.sa, msg.msg_namelen,
+ nread * 3);
+ continue;
+ }
+
+ hd.token.base = nullptr;
+ hd.token.len = 0;
+ break;
+ }
+ }
+
+ auto h = std::make_unique<Handler>(loop_, this);
+ if (h->init(ep, *local_addr, &su.sa, msg.msg_namelen, &hd.scid, &hd.dcid,
+ pocid, hd.token.base, hd.token.len, hd.version,
+ tls_ctx_) != 0) {
+ continue;
+ }
+
+ switch (h->on_read(ep, *local_addr, &su.sa, msg.msg_namelen, &pi,
+ buf.data(), nread)) {
+ case 0:
+ break;
+ case NETWORK_ERR_RETRY:
+ send_retry(&hd, ep, *local_addr, &su.sa, msg.msg_namelen, nread * 3);
+ continue;
+ default:
+ continue;
+ }
+
+ switch (h->on_write()) {
+ case 0:
+ break;
+ default:
+ continue;
+ }
+
+ std::array<ngtcp2_cid, 2> scids;
+ auto conn = h->conn();
+
+ auto num_scid = ngtcp2_conn_get_num_scid(conn);
+
+ assert(num_scid <= scids.size());
+
+ ngtcp2_conn_get_scid(conn, scids.data());
+
+ for (size_t i = 0; i < num_scid; ++i) {
+ handlers_.emplace(util::make_cid_key(&scids[i]), h.get());
+ }
+
+ handlers_.emplace(dcid_key, h.get());
+
+ h.release();
+
+ continue;
+ }
+
+ auto h = (*handler_it).second;
+ auto conn = h->conn();
+ if (ngtcp2_conn_is_in_closing_period(conn)) {
+ // TODO do exponential backoff.
+ switch (h->send_conn_close()) {
+ case 0:
+ break;
+ default:
+ remove(h);
+ }
+ continue;
+ }
+ if (ngtcp2_conn_is_in_draining_period(conn)) {
+ continue;
+ }
+
+ if (auto rv = h->on_read(ep, *local_addr, &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 Address &local_addr,
+ const sockaddr *sa, socklen_t salen) {
+ Buffer buf{NGTCP2_MAX_UDP_PAYLOAD_SIZE};
+ std::array<uint32_t, 1 + max_preferred_versionslen> sv;
+
+ auto p = std::begin(sv);
+
+ *p++ = generate_reserved_version(sa, salen, version);
+
+ if (config.preferred_versions.empty()) {
+ *p++ = NGTCP2_PROTO_VER_V1;
+ } else {
+ for (auto v : config.preferred_versions) {
+ *p++ = v;
+ }
+ }
+
+ auto nwrite = ngtcp2_pkt_write_version_negotiation(
+ buf.wpos(), buf.left(),
+ std::uniform_int_distribution<uint8_t>(
+ 0, std::numeric_limits<uint8_t>::max())(randgen),
+ dcid, dcidlen, scid, scidlen, sv.data(), p - std::begin(sv));
+ if (nwrite < 0) {
+ std::cerr << "ngtcp2_pkt_write_version_negotiation: "
+ << ngtcp2_strerror(nwrite) << std::endl;
+ return -1;
+ }
+
+ buf.push(nwrite);
+
+ ngtcp2_addr laddr{
+ const_cast<sockaddr *>(&local_addr.su.sa),
+ local_addr.len,
+ };
+ ngtcp2_addr raddr{
+ const_cast<sockaddr *>(sa),
+ salen,
+ };
+
+ if (send_packet(ep, laddr, raddr, /* ecn = */ 0, buf.rpos(), buf.size()) !=
+ NETWORK_ERR_OK) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int Server::send_retry(const ngtcp2_pkt_hd *chd, Endpoint &ep,
+ const Address &local_addr, const sockaddr *sa,
+ socklen_t salen, size_t max_pktlen) {
+ std::array<char, NI_MAXHOST> host;
+ std::array<char, NI_MAXSERV> port;
+
+ if (auto rv = getnameinfo(sa, salen, host.data(), host.size(), port.data(),
+ port.size(), NI_NUMERICHOST | NI_NUMERICSERV);
+ rv != 0) {
+ std::cerr << "getnameinfo: " << gai_strerror(rv) << std::endl;
+ return -1;
+ }
+
+ if (!config.quiet) {
+ std::cerr << "Sending Retry packet to [" << host.data()
+ << "]:" << port.data() << std::endl;
+ }
+
+ ngtcp2_cid scid;
+
+ scid.datalen = NGTCP2_SV_SCIDLEN;
+ if (util::generate_secure_random(scid.data, scid.datalen) != 0) {
+ return -1;
+ }
+
+ std::array<uint8_t, NGTCP2_CRYPTO_MAX_RETRY_TOKENLEN> token;
+
+ auto t = std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count();
+
+ auto tokenlen = ngtcp2_crypto_generate_retry_token(
+ token.data(), config.static_secret.data(), config.static_secret.size(),
+ chd->version, sa, salen, &scid, &chd->dcid, t);
+ if (tokenlen < 0) {
+ return -1;
+ }
+
+ if (!config.quiet) {
+ std::cerr << "Generated address validation token:" << std::endl;
+ util::hexdump(stderr, token.data(), tokenlen);
+ }
+
+ Buffer buf{
+ std::min(static_cast<size_t>(NGTCP2_MAX_UDP_PAYLOAD_SIZE), max_pktlen)};
+
+ auto nwrite = ngtcp2_crypto_write_retry(buf.wpos(), buf.left(), chd->version,
+ &chd->scid, &scid, &chd->dcid,
+ token.data(), tokenlen);
+ if (nwrite < 0) {
+ std::cerr << "ngtcp2_crypto_write_retry failed" << std::endl;
+ return -1;
+ }
+
+ buf.push(nwrite);
+
+ ngtcp2_addr laddr{
+ const_cast<sockaddr *>(&local_addr.su.sa),
+ local_addr.len,
+ };
+ ngtcp2_addr raddr{
+ const_cast<sockaddr *>(sa),
+ salen,
+ };
+
+ if (send_packet(ep, laddr, raddr, /* ecn = */ 0, buf.rpos(), buf.size()) !=
+ NETWORK_ERR_OK) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int Server::send_stateless_connection_close(const ngtcp2_pkt_hd *chd,
+ Endpoint &ep,
+ const Address &local_addr,
+ const sockaddr *sa,
+ socklen_t salen) {
+ Buffer buf{NGTCP2_MAX_UDP_PAYLOAD_SIZE};
+
+ auto nwrite = ngtcp2_crypto_write_connection_close(
+ buf.wpos(), buf.left(), chd->version, &chd->scid, &chd->dcid,
+ NGTCP2_INVALID_TOKEN, nullptr, 0);
+ if (nwrite < 0) {
+ std::cerr << "ngtcp2_crypto_write_connection_close failed" << std::endl;
+ return -1;
+ }
+
+ buf.push(nwrite);
+
+ ngtcp2_addr laddr{
+ const_cast<sockaddr *>(&local_addr.su.sa),
+ local_addr.len,
+ };
+ ngtcp2_addr raddr{
+ const_cast<sockaddr *>(sa),
+ salen,
+ };
+
+ if (send_packet(ep, laddr, raddr, /* ecn = */ 0, buf.rpos(), buf.size()) !=
+ NETWORK_ERR_OK) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int Server::verify_retry_token(ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd,
+ const sockaddr *sa, socklen_t salen) {
+ std::array<char, NI_MAXHOST> host;
+ std::array<char, NI_MAXSERV> port;
+
+ if (auto rv = getnameinfo(sa, salen, host.data(), host.size(), port.data(),
+ port.size(), NI_NUMERICHOST | NI_NUMERICSERV);
+ rv != 0) {
+ std::cerr << "getnameinfo: " << gai_strerror(rv) << std::endl;
+ return -1;
+ }
+
+ if (!config.quiet) {
+ std::cerr << "Verifying Retry token from [" << host.data()
+ << "]:" << port.data() << std::endl;
+ util::hexdump(stderr, hd->token.base, hd->token.len);
+ }
+
+ auto t = std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count();
+
+ if (ngtcp2_crypto_verify_retry_token(
+ ocid, hd->token.base, hd->token.len, config.static_secret.data(),
+ config.static_secret.size(), hd->version, sa, salen, &hd->dcid,
+ 10 * NGTCP2_SECONDS, t) != 0) {
+ std::cerr << "Could not verify Retry token" << std::endl;
+
+ return -1;
+ }
+
+ if (!config.quiet) {
+ std::cerr << "Token was successfully validated" << std::endl;
+ }
+
+ return 0;
+}
+
+int Server::verify_token(const ngtcp2_pkt_hd *hd, const sockaddr *sa,
+ socklen_t salen) {
+ std::array<char, NI_MAXHOST> host;
+ std::array<char, NI_MAXSERV> port;
+
+ if (auto rv = getnameinfo(sa, salen, host.data(), host.size(), port.data(),
+ port.size(), NI_NUMERICHOST | NI_NUMERICSERV);
+ rv != 0) {
+ std::cerr << "getnameinfo: " << gai_strerror(rv) << std::endl;
+ return -1;
+ }
+
+ if (!config.quiet) {
+ std::cerr << "Verifying token from [" << host.data() << "]:" << port.data()
+ << std::endl;
+ util::hexdump(stderr, hd->token.base, hd->token.len);
+ }
+
+ auto t = std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count();
+
+ if (ngtcp2_crypto_verify_regular_token(hd->token.base, hd->token.len,
+ config.static_secret.data(),
+ config.static_secret.size(), sa, salen,
+ 3600 * NGTCP2_SECONDS, t) != 0) {
+ std::cerr << "Could not verify token" << std::endl;
+
+ return -1;
+ }
+
+ if (!config.quiet) {
+ std::cerr << "Token was successfully validated" << std::endl;
+ }
+
+ return 0;
+}
+
+int Server::send_packet(Endpoint &ep, const ngtcp2_addr &local_addr,
+ const ngtcp2_addr &remote_addr, unsigned int ecn,
+ const uint8_t *data, size_t datalen) {
+ auto no_gso = false;
+ auto [_, rv] = send_packet(ep, no_gso, local_addr, remote_addr, ecn, data,
+ datalen, datalen);
+
+ return rv;
+}
+
+std::pair<size_t, int>
+Server::send_packet(Endpoint &ep, bool &no_gso, const ngtcp2_addr &local_addr,
+ const ngtcp2_addr &remote_addr, unsigned int ecn,
+ const uint8_t *data, size_t datalen, size_t gso_size) {
+ assert(gso_size);
+
+ if (debug::packet_lost(config.tx_loss_prob)) {
+ if (!config.quiet) {
+ std::cerr << "** Simulated outgoing packet loss **" << std::endl;
+ }
+ return {0, NETWORK_ERR_OK};
+ }
+
+ if (no_gso && datalen > gso_size) {
+ size_t nsent = 0;
+
+ for (auto p = data; p < data + datalen; p += gso_size) {
+ auto len = std::min(gso_size, static_cast<size_t>(data + datalen - p));
+
+ auto [n, rv] =
+ send_packet(ep, no_gso, local_addr, remote_addr, ecn, p, len, len);
+ if (rv != 0) {
+ return {nsent, rv};
+ }
+
+ nsent += n;
+ }
+
+ return {nsent, 0};
+ }
+
+ iovec msg_iov;
+ msg_iov.iov_base = const_cast<uint8_t *>(data);
+ msg_iov.iov_len = datalen;
+
+ msghdr msg{};
+ msg.msg_name = const_cast<sockaddr *>(remote_addr.addr);
+ msg.msg_namelen = remote_addr.addrlen;
+ msg.msg_iov = &msg_iov;
+ msg.msg_iovlen = 1;
+
+ uint8_t
+ msg_ctrl[CMSG_SPACE(sizeof(uint16_t)) + CMSG_SPACE(sizeof(in6_pktinfo))];
+
+ memset(msg_ctrl, 0, sizeof(msg_ctrl));
+
+ msg.msg_control = msg_ctrl;
+ msg.msg_controllen = sizeof(msg_ctrl);
+
+ size_t controllen = 0;
+
+ auto cm = CMSG_FIRSTHDR(&msg);
+
+ switch (local_addr.addr->sa_family) {
+ case AF_INET: {
+ controllen += CMSG_SPACE(sizeof(in_pktinfo));
+ cm->cmsg_level = IPPROTO_IP;
+ cm->cmsg_type = IP_PKTINFO;
+ cm->cmsg_len = CMSG_LEN(sizeof(in_pktinfo));
+ auto pktinfo = reinterpret_cast<in_pktinfo *>(CMSG_DATA(cm));
+ memset(pktinfo, 0, sizeof(in_pktinfo));
+ auto addrin = reinterpret_cast<sockaddr_in *>(local_addr.addr);
+ pktinfo->ipi_spec_dst = addrin->sin_addr;
+ break;
+ }
+ case AF_INET6: {
+ controllen += CMSG_SPACE(sizeof(in6_pktinfo));
+ cm->cmsg_level = IPPROTO_IPV6;
+ cm->cmsg_type = IPV6_PKTINFO;
+ cm->cmsg_len = CMSG_LEN(sizeof(in6_pktinfo));
+ auto pktinfo = reinterpret_cast<in6_pktinfo *>(CMSG_DATA(cm));
+ memset(pktinfo, 0, sizeof(in6_pktinfo));
+ auto addrin = reinterpret_cast<sockaddr_in6 *>(local_addr.addr);
+ pktinfo->ipi6_addr = addrin->sin6_addr;
+ break;
+ }
+ default:
+ assert(0);
+ }
+
+#ifdef UDP_SEGMENT
+ if (datalen > gso_size) {
+ controllen += CMSG_SPACE(sizeof(uint16_t));
+ cm = CMSG_NXTHDR(&msg, cm);
+ cm->cmsg_level = SOL_UDP;
+ cm->cmsg_type = UDP_SEGMENT;
+ cm->cmsg_len = CMSG_LEN(sizeof(uint16_t));
+ *(reinterpret_cast<uint16_t *>(CMSG_DATA(cm))) = gso_size;
+ }
+#endif // UDP_SEGMENT
+
+ msg.msg_controllen = controllen;
+
+ if (ep.ecn != ecn) {
+ ep.ecn = ecn;
+ fd_set_ecn(ep.fd, ep.addr.su.storage.ss_family, ecn);
+ }
+
+ ssize_t nwrite = 0;
+
+ do {
+ nwrite = sendmsg(ep.fd, &msg, 0);
+ } while (nwrite == -1 && errno == EINTR);
+
+ if (nwrite == -1) {
+ switch (errno) {
+ case EAGAIN:
+#if EAGAIN != EWOULDBLOCK
+ case EWOULDBLOCK:
+#endif // EAGAIN != EWOULDBLOCK
+ return {0, NETWORK_ERR_SEND_BLOCKED};
+#ifdef UDP_SEGMENT
+ case EIO:
+ if (datalen > gso_size) {
+ // GSO failure; send each packet in a separate sendmsg call.
+ std::cerr << "sendmsg: disabling GSO due to " << strerror(errno)
+ << std::endl;
+
+ no_gso = true;
+
+ return send_packet(ep, no_gso, local_addr, remote_addr, ecn, data,
+ datalen, gso_size);
+ }
+ break;
+#endif // UDP_SEGMENT
+ }
+
+ std::cerr << "sendmsg: " << strerror(errno) << std::endl;
+ // TODO We have packet which is expected to fail to send (e.g.,
+ // path validation to old path).
+ return {0, NETWORK_ERR_OK};
+ }
+
+ if (!config.quiet) {
+ std::cerr << "Sent packet: local="
+ << util::straddr(local_addr.addr, local_addr.addrlen)
+ << " remote="
+ << util::straddr(remote_addr.addr, remote_addr.addrlen)
+ << " ecn=0x" << std::hex << ecn << std::dec << " " << nwrite
+ << " bytes" << std::endl;
+ }
+
+ return {nwrite, NETWORK_ERR_OK};
+}
+
+void Server::associate_cid(const ngtcp2_cid *cid, Handler *h) {
+ handlers_.emplace(util::make_cid_key(cid), h);
+}
+
+void Server::dissociate_cid(const ngtcp2_cid *cid) {
+ handlers_.erase(util::make_cid_key(cid));
+}
+
+void Server::remove(const Handler *h) {
+ auto conn = h->conn();
+
+ handlers_.erase(
+ util::make_cid_key(ngtcp2_conn_get_client_initial_dcid(conn)));
+
+ std::vector<ngtcp2_cid> cids(ngtcp2_conn_get_num_scid(conn));
+ ngtcp2_conn_get_scid(conn, cids.data());
+
+ for (auto &cid : cids) {
+ handlers_.erase(util::make_cid_key(&cid));
+ }
+
+ delete h;
+}
+
+namespace {
+int parse_host_port(Address &dest, int af, const char *first,
+ const char *last) {
+ if (std::distance(first, last) == 0) {
+ return -1;
+ }
+
+ const char *host_begin, *host_end, *it;
+ if (*first == '[') {
+ host_begin = first + 1;
+ it = std::find(host_begin, last, ']');
+ if (it == last) {
+ return -1;
+ }
+ host_end = it;
+ ++it;
+ if (it == last || *it != ':') {
+ return -1;
+ }
+ } else {
+ host_begin = first;
+ it = std::find(host_begin, last, ':');
+ if (it == last) {
+ return -1;
+ }
+ host_end = it;
+ }
+
+ if (++it == last) {
+ return -1;
+ }
+ auto svc_begin = it;
+
+ std::array<char, NI_MAXHOST> host;
+ *std::copy(host_begin, host_end, std::begin(host)) = '\0';
+
+ addrinfo hints{}, *res;
+ hints.ai_family = af;
+ hints.ai_socktype = SOCK_DGRAM;
+
+ if (auto rv = getaddrinfo(host.data(), svc_begin, &hints, &res); rv != 0) {
+ std::cerr << "getaddrinfo: [" << host.data() << "]:" << svc_begin << ": "
+ << gai_strerror(rv) << std::endl;
+ return -1;
+ }
+
+ dest.len = res->ai_addrlen;
+ memcpy(&dest.su, res->ai_addr, res->ai_addrlen);
+
+ freeaddrinfo(res);
+
+ return 0;
+}
+} // namespace
+
+namespace {
+void print_usage() {
+ std::cerr << "Usage: server [OPTIONS] <ADDR> <PORT> <PRIVATE_KEY_FILE> "
+ "<CERTIFICATE_FILE>"
+ << std::endl;
+}
+} // namespace
+
+namespace {
+void config_set_default(Config &config) {
+ config = Config{};
+ config.tx_loss_prob = 0.;
+ config.rx_loss_prob = 0.;
+ config.ciphers = util::crypto_default_ciphers();
+ config.groups = util::crypto_default_groups();
+ config.timeout = 30 * NGTCP2_SECONDS;
+ {
+ auto path = realpath(".", nullptr);
+ assert(path);
+ config.htdocs = path;
+ free(path);
+ }
+ config.mime_types_file = "/etc/mime.types"sv;
+ config.max_data = 1_m;
+ config.max_stream_data_bidi_local = 256_k;
+ config.max_stream_data_bidi_remote = 256_k;
+ config.max_stream_data_uni = 256_k;
+ config.max_window = 6_m;
+ config.max_stream_window = 6_m;
+ config.max_streams_bidi = 100;
+ config.max_streams_uni = 3;
+ config.max_dyn_length = 20_m;
+ config.cc_algo = NGTCP2_CC_ALGO_CUBIC;
+ config.initial_rtt = NGTCP2_DEFAULT_INITIAL_RTT;
+ config.max_gso_dgrams = 64;
+ config.handshake_timeout = NGTCP2_DEFAULT_HANDSHAKE_TIMEOUT;
+ config.ack_thresh = 2;
+}
+} // namespace
+
+namespace {
+void print_help() {
+ print_usage();
+
+ config_set_default(config);
+
+ std::cout << R"(
+ <ADDR> Address to listen to. '*' binds to any address.
+ <PORT> Port
+ <PRIVATE_KEY_FILE>
+ Path to private key file
+ <CERTIFICATE_FILE>
+ Path to certificate file
+Options:
+ -t, --tx-loss=<P>
+ The probability of losing outgoing packets. <P> must be
+ [0.0, 1.0], inclusive. 0.0 means no packet loss. 1.0
+ means 100% packet loss.
+ -r, --rx-loss=<P>
+ The probability of losing incoming packets. <P> must be
+ [0.0, 1.0], inclusive. 0.0 means no packet loss. 1.0
+ means 100% packet loss.
+ --ciphers=<CIPHERS>
+ Specify the cipher suite list to enable.
+ Default: )"
+ << config.ciphers << R"(
+ --groups=<GROUPS>
+ Specify the supported groups.
+ Default: )"
+ << config.groups << R"(
+ -d, --htdocs=<PATH>
+ Specify document root. If this option is not specified,
+ the document root is the current working directory.
+ -q, --quiet Suppress debug output.
+ -s, --show-secret
+ Print out secrets unless --quiet is used.
+ --timeout=<DURATION>
+ Specify idle timeout.
+ Default: )"
+ << util::format_duration(config.timeout) << R"(
+ -V, --validate-addr
+ Perform address validation.
+ --preferred-ipv4-addr=<ADDR>:<PORT>
+ Specify preferred IPv4 address and port.
+ --preferred-ipv6-addr=<ADDR>:<PORT>
+ Specify preferred IPv6 address and port. A numeric IPv6
+ address must be enclosed by '[' and ']' (e.g.,
+ [::1]:8443)
+ --mime-types-file=<PATH>
+ Path to file that contains MIME media types and the
+ extensions.
+ Default: )"
+ << config.mime_types_file << R"(
+ --early-response
+ Start sending response when it receives HTTP header
+ fields without waiting for request body. If HTTP
+ response data is written before receiving request body,
+ STOP_SENDING is sent.
+ --verify-client
+ Request a client certificate. At the moment, we just
+ request a certificate and no verification is done.
+ --qlog-dir=<PATH>
+ Path to the directory where qlog file is stored. The
+ file name of each qlog is the Source Connection ID of
+ server.
+ --no-quic-dump
+ Disables printing QUIC STREAM and CRYPTO frame data out.
+ --no-http-dump
+ Disables printing HTTP response body out.
+ --max-data=<SIZE>
+ The initial connection-level flow control window.
+ Default: )"
+ << util::format_uint_iec(config.max_data) << R"(
+ --max-stream-data-bidi-local=<SIZE>
+ The initial stream-level flow control window for a
+ bidirectional stream that the local endpoint initiates.
+ Default: )"
+ << util::format_uint_iec(config.max_stream_data_bidi_local) << R"(
+ --max-stream-data-bidi-remote=<SIZE>
+ The initial stream-level flow control window for a
+ bidirectional stream that the remote endpoint initiates.
+ Default: )"
+ << util::format_uint_iec(config.max_stream_data_bidi_remote) << R"(
+ --max-stream-data-uni=<SIZE>
+ The initial stream-level flow control window for a
+ unidirectional stream.
+ Default: )"
+ << util::format_uint_iec(config.max_stream_data_uni) << R"(
+ --max-streams-bidi=<N>
+ The number of the concurrent bidirectional streams.
+ Default: )"
+ << config.max_streams_bidi << R"(
+ --max-streams-uni=<N>
+ The number of the concurrent unidirectional streams.
+ Default: )"
+ << config.max_streams_uni << R"(
+ --max-dyn-length=<SIZE>
+ The maximum length of a dynamically generated content.
+ Default: )"
+ << util::format_uint_iec(config.max_dyn_length) << R"(
+ --cc=(cubic|reno|bbr|bbr2)
+ The name of congestion controller algorithm.
+ Default: )"
+ << util::strccalgo(config.cc_algo) << R"(
+ --initial-rtt=<DURATION>
+ Set an initial RTT.
+ Default: )"
+ << util::format_duration(config.initial_rtt) << R"(
+ --max-udp-payload-size=<SIZE>
+ Override maximum UDP payload size that server transmits.
+ --max-window=<SIZE>
+ Maximum connection-level flow control window size. The
+ window auto-tuning is enabled if nonzero value is given,
+ and window size is scaled up to this value.
+ Default: )"
+ << util::format_uint_iec(config.max_window) << R"(
+ --max-stream-window=<SIZE>
+ Maximum stream-level flow control window size. The
+ window auto-tuning is enabled if nonzero value is given,
+ and window size is scaled up to this value.
+ Default: )"
+ << util::format_uint_iec(config.max_stream_window) << R"(
+ --send-trailers
+ Send trailer fields.
+ --max-gso-dgrams=<N>
+ Maximum number of UDP datagrams that are sent in a
+ single GSO sendmsg call.
+ Default: )"
+ << config.max_gso_dgrams << R"(
+ --handshake-timeout=<DURATION>
+ Set the QUIC handshake timeout.
+ Default: )"
+ << util::format_duration(config.handshake_timeout) << R"(
+ --preferred-versions=<HEX>[[,<HEX>]...]
+ Specify QUIC versions in hex string in the order of
+ preference. Server negotiates one of those versions if
+ client initially selects a less preferred version.
+ These versions must be supported by libngtcp2. Instead
+ of specifying hex string, there are special aliases
+ available: "v1" indicates QUIC v1, and "v2draft"
+ indicates QUIC v2 draft.
+ --other-versions=<HEX>[[,<HEX>]...]
+ Specify QUIC versions in hex string that are sent in
+ other_versions field of version_information transport
+ parameter. This list can include a version which is not
+ supported by libngtcp2. Instead of specifying hex
+ string, there are special aliases available: "v1"
+ indicates QUIC v1, and "v2draft" indicates QUIC v2
+ draft.
+ --no-pmtud Disables Path MTU Discovery.
+ --ack-thresh=<N>
+ The minimum number of the received ACK eliciting packets
+ that triggers immediate acknowledgement.
+ Default: )"
+ << config.ack_thresh << R"(
+ -h, --help Display this help and exit.
+
+---
+
+ The <SIZE> argument is an integer and an optional unit (e.g., 10K is
+ 10 * 1024). Units are K, M and G (powers of 1024).
+
+ The <DURATION> argument is an integer and an optional unit (e.g., 1s
+ is 1 second and 500ms is 500 milliseconds). Units are h, m, s, ms,
+ us, or ns (hours, minutes, seconds, milliseconds, microseconds, and
+ nanoseconds respectively). If a unit is omitted, a second is used
+ as unit.
+
+ The <HEX> argument is an hex string which must start with "0x"
+ (e.g., 0x00000001).)"
+ << std::endl;
+}
+} // namespace
+
+std::ofstream keylog_file;
+
+int main(int argc, char **argv) {
+ config_set_default(config);
+
+ for (;;) {
+ static int flag = 0;
+ constexpr static option long_opts[] = {
+ {"help", no_argument, nullptr, 'h'},
+ {"tx-loss", required_argument, nullptr, 't'},
+ {"rx-loss", required_argument, nullptr, 'r'},
+ {"htdocs", required_argument, nullptr, 'd'},
+ {"quiet", no_argument, nullptr, 'q'},
+ {"show-secret", no_argument, nullptr, 's'},
+ {"validate-addr", no_argument, nullptr, 'V'},
+ {"ciphers", required_argument, &flag, 1},
+ {"groups", required_argument, &flag, 2},
+ {"timeout", required_argument, &flag, 3},
+ {"preferred-ipv4-addr", required_argument, &flag, 4},
+ {"preferred-ipv6-addr", required_argument, &flag, 5},
+ {"mime-types-file", required_argument, &flag, 6},
+ {"early-response", no_argument, &flag, 7},
+ {"verify-client", no_argument, &flag, 8},
+ {"qlog-dir", required_argument, &flag, 9},
+ {"no-quic-dump", no_argument, &flag, 10},
+ {"no-http-dump", no_argument, &flag, 11},
+ {"max-data", required_argument, &flag, 12},
+ {"max-stream-data-bidi-local", required_argument, &flag, 13},
+ {"max-stream-data-bidi-remote", required_argument, &flag, 14},
+ {"max-stream-data-uni", required_argument, &flag, 15},
+ {"max-streams-bidi", required_argument, &flag, 16},
+ {"max-streams-uni", required_argument, &flag, 17},
+ {"max-dyn-length", required_argument, &flag, 18},
+ {"cc", required_argument, &flag, 19},
+ {"initial-rtt", required_argument, &flag, 20},
+ {"max-udp-payload-size", required_argument, &flag, 21},
+ {"send-trailers", no_argument, &flag, 22},
+ {"max-window", required_argument, &flag, 23},
+ {"max-stream-window", required_argument, &flag, 24},
+ {"max-gso-dgrams", required_argument, &flag, 25},
+ {"handshake-timeout", required_argument, &flag, 26},
+ {"preferred-versions", required_argument, &flag, 27},
+ {"other-versions", required_argument, &flag, 28},
+ {"no-pmtud", no_argument, &flag, 29},
+ {"ack-thresh", required_argument, &flag, 30},
+ {nullptr, 0, nullptr, 0}};
+
+ auto optidx = 0;
+ auto c = getopt_long(argc, argv, "d:hqr:st:V", long_opts, &optidx);
+ if (c == -1) {
+ break;
+ }
+ switch (c) {
+ case 'd': {
+ // --htdocs
+ auto path = realpath(optarg, nullptr);
+ if (path == nullptr) {
+ std::cerr << "path: invalid path " << std::quoted(optarg) << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.htdocs = path;
+ free(path);
+ break;
+ }
+ case 'h':
+ // --help
+ print_help();
+ exit(EXIT_SUCCESS);
+ case 'q':
+ // --quiet
+ config.quiet = true;
+ break;
+ case 'r':
+ // --rx-loss
+ config.rx_loss_prob = strtod(optarg, nullptr);
+ break;
+ case 's':
+ // --show-secret
+ config.show_secret = true;
+ break;
+ case 't':
+ // --tx-loss
+ config.tx_loss_prob = strtod(optarg, nullptr);
+ break;
+ case 'V':
+ // --validate-addr
+ config.validate_addr = true;
+ break;
+ case '?':
+ print_usage();
+ exit(EXIT_FAILURE);
+ case 0:
+ switch (flag) {
+ case 1:
+ // --ciphers
+ config.ciphers = optarg;
+ break;
+ case 2:
+ // --groups
+ config.groups = optarg;
+ break;
+ case 3:
+ // --timeout
+ if (auto t = util::parse_duration(optarg); !t) {
+ std::cerr << "timeout: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.timeout = *t;
+ }
+ break;
+ case 4:
+ // --preferred-ipv4-addr
+ if (parse_host_port(config.preferred_ipv4_addr, AF_INET, optarg,
+ optarg + strlen(optarg)) != 0) {
+ std::cerr << "preferred-ipv4-addr: could not use "
+ << std::quoted(optarg) << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 5:
+ // --preferred-ipv6-addr
+ if (parse_host_port(config.preferred_ipv6_addr, AF_INET6, optarg,
+ optarg + strlen(optarg)) != 0) {
+ std::cerr << "preferred-ipv6-addr: could not use "
+ << std::quoted(optarg) << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 6:
+ // --mime-types-file
+ config.mime_types_file = optarg;
+ break;
+ case 7:
+ // --early-response
+ config.early_response = true;
+ break;
+ case 8:
+ // --verify-client
+ config.verify_client = true;
+ break;
+ case 9:
+ // --qlog-dir
+ config.qlog_dir = optarg;
+ break;
+ case 10:
+ // --no-quic-dump
+ config.no_quic_dump = true;
+ break;
+ case 11:
+ // --no-http-dump
+ config.no_http_dump = true;
+ break;
+ case 12:
+ // --max-data
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-data: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_data = *n;
+ }
+ break;
+ case 13:
+ // --max-stream-data-bidi-local
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-stream-data-bidi-local: invalid argument"
+ << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_stream_data_bidi_local = *n;
+ }
+ break;
+ case 14:
+ // --max-stream-data-bidi-remote
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-stream-data-bidi-remote: invalid argument"
+ << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_stream_data_bidi_remote = *n;
+ }
+ break;
+ case 15:
+ // --max-stream-data-uni
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-stream-data-uni: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_stream_data_uni = *n;
+ }
+ break;
+ case 16:
+ // --max-streams-bidi
+ if (auto n = util::parse_uint(optarg); !n) {
+ std::cerr << "max-streams-bidi: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_streams_bidi = *n;
+ }
+ break;
+ case 17:
+ // --max-streams-uni
+ if (auto n = util::parse_uint(optarg); !n) {
+ std::cerr << "max-streams-uni: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_streams_uni = *n;
+ }
+ break;
+ case 18:
+ // --max-dyn-length
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-dyn-length: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_dyn_length = *n;
+ }
+ break;
+ case 19:
+ // --cc
+ if (strcmp("cubic", optarg) == 0) {
+ config.cc_algo = NGTCP2_CC_ALGO_CUBIC;
+ break;
+ }
+ if (strcmp("reno", optarg) == 0) {
+ config.cc_algo = NGTCP2_CC_ALGO_RENO;
+ break;
+ }
+ if (strcmp("bbr", optarg) == 0) {
+ config.cc_algo = NGTCP2_CC_ALGO_BBR;
+ break;
+ }
+ if (strcmp("bbr2", optarg) == 0) {
+ config.cc_algo = NGTCP2_CC_ALGO_BBR2;
+ break;
+ }
+ std::cerr << "cc: specify cubic, reno, bbr, or bbr2" << std::endl;
+ exit(EXIT_FAILURE);
+ case 20:
+ // --initial-rtt
+ if (auto t = util::parse_duration(optarg); !t) {
+ std::cerr << "initial-rtt: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.initial_rtt = *t;
+ }
+ break;
+ case 21:
+ // --max-udp-payload-size
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-udp-payload-size: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else if (*n > 64_k) {
+ std::cerr << "max-udp-payload-size: must not exceed 65536"
+ << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_udp_payload_size = *n;
+ }
+ break;
+ case 22:
+ // --send-trailers
+ config.send_trailers = true;
+ break;
+ case 23:
+ // --max-window
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-window: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_window = *n;
+ }
+ break;
+ case 24:
+ // --max-stream-window
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-stream-window: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_stream_window = *n;
+ }
+ break;
+ case 25:
+ // --max-gso-dgrams
+ if (auto n = util::parse_uint(optarg); !n) {
+ std::cerr << "max-gso-dgrams: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_gso_dgrams = *n;
+ }
+ break;
+ case 26:
+ // --handshake-timeout
+ if (auto t = util::parse_duration(optarg); !t) {
+ std::cerr << "handshake-timeout: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.handshake_timeout = *t;
+ }
+ break;
+ case 27: {
+ // --preferred-versions
+ auto l = util::split_str(optarg);
+ if (l.size() > max_preferred_versionslen) {
+ std::cerr << "preferred-versions: too many versions > "
+ << max_preferred_versionslen << std::endl;
+ }
+ config.preferred_versions.resize(l.size());
+ auto it = std::begin(config.preferred_versions);
+ for (const auto &k : l) {
+ if (k == "v1"sv) {
+ *it++ = NGTCP2_PROTO_VER_V1;
+ continue;
+ }
+ if (k == "v2draft"sv) {
+ *it++ = NGTCP2_PROTO_VER_V2_DRAFT;
+ continue;
+ }
+ auto rv = util::parse_version(k);
+ if (!rv) {
+ std::cerr << "preferred-versions: invalid version "
+ << std::quoted(k) << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ if (!ngtcp2_is_supported_version(*rv)) {
+ std::cerr << "preferred-versions: unsupported version "
+ << std::quoted(k) << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ *it++ = *rv;
+ }
+ break;
+ }
+ case 28: {
+ // --other-versions
+ auto l = util::split_str(optarg);
+ config.other_versions.resize(l.size());
+ auto it = std::begin(config.other_versions);
+ for (const auto &k : l) {
+ if (k == "v1"sv) {
+ *it++ = NGTCP2_PROTO_VER_V1;
+ continue;
+ }
+ if (k == "v2draft"sv) {
+ *it++ = NGTCP2_PROTO_VER_V2_DRAFT;
+ continue;
+ }
+ auto rv = util::parse_version(k);
+ if (!rv) {
+ std::cerr << "other-versions: invalid version " << std::quoted(k)
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ *it++ = *rv;
+ }
+ break;
+ }
+ case 29:
+ // --no-pmtud
+ config.no_pmtud = true;
+ break;
+ case 30:
+ // --ack-thresh
+ if (auto n = util::parse_uint(optarg); !n) {
+ std::cerr << "ack-thresh: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else if (*n > 100) {
+ std::cerr << "ack-thresh: must not exceed 100" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.ack_thresh = *n;
+ }
+ break;
+ }
+ break;
+ default:
+ break;
+ };
+ }
+
+ if (argc - optind < 4) {
+ std::cerr << "Too few arguments" << std::endl;
+ print_usage();
+ exit(EXIT_FAILURE);
+ }
+
+ auto addr = argv[optind++];
+ auto port = argv[optind++];
+ auto private_key_file = argv[optind++];
+ auto cert_file = argv[optind++];
+
+ if (auto n = util::parse_uint(port); !n) {
+ std::cerr << "port: invalid port number" << std::endl;
+ exit(EXIT_FAILURE);
+ } else if (*n > 65535) {
+ std::cerr << "port: must not exceed 65535" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.port = *n;
+ }
+
+ if (auto mt = util::read_mime_types(config.mime_types_file); !mt) {
+ std::cerr << "mime-types-file: Could not read MIME media types file "
+ << std::quoted(config.mime_types_file) << std::endl;
+ } else {
+ config.mime_types = std::move(*mt);
+ }
+
+ TLSServerContext tls_ctx;
+
+ if (tls_ctx.init(private_key_file, cert_file, AppProtocol::HQ) != 0) {
+ exit(EXIT_FAILURE);
+ }
+
+ if (config.htdocs.back() != '/') {
+ config.htdocs += '/';
+ }
+
+ std::cerr << "Using document root " << config.htdocs << std::endl;
+
+ auto ev_loop_d = defer(ev_loop_destroy, EV_DEFAULT);
+
+ auto keylog_filename = getenv("SSLKEYLOGFILE");
+ if (keylog_filename) {
+ keylog_file.open(keylog_filename, std::ios_base::app);
+ if (keylog_file) {
+ tls_ctx.enable_keylog();
+ }
+ }
+
+ if (util::generate_secret(config.static_secret.data(),
+ config.static_secret.size()) != 0) {
+ std::cerr << "Unable to generate static secret" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ Server s(EV_DEFAULT, tls_ctx);
+ if (s.init(addr, port) != 0) {
+ exit(EXIT_FAILURE);
+ }
+
+ ev_run(EV_DEFAULT, 0);
+
+ s.disconnect();
+ s.close();
+
+ return EXIT_SUCCESS;
+}