Merge pull request #4 from tatsuhiro-t/master

Merging tatsuhiro-t:master -> nshoemaker:master
This commit is contained in:
Nora Shoemaker 2015-07-24 13:38:04 -07:00
commit e2808dcd96
48 changed files with 1005 additions and 420 deletions

View File

@ -25,7 +25,7 @@ dnl Do not change user variables!
dnl http://www.gnu.org/software/automake/manual/html_node/Flag-Variables-Ordering.html
AC_PREREQ(2.61)
AC_INIT([nghttp2], [1.1.2-DEV], [t-tujikawa@users.sourceforge.net])
AC_INIT([nghttp2], [1.1.3-DEV], [t-tujikawa@users.sourceforge.net])
AC_USE_SYSTEM_EXTENSIONS
LT_PREREQ([2.2.6])

View File

@ -223,7 +223,7 @@ $(APIDOC): apidoc.stamp
fi
clean-local:
-rm $(APIDOCS)
-rm -f $(APIDOCS)
-rm -rf $(BUILDDIR)/*
html-local: apiref.rst

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "H2LOAD" "1" "July 15, 2015" "1.1.1" "nghttp2"
.TH "H2LOAD" "1" "July 18, 2015" "1.1.2" "nghttp2"
.SH NAME
h2load \- HTTP/2 benchmarking tool
.

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "NGHTTP" "1" "July 15, 2015" "1.1.1" "nghttp2"
.TH "NGHTTP" "1" "July 18, 2015" "1.1.2" "nghttp2"
.SH NAME
nghttp \- HTTP/2 experimental client
.

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "NGHTTPD" "1" "July 15, 2015" "1.1.1" "nghttp2"
.TH "NGHTTPD" "1" "July 18, 2015" "1.1.2" "nghttp2"
.SH NAME
nghttpd \- HTTP/2 experimental server
.

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "NGHTTPX" "1" "July 15, 2015" "1.1.1" "nghttp2"
.TH "NGHTTPX" "1" "July 18, 2015" "1.1.2" "nghttp2"
.SH NAME
nghttpx \- HTTP/2 experimental proxy
.

View File

@ -91,6 +91,8 @@ OPTIONS = [
"header-field-buffer",
"max-header-fields",
"include",
"tls-ticket-cipher",
"host-rewrite",
"conf",
]

View File

@ -3278,10 +3278,6 @@ NGHTTP2_EXTERN int nghttp2_submit_rst_stream(nghttp2_session *session,
* :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
* The |iv| contains invalid value (e.g., initial window size
* strictly greater than (1 << 31) - 1.
* :enum:`NGHTTP2_ERR_TOO_MANY_INFLIGHT_SETTINGS`
* There is already another in-flight SETTINGS. Note that the
* current implementation only allows 1 in-flight SETTINGS frame
* without ACK flag set.
* :enum:`NGHTTP2_ERR_NOMEM`
* Out of memory.
*/
@ -3777,11 +3773,22 @@ NGHTTP2_EXTERN void nghttp2_hd_inflate_del(nghttp2_hd_inflater *inflater);
* The |settings_hd_table_bufsize_max| should be the value transmitted
* in SETTINGS_HEADER_TABLE_SIZE.
*
* This function must not be called while header block is being
* inflated. In other words, this function must be called after
* initialization of |inflater|, but before calling
* `nghttp2_hd_inflate_hd()`, or after
* `nghttp2_hd_inflate_end_headers()`. Otherwise,
* `NGHTTP2_ERR_INVALID_STATE` was returned.
*
* This function returns 0 if it succeeds, or one of the following
* negative error codes:
*
* :enum:`NGHTTP2_ERR_NOMEM`
* Out of memory.
* :enum:`NGHTTP2_ERR_INVALID_STATE`
* The function is called while header block is being inflated.
* Probably, application missed to call
* `nghttp2_hd_inflate_end_headers()`.
*/
NGHTTP2_EXTERN int
nghttp2_hd_inflate_change_table_size(nghttp2_hd_inflater *inflater,

View File

@ -704,7 +704,7 @@ int nghttp2_hd_inflate_init(nghttp2_hd_inflater *inflater, nghttp2_mem *mem) {
inflater->nv_keep = NULL;
inflater->opcode = NGHTTP2_HD_OPCODE_NONE;
inflater->state = NGHTTP2_HD_STATE_OPCODE;
inflater->state = NGHTTP2_HD_STATE_INFLATE_START;
rv = nghttp2_bufs_init3(&inflater->nvbufs, NGHTTP2_HD_MAX_NV / 8, 8, 1, 0,
mem);
@ -1261,6 +1261,15 @@ int nghttp2_hd_deflate_change_table_size(nghttp2_hd_deflater *deflater,
int nghttp2_hd_inflate_change_table_size(nghttp2_hd_inflater *inflater,
size_t settings_hd_table_bufsize_max) {
switch (inflater->state) {
case NGHTTP2_HD_STATE_EXPECT_TABLE_SIZE:
case NGHTTP2_HD_STATE_INFLATE_START:
break;
default:
return NGHTTP2_ERR_INVALID_STATE;
}
inflater->state = NGHTTP2_HD_STATE_EXPECT_TABLE_SIZE;
inflater->settings_hd_table_bufsize_max = settings_hd_table_bufsize_max;
inflater->ctx.hd_table_bufsize_max = settings_hd_table_bufsize_max;
hd_context_shrink_table_size(&inflater->ctx);
@ -1951,9 +1960,25 @@ ssize_t nghttp2_hd_inflate_hd2(nghttp2_hd_inflater *inflater,
for (; in != last || busy;) {
busy = 0;
switch (inflater->state) {
case NGHTTP2_HD_STATE_EXPECT_TABLE_SIZE:
if ((*in & 0xe0u) != 0x20u) {
DEBUGF(fprintf(stderr, "inflatehd: header table size change was "
"expected, but saw 0x%02x as first byte",
*in));
rv = NGHTTP2_ERR_HEADER_COMP;
goto fail;
}
/* fall through */
case NGHTTP2_HD_STATE_INFLATE_START:
case NGHTTP2_HD_STATE_OPCODE:
if ((*in & 0xe0u) == 0x20u) {
DEBUGF(fprintf(stderr, "inflatehd: header table size change\n"));
if (inflater->state == NGHTTP2_HD_STATE_OPCODE) {
DEBUGF(fprintf(stderr, "inflatehd: header table size change must "
"appear at the head of header block\n"));
rv = NGHTTP2_ERR_HEADER_COMP;
goto fail;
}
inflater->opcode = NGHTTP2_HD_OPCODE_INDEXED;
inflater->state = NGHTTP2_HD_STATE_READ_TABLE_SIZE;
} else if (*in & 0x80u) {
@ -1997,7 +2022,7 @@ ssize_t nghttp2_hd_inflate_hd2(nghttp2_hd_inflater *inflater,
DEBUGF(fprintf(stderr, "inflatehd: table_size=%zu\n", inflater->left));
inflater->ctx.hd_table_bufsize_max = inflater->left;
hd_context_shrink_table_size(&inflater->ctx);
inflater->state = NGHTTP2_HD_STATE_OPCODE;
inflater->state = NGHTTP2_HD_STATE_INFLATE_START;
break;
case NGHTTP2_HD_STATE_READ_INDEX: {
size_t prefixlen;
@ -2282,6 +2307,7 @@ fail:
int nghttp2_hd_inflate_end_headers(nghttp2_hd_inflater *inflater) {
hd_inflate_keep_free(inflater);
inflater->state = NGHTTP2_HD_STATE_INFLATE_START;
return 0;
}

View File

@ -151,6 +151,8 @@ typedef enum {
} nghttp2_hd_opcode;
typedef enum {
NGHTTP2_HD_STATE_EXPECT_TABLE_SIZE,
NGHTTP2_HD_STATE_INFLATE_START,
NGHTTP2_HD_STATE_OPCODE,
NGHTTP2_HD_STATE_READ_TABLE_SIZE,
NGHTTP2_HD_STATE_READ_INDEX,

View File

@ -362,8 +362,6 @@ static int session_new(nghttp2_session **session_ptr,
(*session_ptr)->local_last_stream_id = (1u << 31) - 1;
(*session_ptr)->remote_last_stream_id = (1u << 31) - 1;
(*session_ptr)->inflight_niv = -1;
(*session_ptr)->pending_local_max_concurrent_stream =
NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS;
(*session_ptr)->pending_enable_push = 1;
@ -561,8 +559,42 @@ static void ob_q_free(nghttp2_outbound_queue *q, nghttp2_mem *mem) {
}
}
static int inflight_settings_new(nghttp2_inflight_settings **settings_ptr,
const nghttp2_settings_entry *iv, size_t niv,
nghttp2_mem *mem) {
*settings_ptr = nghttp2_mem_malloc(mem, sizeof(nghttp2_inflight_settings));
if (!*settings_ptr) {
return NGHTTP2_ERR_NOMEM;
}
if (niv > 0) {
(*settings_ptr)->iv = nghttp2_frame_iv_copy(iv, niv, mem);
if (!(*settings_ptr)->iv) {
return NGHTTP2_ERR_NOMEM;
}
} else {
(*settings_ptr)->iv = NULL;
}
(*settings_ptr)->niv = niv;
(*settings_ptr)->next = NULL;
return 0;
}
static void inflight_settings_del(nghttp2_inflight_settings *settings,
nghttp2_mem *mem) {
if (!settings) {
return;
}
nghttp2_mem_free(mem, settings->iv);
nghttp2_mem_free(mem, settings);
}
void nghttp2_session_del(nghttp2_session *session) {
nghttp2_mem *mem;
nghttp2_inflight_settings *settings;
if (session == NULL) {
return;
@ -570,7 +602,11 @@ void nghttp2_session_del(nghttp2_session *session) {
mem = &session->mem;
nghttp2_mem_free(mem, session->inflight_iv);
for (settings = session->inflight_settings_head; settings;) {
nghttp2_inflight_settings *next = settings->next;
inflight_settings_del(settings, mem);
settings = next;
}
nghttp2_stream_roots_free(&session->roots);
@ -3939,10 +3975,6 @@ int nghttp2_session_update_local_settings(nghttp2_session *session,
}
}
session->pending_local_max_concurrent_stream =
NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS;
session->pending_enable_push = 1;
return 0;
}
@ -3951,6 +3983,7 @@ int nghttp2_session_on_settings_received(nghttp2_session *session,
int rv;
size_t i;
nghttp2_mem *mem;
nghttp2_inflight_settings *settings;
mem = &session->mem;
@ -3964,15 +3997,21 @@ int nghttp2_session_on_settings_received(nghttp2_session *session,
session, frame, NGHTTP2_ERR_FRAME_SIZE_ERROR,
"SETTINGS: ACK and payload != 0");
}
if (session->inflight_niv == -1) {
settings = session->inflight_settings_head;
if (!settings) {
return session_handle_invalid_connection(
session, frame, NGHTTP2_ERR_PROTO, "SETTINGS: unexpected ACK");
}
rv = nghttp2_session_update_local_settings(session, session->inflight_iv,
session->inflight_niv);
nghttp2_mem_free(mem, session->inflight_iv);
session->inflight_iv = NULL;
session->inflight_niv = -1;
rv = nghttp2_session_update_local_settings(session, settings->iv,
settings->niv);
session->inflight_settings_head = settings->next;
inflight_settings_del(settings, mem);
if (rv != 0) {
if (nghttp2_is_fatal(rv)) {
return rv;
@ -4633,7 +4672,7 @@ static int session_on_data_received_fail_fast(nghttp2_session *session) {
if (!stream) {
if (session_detect_idle_stream(session, stream_id)) {
failure_reason = "DATA: stream in idle";
error_code = NGHTTP2_STREAM_CLOSED;
error_code = NGHTTP2_PROTOCOL_ERROR;
goto fail;
}
return NGHTTP2_ERR_IGN_PAYLOAD;
@ -6091,6 +6130,22 @@ int nghttp2_session_add_window_update(nghttp2_session *session, uint8_t flags,
return 0;
}
static void
session_append_inflight_settings(nghttp2_session *session,
nghttp2_inflight_settings *settings) {
nghttp2_inflight_settings *i;
if (!session->inflight_settings_head) {
session->inflight_settings_head = settings;
return;
}
for (i = session->inflight_settings_head; i->next; i = i->next)
;
i->next = settings;
}
int nghttp2_session_add_settings(nghttp2_session *session, uint8_t flags,
const nghttp2_settings_entry *iv, size_t niv) {
nghttp2_outbound_item *item;
@ -6099,16 +6154,13 @@ int nghttp2_session_add_settings(nghttp2_session *session, uint8_t flags,
size_t i;
int rv;
nghttp2_mem *mem;
nghttp2_inflight_settings *inflight_settings = NULL;
mem = &session->mem;
if (flags & NGHTTP2_FLAG_ACK) {
if (niv != 0) {
if ((flags & NGHTTP2_FLAG_ACK) && niv != 0) {
return NGHTTP2_ERR_INVALID_ARGUMENT;
}
} else if (session->inflight_niv != -1) {
return NGHTTP2_ERR_TOO_MANY_INFLIGHT_SETTINGS;
}
if (!nghttp2_iv_check(iv, niv)) {
return NGHTTP2_ERR_INVALID_ARGUMENT;
@ -6130,19 +6182,13 @@ int nghttp2_session_add_settings(nghttp2_session *session, uint8_t flags,
}
if ((flags & NGHTTP2_FLAG_ACK) == 0) {
if (niv > 0) {
session->inflight_iv = nghttp2_frame_iv_copy(iv, niv, mem);
if (session->inflight_iv == NULL) {
rv = inflight_settings_new(&inflight_settings, iv, niv, mem);
if (rv != 0) {
assert(nghttp2_is_fatal(rv));
nghttp2_mem_free(mem, iv_copy);
nghttp2_mem_free(mem, item);
return NGHTTP2_ERR_NOMEM;
return rv;
}
} else {
session->inflight_iv = NULL;
}
session->inflight_niv = niv;
}
nghttp2_outbound_item_init(item);
@ -6155,11 +6201,7 @@ int nghttp2_session_add_settings(nghttp2_session *session, uint8_t flags,
/* The only expected error is fatal one */
assert(nghttp2_is_fatal(rv));
if ((flags & NGHTTP2_FLAG_ACK) == 0) {
nghttp2_mem_free(mem, session->inflight_iv);
session->inflight_iv = NULL;
session->inflight_niv = -1;
}
inflight_settings_del(inflight_settings, mem);
nghttp2_frame_settings_free(&frame->settings, mem);
nghttp2_mem_free(mem, item);
@ -6167,6 +6209,8 @@ int nghttp2_session_add_settings(nghttp2_session *session, uint8_t flags,
return rv;
}
session_append_inflight_settings(session, inflight_settings);
/* Extract NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS and ENABLE_PUSH
here. We use it to refuse the incoming stream and PUSH_PROMISE
with RST_STREAM. */

View File

@ -140,6 +140,17 @@ typedef enum {
NGHTTP2_GOAWAY_RECV = 0x8
} nghttp2_goaway_flag;
/* nghttp2_inflight_settings stores the SETTINGS entries which local
endpoint has sent to the remote endpoint, and has not received ACK
yet. */
struct nghttp2_inflight_settings {
struct nghttp2_inflight_settings *next;
nghttp2_settings_entry *iv;
size_t niv;
};
typedef struct nghttp2_inflight_settings nghttp2_inflight_settings;
struct nghttp2_session {
nghttp2_map /* <nghttp2_stream*> */ streams;
nghttp2_stream_roots roots;
@ -176,12 +187,9 @@ struct nghttp2_session {
/* Points to the oldest idle stream. NULL if there is no idle
stream. Only used when session is initialized as erver. */
nghttp2_stream *idle_stream_tail;
/* In-flight SETTINGS values. NULL does not necessarily mean there
is no in-flight SETTINGS. */
nghttp2_settings_entry *inflight_iv;
/* The number of entries in |inflight_iv|. -1 if there is no
in-flight SETTINGS. */
ssize_t inflight_niv;
/* Queue of In-flight SETTINGS values. SETTINGS bearing ACK is not
considered as in-flight. */
nghttp2_inflight_settings *inflight_settings_head;
/* The number of outgoing streams. This will be capped by
remote_settings.max_concurrent_streams. */
size_t num_outgoing_streams;

View File

@ -37,7 +37,7 @@ install-exec-local:
uninstall-local:
rm -f $(DESTDIR)$(libdir)/python*/site-packages/nghttp2.so
rm -f $(DESTDIR)$(libdir)/python*/site-packages/python_nghttp2-*.egg-info
rm -f $(DESTDIR)$(libdir)/python*/site-packages/python_nghttp2-*.egg
clean-local:
$(PYTHON) setup.py clean --all

View File

@ -571,6 +571,12 @@ int Http2Handler::tls_handshake() {
return -1;
}
if (sessions_->get_config()->verbose) {
if (SSL_session_reused(ssl_)) {
std::cerr << "SSL/TLS session reused" << std::endl;
}
}
return 0;
}
@ -1102,7 +1108,7 @@ void prepare_response(Stream *stream, Http2Handler *hd,
if (last_mod_found && static_cast<time_t>(buf.st_mtime) <= last_mod) {
close(file);
prepare_status_response(stream, hd, 304);
hd->submit_response("304", stream->stream_id, nullptr);
return;
}
@ -1111,7 +1117,7 @@ void prepare_response(Stream *stream, Http2Handler *hd,
path, FileEntry(path, buf.st_size, buf.st_mtime, file));
} else if (last_mod_found && file_ent->mtime <= last_mod) {
sessions->release_fd(file_ent->path);
prepare_status_response(stream, hd, 304);
hd->submit_response("304", stream->stream_id, nullptr);
return;
}
@ -1630,7 +1636,6 @@ FileEntry make_status_body(int status, uint16_t port) {
enum {
IDX_200,
IDX_301,
IDX_304,
IDX_400,
IDX_404,
};
@ -1639,7 +1644,6 @@ HttpServer::HttpServer(const Config *config) : config_(config) {
status_pages_ = std::vector<StatusPage>{
{"200", make_status_body(200, config_->port)},
{"301", make_status_body(301, config_->port)},
{"304", make_status_body(304, config_->port)},
{"400", make_status_body(400, config_->port)},
{"404", make_status_body(404, config_->port)},
};
@ -1666,7 +1670,6 @@ int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) {
namespace {
int start_listen(HttpServer *sv, struct ev_loop *loop, Sessions *sessions,
const Config *config) {
addrinfo hints;
int r;
bool ok = false;
const char *addr = nullptr;
@ -1674,7 +1677,7 @@ int start_listen(HttpServer *sv, struct ev_loop *loop, Sessions *sessions,
auto acceptor = std::make_shared<AcceptHandler>(sv, sessions, config);
auto service = util::utos(config->port);
memset(&hints, 0, sizeof(addrinfo));
addrinfo hints{};
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
@ -1885,8 +1888,6 @@ const StatusPage *HttpServer::get_status_page(int status) const {
return &status_pages_[IDX_200];
case 301:
return &status_pages_[IDX_301];
case 304:
return &status_pages_[IDX_304];
case 400:
return &status_pages_[IDX_400];
case 404:

View File

@ -176,6 +176,7 @@ lib_LTLIBRARIES = libnghttp2_asio.la
libnghttp2_asio_la_SOURCES = \
util.cc util.h http2.cc http2.h \
ssl.cc ssl.h \
timegm.c timegm.h \
asio_common.cc asio_common.h \
asio_io_service_pool.cc asio_io_service_pool.h \
asio_server_http2.cc \

View File

@ -74,8 +74,8 @@ namespace h2load {
Config::Config()
: data_length(-1), addrs(nullptr), nreqs(1), nclients(1), nthreads(1),
max_concurrent_streams(-1), window_bits(30), connection_window_bits(30),
no_tls_proto(PROTO_HTTP2), data_fd(-1), port(0), default_port(0),
verbose(false) {}
rate(0), nconns(0), no_tls_proto(PROTO_HTTP2), data_fd(-1), port(0),
default_port(0), verbose(false), current_worker(0) {}
Config::~Config() {
freeaddrinfo(addrs);
@ -85,6 +85,8 @@ Config::~Config() {
}
}
bool Config::is_rate_mode() { return (this->rate != 0); }
Config config;
namespace {
@ -261,7 +263,7 @@ void Client::process_abandoned_streams() {
}
void Client::report_progress() {
if (worker->id == 0 &&
if (!worker->config->is_rate_mode() && worker->id == 0 &&
worker->stats.req_done % worker->progress_interval == 0) {
std::cout << "progress: "
<< worker->stats.req_done * 100 / worker->stats.req_todo
@ -839,9 +841,8 @@ process_time_stats(const std::vector<std::unique_ptr<Worker>> &workers) {
namespace {
void resolve_host() {
int rv;
addrinfo hints, *res;
addrinfo hints{}, *res;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = 0;
@ -898,29 +899,30 @@ int client_select_next_proto_cb(SSL *ssl, unsigned char **out,
} // namespace
namespace {
template <typename Iterator>
std::vector<std::string> parse_uris(Iterator first, Iterator last) {
// Use std::vector<std::string>::iterator explicitly, without that,
// http_parser_url u{} fails with clang-3.4.
std::vector<std::string> parse_uris(std::vector<std::string>::iterator first,
std::vector<std::string>::iterator last) {
std::vector<std::string> reqlines;
// First URI is treated specially. We use scheme, host and port of
// this URI and ignore those in the remaining URIs if present.
http_parser_url u;
memset(&u, 0, sizeof(u));
if (first == last) {
std::cerr << "no URI available" << std::endl;
exit(EXIT_FAILURE);
}
auto uri = (*first).c_str();
++first;
if (http_parser_parse_url(uri, strlen(uri), 0, &u) != 0 ||
// First URI is treated specially. We use scheme, host and port of
// this URI and ignore those in the remaining URIs if present.
http_parser_url u{};
if (http_parser_parse_url(uri, (*first).size(), 0, &u) != 0 ||
!util::has_uri_field(u, UF_SCHEMA) || !util::has_uri_field(u, UF_HOST)) {
std::cerr << "invalid URI: " << uri << std::endl;
exit(EXIT_FAILURE);
}
++first;
config.scheme = util::get_uri_field(uri, u, UF_SCHEMA);
config.host = util::get_uri_field(uri, u, UF_HOST);
config.default_port = util::get_default_port(uri, u);
@ -933,12 +935,11 @@ std::vector<std::string> parse_uris(Iterator first, Iterator last) {
reqlines.push_back(get_reqline(uri, u));
for (; first != last; ++first) {
http_parser_url u;
memset(&u, 0, sizeof(u));
http_parser_url u{};
auto uri = (*first).c_str();
if (http_parser_parse_url(uri, strlen(uri), 0, &u) != 0) {
if (http_parser_parse_url(uri, (*first).size(), 0, &u) != 0) {
std::cerr << "invalid URI: " << uri << std::endl;
exit(EXIT_FAILURE);
}
@ -962,6 +963,31 @@ std::vector<std::string> read_uri_from_file(std::istream &infile) {
}
} // namespace
namespace {
// Called every second when rate mode is being used
void second_timeout_cb(EV_P_ ev_timer *w, int revents) {
auto config = static_cast<Config *>(w->data);
auto nclients_per_worker = config->rate;
auto nreqs_per_worker = config->max_concurrent_streams * config->rate;
if (config->current_worker >= std::max((ssize_t)0, (config->seconds - 1))) {
nclients_per_worker = config->rate + config->conns_remainder;
nreqs_per_worker = (int)config->max_concurrent_streams *
(config->rate + config->conns_remainder);
ev_timer_stop(config->rate_loop, w);
}
config->workers.push_back(
make_unique<Worker>(config->current_worker, config->ssl_ctx,
nreqs_per_worker, nclients_per_worker, config));
config->current_worker++;
config->workers.back()->run();
}
} // namespace
namespace {
void print_version(std::ostream &out) {
out << "h2load nghttp2/" NGHTTP2_VERSION << std::endl;
@ -996,10 +1022,10 @@ Options:
-t, --threads=<N>
Number of native threads.
Default: )" << config.nthreads << R"(
-i, --input-file=<FILE>
-i, --input-file=<PATH>
Path of a file with multiple URIs are separated by EOLs.
This option will disable URIs getting from command-line.
If '-' is given as <FILE>, URIs will be read from stdin.
If '-' is given as <PATH>, URIs will be read from stdin.
URIs are used in this order for each client. All URIs
are used, then first URI is used and then 2nd URI, and
so on. The scheme, host and port in the subsequent
@ -1027,6 +1053,7 @@ Options:
-p, --no-tls-proto=<PROTOID>
Specify ALPN identifier of the protocol to be used when
accessing http URI without SSL/TLS.)";
#ifdef HAVE_SPDYLAY
out << R"(
Available protocols: spdy/2, spdy/3, spdy/3.1 and )";
@ -1036,9 +1063,27 @@ Options:
#endif // !HAVE_SPDYLAY
out << NGHTTP2_CLEARTEXT_PROTO_VERSION_ID << R"(
Default: )" << NGHTTP2_CLEARTEXT_PROTO_VERSION_ID << R"(
-d, --data=<FILE>
-d, --data=<PATH>
Post FILE to server. The request method is changed to
POST.
-r, --rate=<N>
Specified the fixed rate at which connections are
created. The rate must be a positive integer,
representing the number of connections to be made per
second. When the rate is 0, the program will run as it
normally does, creating connections at whatever variable
rate it wants. The default value for this option is 0.
-C, --num-conns=<N>
Specifies the total number of connections to create.
The total number of connections must be a positive
integer. On each connection, -m requests are made. The
test stops once as soon as the N connections have either
completed or failed. When the number of connections is
0, the program will run as it normally does, creating as
many connections as it needs in order to make the -n
requests specified. The default value for this option
is 0. The -n option is not required if the -C option is
being used.
-v, --verbose
Output debug information.
--version Display version information and exit.
@ -1073,9 +1118,11 @@ int main(int argc, char **argv) {
{"help", no_argument, nullptr, 'h'},
{"version", no_argument, &flag, 1},
{"ciphers", required_argument, &flag, 2},
{"rate", required_argument, nullptr, 'r'},
{"num-conns", required_argument, nullptr, 'C'},
{nullptr, 0, nullptr, 0}};
int option_index = 0;
auto c = getopt_long(argc, argv, "hvW:c:d:m:n:p:t:w:H:i:", long_options,
auto c = getopt_long(argc, argv, "hvW:c:d:m:n:p:t:w:H:i:r:C:", long_options,
&option_index);
if (c == -1) {
break;
@ -1170,6 +1217,22 @@ int main(int argc, char **argv) {
exit(EXIT_FAILURE);
}
break;
case 'r':
config.rate = strtoul(optarg, nullptr, 10);
if (config.rate <= 0) {
std::cerr << "-r: the rate at which connections are made "
<< "must be positive." << std::endl;
exit(EXIT_FAILURE);
}
break;
case 'C':
config.nconns = strtoul(optarg, nullptr, 10);
if (config.nconns <= 0) {
std::cerr << "-C: the total number of connections made "
<< "must be positive." << std::endl;
exit(EXIT_FAILURE);
}
break;
case 'v':
config.verbose = true;
break;
@ -1238,6 +1301,23 @@ int main(int argc, char **argv) {
<< "cores." << std::endl;
}
if (config.nconns < 0) {
std::cerr << "-C: the total number of connections made "
<< "cannot be negative." << std::endl;
exit(EXIT_FAILURE);
}
if (config.rate < 0) {
std::cerr << "-r: the rate at which connections are made "
<< "cannot be negative." << std::endl;
exit(EXIT_FAILURE);
}
if (config.rate != 0 && config.nthreads != 1) {
std::cerr << "-r, -t: warning: the -t option will be ignored when the -r "
<< "option is in use." << std::endl;
}
if (!datafile.empty()) {
config.data_fd = open(datafile.c_str(), O_RDONLY | O_BINARY);
if (config.data_fd == -1) {
@ -1252,8 +1332,7 @@ int main(int argc, char **argv) {
config.data_length = data_stat.st_size;
}
struct sigaction act;
memset(&act, 0, sizeof(struct sigaction));
struct sigaction act {};
act.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &act, nullptr);
@ -1326,6 +1405,41 @@ int main(int argc, char **argv) {
config.max_concurrent_streams = reqlines.size();
}
// if not in rate mode and -C is set, warn that we are ignoring it
if (!config.is_rate_mode() && config.nconns != 0) {
std::cerr << "-C: warning: This option can only be used with -r, and"
<< " will be ignored otherwise." << std::endl;
}
ssize_t n_time = 0;
ssize_t c_time = 0;
// only care about n_time and c_time in rate mode
if (config.is_rate_mode() && config.max_concurrent_streams != 0) {
n_time = (int)config.nreqs /
((int)config.rate * (int)config.max_concurrent_streams);
c_time = (int)config.nconns / (int)config.rate;
}
// check to see if the two ways of determining test time conflict
if (config.is_rate_mode() && (int)config.max_concurrent_streams != 0 &&
(n_time != c_time) && config.nreqs != 1 && config.nconns != 0) {
if ((int)config.nreqs < config.nconns) {
std::cerr << "-C, -n: warning: number of requests conflict. "
<< std::endl;
std::cerr << "The test will create "
<< (config.max_concurrent_streams * config.nconns)
<< " total requests." << std::endl;
} else {
std::cout << "-C, -n: warning: number of requests conflict. "
<< std::endl;
std::cout << "The smaller of the two will be chosen and the test will "
<< "create "
<< std::min(
config.nreqs,
(size_t)(config.max_concurrent_streams * config.nconns))
<< " total requests." << std::endl;
}
}
Headers shared_nva;
shared_nva.emplace_back(":scheme", config.scheme);
if (config.port != config.default_port) {
@ -1405,9 +1519,9 @@ int main(int argc, char **argv) {
auto start = std::chrono::steady_clock::now();
std::vector<std::unique_ptr<Worker>> workers;
workers.reserve(config.nthreads);
// if not in rate mode, continue making workers and clients normally
if (!config.is_rate_mode()) {
config.workers.reserve(config.nthreads);
#ifndef NOTHREADS
std::vector<std::future<void>> futures;
for (size_t i = 0; i < config.nthreads - 1; ++i) {
@ -1416,9 +1530,9 @@ int main(int argc, char **argv) {
std::cout << "spawning thread #" << i << ": " << nclients
<< " concurrent clients, " << nreqs << " total requests"
<< std::endl;
workers.push_back(
config.workers.push_back(
make_unique<Worker>(i, ssl_ctx, nreqs, nclients, &config));
auto &worker = workers.back();
auto &worker = config.workers.back();
futures.push_back(
std::async(std::launch::async, [&worker]() { worker->run(); }));
}
@ -1429,22 +1543,68 @@ int main(int argc, char **argv) {
std::cout << "spawning thread #" << (config.nthreads - 1) << ": "
<< nclients_last << " concurrent clients, " << nreqs_last
<< " total requests" << std::endl;
workers.push_back(make_unique<Worker>(config.nthreads - 1, ssl_ctx,
nreqs_last, nclients_last, &config));
workers.back()->run();
config.workers.push_back(make_unique<Worker>(
config.nthreads - 1, ssl_ctx, nreqs_last, nclients_last, &config));
config.workers.back()->run();
#ifndef NOTHREADS
for (auto &fut : futures) {
fut.get();
}
#endif // NOTHREADS
} //! config.is_rate_mode()
// if in rate mode, create a new worker each second
else {
// set various config values
if ((int)config.nreqs < config.nconns) {
config.seconds = c_time;
} else if (config.nconns == 0) {
config.seconds = n_time;
} else {
config.seconds = std::min(n_time, c_time);
}
config.workers.reserve(config.seconds);
config.conns_remainder = config.nconns % config.rate;
// config.seconds must be positive or else an exception is thrown
if (config.seconds <= 0) {
std::cerr << "Test cannot be run with current option values."
<< " Please look at documentation for -r option for"
<< " more information." << std::endl;
exit(EXIT_FAILURE);
}
config.current_worker = 0;
config.ssl_ctx = ssl_ctx;
// create timer that will go off every second
ev_timer timeout_watcher;
// create loop for running the timer
struct ev_loop *rate_loop = EV_DEFAULT;
config.rate_loop = rate_loop;
// giving the second_timeout_cb access to config
timeout_watcher.data = &config;
ev_init(&timeout_watcher, second_timeout_cb);
timeout_watcher.repeat = 1.;
ev_timer_again(rate_loop, &timeout_watcher);
// call callback so that we don't waste first 1 second.
second_timeout_cb(rate_loop, &timeout_watcher, 0);
ev_run(rate_loop, 0);
} // end rate mode section
auto end = std::chrono::steady_clock::now();
auto duration =
std::chrono::duration_cast<std::chrono::microseconds>(end - start);
Stats stats(0);
for (const auto &w : workers) {
for (const auto &w : config.workers) {
const auto &s = w->stats;
stats.req_todo += s.req_todo;
@ -1463,7 +1623,7 @@ int main(int argc, char **argv) {
}
}
auto ts = process_time_stats(workers);
auto ts = process_time_stats(config.workers);
// Requests which have not been issued due to connection errors, are
// counted towards req_failed and req_error.
@ -1476,7 +1636,7 @@ int main(int argc, char **argv) {
//
// [1] https://github.com/lighttpd/weighttp
// [2] https://github.com/wg/wrk
size_t rps = 0;
double rps = 0;
int64_t bps = 0;
if (duration.count() > 0) {
auto secd = static_cast<double>(duration.count()) / (1000 * 1000);

View File

@ -57,6 +57,7 @@ using namespace nghttp2;
namespace h2load {
class Session;
struct Worker;
struct Config {
std::vector<std::vector<nghttp2_nv>> nva;
@ -76,6 +77,10 @@ struct Config {
ssize_t max_concurrent_streams;
size_t window_bits;
size_t connection_window_bits;
// rate at which connections should be made
ssize_t rate;
// number of connections made
ssize_t nconns;
enum { PROTO_HTTP2, PROTO_SPDY2, PROTO_SPDY3, PROTO_SPDY3_1 } no_tls_proto;
// file descriptor for upload data
int data_fd;
@ -83,8 +88,17 @@ struct Config {
uint16_t default_port;
bool verbose;
ssize_t current_worker;
std::vector<std::unique_ptr<Worker>> workers;
SSL_CTX *ssl_ctx;
struct ev_loop *rate_loop;
ssize_t seconds;
ssize_t conns_remainder;
Config();
~Config();
bool is_rate_mode();
};
struct RequestStat {

View File

@ -191,8 +191,7 @@ void check_rewrite_location_uri(const std::string &want, const std::string &uri,
const std::string &match_host,
const std::string &req_authority,
const std::string &upstream_scheme) {
http_parser_url u;
memset(&u, 0, sizeof(u));
http_parser_url u{};
CU_ASSERT(0 == http_parser_parse_url(uri.c_str(), uri.size(), 0, &u));
auto got = http2::rewrite_location_uri(uri, u, match_host, req_authority,
upstream_scheme);

View File

@ -501,9 +501,8 @@ bool HttpClient::need_upgrade() const {
int HttpClient::resolve_host(const std::string &host, uint16_t port) {
int rv;
addrinfo hints;
this->host = host;
memset(&hints, 0, sizeof(hints));
addrinfo hints{};
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = 0;
@ -1260,8 +1259,7 @@ bool HttpClient::add_request(const std::string &uri,
const nghttp2_data_provider *data_prd,
int64_t data_length,
const nghttp2_priority_spec &pri_spec, int level) {
http_parser_url u;
memset(&u, 0, sizeof(u));
http_parser_url u{};
if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
return false;
}
@ -1485,8 +1483,7 @@ void update_html_parser(HttpClient *client, Request *req, const uint8_t *data,
auto uri = strip_fragment(p.first.c_str());
auto res_type = p.second;
http_parser_url u;
memset(&u, 0, sizeof(u));
http_parser_url u{};
if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) == 0 &&
util::fieldeq(uri.c_str(), u, req->uri.c_str(), req->u, UF_SCHEMA) &&
util::fieldeq(uri.c_str(), u, req->uri.c_str(), req->u, UF_HOST) &&
@ -1650,8 +1647,7 @@ int on_begin_headers_callback(nghttp2_session *session,
}
case NGHTTP2_PUSH_PROMISE: {
auto stream_id = frame->push_promise.promised_stream_id;
http_parser_url u;
memset(&u, 0, sizeof(u));
http_parser_url u{};
// TODO Set pri and level
nghttp2_priority_spec pri_spec;
@ -1820,8 +1816,7 @@ int on_frame_recv_callback2(nghttp2_session *session,
uri += "://";
uri += authority->value;
uri += path->value;
http_parser_url u;
memset(&u, 0, sizeof(u));
http_parser_url u{};
if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
frame->push_promise.promised_stream_id,
@ -2299,8 +2294,7 @@ int run(char **uris, int n) {
std::vector<std::tuple<std::string, nghttp2_data_provider *, int64_t>>
requests;
for (int i = 0; i < n; ++i) {
http_parser_url u;
memset(&u, 0, sizeof(u));
http_parser_url u{};
auto uri = strip_fragment(uris[i]);
if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
std::cerr << "[ERROR] Could not parse URI " << uri << std::endl;
@ -2400,7 +2394,7 @@ Options:
must be in PEM format.
--key=<KEY> Use the client private key file. The file must be in
PEM format.
-d, --data=<FILE>
-d, --data=<PATH>
Post FILE to server. If '-' is given, data will be read
from stdin.
-m, --multiply=<N>
@ -2424,8 +2418,8 @@ Options:
-b, --padding=<N>
Add at most <N> bytes to a frame payload as padding.
Specify 0 to disable padding.
-r, --har=<FILE>
Output HTTP transactions <FILE> in HAR format. If '-'
-r, --har=<PATH>
Output HTTP transactions <PATH> in HAR format. If '-'
is given, data is written to stdout.
--color Force colored log output.
--continuation
@ -2701,8 +2695,7 @@ int main(int argc, char **argv) {
nghttp2_option_set_peer_max_concurrent_streams(
config.http2_option, config.peer_max_concurrent_streams);
struct sigaction act;
memset(&act, 0, sizeof(struct sigaction));
struct sigaction act {};
act.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &act, nullptr);
reset_timer();

View File

@ -371,8 +371,7 @@ int main(int argc, char **argv) {
set_color_output(color || isatty(fileno(stdout)));
struct sigaction act;
memset(&act, 0, sizeof(struct sigaction));
struct sigaction act {};
act.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &act, nullptr);

View File

@ -122,6 +122,8 @@ int main(int argc, char *argv[]) {
shrpx::test_shrpx_config_parse_log_format) ||
!CU_add_test(pSuite, "config_read_tls_ticket_key_file",
shrpx::test_shrpx_config_read_tls_ticket_key_file) ||
!CU_add_test(pSuite, "config_read_tls_ticket_key_file_aes_256",
shrpx::test_shrpx_config_read_tls_ticket_key_file_aes_256) ||
!CU_add_test(pSuite, "config_match_downstream_addr_group",
shrpx::test_shrpx_config_match_downstream_addr_group) ||
!CU_add_test(pSuite, "util_streq", shrpx::test_util_streq) ||

View File

@ -119,12 +119,11 @@ const int GRACEFUL_SHUTDOWN_SIGNAL = SIGQUIT;
namespace {
int resolve_hostname(sockaddr_union *addr, size_t *addrlen,
const char *hostname, uint16_t port, int family) {
addrinfo hints;
int rv;
auto service = util::utos(port);
memset(&hints, 0, sizeof(addrinfo));
addrinfo hints{};
hints.ai_family = family;
hints.ai_socktype = SOCK_STREAM;
#ifdef AI_ADDRCONFIG
@ -279,12 +278,11 @@ std::unique_ptr<AcceptHandler> create_acceptor(ConnectionHandler *handler,
}
}
addrinfo hints;
int fd = -1;
int rv;
auto service = util::utos(get_config()->port);
memset(&hints, 0, sizeof(addrinfo));
addrinfo hints{};
hints.ai_family = family;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
@ -606,44 +604,88 @@ void graceful_shutdown_signal_cb(struct ev_loop *loop, ev_signal *w,
}
} // namespace
namespace {
int generate_ticket_key(TicketKey &ticket_key) {
ticket_key.cipher = get_config()->tls_ticket_cipher;
ticket_key.hmac = EVP_sha256();
ticket_key.hmac_keylen = EVP_MD_size(ticket_key.hmac);
assert(static_cast<size_t>(EVP_CIPHER_key_length(ticket_key.cipher)) <=
sizeof(ticket_key.data.enc_key));
assert(ticket_key.hmac_keylen <= sizeof(ticket_key.data.hmac_key));
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "enc_keylen=" << EVP_CIPHER_key_length(ticket_key.cipher)
<< ", hmac_keylen=" << ticket_key.hmac_keylen;
}
if (RAND_bytes(reinterpret_cast<unsigned char *>(&ticket_key.data),
sizeof(ticket_key.data)) == 0) {
return -1;
}
return 0;
}
} // namespace
namespace {
void renew_ticket_key_cb(struct ev_loop *loop, ev_timer *w, int revents) {
auto conn_handler = static_cast<ConnectionHandler *>(w->data);
const auto &old_ticket_keys = conn_handler->get_ticket_keys();
auto ticket_keys = std::make_shared<TicketKeys>();
LOG(NOTICE) << "Renew ticket keys: main";
LOG(NOTICE) << "Renew new ticket keys";
// We store at most 2 ticket keys
// If old_ticket_keys is not empty, it should contain at least 2
// keys: one for encryption, and last one for the next encryption
// key but decryption only. The keys in between are old keys and
// decryption only. The next key is provided to ensure to mitigate
// possible problem when one worker encrypt new key, but one worker,
// which did not take the that key yet, and cannot decrypt it.
//
// We keep keys for get_config()->tls_session_timeout seconds. The
// default is 12 hours. Thus the maximum ticket vector size is 12.
if (old_ticket_keys) {
auto &old_keys = old_ticket_keys->keys;
auto &new_keys = ticket_keys->keys;
assert(!old_keys.empty());
new_keys.resize(2);
new_keys[1] = old_keys[0];
auto max_tickets =
static_cast<size_t>(std::chrono::duration_cast<std::chrono::hours>(
get_config()->tls_session_timeout).count());
new_keys.resize(std::min(max_tickets, old_keys.size() + 1));
std::copy_n(std::begin(old_keys), new_keys.size() - 1,
std::begin(new_keys) + 1);
} else {
ticket_keys->keys.resize(1);
}
if (RAND_bytes(reinterpret_cast<unsigned char *>(&ticket_keys->keys[0]),
sizeof(ticket_keys->keys[0])) == 0) {
auto &new_key = ticket_keys->keys[0];
if (generate_ticket_key(new_key) != 0) {
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "failed to renew ticket key";
LOG(INFO) << "failed to generate ticket key";
}
conn_handler->set_ticket_keys(nullptr);
conn_handler->set_ticket_keys_to_worker(nullptr);
return;
}
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "ticket keys generation done";
for (auto &key : ticket_keys->keys) {
LOG(INFO) << "name: " << util::format_hex(key.name, sizeof(key.name));
assert(ticket_keys->keys.size() >= 1);
LOG(INFO) << 0 << " enc+dec: "
<< util::format_hex(ticket_keys->keys[0].data.name);
for (size_t i = 1; i < ticket_keys->keys.size(); ++i) {
auto &key = ticket_keys->keys[i];
LOG(INFO) << i << " dec: " << util::format_hex(key.data.name);
}
}
conn_handler->set_ticket_keys(ticket_keys);
conn_handler->worker_renew_ticket_keys(ticket_keys);
conn_handler->set_ticket_keys_to_worker(ticket_keys);
}
} // namespace
@ -709,8 +751,17 @@ int event_loop() {
if (!get_config()->upstream_no_tls) {
bool auto_tls_ticket_key = true;
if (!get_config()->tls_ticket_key_files.empty()) {
auto ticket_keys =
read_tls_ticket_key_file(get_config()->tls_ticket_key_files);
if (!get_config()->tls_ticket_cipher_given) {
LOG(WARN) << "It is strongly recommended to specify "
"--tls-ticket-cipher=aes-128-cbc (or "
"tls-ticket-cipher=aes-128-cbc in configuration file) "
"when --tls-ticket-key-file is used for the smooth "
"transition when the default value of --tls-ticket-cipher "
"becomes aes-256-cbc";
}
auto ticket_keys = read_tls_ticket_key_file(
get_config()->tls_ticket_key_files, get_config()->tls_ticket_cipher,
EVP_sha256());
if (!ticket_keys) {
LOG(WARN) << "Use internal session ticket key generator";
} else {
@ -719,8 +770,8 @@ int event_loop() {
}
}
if (auto_tls_ticket_key) {
// Renew ticket key every 12hrs
ev_timer_init(&renew_ticket_key_timer, renew_ticket_key_cb, 0., 12_h);
// Generate new ticket key every 1hr.
ev_timer_init(&renew_ticket_key_timer, renew_ticket_key_cb, 0., 1_h);
renew_ticket_key_timer.data = conn_handler.get();
ev_timer_again(loop, &renew_ticket_key_timer);
@ -827,7 +878,7 @@ int16_t DEFAULT_DOWNSTREAM_PORT = 80;
namespace {
void fill_default_config() {
memset(mod_config(), 0, sizeof(*mod_config()));
*mod_config() = {};
mod_config()->verbose = false;
mod_config()->daemon = false;
@ -948,7 +999,7 @@ void fill_default_config() {
mod_config()->tls_proto_mask = 0;
mod_config()->no_location_rewrite = false;
mod_config()->no_host_rewrite = false;
mod_config()->no_host_rewrite = true;
mod_config()->argc = 0;
mod_config()->argv = nullptr;
mod_config()->downstream_connections_per_host = 8;
@ -967,6 +1018,9 @@ void fill_default_config() {
mod_config()->header_field_buffer = 64_k;
mod_config()->max_header_fields = 100;
mod_config()->downstream_addr_group_catch_all = 0;
mod_config()->tls_ticket_cipher = EVP_aes_128_cbc();
mod_config()->tls_ticket_cipher_given = false;
mod_config()->tls_session_timeout = std::chrono::hours(12);
}
} // namespace
@ -1278,8 +1332,11 @@ SSL/TLS:
are treated as a part of protocol string.
Default: )" << DEFAULT_TLS_PROTO_LIST << R"(
--tls-ticket-key-file=<PATH>
Path to file that contains 48 bytes random data to
construct TLS session ticket parameters. This options
Path to file that contains random data to construct TLS
session ticket parameters. If aes-128-cbc is given in
--tls-ticket-cipher, the file must contain exactly 48
bytes. If aes-256-cbc is given in --tls-ticket-cipher,
the file must contain exactly 80 bytes. This options
can be used repeatedly to specify multiple ticket
parameters. If several files are given, only the first
key is used to encrypt TLS session tickets. Other keys
@ -1291,9 +1348,14 @@ SSL/TLS:
opening or reading given file fails, all loaded keys are
discarded and it is treated as if none of this option is
given. If this option is not given or an error occurred
while opening or reading a file, key is generated
automatically and renewed every 12hrs. At most 2 keys
are stored in memory.
while opening or reading a file, key is generated every
1 hour internally and they are valid for 12 hours. This
is recommended if ticket key sharing between nghttpx
instances is not required.
--tls-ticket-cipher=<TICKET_CIPHER>
Specify cipher to encrypt TLS session ticket. Specify
either aes-128-cbc or aes-256-cbc. By default,
aes-128-cbc is used.
--fetch-ocsp-response-file=<PATH>
Path to fetch-ocsp-response script file. It should be
absolute path.
@ -1441,8 +1503,8 @@ HTTP:
--client and default mode. For --http2-proxy and
--client-proxy mode, location header field will not be
altered regardless of this option.
--no-host-rewrite
Don't rewrite host and :authority header fields on
--host-rewrite
Rewrite host and :authority header fields on
--http2-bridge, --client and default mode. For
--http2-proxy and --client-proxy mode, these headers
will not be altered regardless of this option.
@ -1544,6 +1606,10 @@ int main(int argc, char **argv) {
create_config();
fill_default_config();
// First open log files with default configuration, so that we can
// log errors/warnings while reading configuration files.
reopen_log_files();
// We have to copy argv, since getopt_long may change its content.
mod_config()->argc = argc;
mod_config()->argv = new char *[argc];
@ -1660,6 +1726,8 @@ int main(int argc, char **argv) {
{SHRPX_OPT_MAX_HEADER_FIELDS, required_argument, &flag, 81},
{SHRPX_OPT_ADD_REQUEST_HEADER, required_argument, &flag, 82},
{SHRPX_OPT_INCLUDE, required_argument, &flag, 83},
{SHRPX_OPT_TLS_TICKET_CIPHER, required_argument, &flag, 84},
{SHRPX_OPT_HOST_REWRITE, no_argument, &flag, 85},
{nullptr, 0, nullptr, 0}};
int option_index = 0;
@ -2026,6 +2094,14 @@ int main(int argc, char **argv) {
// --include
cmdcfgs.emplace_back(SHRPX_OPT_INCLUDE, optarg);
break;
case 84:
// --tls-ticket-cipher
cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_CIPHER, optarg);
break;
case 85:
// --host-rewrite
cmdcfgs.emplace_back(SHRPX_OPT_HOST_REWRITE, "yes");
break;
default:
break;
}
@ -2050,8 +2126,7 @@ int main(int argc, char **argv) {
cmdcfgs.emplace_back(SHRPX_OPT_CERTIFICATE_FILE, argv[optind++]);
}
// First open default log files to deal with errors occurred while
// parsing option values.
// Reopen log files using configurations in file
reopen_log_files();
{
@ -2323,8 +2398,7 @@ int main(int argc, char **argv) {
reset_timer();
}
struct sigaction act;
memset(&act, 0, sizeof(struct sigaction));
struct sigaction act {};
act.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &act, nullptr);

View File

@ -144,36 +144,72 @@ bool is_secure(const char *filename) {
} // namespace
std::unique_ptr<TicketKeys>
read_tls_ticket_key_file(const std::vector<std::string> &files) {
read_tls_ticket_key_file(const std::vector<std::string> &files,
const EVP_CIPHER *cipher, const EVP_MD *hmac) {
auto ticket_keys = make_unique<TicketKeys>();
auto &keys = ticket_keys->keys;
keys.resize(files.size());
auto enc_keylen = EVP_CIPHER_key_length(cipher);
auto hmac_keylen = EVP_MD_size(hmac);
if (cipher == EVP_aes_128_cbc()) {
// backward compatibility, as a legacy of using same file format
// with nginx and apache.
hmac_keylen = 16;
}
auto expectedlen = sizeof(keys[0].data.name) + enc_keylen + hmac_keylen;
char buf[256];
assert(sizeof(buf) >= expectedlen);
size_t i = 0;
for (auto &file : files) {
struct stat fst {};
if (stat(file.c_str(), &fst) == -1) {
auto error = errno;
LOG(ERROR) << "tls-ticket-key-file: could not stat file " << file
<< ", errno=" << error;
return nullptr;
}
if (static_cast<size_t>(fst.st_size) != expectedlen) {
LOG(ERROR) << "tls-ticket-key-file: the expected file size is "
<< expectedlen << ", the actual file size is " << fst.st_size;
return nullptr;
}
std::ifstream f(file.c_str());
if (!f) {
LOG(ERROR) << "tls-ticket-key-file: could not open file " << file;
return nullptr;
}
char buf[48];
f.read(buf, sizeof(buf));
if (f.gcount() != sizeof(buf)) {
LOG(ERROR) << "tls-ticket-key-file: want to read 48 bytes but read "
<< f.gcount() << " bytes from " << file;
f.read(buf, expectedlen);
if (static_cast<size_t>(f.gcount()) != expectedlen) {
LOG(ERROR) << "tls-ticket-key-file: want to read " << expectedlen
<< " bytes but only read " << f.gcount() << " bytes from "
<< file;
return nullptr;
}
auto &key = keys[i++];
auto p = buf;
memcpy(key.name, p, sizeof(key.name));
p += sizeof(key.name);
memcpy(key.aes_key, p, sizeof(key.aes_key));
p += sizeof(key.aes_key);
memcpy(key.hmac_key, p, sizeof(key.hmac_key));
key.cipher = cipher;
key.hmac = hmac;
key.hmac_keylen = hmac_keylen;
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "session ticket key: " << util::format_hex(key.name,
sizeof(key.name));
LOG(INFO) << "enc_keylen=" << enc_keylen
<< ", hmac_keylen=" << key.hmac_keylen;
}
auto p = buf;
memcpy(key.data.name, p, sizeof(key.data.name));
p += sizeof(key.data.name);
memcpy(key.data.enc_key, p, enc_keylen);
p += enc_keylen;
memcpy(key.data.hmac_key, p, hmac_keylen);
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "session ticket key: " << util::format_hex(key.data.name);
}
}
return ticket_keys;
@ -225,35 +261,36 @@ std::string read_passwd_from_file(const char *filename) {
return line;
}
std::vector<char *> parse_config_str_list(const char *s, char delim) {
std::vector<Range<const char *>> split_config_str_list(const char *s,
char delim) {
size_t len = 1;
for (const char *first = s, *p = nullptr; (p = strchr(first, delim));
++len, first = p + 1)
auto last = s + strlen(s);
for (const char *first = s, *d = nullptr;
(d = std::find(first, last, delim)) != last; ++len, first = d + 1)
;
auto list = std::vector<char *>(len);
auto first = strdup(s);
auto list = std::vector<Range<const char *>>(len);
len = 0;
for (;;) {
auto p = strchr(first, delim);
if (p == nullptr) {
for (auto first = s;; ++len) {
auto stop = std::find(first, last, delim);
list[len] = {first, stop};
if (stop == last) {
break;
}
list[len++] = first;
*p = '\0';
first = p + 1;
first = stop + 1;
}
list[len++] = first;
return list;
}
void clear_config_str_list(std::vector<char *> &list) {
if (list.empty()) {
return;
std::vector<std::string> parse_config_str_list(const char *s, char delim) {
auto ranges = split_config_str_list(s, delim);
auto res = std::vector<std::string>();
res.reserve(ranges.size());
for (const auto &range : ranges) {
res.emplace_back(range.first, range.second);
}
free(list[0]);
list.clear();
return res;
}
std::pair<std::string, std::string> parse_header(const char *optarg) {
@ -554,22 +591,21 @@ namespace {
void parse_mapping(const DownstreamAddr &addr, const char *src) {
// This returns at least 1 element (it could be empty string). We
// will append '/' to all patterns, so it becomes catch-all pattern.
auto mapping = parse_config_str_list(src, ':');
auto mapping = split_config_str_list(src, ':');
assert(!mapping.empty());
for (auto raw_pattern : mapping) {
for (const auto &raw_pattern : mapping) {
auto done = false;
std::string pattern;
auto slash = strchr(raw_pattern, '/');
if (slash == nullptr) {
auto slash = std::find(raw_pattern.first, raw_pattern.second, '/');
if (slash == raw_pattern.second) {
// This effectively makes empty pattern to "/".
pattern = raw_pattern;
pattern.assign(raw_pattern.first, raw_pattern.second);
util::inp_strlower(pattern);
pattern += "/";
} else {
pattern.assign(raw_pattern, slash);
pattern.assign(raw_pattern.first, slash);
util::inp_strlower(pattern);
pattern +=
http2::normalize_path(slash, raw_pattern + strlen(raw_pattern));
pattern += http2::normalize_path(slash, raw_pattern.second);
}
for (auto &g : mod_config()->downstream_addr_groups) {
if (g.pattern == pattern) {
@ -585,7 +621,6 @@ void parse_mapping(const DownstreamAddr &addr, const char *src) {
g.addrs.push_back(addr);
mod_config()->downstream_addr_groups.push_back(std::move(g));
}
clear_config_str_list(mapping);
}
} // namespace
@ -639,6 +674,7 @@ enum {
SHRPX_OPTID_FRONTEND_READ_TIMEOUT,
SHRPX_OPTID_FRONTEND_WRITE_TIMEOUT,
SHRPX_OPTID_HEADER_FIELD_BUFFER,
SHRPX_OPTID_HOST_REWRITE,
SHRPX_OPTID_HTTP2_BRIDGE,
SHRPX_OPTID_HTTP2_MAX_CONCURRENT_STREAMS,
SHRPX_OPTID_HTTP2_NO_COOKIE_CRUMBLING,
@ -668,6 +704,7 @@ enum {
SHRPX_OPTID_SUBCERT,
SHRPX_OPTID_SYSLOG_FACILITY,
SHRPX_OPTID_TLS_PROTO_LIST,
SHRPX_OPTID_TLS_TICKET_CIPHER,
SHRPX_OPTID_TLS_TICKET_KEY_FILE,
SHRPX_OPTID_USER,
SHRPX_OPTID_VERIFY_CLIENT,
@ -845,6 +882,9 @@ int option_lookup_token(const char *name, size_t namelen) {
}
break;
case 'e':
if (util::strieq_l("host-rewrit", name, 11)) {
return SHRPX_OPTID_HOST_REWRITE;
}
if (util::strieq_l("http2-bridg", name, 11)) {
return SHRPX_OPTID_HTTP2_BRIDGE;
}
@ -959,6 +999,11 @@ int option_lookup_token(const char *name, size_t namelen) {
return SHRPX_OPTID_WORKER_WRITE_RATE;
}
break;
case 'r':
if (util::strieq_l("tls-ticket-ciphe", name, 16)) {
return SHRPX_OPTID_TLS_TICKET_CIPHER;
}
break;
case 's':
if (util::strieq_l("max-header-field", name, 16)) {
return SHRPX_OPTID_MAX_HEADER_FIELDS;
@ -1533,8 +1578,7 @@ int parse_config(const char *opt, const char *optarg,
return 0;
case SHRPX_OPTID_BACKEND_HTTP_PROXY_URI: {
// parse URI and get hostname, port and optionally userinfo.
http_parser_url u;
memset(&u, 0, sizeof(u));
http_parser_url u{};
int rv = http_parser_parse_url(optarg, strlen(optarg), 0, &u);
if (rv == 0) {
std::string val;
@ -1588,12 +1632,10 @@ int parse_config(const char *opt, const char *optarg,
LOG(WARN) << opt << ": not implemented yet";
return parse_uint_with_unit(&mod_config()->worker_write_burst, opt, optarg);
case SHRPX_OPTID_NPN_LIST:
clear_config_str_list(mod_config()->npn_list);
mod_config()->npn_list = parse_config_str_list(optarg);
return 0;
case SHRPX_OPTID_TLS_PROTO_LIST:
clear_config_str_list(mod_config()->tls_proto_list);
mod_config()->tls_proto_list = parse_config_str_list(optarg);
return 0;
@ -1648,7 +1690,7 @@ int parse_config(const char *opt, const char *optarg,
int port;
if (parse_uint(&port, opt, tokens[1]) != 0) {
if (parse_uint(&port, opt, tokens[1].c_str()) != 0) {
return -1;
}
@ -1660,18 +1702,16 @@ int parse_config(const char *opt, const char *optarg,
AltSvc altsvc;
altsvc.port = port;
altsvc.protocol_id = std::move(tokens[0]);
altsvc.protocol_id = tokens[0];
altsvc.protocol_id_len = strlen(altsvc.protocol_id);
altsvc.port = port;
altsvc.service = std::move(tokens[1]);
if (tokens.size() > 2) {
altsvc.host = tokens[2];
altsvc.host_len = strlen(altsvc.host);
altsvc.host = std::move(tokens[2]);
if (tokens.size() > 3) {
altsvc.origin = tokens[3];
altsvc.origin_len = strlen(altsvc.origin);
altsvc.origin = std::move(tokens[3]);
}
}
@ -1700,7 +1740,10 @@ int parse_config(const char *opt, const char *optarg,
return 0;
case SHRPX_OPTID_NO_HOST_REWRITE:
mod_config()->no_host_rewrite = util::strieq(optarg, "yes");
LOG(WARN) << SHRPX_OPT_NO_HOST_REWRITE
<< ": deprecated. :authority and host header fields are NOT "
"altered by default. To rewrite these headers, use "
"--host-rewrite option.";
return 0;
case SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_HOST: {
@ -1795,7 +1838,7 @@ int parse_config(const char *opt, const char *optarg,
return -1;
}
included_set.emplace(optarg);
included_set.insert(optarg);
auto rv = load_config(optarg, included_set);
included_set.erase(optarg);
@ -1805,6 +1848,23 @@ int parse_config(const char *opt, const char *optarg,
return 0;
}
case SHRPX_OPTID_TLS_TICKET_CIPHER:
if (util::strieq(optarg, "aes-128-cbc")) {
mod_config()->tls_ticket_cipher = EVP_aes_128_cbc();
} else if (util::strieq(optarg, "aes-256-cbc")) {
mod_config()->tls_ticket_cipher = EVP_aes_256_cbc();
} else {
LOG(ERROR) << opt
<< ": unsupported cipher for ticket encryption: " << optarg;
return -1;
}
mod_config()->tls_ticket_cipher_given = true;
return 0;
case SHRPX_OPTID_HOST_REWRITE:
mod_config()->no_host_rewrite = !util::strieq(optarg, "yes");
return 0;
case SHRPX_OPTID_CONF:
LOG(WARN) << "conf: ignored";

View File

@ -171,6 +171,8 @@ constexpr char SHRPX_OPT_NO_OCSP[] = "no-ocsp";
constexpr char SHRPX_OPT_HEADER_FIELD_BUFFER[] = "header-field-buffer";
constexpr char SHRPX_OPT_MAX_HEADER_FIELDS[] = "max-header-fields";
constexpr char SHRPX_OPT_INCLUDE[] = "include";
constexpr char SHRPX_OPT_TLS_TICKET_CIPHER[] = "tls-ticket-cipher";
constexpr char SHRPX_OPT_HOST_REWRITE[] = "host-rewrite";
union sockaddr_union {
sockaddr_storage storage;
@ -183,17 +185,9 @@ union sockaddr_union {
enum shrpx_proto { PROTO_HTTP2, PROTO_HTTP };
struct AltSvc {
AltSvc()
: protocol_id(nullptr), host(nullptr), origin(nullptr),
protocol_id_len(0), host_len(0), origin_len(0), port(0) {}
AltSvc() : port(0) {}
char *protocol_id;
char *host;
char *origin;
size_t protocol_id_len;
size_t host_len;
size_t origin_len;
std::string protocol_id, host, origin, service;
uint16_t port;
};
@ -224,9 +218,17 @@ struct DownstreamAddrGroup {
};
struct TicketKey {
const EVP_CIPHER *cipher;
const EVP_MD *hmac;
size_t hmac_keylen;
struct {
// name of this ticket configuration
uint8_t name[16];
uint8_t aes_key[16];
uint8_t hmac_key[16];
// encryption key for |cipher|
uint8_t enc_key[32];
// hmac key for |hmac|
uint8_t hmac_key[32];
} data;
};
struct TicketKeys {
@ -244,8 +246,14 @@ struct Config {
std::vector<LogFragment> accesslog_format;
std::vector<DownstreamAddrGroup> downstream_addr_groups;
std::vector<std::string> tls_ticket_key_files;
// list of supported NPN/ALPN protocol strings in the order of
// preference.
std::vector<std::string> npn_list;
// list of supported SSL/TLS protocol strings.
std::vector<std::string> tls_proto_list;
// binary form of http proxy host and port
sockaddr_union downstream_http_proxy_addr;
std::chrono::seconds tls_session_timeout;
ev_tstamp http2_upstream_read_timeout;
ev_tstamp upstream_read_timeout;
ev_tstamp upstream_write_timeout;
@ -263,7 +271,6 @@ struct Config {
std::unique_ptr<char[]> private_key_passwd;
std::unique_ptr<char[]> cert_file;
std::unique_ptr<char[]> dh_param_file;
const char *server_name;
std::unique_ptr<char[]> backend_tls_sni_name;
std::unique_ptr<char[]> pid_file;
std::unique_ptr<char[]> conf_path;
@ -279,13 +286,6 @@ struct Config {
// ev_token_bucket_cfg *rate_limit_cfg;
// // Rate limit configuration per worker (thread)
// ev_token_bucket_cfg *worker_rate_limit_cfg;
// list of supported NPN/ALPN protocol strings in the order of
// preference. The each element of this list is a NULL-terminated
// string.
std::vector<char *> npn_list;
// list of supported SSL/TLS protocol strings. The each element of
// this list is a NULL-terminated string.
std::vector<char *> tls_proto_list;
// Path to file containing CA certificate solely used for client
// certificate validation
std::unique_ptr<char[]> verify_client_cacert;
@ -294,12 +294,15 @@ struct Config {
std::unique_ptr<char[]> accesslog_file;
std::unique_ptr<char[]> errorlog_file;
std::unique_ptr<char[]> fetch_ocsp_response_file;
std::unique_ptr<char[]> user;
FILE *http2_upstream_dump_request_header;
FILE *http2_upstream_dump_response_header;
nghttp2_session_callbacks *http2_upstream_callbacks;
nghttp2_session_callbacks *http2_downstream_callbacks;
nghttp2_option *http2_option;
nghttp2_option *http2_client_option;
const EVP_CIPHER *tls_ticket_cipher;
const char *server_name;
char **argv;
char *cwd;
size_t num_worker;
@ -338,7 +341,6 @@ struct Config {
int syslog_facility;
int backlog;
int argc;
std::unique_ptr<char[]> user;
uid_t uid;
gid_t gid;
pid_t pid;
@ -376,6 +378,8 @@ struct Config {
// true if host contains UNIX domain socket path
bool host_unix;
bool no_ocsp;
// true if --tls-ticket-cipher is used
bool tls_ticket_cipher_given;
};
const Config *get_config();
@ -398,19 +402,19 @@ int load_config(const char *filename, std::set<std::string> &include_set);
// Read passwd from |filename|
std::string read_passwd_from_file(const char *filename);
// Parses delimited strings in |s| and returns the array of pointers,
// each element points to the each substring in |s|. The delimiter is
// given by |delim. The |s| must be comma delimited list of strings.
// The strings must be delimited by a single comma and any white
// spaces around it are treated as a part of protocol strings. This
// function copies |s| and first element in the return value points to
// it. It is caller's responsibility to deallocate its memory.
std::vector<char *> parse_config_str_list(const char *s, char delim = ',');
template <typename T> using Range = std::pair<T, T>;
// Clears all elements of |list|, which is returned by
// parse_config_str_list(). If list is not empty, list[0] is freed by
// free(2). After this call, list.empty() must be true.
void clear_config_str_list(std::vector<char *> &list);
// Parses delimited strings in |s| and returns the array of substring,
// delimited by |delim|. The any white spaces around substring are
// treated as a part of substring.
std::vector<std::string> parse_config_str_list(const char *s, char delim = ',');
// Parses delimited strings in |s| and returns the array of pointers,
// each element points to the beginning and one beyond last of
// substring in |s|. The delimiter is given by |delim|. The any
// white spaces around substring are treated as a part of substring.
std::vector<Range<const char *>> split_config_str_list(const char *s,
char delim);
// Parses header field in |optarg|. We expect header field is formed
// like "NAME: VALUE". We require that NAME is non empty string. ":"
@ -447,10 +451,12 @@ int int_syslog_facility(const char *strfacility);
FILE *open_file_for_write(const char *filename);
// Reads TLS ticket key file in |files| and returns TicketKey which
// stores read key data. This function returns TicketKey if it
// stores read key data. The given |cipher| and |hmac| determine the
// expected file size. This function returns TicketKey if it
// succeeds, or nullptr.
std::unique_ptr<TicketKeys>
read_tls_ticket_key_file(const std::vector<std::string> &files);
read_tls_ticket_key_file(const std::vector<std::string> &files,
const EVP_CIPHER *cipher, const EVP_MD *hmac);
// Selects group based on request's |hostport| and |path|. |hostport|
// is the value taken from :authority or host header field, and may

View File

@ -39,34 +39,29 @@ namespace shrpx {
void test_shrpx_config_parse_config_str_list(void) {
auto res = parse_config_str_list("a");
CU_ASSERT(1 == res.size());
CU_ASSERT(0 == strcmp("a", res[0]));
clear_config_str_list(res);
CU_ASSERT("a" == res[0]);
res = parse_config_str_list("a,");
CU_ASSERT(2 == res.size());
CU_ASSERT(0 == strcmp("a", res[0]));
CU_ASSERT(0 == strcmp("", res[1]));
clear_config_str_list(res);
CU_ASSERT("a" == res[0]);
CU_ASSERT("" == res[1]);
res = parse_config_str_list(":a::", ':');
CU_ASSERT(4 == res.size());
CU_ASSERT(0 == strcmp("", res[0]));
CU_ASSERT(0 == strcmp("a", res[1]));
CU_ASSERT(0 == strcmp("", res[2]));
CU_ASSERT(0 == strcmp("", res[3]));
clear_config_str_list(res);
CU_ASSERT("" == res[0]);
CU_ASSERT("a" == res[1]);
CU_ASSERT("" == res[2]);
CU_ASSERT("" == res[3]);
res = parse_config_str_list("");
CU_ASSERT(1 == res.size());
CU_ASSERT(0 == strcmp("", res[0]));
clear_config_str_list(res);
CU_ASSERT("" == res[0]);
res = parse_config_str_list("alpha,bravo,charlie");
CU_ASSERT(3 == res.size());
CU_ASSERT(0 == strcmp("alpha", res[0]));
CU_ASSERT(0 == strcmp("bravo", res[1]));
CU_ASSERT(0 == strcmp("charlie", res[2]));
clear_config_str_list(res);
CU_ASSERT("alpha" == res[0]);
CU_ASSERT("bravo" == res[1]);
CU_ASSERT("charlie" == res[2]);
}
void test_shrpx_config_parse_header(void) {
@ -190,24 +185,62 @@ void test_shrpx_config_read_tls_ticket_key_file(void) {
close(fd1);
close(fd2);
auto ticket_keys = read_tls_ticket_key_file({file1, file2});
auto ticket_keys =
read_tls_ticket_key_file({file1, file2}, EVP_aes_128_cbc(), EVP_sha256());
unlink(file1);
unlink(file2);
CU_ASSERT(ticket_keys.get() != nullptr);
CU_ASSERT(2 == ticket_keys->keys.size());
auto key = &ticket_keys->keys[0];
CU_ASSERT(0 == memcmp("0..............1", key->name, sizeof(key->name)));
CU_ASSERT(0 ==
memcmp("2..............3", key->aes_key, sizeof(key->aes_key)));
CU_ASSERT(0 ==
memcmp("4..............5", key->hmac_key, sizeof(key->hmac_key)));
memcmp("0..............1", key->data.name, sizeof(key->data.name)));
CU_ASSERT(0 == memcmp("2..............3", key->data.enc_key, 16));
CU_ASSERT(0 == memcmp("4..............5", key->data.hmac_key, 16));
key = &ticket_keys->keys[1];
CU_ASSERT(0 == memcmp("6..............7", key->name, sizeof(key->name)));
CU_ASSERT(0 ==
memcmp("8..............9", key->aes_key, sizeof(key->aes_key)));
memcmp("6..............7", key->data.name, sizeof(key->data.name)));
CU_ASSERT(0 == memcmp("8..............9", key->data.enc_key, 16));
CU_ASSERT(0 == memcmp("a..............b", key->data.hmac_key, 16));
}
void test_shrpx_config_read_tls_ticket_key_file_aes_256(void) {
char file1[] = "/tmp/nghttpx-unittest.XXXXXX";
auto fd1 = mkstemp(file1);
assert(fd1 != -1);
assert(80 == write(fd1, "0..............12..............................34..."
"...........................5",
80));
char file2[] = "/tmp/nghttpx-unittest.XXXXXX";
auto fd2 = mkstemp(file2);
assert(fd2 != -1);
assert(80 == write(fd2, "6..............78..............................9a..."
"...........................b",
80));
close(fd1);
close(fd2);
auto ticket_keys =
read_tls_ticket_key_file({file1, file2}, EVP_aes_256_cbc(), EVP_sha256());
unlink(file1);
unlink(file2);
CU_ASSERT(ticket_keys.get() != nullptr);
CU_ASSERT(2 == ticket_keys->keys.size());
auto key = &ticket_keys->keys[0];
CU_ASSERT(0 ==
memcmp("a..............b", key->hmac_key, sizeof(key->hmac_key)));
memcmp("0..............1", key->data.name, sizeof(key->data.name)));
CU_ASSERT(0 ==
memcmp("2..............................3", key->data.enc_key, 32));
CU_ASSERT(0 ==
memcmp("4..............................5", key->data.hmac_key, 32));
key = &ticket_keys->keys[1];
CU_ASSERT(0 ==
memcmp("6..............7", key->data.name, sizeof(key->data.name)));
CU_ASSERT(0 ==
memcmp("8..............................9", key->data.enc_key, 32));
CU_ASSERT(0 ==
memcmp("a..............................b", key->data.hmac_key, 32));
}
void test_shrpx_config_match_downstream_addr_group(void) {

View File

@ -35,6 +35,7 @@ void test_shrpx_config_parse_config_str_list(void);
void test_shrpx_config_parse_header(void);
void test_shrpx_config_parse_log_format(void);
void test_shrpx_config_read_tls_ticket_key_file(void);
void test_shrpx_config_read_tls_ticket_key_file_aes_256(void);
void test_shrpx_config_match_downstream_addr_group(void);
} // namespace shrpx

View File

@ -62,7 +62,13 @@ Connection::Connection(struct ev_loop *loop, int fd, SSL *ssl,
tls.last_write_time = 0.;
}
Connection::~Connection() { disconnect(); }
Connection::~Connection() {
disconnect();
if (tls.ssl) {
SSL_free(tls.ssl);
}
}
void Connection::disconnect() {
ev_timer_stop(loop, &rt);
@ -75,10 +81,13 @@ void Connection::disconnect() {
SSL_set_app_data(tls.ssl, nullptr);
SSL_set_shutdown(tls.ssl, SSL_RECEIVED_SHUTDOWN);
ERR_clear_error();
SSL_shutdown(tls.ssl);
// To reuse SSL/TLS session, we have to shutdown, and don't free
// tls.ssl.
if (SSL_shutdown(tls.ssl) != 1) {
SSL_free(tls.ssl);
tls.ssl = nullptr;
}
}
if (fd != -1) {
shutdown(fd, SHUT_WR);

View File

@ -128,24 +128,17 @@ ConnectionHandler::~ConnectionHandler() {
}
}
void ConnectionHandler::worker_reopen_log_files() {
WorkerEvent wev;
memset(&wev, 0, sizeof(wev));
wev.type = REOPEN_LOG;
for (auto &worker : workers_) {
worker->send(wev);
}
}
void ConnectionHandler::worker_renew_ticket_keys(
void ConnectionHandler::set_ticket_keys_to_worker(
const std::shared_ptr<TicketKeys> &ticket_keys) {
WorkerEvent wev;
for (auto &worker : workers_) {
worker->set_ticket_keys(ticket_keys);
}
}
memset(&wev, 0, sizeof(wev));
wev.type = RENEW_TICKET_KEYS;
wev.ticket_keys = ticket_keys;
void ConnectionHandler::worker_reopen_log_files() {
WorkerEvent wev{};
wev.type = REOPEN_LOG;
for (auto &worker : workers_) {
worker->send(wev);
@ -216,8 +209,7 @@ void ConnectionHandler::graceful_shutdown_worker() {
return;
}
WorkerEvent wev;
memset(&wev, 0, sizeof(wev));
WorkerEvent wev{};
wev.type = GRACEFUL_SHUTDOWN;
if (LOG_ENABLED(INFO)) {
@ -266,8 +258,7 @@ int ConnectionHandler::handle_connection(int fd, sockaddr *addr, int addrlen) {
LOG(INFO) << "Dispatch connection to worker #" << idx;
}
++worker_round_robin_cnt_;
WorkerEvent wev;
memset(&wev, 0, sizeof(wev));
WorkerEvent wev{};
wev.type = NEW_CONNECTION;
wev.client_fd = fd;
memcpy(&wev.client_addr, addr, addrlen);

View File

@ -76,8 +76,9 @@ public:
// Creates |num| Worker objects for multi threaded configuration.
// The |num| must be strictly more than 1.
void create_worker_thread(size_t num);
void
set_ticket_keys_to_worker(const std::shared_ptr<TicketKeys> &ticket_keys);
void worker_reopen_log_files();
void worker_renew_ticket_keys(const std::shared_ptr<TicketKeys> &ticket_keys);
void set_ticket_keys(std::shared_ptr<TicketKeys> ticket_keys);
const std::shared_ptr<TicketKeys> &get_ticket_keys() const;
struct ev_loop *get_loop() const;

View File

@ -621,8 +621,7 @@ void Downstream::rewrite_location_response_header(
if (!hd) {
return;
}
http_parser_url u;
memset(&u, 0, sizeof(u));
http_parser_url u{};
int rv =
http_parser_parse_url((*hd).value.c_str(), (*hd).value.size(), 0, &u);
if (rv != 0) {
@ -1208,4 +1207,10 @@ void Downstream::add_request_headers_sum(size_t amount) {
request_headers_sum_ += amount;
}
bool Downstream::can_detach_downstream_connection() const {
return dconn_ && response_state_ == Downstream::MSG_COMPLETE &&
request_state_ == Downstream::MSG_COMPLETE && !upgraded_ &&
!response_connection_close_;
}
} // namespace shrpx

View File

@ -332,6 +332,9 @@ public:
void attach_blocked_link(BlockedLink *l);
BlockedLink *detach_blocked_link();
// Returns true if downstream_connection can be detached and reused.
bool can_detach_downstream_connection() const;
enum {
EVENT_ERROR = 0x1,
EVENT_TIMEOUT = 0x2,

View File

@ -320,13 +320,16 @@ int Http2Session::initiate_connection() {
SSLOG(INFO, this) << "Connecting to downstream server";
}
if (ssl_ctx_) {
// We are establishing TLS connection.
// We are establishing TLS connection. If conn_.tls.ssl, we may
// reuse the previous session.
if (!conn_.tls.ssl) {
conn_.tls.ssl = SSL_new(ssl_ctx_);
if (!conn_.tls.ssl) {
SSLOG(ERROR, this) << "SSL_new() failed: "
<< ERR_error_string(ERR_get_error(), NULL);
return -1;
}
}
const char *sni_name = nullptr;
if (get_config()->backend_tls_sni_name) {

View File

@ -74,21 +74,12 @@ int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
return 0;
}
downstream->set_request_state(Downstream::STREAM_CLOSED);
if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
// At this point, downstream response was read
if (!downstream->get_upgraded() &&
!downstream->get_response_connection_close()) {
if (downstream->can_detach_downstream_connection()) {
// Keep-alive
downstream->detach_downstream_connection();
}
upstream->remove_downstream(downstream);
// downstream was deleted
return 0;
}
downstream->set_request_state(Downstream::STREAM_CLOSED);
// At this point, downstream read may be paused.
@ -915,10 +906,8 @@ int Http2Upstream::downstream_read(DownstreamConnection *dconn) {
}
return downstream_error(dconn, Downstream::EVENT_ERROR);
}
// Detach downstream connection early so that it could be reused
// without hitting server's request timeout.
if (downstream->get_response_state() == Downstream::MSG_COMPLETE &&
!downstream->get_response_connection_close()) {
if (downstream->can_detach_downstream_connection()) {
// Keep-alive
downstream->detach_downstream_connection();
}
@ -1482,8 +1471,7 @@ int Http2Upstream::on_downstream_reset(bool no_retry) {
int Http2Upstream::prepare_push_promise(Downstream *downstream) {
int rv;
http_parser_url u;
memset(&u, 0, sizeof(u));
http_parser_url u{};
rv = http_parser_parse_url(downstream->get_request_path().c_str(),
downstream->get_request_path().size(), 0, &u);
if (rv != 0) {
@ -1513,8 +1501,7 @@ int Http2Upstream::prepare_push_promise(Downstream *downstream) {
const char *relq = nullptr;
size_t relqlen = 0;
http_parser_url v;
memset(&v, 0, sizeof(v));
http_parser_url v{};
rv = http_parser_parse_url(link_url, link_urllen, 0, &v);
if (rv != 0) {
assert(link_urllen);

View File

@ -540,7 +540,8 @@ int HttpsUpstream::on_write() {
// We need to postpone detachment until all data are sent so that
// we can notify nghttp2 library all data consumed.
if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
if (downstream->get_response_connection_close()) {
if (downstream->get_response_connection_close() ||
downstream->get_request_state() != Downstream::MSG_COMPLETE) {
// Connection close
downstream->pop_downstream_connection();
// dconn was deleted
@ -607,10 +608,7 @@ int HttpsUpstream::downstream_read(DownstreamConnection *dconn) {
goto end;
}
// Detach downstream connection early so that it could be reused
// without hitting server's request timeout.
if (downstream->get_response_state() == Downstream::MSG_COMPLETE &&
!downstream->get_response_connection_close()) {
if (downstream->can_detach_downstream_connection()) {
// Keep-alive
downstream->detach_downstream_connection();
}
@ -844,12 +842,12 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
if (!get_config()->altsvcs.empty()) {
hdrs += "Alt-Svc: ";
for (auto &altsvc : get_config()->altsvcs) {
for (const auto &altsvc : get_config()->altsvcs) {
hdrs += util::percent_encode_token(altsvc.protocol_id);
hdrs += "=\"";
hdrs += util::quote_string(std::string(altsvc.host, altsvc.host_len));
hdrs += util::quote_string(altsvc.host);
hdrs += ":";
hdrs += util::utos(altsvc.port);
hdrs += altsvc.service;
hdrs += "\", ";
}

View File

@ -109,19 +109,12 @@ void on_stream_close_callback(spdylay_session *session, int32_t stream_id,
return;
}
downstream->set_request_state(Downstream::STREAM_CLOSED);
if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
// At this point, downstream response was read
if (!downstream->get_upgraded() &&
!downstream->get_response_connection_close()) {
if (downstream->can_detach_downstream_connection()) {
// Keep-alive
downstream->detach_downstream_connection();
}
upstream->remove_downstream(downstream);
// downstrea was deleted
return;
}
downstream->set_request_state(Downstream::STREAM_CLOSED);
// At this point, downstream read may be paused.
@ -452,8 +445,7 @@ SpdyUpstream::SpdyUpstream(uint16_t version, ClientHandler *handler)
: 0,
!get_config()->http2_proxy),
handler_(handler), session_(nullptr) {
spdylay_session_callbacks callbacks;
memset(&callbacks, 0, sizeof(callbacks));
spdylay_session_callbacks callbacks{};
callbacks.send_callback = send_callback;
callbacks.recv_callback = recv_callback;
callbacks.on_stream_close_callback = on_stream_close_callback;
@ -590,10 +582,7 @@ int SpdyUpstream::downstream_read(DownstreamConnection *dconn) {
}
return downstream_error(dconn, Downstream::EVENT_ERROR);
}
// Detach downstream connection early so that it could be reused
// without hitting server's request timeout.
if (downstream->get_response_state() == Downstream::MSG_COMPLETE &&
!downstream->get_response_connection_close()) {
if (downstream->can_detach_downstream_connection()) {
// Keep-alive
downstream->detach_downstream_connection();
}

View File

@ -86,18 +86,17 @@ int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) {
}
} // namespace
std::vector<unsigned char> set_alpn_prefs(const std::vector<char *> &protos) {
std::vector<unsigned char>
set_alpn_prefs(const std::vector<std::string> &protos) {
size_t len = 0;
for (auto proto : protos) {
auto n = strlen(proto);
if (n > 255) {
LOG(FATAL) << "Too long ALPN identifier: " << n;
for (const auto &proto : protos) {
if (proto.size() > 255) {
LOG(FATAL) << "Too long ALPN identifier: " << proto.size();
DIE();
}
len += 1 + n;
len += 1 + proto.size();
}
if (len > (1 << 16) - 1) {
@ -108,12 +107,10 @@ std::vector<unsigned char> set_alpn_prefs(const std::vector<char *> &protos) {
auto out = std::vector<unsigned char>(len);
auto ptr = out.data();
for (auto proto : protos) {
auto proto_len = strlen(proto);
*ptr++ = proto_len;
memcpy(ptr, proto, proto_len);
ptr += proto_len;
for (const auto &proto : protos) {
*ptr++ = proto.size();
memcpy(ptr, proto.c_str(), proto.size());
ptr += proto.size();
}
return out;
@ -191,7 +188,7 @@ int ticket_key_cb(SSL *ssl, unsigned char *key_name, unsigned char *iv,
EVP_CIPHER_CTX *ctx, HMAC_CTX *hctx, int enc) {
auto handler = static_cast<ClientHandler *>(SSL_get_app_data(ssl));
auto worker = handler->get_worker();
const auto &ticket_keys = worker->get_ticket_keys();
auto ticket_keys = worker->get_ticket_keys();
if (!ticket_keys) {
// No ticket keys available.
@ -213,21 +210,21 @@ int ticket_key_cb(SSL *ssl, unsigned char *key_name, unsigned char *iv,
if (LOG_ENABLED(INFO)) {
CLOG(INFO, handler) << "encrypt session ticket key: "
<< util::format_hex(key.name, 16);
<< util::format_hex(key.data.name);
}
memcpy(key_name, key.name, sizeof(key.name));
memcpy(key_name, key.data.name, sizeof(key.data.name));
EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), nullptr, key.aes_key, iv);
HMAC_Init_ex(hctx, key.hmac_key, sizeof(key.hmac_key), EVP_sha256(),
nullptr);
EVP_EncryptInit_ex(ctx, get_config()->tls_ticket_cipher, nullptr,
key.data.enc_key, iv);
HMAC_Init_ex(hctx, key.data.hmac_key, key.hmac_keylen, key.hmac, nullptr);
return 1;
}
size_t i;
for (i = 0; i < keys.size(); ++i) {
auto &key = keys[0];
if (memcmp(key.name, key_name, sizeof(key.name)) == 0) {
if (memcmp(key_name, key.data.name, sizeof(key.data.name)) == 0) {
break;
}
}
@ -246,8 +243,8 @@ int ticket_key_cb(SSL *ssl, unsigned char *key_name, unsigned char *iv,
}
auto &key = keys[i];
HMAC_Init_ex(hctx, key.hmac_key, sizeof(key.hmac_key), EVP_sha256(), nullptr);
EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), nullptr, key.aes_key, iv);
HMAC_Init_ex(hctx, key.data.hmac_key, key.hmac_keylen, key.hmac, nullptr);
EVP_DecryptInit_ex(ctx, key.cipher, nullptr, key.data.enc_key, iv);
return i == 0 ? 1 : 2;
}
@ -281,16 +278,14 @@ int alpn_select_proto_cb(SSL *ssl, const unsigned char **out,
// We assume that get_config()->npn_list contains ALPN protocol
// identifier sorted by preference order. So we just break when we
// found the first overlap.
for (auto target_proto_id : get_config()->npn_list) {
auto target_proto_len =
strlen(reinterpret_cast<const char *>(target_proto_id));
for (const auto &target_proto_id : get_config()->npn_list) {
for (auto p = in, end = in + inlen; p < end;) {
auto proto_id = p + 1;
auto proto_len = *p;
if (proto_id + proto_len <= end && target_proto_len == proto_len &&
memcmp(target_proto_id, proto_id, proto_len) == 0) {
if (proto_id + proto_len <= end &&
util::streq(target_proto_id.c_str(), target_proto_id.size(), proto_id,
proto_len)) {
*out = reinterpret_cast<const unsigned char *>(proto_id);
*outlen = proto_len;
@ -314,7 +309,7 @@ constexpr long int tls_masks[] = {SSL_OP_NO_TLSv1_2, SSL_OP_NO_TLSv1_1,
SSL_OP_NO_TLSv1};
} // namespace
long int create_tls_proto_mask(const std::vector<char *> &tls_proto_list) {
long int create_tls_proto_mask(const std::vector<std::string> &tls_proto_list) {
long int res = 0;
for (size_t i = 0; i < tls_namelen; ++i) {
@ -351,6 +346,7 @@ SSL_CTX *create_ssl_context(const char *private_key_file,
const unsigned char sid_ctx[] = "shrpx";
SSL_CTX_set_session_id_context(ssl_ctx, sid_ctx, sizeof(sid_ctx) - 1);
SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_SERVER);
SSL_CTX_set_timeout(ssl_ctx, get_config()->tls_session_timeout.count());
const char *ciphers;
if (get_config()->ciphers) {
@ -949,10 +945,10 @@ int cert_lookup_tree_add_cert_from_file(CertLookupTree *lt, SSL_CTX *ssl_ctx,
return 0;
}
bool in_proto_list(const std::vector<char *> &protos,
bool in_proto_list(const std::vector<std::string> &protos,
const unsigned char *needle, size_t len) {
for (auto proto : protos) {
if (strlen(proto) == len && memcmp(proto, needle, len) == 0) {
for (auto &proto : protos) {
if (util::streq(proto.c_str(), proto.size(), needle, len)) {
return true;
}
}

View File

@ -140,7 +140,7 @@ int cert_lookup_tree_add_cert_from_file(CertLookupTree *lt, SSL_CTX *ssl_ctx,
// Returns true if |needle| which has |len| bytes is included in the
// protocol list |protos|.
bool in_proto_list(const std::vector<char *> &protos,
bool in_proto_list(const std::vector<std::string> &protos,
const unsigned char *needle, size_t len);
// Returns true if security requirement for HTTP/2 is fulfilled.
@ -149,9 +149,10 @@ bool check_http2_requirement(SSL *ssl);
// Returns SSL/TLS option mask to disable SSL/TLS protocol version not
// included in |tls_proto_list|. The returned mask can be directly
// passed to SSL_CTX_set_options().
long int create_tls_proto_mask(const std::vector<char *> &tls_proto_list);
long int create_tls_proto_mask(const std::vector<std::string> &tls_proto_list);
std::vector<unsigned char> set_alpn_prefs(const std::vector<char *> &protos);
std::vector<unsigned char>
set_alpn_prefs(const std::vector<std::string> &protos);
// Setups server side SSL_CTX. This function inspects get_config()
// and if upstream_no_tls is true, returns nullptr. Otherwise

View File

@ -172,12 +172,6 @@ void Worker::process_events() {
break;
}
case RENEW_TICKET_KEYS:
WLOG(NOTICE, this) << "Renew ticket keys: worker(" << this << ")";
ticket_keys_ = wev.ticket_keys;
break;
case REOPEN_LOG:
WLOG(NOTICE, this) << "Reopening log files: worker(" << this << ")";
@ -206,11 +200,13 @@ void Worker::process_events() {
ssl::CertLookupTree *Worker::get_cert_lookup_tree() const { return cert_tree_; }
const std::shared_ptr<TicketKeys> &Worker::get_ticket_keys() const {
std::shared_ptr<TicketKeys> Worker::get_ticket_keys() {
std::lock_guard<std::mutex> g(m_);
return ticket_keys_;
}
void Worker::set_ticket_keys(std::shared_ptr<TicketKeys> ticket_keys) {
std::lock_guard<std::mutex> g(m_);
ticket_keys_ = std::move(ticket_keys);
}

View File

@ -75,7 +75,6 @@ enum WorkerEventType {
NEW_CONNECTION = 0x01,
REOPEN_LOG = 0x02,
GRACEFUL_SHUTDOWN = 0x03,
RENEW_TICKET_KEYS = 0x04,
};
struct WorkerEvent {
@ -100,8 +99,12 @@ public:
void send(const WorkerEvent &event);
ssl::CertLookupTree *get_cert_lookup_tree() const;
const std::shared_ptr<TicketKeys> &get_ticket_keys() const;
// These 2 functions make a lock m_ to get/set ticket keys
// atomically.
std::shared_ptr<TicketKeys> get_ticket_keys();
void set_ticket_keys(std::shared_ptr<TicketKeys> ticket_keys);
WorkerStat *get_worker_stat();
DownstreamConnectionPool *get_dconn_pool();
Http2Session *next_http2_session(size_t group);

View File

@ -53,3 +53,36 @@ time_t nghttp2_timegm(struct tm *tm) {
return (time_t)t;
}
/* Returns nonzero if the |y| is the leap year. The |y| is the year,
including century (e.g., 2012) */
static int is_leap_year(int y) {
return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0);
}
/* The number of days before ith month begins */
static int daysum[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
time_t nghttp2_timegm_without_yday(struct tm *tm) {
int days;
int num_leap_year;
int64_t t;
if (tm->tm_mon > 11) {
return -1;
}
num_leap_year = count_leap_year(tm->tm_year + 1900) - count_leap_year(1970);
days = (tm->tm_year - 70) * 365 + num_leap_year + daysum[tm->tm_mon] +
tm->tm_mday - 1;
if (tm->tm_mon >= 2 && is_leap_year(tm->tm_year + 1900)) {
++days;
}
t = ((int64_t)days * 24 + tm->tm_hour) * 3600 + tm->tm_min * 60 + tm->tm_sec;
#if SIZEOF_TIME_T == 4
if (t < INT32_MIN || t > INT32_MAX) {
return -1;
}
#endif /* SIZEOF_TIME_T == 4 */
return t;
}

View File

@ -39,6 +39,11 @@ extern "C" {
time_t nghttp2_timegm(struct tm *tm);
/* Just like nghttp2_timegm, but without using tm->tm_yday. This is
useful if we use tm from strptime, since some platforms do not
calculate tm_yday with that call. */
time_t nghttp2_timegm_without_yday(struct tm *tm);
#ifdef __cplusplus
}
#endif /* __cplusplus */

View File

@ -348,13 +348,12 @@ std::string iso8601_date(int64_t ms) {
}
time_t parse_http_date(const std::string &s) {
tm tm;
memset(&tm, 0, sizeof(tm));
tm tm{};
char *r = strptime(s.c_str(), "%a, %d %b %Y %H:%M:%S GMT", &tm);
if (r == 0) {
return 0;
}
return nghttp2_timegm(&tm);
return nghttp2_timegm_without_yday(&tm);
}
namespace {
@ -637,9 +636,8 @@ void write_uri_field(std::ostream &o, const char *uri, const http_parser_url &u,
}
bool numeric_host(const char *hostname) {
struct addrinfo hints;
struct addrinfo *res;
memset(&hints, 0, sizeof(hints));
struct addrinfo hints {};
hints.ai_family = AF_UNSPEC;
hints.ai_flags = AI_NUMERICHOST;
if (getaddrinfo(hostname, nullptr, &hints, &res)) {

View File

@ -212,6 +212,10 @@ std::string quote_string(const std::string &target);
std::string format_hex(const unsigned char *s, size_t len);
template <size_t N> std::string format_hex(const unsigned char (&s)[N]) {
return format_hex(s, N);
}
std::string http_date(time_t t);
// Returns given time |t| from epoch in Common Log format (e.g.,
@ -345,6 +349,10 @@ inline bool strieq(const std::string &a, const std::string &b) {
bool strieq(const char *a, const char *b);
inline bool strieq(const char *a, const std::string &b) {
return strieq(a, b.c_str(), b.size());
}
template <typename InputIt, size_t N>
bool strieq_l(const char (&a)[N], InputIt b, size_t blen) {
return strieq(a, N - 1, b, blen);

View File

@ -171,6 +171,8 @@ int main(int argc _U_, char *argv[] _U_) {
test_nghttp2_submit_settings) ||
!CU_add_test(pSuite, "session_submit_settings_update_local_window_size",
test_nghttp2_submit_settings_update_local_window_size) ||
!CU_add_test(pSuite, "session_submit_settings_multiple_times",
test_nghttp2_submit_settings_multiple_times) ||
!CU_add_test(pSuite, "session_submit_push_promise",
test_nghttp2_submit_push_promise) ||
!CU_add_test(pSuite, "submit_window_update",
@ -326,6 +328,10 @@ int main(int argc _U_, char *argv[] _U_) {
test_nghttp2_hd_inflate_clearall_inc) ||
!CU_add_test(pSuite, "hd_inflate_zero_length_huffman",
test_nghttp2_hd_inflate_zero_length_huffman) ||
!CU_add_test(pSuite, "hd_inflate_expect_table_size_update",
test_nghttp2_hd_inflate_expect_table_size_update) ||
!CU_add_test(pSuite, "hd_inflate_unexpected_table_size_update",
test_nghttp2_hd_inflate_unexpected_table_size_update) ||
!CU_add_test(pSuite, "hd_ringbuf_reserve",
test_nghttp2_hd_ringbuf_reserve) ||
!CU_add_test(pSuite, "hd_change_table_size",

View File

@ -540,6 +540,54 @@ void test_nghttp2_hd_inflate_zero_length_huffman(void) {
nghttp2_hd_inflate_free(&inflater);
}
void test_nghttp2_hd_inflate_expect_table_size_update(void) {
nghttp2_hd_inflater inflater;
nghttp2_bufs bufs;
nghttp2_mem *mem;
/* Indexed Header: :method: GET */
uint8_t data[] = {0x82};
nva_out out;
mem = nghttp2_mem_default();
frame_pack_bufs_init(&bufs);
nva_out_init(&out);
nghttp2_bufs_add(&bufs, data, sizeof(data));
nghttp2_hd_inflate_init(&inflater, mem);
/* This will make inflater require table size update in the next
inflation. */
nghttp2_hd_inflate_change_table_size(&inflater, 4096);
CU_ASSERT(NGHTTP2_ERR_HEADER_COMP ==
inflate_hd(&inflater, &out, &bufs, 0, mem));
nva_out_reset(&out, mem);
nghttp2_bufs_free(&bufs);
nghttp2_hd_inflate_free(&inflater);
}
void test_nghttp2_hd_inflate_unexpected_table_size_update(void) {
nghttp2_hd_inflater inflater;
nghttp2_bufs bufs;
nghttp2_mem *mem;
/* Indexed Header: :method: GET, followed by table size update.
This violates RFC 7541. */
uint8_t data[] = {0x82, 0x20};
nva_out out;
mem = nghttp2_mem_default();
frame_pack_bufs_init(&bufs);
nva_out_init(&out);
nghttp2_bufs_add(&bufs, data, sizeof(data));
nghttp2_hd_inflate_init(&inflater, mem);
CU_ASSERT(NGHTTP2_ERR_HEADER_COMP ==
inflate_hd(&inflater, &out, &bufs, 0, mem));
nva_out_reset(&out, mem);
nghttp2_bufs_free(&bufs);
nghttp2_hd_inflate_free(&inflater);
}
void test_nghttp2_hd_ringbuf_reserve(void) {
nghttp2_hd_deflater deflater;
nghttp2_hd_inflater inflater;

View File

@ -35,6 +35,8 @@ void test_nghttp2_hd_inflate_newname_noinc(void);
void test_nghttp2_hd_inflate_newname_inc(void);
void test_nghttp2_hd_inflate_clearall_inc(void);
void test_nghttp2_hd_inflate_zero_length_huffman(void);
void test_nghttp2_hd_inflate_expect_table_size_update(void);
void test_nghttp2_hd_inflate_unexpected_table_size_update(void);
void test_nghttp2_hd_ringbuf_reserve(void);
void test_nghttp2_hd_change_table_size(void);
void test_nghttp2_hd_deflate_inflate(void);

View File

@ -2300,18 +2300,11 @@ void test_nghttp2_session_on_settings_received(void) {
nghttp2_session_server_new(&session, &callbacks, NULL);
nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_ACK, dup_iv(iv, 1),
1);
/* Specify inflight_iv deliberately */
session->inflight_iv = frame.settings.iv;
session->inflight_niv = frame.settings.niv;
CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0));
item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(item != NULL);
CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
session->inflight_iv = NULL;
session->inflight_niv = -1;
nghttp2_frame_settings_free(&frame.settings, mem);
nghttp2_session_del(session);
@ -4041,8 +4034,8 @@ void test_nghttp2_submit_settings(void) {
CU_ASSERT(16 * 1024 == session->local_settings.initial_window_size);
CU_ASSERT(0 == session->hd_inflater.ctx.hd_table_bufsize_max);
CU_ASSERT(50 == session->local_settings.max_concurrent_streams);
CU_ASSERT(NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS ==
session->pending_local_max_concurrent_stream);
/* We just keep the last seen value */
CU_ASSERT(50 == session->pending_local_max_concurrent_stream);
nghttp2_session_del(session);
}
@ -4113,6 +4106,83 @@ void test_nghttp2_submit_settings_update_local_window_size(void) {
nghttp2_frame_settings_free(&ack_frame.settings, mem);
}
void test_nghttp2_submit_settings_multiple_times(void) {
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
nghttp2_settings_entry iv[4];
nghttp2_frame frame;
nghttp2_inflight_settings *inflight_settings;
memset(&callbacks, 0, sizeof(callbacks));
callbacks.send_callback = null_send_callback;
nghttp2_session_client_new(&session, &callbacks, NULL);
/* first SETTINGS */
iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
iv[0].value = 100;
iv[1].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
iv[1].value = 0;
CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 2));
inflight_settings = session->inflight_settings_head;
CU_ASSERT(NULL != inflight_settings);
CU_ASSERT(NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS ==
inflight_settings->iv[0].settings_id);
CU_ASSERT(100 == inflight_settings->iv[0].value);
CU_ASSERT(2 == inflight_settings->niv);
CU_ASSERT(NULL == inflight_settings->next);
CU_ASSERT(100 == session->pending_local_max_concurrent_stream);
CU_ASSERT(0 == session->pending_enable_push);
/* second SETTINGS */
iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
iv[0].value = 99;
CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 1));
inflight_settings = session->inflight_settings_head->next;
CU_ASSERT(NULL != inflight_settings);
CU_ASSERT(NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS ==
inflight_settings->iv[0].settings_id);
CU_ASSERT(99 == inflight_settings->iv[0].value);
CU_ASSERT(1 == inflight_settings->niv);
CU_ASSERT(NULL == inflight_settings->next);
CU_ASSERT(99 == session->pending_local_max_concurrent_stream);
CU_ASSERT(0 == session->pending_enable_push);
nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_ACK, NULL, 0);
/* receive SETTINGS ACK */
CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0));
inflight_settings = session->inflight_settings_head;
/* first inflight SETTINGS was removed */
CU_ASSERT(NULL != inflight_settings);
CU_ASSERT(NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS ==
inflight_settings->iv[0].settings_id);
CU_ASSERT(99 == inflight_settings->iv[0].value);
CU_ASSERT(1 == inflight_settings->niv);
CU_ASSERT(NULL == inflight_settings->next);
CU_ASSERT(100 == session->local_settings.max_concurrent_streams);
/* receive SETTINGS ACK again */
CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0));
CU_ASSERT(NULL == session->inflight_settings_head);
CU_ASSERT(99 == session->local_settings.max_concurrent_streams);
nghttp2_session_del(session);
}
void test_nghttp2_submit_push_promise(void) {
nghttp2_session *session;
nghttp2_session_callbacks callbacks;

View File

@ -79,6 +79,7 @@ void test_nghttp2_submit_headers_continuation(void);
void test_nghttp2_submit_priority(void);
void test_nghttp2_submit_settings(void);
void test_nghttp2_submit_settings_update_local_window_size(void);
void test_nghttp2_submit_settings_multiple_times(void);
void test_nghttp2_submit_push_promise(void);
void test_nghttp2_submit_window_update(void);
void test_nghttp2_submit_window_update_local_window_size(void);