/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 = ""; body += status_string; body += ' '; body += reason_phrase; body += "

"; body += status_string; body += ' '; body += reason_phrase; body += "


"; body += NGTCP2_SERVER; body += " at port "; body += util::format_uint(config.port); body += "
"; body += ""; 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 file_cache; } // namespace std::pair 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(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(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 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(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(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(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(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(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 token; auto path = ngtcp2_conn_get_path(conn_); auto t = std::chrono::duration_cast( 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(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(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(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(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_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(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(); 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(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(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(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(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(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 = token; settings.tokenlen = 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.available_versions.empty()) { settings.available_versions = config.available_versions.data(); settings.available_versionslen = config.available_versions.size(); } ngtcp2_transport_params params; ngtcp2_transport_params_default(¶ms); 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(&local_addr.su.sa), local_addr.len, }, { const_cast(sa), salen, }, const_cast(&ep), }; if (auto rv = ngtcp2_conn_server_new(&conn_, dcid, &scid_, &path, version, &callbacks, &settings, ¶ms, 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(&local_addr.su.sa), local_addr.len, }, { const_cast(sa), salen, }, const_cast(&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; } } ev_io_stop(loop_, &wev_); 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(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(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); } } // 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(nwrite) > gso_size || (gso_size > path_max_udp_payload_size && static_cast(nwrite) != gso_size)) { auto &ep = *static_cast(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(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(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(nwrite) < gso_size) { auto &ep = *static_cast(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(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(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(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(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(now - expiry) / NGTCP2_SECONDS; std::cerr << "Timer has already expired: " << std::fixed << t << "s" << std::defaultfloat << std::endl; } ev_feed_event(loop_, &timer_, EV_TIMER); return; } auto t = static_cast(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(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(htp->data); s->uri.append(data, datalen); return 0; } } // namespace namespace { int on_msg_complete(http_parser *htp) { auto s = static_cast(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(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 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(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(sizeof(val))) == -1) { close(fd); continue; } if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val, static_cast(sizeof(val))) == -1) { close(fd); continue; } } else if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &val, static_cast(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(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 &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 &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(sizeof(val))) == -1) { std::cerr << "setsockopt: " << strerror(errno) << std::endl; close(fd); return -1; } if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val, static_cast(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(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(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 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 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_)) { if (auto rv = ngtcp2_accept(&hd, buf.data(), nread); rv != 0) { 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.tokenlen) { std::cerr << "Perform stateless address validation" << std::endl; if (hd.tokenlen == 0) { send_retry(&hd, ep, *local_addr, &su.sa, msg.msg_namelen, nread * 3); continue; } if (hd.token[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[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 = nullptr; hd.tokenlen = 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 = nullptr; hd.tokenlen = 0; break; } } auto h = std::make_unique(loop_, this); if (h->init(ep, *local_addr, &su.sa, msg.msg_namelen, &hd.scid, &hd.dcid, pocid, hd.token, hd.tokenlen, 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 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) { associate_cid(&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 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()(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(&local_addr.su.sa), local_addr.len, }; ngtcp2_addr raddr{ const_cast(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 host; std::array 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 token; auto t = std::chrono::duration_cast( 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(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(&local_addr.su.sa), local_addr.len, }; ngtcp2_addr raddr{ const_cast(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(&local_addr.su.sa), local_addr.len, }; ngtcp2_addr raddr{ const_cast(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 host; std::array 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, hd->tokenlen); } auto t = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()) .count(); if (ngtcp2_crypto_verify_retry_token( ocid, hd->token, hd->tokenlen, 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 host; std::array 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, hd->tokenlen); } auto t = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()) .count(); if (ngtcp2_crypto_verify_regular_token(hd->token, hd->tokenlen, 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 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(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(data); msg_iov.iov_len = datalen; msghdr msg{}; msg.msg_name = const_cast(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(CMSG_DATA(cm)); memset(pktinfo, 0, sizeof(in_pktinfo)); auto addrin = reinterpret_cast(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(CMSG_DATA(cm)); memset(pktinfo, 0, sizeof(in6_pktinfo)); auto addrin = reinterpret_cast(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(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(); dissociate_cid(ngtcp2_conn_get_client_initial_dcid(conn)); std::vector cids(ngtcp2_conn_get_num_scid(conn)); ngtcp2_conn_get_scid(conn, cids.data()); for (auto &cid : cids) { dissociate_cid(&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 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] " "" << 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 = UINT64_MAX; config.ack_thresh = 2; } } // namespace namespace { void print_help() { print_usage(); config_set_default(config); std::cout << R"( Address to listen to. '*' binds to any address. Port Path to private key file Path to certificate file Options: -t, --tx-loss=

The probability of losing outgoing packets.

must be [0.0, 1.0], inclusive. 0.0 means no packet loss. 1.0 means 100% packet loss. -r, --rx-loss=

The probability of losing incoming packets.

must be [0.0, 1.0], inclusive. 0.0 means no packet loss. 1.0 means 100% packet loss. --ciphers= Specify the cipher suite list to enable. Default: )" << config.ciphers << R"( --groups= Specify the supported groups. Default: )" << config.groups << R"( -d, --htdocs= 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= Specify idle timeout. Default: )" << util::format_duration(config.timeout) << R"( -V, --validate-addr Perform address validation. --preferred-ipv4-addr=: Specify preferred IPv4 address and port. --preferred-ipv6-addr=: Specify preferred IPv6 address and port. A numeric IPv6 address must be enclosed by '[' and ']' (e.g., [::1]:8443) --mime-types-file= 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 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= The initial connection-level flow control window. Default: )" << util::format_uint_iec(config.max_data) << R"( --max-stream-data-bidi-local= 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= 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= 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= The number of the concurrent bidirectional streams. Default: )" << config.max_streams_bidi << R"( --max-streams-uni= The number of the concurrent unidirectional streams. Default: )" << config.max_streams_uni << R"( --max-dyn-length= 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= Set an initial RTT. Default: )" << util::format_duration(config.initial_rtt) << R"( --max-udp-payload-size= Override maximum UDP payload size that server transmits. --max-window= 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= 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= Maximum number of UDP datagrams that are sent in a single GSO sendmsg call. Default: )" << config.max_gso_dgrams << R"( --handshake-timeout= Set the QUIC handshake timeout. It defaults to no timeout. --preferred-versions=[[,]...] 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 "v2" indicates QUIC v2. --available-versions=[[,]...] Specify QUIC versions in hex string that are sent in available_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 "v2" indicates QUIC v2. --no-pmtud Disables Path MTU Discovery. --ack-thresh= 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 argument is an integer and an optional unit (e.g., 10K is 10 * 1024). Units are K, M and G (powers of 1024). The 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 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}, {"available-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 == "v2"sv) { *it++ = NGTCP2_PROTO_VER_V2; 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: { // --available-versions auto l = util::split_str(optarg); config.available_versions.resize(l.size()); auto it = std::begin(config.available_versions); for (const auto &k : l) { if (k == "v1"sv) { *it++ = NGTCP2_PROTO_VER_V1; continue; } if (k == "v2"sv) { *it++ = NGTCP2_PROTO_VER_V2; continue; } auto rv = util::parse_version(k); if (!rv) { std::cerr << "available-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; }