Compare commits

...

28 Commits

Author SHA1 Message Date
Tatsuhiro Tsujikawa
48244b9aca Update man pages 2015-08-14 23:59:47 +09:00
Tatsuhiro Tsujikawa
eb42fb4f49 Bump up version number to 1.2.1, LT revision to 14:8:0 2015-08-14 23:55:08 +09:00
Tatsuhiro Tsujikawa
b37834c584 h2load: Use default Config values to show help 2015-08-14 23:22:26 +09:00
Tatsuhiro Tsujikawa
d197d115dc h2load: Use floating point duration cast 2015-08-14 22:44:14 +09:00
Tatsuhiro Tsujikawa
3a1c37248a Update doc 2015-08-14 22:38:23 +09:00
Tatsuhiro Tsujikawa
f7fa9eb21b Fix sum_norest_weight calculation bug in stream dependency tree 2015-08-14 01:27:50 +09:00
Tatsuhiro Tsujikawa
4839d6d513 Merge branch 'nshoemaker-r_n' 2015-08-14 00:04:21 +09:00
nshoemaker
e1621584fa Fixing -r > -n check for when -n is not specified. 2015-08-13 05:09:40 -07:00
Tatsuhiro Tsujikawa
473311269d nghttpx: Drop connection if client TLS handshake packet is too large 2015-08-13 01:24:59 +09:00
Tatsuhiro Tsujikawa
0c4cbc800b Document that previous OCSP response is continued to be used 2015-08-13 00:47:32 +09:00
Tatsuhiro Tsujikawa
b8f05c89bd nghttpx: App data in SSL is Connection, not ClientHandler 2015-08-13 00:42:59 +09:00
Tatsuhiro Tsujikawa
e91a576179 nghttpx: Rewrite TLS async handshake using memchunk buffers 2015-08-13 00:42:59 +09:00
Tatsuhiro Tsujikawa
72c661f1dd Merge branch 'yuki-kodama-openssl_no_ec' 2015-08-13 00:41:58 +09:00
Tatsuhiro Tsujikawa
2277cc771a Merge branch 'openssl_no_ec' of https://github.com/yuki-kodama/nghttp2 into yuki-kodama-openssl_no_ec 2015-08-13 00:32:15 +09:00
Tatsuhiro Tsujikawa
f8c30d0229 nghttpx: Fix heap-use-after-free 2015-08-12 21:14:18 +09:00
yuuki-kodama
53b5ffa103 Guard ecdh use with !OPENSSL_NO_EC @asio_server_tls_context.cc 2015-08-12 15:32:54 +09:00
Tatsuhiro Tsujikawa
b384b76f66 nghttpx: Fix hmac_keylen should be 16 for aes-128-cbc 2015-08-12 00:38:07 +09:00
Tatsuhiro Tsujikawa
b406d2da9e h2load: Stop timeout_watcher in Worker dtor 2015-08-11 23:51:08 +09:00
Tatsuhiro Tsujikawa
dd97b53554 h2load: Fix division by zero if -r > -n 2015-08-11 23:49:34 +09:00
Tatsuhiro Tsujikawa
0f7e84bb62 h2load: Always initialize timeout_watcher 2015-08-11 23:44:22 +09:00
Tatsuhiro Tsujikawa
81add96b1e src: Fix bug in get_uint64 2015-08-11 23:42:32 +09:00
Tatsuhiro Tsujikawa
020e66b9da nghttpx: Start read watcher after handshake 2015-08-11 23:14:48 +09:00
Tatsuhiro Tsujikawa
d2a63a88a0 nghttpx: Fix stall if read buffer has app data when handshake finished 2015-08-11 22:37:15 +09:00
Tatsuhiro Tsujikawa
e5a9f6c163 Merge branch 'skip2-reword-hpac-tutorial' 2015-08-11 00:24:50 +09:00
Tom Harwood
560955f50d doc: Reword the HPAC tutorial.
The overall meaning is the same. I added a paragraph to introduce nghttp2_hd_inflate_hd(), and added an explanation of using NGHTTP2_NV_FLAG_NO_INDEX for security sensitive headers.
2015-08-09 17:05:52 +01:00
Tatsuhiro Tsujikawa
8410f684fb nghttpx: Drop connection if client hello is too large 2015-08-10 00:29:43 +09:00
Tatsuhiro Tsujikawa
ff44e211ed nghttpx: Fix tls handshake bug
This fixes 2 things:
1. potential busy loop
2. disabling ticket is not working after resumption
2015-08-09 18:33:49 +09:00
Tatsuhiro Tsujikawa
73442ba5ba Bump up version number to 1.2.1-DEV 2015-08-09 01:27:50 +09:00
31 changed files with 612 additions and 228 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 dnl http://www.gnu.org/software/automake/manual/html_node/Flag-Variables-Ordering.html
AC_PREREQ(2.61) AC_PREREQ(2.61)
AC_INIT([nghttp2], [1.2.0], [t-tujikawa@users.sourceforge.net]) AC_INIT([nghttp2], [1.2.1], [t-tujikawa@users.sourceforge.net])
AC_CONFIG_AUX_DIR([.]) AC_CONFIG_AUX_DIR([.])
AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_HEADERS([config.h]) AC_CONFIG_HEADERS([config.h])
@@ -48,7 +48,7 @@ m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
dnl See versioning rule: dnl See versioning rule:
dnl http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html dnl http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
AC_SUBST(LT_CURRENT, 14) AC_SUBST(LT_CURRENT, 14)
AC_SUBST(LT_REVISION, 7) AC_SUBST(LT_REVISION, 8)
AC_SUBST(LT_AGE, 0) AC_SUBST(LT_AGE, 0)
major=`echo $PACKAGE_VERSION |cut -d. -f1 | sed -e "s/[^0-9]//g"` major=`echo $PACKAGE_VERSION |cut -d. -f1 | sed -e "s/[^0-9]//g"`

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText. .\" Man page generated from reStructuredText.
. .
.TH "H2LOAD" "1" "August 09, 2015" "1.2.0" "nghttp2" .TH "H2LOAD" "1" "August 14, 2015" "1.2.1" "nghttp2"
.SH NAME .SH NAME
h2load \- HTTP/2 benchmarking tool h2load \- HTTP/2 benchmarking tool
. .
@@ -196,9 +196,9 @@ The number of requests failed, including HTTP level failures
.TP .TP
.B errored .B errored
The number of requests failed, except for HTTP level failures. The number of requests failed, except for HTTP level failures.
status code. This is the subset of the number reported in This is the subset of the number reported in \fBfailed\fP and most
\fBfailed\fP and most likely the network level failures or stream likely the network level failures or stream was reset by
was reset by RST_STREAM. RST_STREAM.
.UNINDENT .UNINDENT
.TP .TP
.B status codes .B status codes

View File

@@ -154,9 +154,9 @@ requests
(non-successful HTTP status code). (non-successful HTTP status code).
errored errored
The number of requests failed, except for HTTP level failures. The number of requests failed, except for HTTP level failures.
status code. This is the subset of the number reported in This is the subset of the number reported in ``failed`` and most
``failed`` and most likely the network level failures or stream likely the network level failures or stream was reset by
was reset by RST_STREAM. RST_STREAM.
status codes status codes
The number of status code h2load received. The number of status code h2load received.

View File

@@ -16,9 +16,9 @@ requests
(non-successful HTTP status code). (non-successful HTTP status code).
errored errored
The number of requests failed, except for HTTP level failures. The number of requests failed, except for HTTP level failures.
status code. This is the subset of the number reported in This is the subset of the number reported in ``failed`` and most
``failed`` and most likely the network level failures or stream likely the network level failures or stream was reset by
was reset by RST_STREAM. RST_STREAM.
status codes status codes
The number of status code h2load received. The number of status code h2load received.

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText. .\" Man page generated from reStructuredText.
. .
.TH "NGHTTP" "1" "August 09, 2015" "1.2.0" "nghttp2" .TH "NGHTTP" "1" "August 14, 2015" "1.2.1" "nghttp2"
.SH NAME .SH NAME
nghttp \- HTTP/2 experimental client nghttp \- HTTP/2 experimental client
. .

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText. .\" Man page generated from reStructuredText.
. .
.TH "NGHTTPD" "1" "August 09, 2015" "1.2.0" "nghttp2" .TH "NGHTTPD" "1" "August 14, 2015" "1.2.1" "nghttp2"
.SH NAME .SH NAME
nghttpd \- HTTP/2 experimental server nghttpd \- HTTP/2 experimental server
. .

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText. .\" Man page generated from reStructuredText.
. .
.TH "NGHTTPX" "1" "August 09, 2015" "1.2.0" "nghttp2" .TH "NGHTTPX" "1" "August 14, 2015" "1.2.1" "nghttp2"
.SH NAME .SH NAME
nghttpx \- HTTP/2 experimental proxy nghttpx \- HTTP/2 experimental proxy
. .
@@ -1037,6 +1037,9 @@ translated into Python.
The script file is usually installed under The script file is usually installed under
\fB$(prefix)/share/nghttp2/\fP directory. The actual path to script can \fB$(prefix)/share/nghttp2/\fP directory. The actual path to script can
be customized using \fI\%\-\-fetch\-ocsp\-response\-file\fP option. be customized using \fI\%\-\-fetch\-ocsp\-response\-file\fP option.
.sp
If OCSP query is failed, previous OCSP response, if any, is continued
to be used.
.SH TLS SESSION RESUMPTION .SH TLS SESSION RESUMPTION
.sp .sp
nghttpx supports TLS session resumption through both session ID and nghttpx supports TLS session resumption through both session ID and

View File

@@ -943,6 +943,9 @@ The script file is usually installed under
``$(prefix)/share/nghttp2/`` directory. The actual path to script can ``$(prefix)/share/nghttp2/`` directory. The actual path to script can
be customized using :option:`--fetch-ocsp-response-file` option. be customized using :option:`--fetch-ocsp-response-file` option.
If OCSP query is failed, previous OCSP response, if any, is continued
to be used.
TLS SESSION RESUMPTION TLS SESSION RESUMPTION
---------------------- ----------------------

View File

@@ -96,6 +96,9 @@ The script file is usually installed under
``$(prefix)/share/nghttp2/`` directory. The actual path to script can ``$(prefix)/share/nghttp2/`` directory. The actual path to script can
be customized using :option:`--fetch-ocsp-response-file` option. be customized using :option:`--fetch-ocsp-response-file` option.
If OCSP query is failed, previous OCSP response, if any, is continued
to be used.
TLS SESSION RESUMPTION TLS SESSION RESUMPTION
---------------------- ----------------------

View File

@@ -1,118 +1,129 @@
Tutorial: HPACK API Tutorial: HPACK API
=================== ===================
In this tutorial, we describe basic use of HPACK API in nghttp2 In this tutorial, we describe basic use of nghttp2's HPACK API. We
library. We briefly describe APIs for deflating and inflating header briefly describe the APIs for deflating and inflating header fields.
fields. The example of using these APIs are presented as complete The full example of using these APIs, `deflate.c`_, is attached at the
source code `deflate.c`_. end of this page. It also resides in the examples directory in the
archive or repository.
Deflating (encoding) headers Deflating (encoding) headers
---------------------------- ----------------------------
First we need to initialize :type:`nghttp2_hd_deflater` object using First we need to initialize a :type:`nghttp2_hd_deflater` object using
`nghttp2_hd_deflate_new()` function:: the `nghttp2_hd_deflate_new()` function::
int nghttp2_hd_deflate_new(nghttp2_hd_deflater **deflater_ptr, int nghttp2_hd_deflate_new(nghttp2_hd_deflater **deflater_ptr,
size_t deflate_hd_table_bufsize_max); size_t deflate_hd_table_bufsize_max);
This function allocates :type:`nghttp2_hd_deflater` object and This function allocates a :type:`nghttp2_hd_deflater` object,
initializes it and assigns its pointer to ``*deflater_ptr`` passed by initializes it, and assigns its pointer to ``*deflater_ptr``. The
parameter. The *deflate_hd_table_bufsize_max* is the upper bound of *deflate_hd_table_bufsize_max* is the upper bound of header table size
header table size the deflater will use. This will limit the memory the deflater will use. This will limit the memory usage by the
usage in deflater object for dynamic header table. If you doubt, just deflater object for the dynamic header table. If in doubt, just
specify 4096 here, which is the default upper bound of dynamic header specify 4096 here, which is the default upper bound of dynamic header
table buffer size. table buffer size.
To encode header fields, `nghttp2_hd_deflate_hd()` function:: To encode header fields, use the `nghttp2_hd_deflate_hd()` function::
ssize_t nghttp2_hd_deflate_hd(nghttp2_hd_deflater *deflater, ssize_t nghttp2_hd_deflate_hd(nghttp2_hd_deflater *deflater,
uint8_t *buf, size_t buflen, uint8_t *buf, size_t buflen,
const nghttp2_nv *nva, size_t nvlen); const nghttp2_nv *nva, size_t nvlen);
The *deflater* is the deflater object initialized by The *deflater* is the deflater object initialized by
`nghttp2_hd_deflate_new()` function described above. The *buf* is a `nghttp2_hd_deflate_new()` described above. The encoded byte string is
pointer to buffer to store encoded byte string. The *buflen* is written to the buffer *buf*, which has length *buflen*. The *nva* is
capacity of *buf*. The *nva* is a pointer to :type:`nghttp2_nv`, a pointer to an array of headers fields, each of type
which is an array of header fields to deflate. The *nvlen* is the :type:`nghttp2_nv`. *nvlen* is the number of header fields which
number of header fields which *nva* contains. *nva* contains.
It is important to initialize and assign all members of It is important to initialize and assign all members of
:type:`nghttp2_nv`. If a header field should not be inserted in :type:`nghttp2_nv`. For security sensitive header fields (such as
dynamic header table for a security reason, set cookies), set the :macro:`NGHTTP2_NV_FLAG_NO_INDEX` flag in
:macro:`NGHTTP2_NV_FLAG_NO_INDEX` flag in :member:`nghttp2_nv.flags`. :member:`nghttp2_nv.flags`. Setting this flag prevents recovery of
sensitive header fields by compression based attacks: This is achieved
by not inserting the header field into the dynamic header table.
`nghttp2_hd_deflate_hd()` processes all headers given in *nva*. The `nghttp2_hd_deflate_hd()` processes all headers given in *nva*. The
*nva* must include all request or response header fields to be sent in *nva* must include all request or response header fields to be sent in
one HEADERS (or optionally following (multiple) CONTINUATION one HEADERS (or optionally following (multiple) CONTINUATION
frame(s)). The *buf* must have enough space to store the encoded frame(s)). The *buf* must have enough space to store the encoded
result. Otherwise, the function will fail. To estimate the upper result, otherwise the function will fail. To estimate the upper bound
bound of encoded result, use `nghttp2_hd_deflate_bound()` function:: of the encoded result length, use `nghttp2_hd_deflate_bound()`::
size_t nghttp2_hd_deflate_bound(nghttp2_hd_deflater *deflater, size_t nghttp2_hd_deflate_bound(nghttp2_hd_deflater *deflater,
const nghttp2_nv *nva, size_t nvlen); const nghttp2_nv *nva, size_t nvlen);
Pass this function with the same parameters *deflater*, *nva* and Pass this function the same parameters (*deflater*, *nva*, and
*nvlen* which will be passed to `nghttp2_hd_deflate_hd()`. *nvlen*) which will be passed to `nghttp2_hd_deflate_hd()`.
The subsequent call of `nghttp2_hd_deflate_hd()` will use current Subsequent calls to `nghttp2_hd_deflate_hd()` will use the current
encoder state and perform differential encoding which is the encoder state and perform differential encoding, which yields HPAC's
fundamental compression gain for HPACK. fundamental compression gain.
Once `nghttp2_hd_deflate_hd()` fails, it cannot be undone and its If `nghttp2_hd_deflate_hd()` fails, the failure is fatal and any
further call with the same deflater object shall fail. So it is very further calls with the same deflater object will fail. Thus it's very
important to use `nghttp2_hd_deflate_bound()` to know the required important to use `nghttp2_hd_deflate_bound()` to determine the
size of buffer. required size of the output buffer.
To delete :type:`nghttp2_hd_deflater` object, use `nghttp2_hd_deflate_del()` To delete a :type:`nghttp2_hd_deflater` object, use the
function. `nghttp2_hd_deflate_del()` function.
Inflating (decoding) headers Inflating (decoding) headers
---------------------------- ----------------------------
We use :type:`nghttp2_hd_inflater` object to inflate compressed header A :type:`nghttp2_hd_inflater` object is used to inflate compressed
data. To initialize the object, use `nghttp2_hd_inflate_new()`:: header data. To initialize the object, use
`nghttp2_hd_inflate_new()`::
int nghttp2_hd_inflate_new(nghttp2_hd_inflater **inflater_ptr); int nghttp2_hd_inflate_new(nghttp2_hd_inflater **inflater_ptr);
To inflate header data, use `nghttp2_hd_inflate_hd()` function:: To inflate header data, use `nghttp2_hd_inflate_hd()`::
ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater, ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater,
nghttp2_nv *nv_out, int *inflate_flags, nghttp2_nv *nv_out, int *inflate_flags,
uint8_t *in, size_t inlen, int in_final); uint8_t *in, size_t inlen, int in_final);
`nghttp2_hd_inflate_hd()` reads a stream of bytes and outputs a single
header field at a time. Multiple calls are normally required to read a
full stream of bytes and output all of the header fields.
The *inflater* is the inflater object initialized above. The *nv_out* The *inflater* is the inflater object initialized above. The *nv_out*
is a pointer to :type:`nghttp2_nv` to store the result. The *in* is a is a pointer to a :type:`nghttp2_nv` into which one header field may
pointer to input data and *inlen* is its length. The caller is not be stored. The *in* is a pointer to input data, and *inlen* is its
required to specify whole deflated header data to *in* at once. It length. The caller is not required to specify the whole deflated
can call this function multiple times for portion of the data in header data via *in* at once: Instead it can call this function
streaming way. If *in_final* is nonzero, it tells the function that multiple times as additional data bytes become available. If
the passed data is the final sequence of deflated header data. The *in_final* is nonzero, it tells the function that the passed data is
*inflate_flags* is output parameter and successful call of this the final sequence of deflated header data.
function stores a set of flags in it. It will be described later.
The *inflate_flags* is an output parameter; on success the function
sets it to a bitset of flags. It will be described later.
This function returns when each header field is inflated. When this This function returns when each header field is inflated. When this
happens, the function sets :macro:`NGHTTP2_HD_INFLATE_EMIT` flag to happens, the function sets the :macro:`NGHTTP2_HD_INFLATE_EMIT` flag
*inflate_flag* parameter and header field is stored in *nv_out*. The in *inflate_flags*, and a header field is stored in *nv_out*. The
return value indicates the number of data read from *in* to processed return value indicates the number of bytes read from *in* processed so
so far. It may be less than *inlen*. The caller should call the far, which may be less than *inlen*. The caller should call the
function repeatedly until all data are processed by adjusting *in* and function repeatedly until all bytes are processed. Processed bytes
*inlen* with the processed bytes. should be removed from *in*, and *inlen* should be adjusted
appropriately.
If *in_final* is nonzero and all given data was processed, the If *in_final* is nonzero and all given data was processed, the
function sets :macro:`NGHTTP2_HD_INFLATE_FINAL` flag to function sets the :macro:`NGHTTP2_HD_INFLATE_FINAL` flag in
*inflate_flag*. If the caller sees this flag set, call *inflate_flags*. When you see this flag set, call the
`nghttp2_hd_inflate_end_headers()` function. `nghttp2_hd_inflate_end_headers()` function.
If *in_final* is zero and :macro:`NGHTTP2_HD_INFLATE_EMIT` flag is not If *in_final* is zero and the :macro:`NGHTTP2_HD_INFLATE_EMIT` flag is
set, it indicates that all given data was processed. The caller is not set, it indicates that all given data was processed. The caller
required to pass subsequent data. is required to pass additional data.
It is important to note that the function may produce one or more It is important to note that the function may produce one or more
header fields even if *inlen* is 0 when *in_final* is nonzero, due to header fields even if *inlen* is 0 when *in_final* is nonzero, due to
differential encoding. differential encoding.
The example use of `nghttp2_hd_inflate_hd()` is shown in Example usage of `nghttp2_hd_inflate_hd()` is shown in the
`inflate_header_block()` function in `deflate.c`_. `inflate_header_block()` function in `deflate.c`_.
To delete :type:`nghttp2_hd_inflater` object, use `nghttp2_hd_inflate_del()` Finally, to delete a :type:`nghttp2_hd_inflater` object, use
function. `nghttp2_hd_inflate_del()`.

View File

@@ -989,8 +989,14 @@ int nghttp2_stream_dep_insert_subtree(nghttp2_stream *dep_stream,
return 0; return 0;
} }
/* If dep_stream has stream whose dpri is NGHTTP2_DPRI_TOP in its
subtree, parent stream already accounted dep_stream->weight in
its sum_norest_weight */
if (dep_stream->sum_norest_weight == 0) {
stream_update_dep_sum_norest_weight(dep_stream->dep_prev,
dep_stream->weight);
}
dep_stream->sum_norest_weight = stream->weight; dep_stream->sum_norest_weight = stream->weight;
stream_update_dep_sum_norest_weight(dep_stream->dep_prev, dep_stream->weight);
rv = stream_update_dep_queue_top(stream, session); rv = stream_update_dep_queue_top(stream, session);
if (rv != 0) { if (rv != 0) {

View File

@@ -61,11 +61,13 @@ configure_tls_context_easy(boost::system::error_code &ec,
SSL_CTX_set_cipher_list(ctx, ssl::DEFAULT_CIPHER_LIST); SSL_CTX_set_cipher_list(ctx, ssl::DEFAULT_CIPHER_LIST);
#ifndef OPENSSL_NO_EC
auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
if (ecdh) { if (ecdh) {
SSL_CTX_set_tmp_ecdh(ctx, ecdh); SSL_CTX_set_tmp_ecdh(ctx, ecdh);
EC_KEY_free(ecdh); EC_KEY_free(ecdh);
} }
#endif /* OPENSSL_NO_EC */
SSL_CTX_set_next_protos_advertised_cb( SSL_CTX_set_next_protos_advertised_cb(
ctx, ctx,

View File

@@ -737,11 +737,11 @@ Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients,
auto nreqs_per_client = req_todo / nclients; auto nreqs_per_client = req_todo / nclients;
auto nreqs_rem = req_todo % nclients; auto nreqs_rem = req_todo % nclients;
if (config->is_rate_mode()) { // create timer that will go off every second
// create timer that will go off every second ev_timer_init(&timeout_watcher, second_timeout_w_cb, 0., 1.);
ev_timer_init(&timeout_watcher, second_timeout_w_cb, 0., 1.); timeout_watcher.data = this;
timeout_watcher.data = this;
} else { if (!config->is_rate_mode()) {
for (size_t i = 0; i < nclients; ++i) { for (size_t i = 0; i < nclients; ++i) {
auto req_todo = nreqs_per_client; auto req_todo = nreqs_per_client;
if (nreqs_rem > 0) { if (nreqs_rem > 0) {
@@ -754,6 +754,8 @@ Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients,
} }
Worker::~Worker() { Worker::~Worker() {
ev_timer_stop(loop, &timeout_watcher);
// first clear clients so that io watchers are stopped before // first clear clients so that io watchers are stopped before
// destructing ev_loop. // destructing ev_loop.
clients.clear(); clients.clear();
@@ -1017,6 +1019,8 @@ namespace {
void print_help(std::ostream &out) { void print_help(std::ostream &out) {
print_usage(out); print_usage(out);
auto config = Config();
out << R"( out << R"(
<URI> Specify URI to access. Multiple URIs can be specified. <URI> Specify URI to access. Multiple URIs can be specified.
URIs are used in this order for each client. All URIs URIs are used in this order for each client. All URIs
@@ -1113,6 +1117,7 @@ int main(int argc, char **argv) {
OPENSSL_config(nullptr); OPENSSL_config(nullptr);
std::string datafile; std::string datafile;
bool nreqs_set_manually = false;
while (1) { while (1) {
static int flag = 0; static int flag = 0;
static option long_options[] = { static option long_options[] = {
@@ -1142,6 +1147,7 @@ int main(int argc, char **argv) {
switch (c) { switch (c) {
case 'n': case 'n':
config.nreqs = strtoul(optarg, nullptr, 10); config.nreqs = strtoul(optarg, nullptr, 10);
nreqs_set_manually = true;
break; break;
case 'c': case 'c':
config.nclients = strtoul(optarg, nullptr, 10); config.nclients = strtoul(optarg, nullptr, 10);
@@ -1320,6 +1326,12 @@ int main(int argc, char **argv) {
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
if (nreqs_set_manually && config.rate > config.nreqs) {
std::cerr << "-r, -n: the connection rate must be smaller than or equal "
"to the number of requests." << std::endl;
exit(EXIT_FAILURE);
}
if (config.nconns != 0 && config.nconns < config.nthreads) { if (config.nconns != 0 && config.nconns < config.nthreads) {
std::cerr std::cerr
<< "-C, -t: the total number of connections must be greater than " << "-C, -t: the total number of connections must be greater than "
@@ -1327,6 +1339,13 @@ int main(int argc, char **argv) {
<< "to the number of threads." << std::endl; << "to the number of threads." << std::endl;
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
if (config.nconns == 0 && !nreqs_set_manually) {
std::cerr
<< "-r: the rate option must be used with either the -n option "
"or the -C option." << std::endl;
exit(EXIT_FAILURE);
}
} }
if (!datafile.empty()) { if (!datafile.empty()) {
@@ -1666,9 +1685,10 @@ int main(int argc, char **argv) {
double rps = 0; double rps = 0;
int64_t bps = 0; int64_t bps = 0;
if (duration.count() > 0) { if (duration.count() > 0) {
auto secd = static_cast<double>(duration.count()) / (1000 * 1000); auto secd = std::chrono::duration_cast<
rps = stats.req_success / secd; std::chrono::duration<double, std::chrono::seconds::period>>(duration);
bps = stats.bytes_total / secd; rps = stats.req_success / secd.count();
bps = stats.bytes_total / secd.count();
} }
std::cout << R"( std::cout << R"(

View File

@@ -117,6 +117,31 @@ template <typename T> struct Pool {
template <typename Memchunk> struct Memchunks { template <typename Memchunk> struct Memchunks {
Memchunks(Pool<Memchunk> *pool) Memchunks(Pool<Memchunk> *pool)
: pool(pool), head(nullptr), tail(nullptr), len(0) {} : pool(pool), head(nullptr), tail(nullptr), len(0) {}
Memchunks(const Memchunks &) = delete;
Memchunks(Memchunks &&other)
: pool(other.pool), head(other.head), tail(other.head), len(other.len) {
// keep other.pool
other.head = other.tail = nullptr;
other.len = 0;
}
Memchunks &operator=(const Memchunks &) = delete;
Memchunks &operator=(Memchunks &&other) {
if (this == &other) {
return *this;
}
reset();
pool = other.pool;
head = other.head;
tail = other.tail;
len = other.len;
other.head = other.tail = nullptr;
other.len = 0;
return *this;
}
~Memchunks() { ~Memchunks() {
if (!pool) { if (!pool) {
return; return;
@@ -223,15 +248,142 @@ template <typename Memchunk> struct Memchunks {
return i; return i;
} }
size_t rleft() const { return len; } size_t rleft() const { return len; }
void reset() {
for (auto m = head; m;) {
auto next = m->next;
pool->recycle(m);
m = next;
}
len = 0;
head = tail = nullptr;
}
Pool<Memchunk> *pool; Pool<Memchunk> *pool;
Memchunk *head, *tail; Memchunk *head, *tail;
size_t len; size_t len;
}; };
// Wrapper around Memchunks to offer "peeking" functionality.
template <typename Memchunk> struct PeekMemchunks {
PeekMemchunks(Pool<Memchunk> *pool)
: memchunks(pool), cur(nullptr), cur_pos(nullptr), cur_last(nullptr),
len(0), peeking(true) {}
PeekMemchunks(const PeekMemchunks &) = delete;
PeekMemchunks(PeekMemchunks &&other)
: memchunks(std::move(other.memchunks)), cur(other.cur),
cur_pos(other.cur_pos), cur_last(other.cur_last), len(other.len),
peeking(other.peeking) {
other.reset();
}
PeekMemchunks &operator=(const PeekMemchunks &) = delete;
PeekMemchunks &operator=(PeekMemchunks &&other) {
if (this == &other) {
return *this;
}
memchunks = std::move(other.memchunks);
cur = other.cur;
cur_pos = other.cur_pos;
cur_last = other.cur_last;
len = other.len;
peeking = other.peeking;
other.reset();
return *this;
}
size_t append(const void *src, size_t count) {
count = memchunks.append(src, count);
len += count;
return count;
}
size_t remove(void *dest, size_t count) {
if (!peeking) {
count = memchunks.remove(dest, count);
len -= count;
return count;
}
if (count == 0 || len == 0) {
return 0;
}
if (!cur) {
cur = memchunks.head;
cur_pos = cur->pos;
}
// cur_last could be updated in append
cur_last = cur->last;
if (cur_pos == cur_last) {
assert(cur->next);
cur = cur->next;
}
auto first = static_cast<uint8_t *>(dest);
auto last = first + count;
for (;;) {
auto n = std::min(last - first, cur_last - cur_pos);
first = std::copy_n(cur_pos, n, first);
cur_pos += n;
len -= n;
if (first == last) {
break;
}
assert(cur_pos == cur_last);
if (!cur->next) {
break;
}
cur = cur->next;
cur_pos = cur->pos;
cur_last = cur->last;
}
return first - static_cast<uint8_t *>(dest);
}
size_t rleft() const { return len; }
size_t rleft_buffered() const { return memchunks.rleft(); }
void disable_peek(bool drain) {
if (!peeking) {
return;
}
if (drain) {
auto n = rleft_buffered() - rleft();
memchunks.drain(n);
assert(len == memchunks.rleft());
} else {
len = memchunks.rleft();
}
cur = nullptr;
cur_pos = cur_last = nullptr;
peeking = false;
}
void reset() {
memchunks.reset();
cur = nullptr;
cur_pos = cur_last = nullptr;
len = 0;
peeking = true;
}
Memchunks<Memchunk> memchunks;
// Pointer to the Memchunk currently we are reading/writing.
Memchunk *cur;
// Region inside cur, we have processed to cur_pos.
uint8_t *cur_pos, *cur_last;
// This is the length we have left unprocessed. len <=
// memchunk.rleft() must hold.
size_t len;
// true if peeking is enabled. Initially it is true.
bool peeking;
};
using Memchunk16K = Memchunk<16_k>; using Memchunk16K = Memchunk<16_k>;
using MemchunkPool = Pool<Memchunk16K>; using MemchunkPool = Pool<Memchunk16K>;
using DefaultMemchunks = Memchunks<Memchunk16K>; using DefaultMemchunks = Memchunks<Memchunk16K>;
using DefaultPeekMemchunks = PeekMemchunks<Memchunk16K>;
#define DEFAULT_WR_IOVCNT 16 #define DEFAULT_WR_IOVCNT 16

View File

@@ -84,6 +84,7 @@ void test_pool_recycle(void) {
using Memchunk16 = Memchunk<16>; using Memchunk16 = Memchunk<16>;
using MemchunkPool16 = Pool<Memchunk16>; using MemchunkPool16 = Pool<Memchunk16>;
using Memchunks16 = Memchunks<Memchunk16>; using Memchunks16 = Memchunks<Memchunk16>;
using PeekMemchunks16 = PeekMemchunks<Memchunk16>;
void test_memchunks_append(void) { void test_memchunks_append(void) {
MemchunkPool16 pool; MemchunkPool16 pool;
@@ -196,4 +197,144 @@ void test_memchunks_recycle(void) {
CU_ASSERT(nullptr == m->next); CU_ASSERT(nullptr == m->next);
} }
void test_memchunks_reset(void) {
MemchunkPool16 pool;
Memchunks16 chunks(&pool);
std::array<uint8_t, 32> b{};
chunks.append(b.data(), b.size());
CU_ASSERT(32 == chunks.rleft());
chunks.reset();
CU_ASSERT(0 == chunks.rleft());
CU_ASSERT(nullptr == chunks.head);
CU_ASSERT(nullptr == chunks.tail);
auto m = pool.freelist;
CU_ASSERT(nullptr != m);
CU_ASSERT(nullptr != m->next);
CU_ASSERT(nullptr == m->next->next);
}
void test_peek_memchunks_append(void) {
MemchunkPool16 pool;
PeekMemchunks16 pchunks(&pool);
std::array<uint8_t, 32> b{{
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4',
'5', '6', '7', '8', '9', '0', '1',
}},
d;
pchunks.append(b.data(), b.size());
CU_ASSERT(32 == pchunks.rleft());
CU_ASSERT(32 == pchunks.rleft_buffered());
CU_ASSERT(0 == pchunks.remove(nullptr, 0));
CU_ASSERT(32 == pchunks.rleft());
CU_ASSERT(32 == pchunks.rleft_buffered());
CU_ASSERT(12 == pchunks.remove(d.data(), 12));
CU_ASSERT(std::equal(std::begin(b), std::begin(b) + 12, std::begin(d)));
CU_ASSERT(20 == pchunks.rleft());
CU_ASSERT(32 == pchunks.rleft_buffered());
CU_ASSERT(20 == pchunks.remove(d.data(), d.size()));
CU_ASSERT(std::equal(std::begin(b) + 12, std::end(b), std::begin(d)));
CU_ASSERT(0 == pchunks.rleft());
CU_ASSERT(32 == pchunks.rleft_buffered());
}
void test_peek_memchunks_disable_peek_drain(void) {
MemchunkPool16 pool;
PeekMemchunks16 pchunks(&pool);
std::array<uint8_t, 32> b{{
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4',
'5', '6', '7', '8', '9', '0', '1',
}},
d;
pchunks.append(b.data(), b.size());
CU_ASSERT(12 == pchunks.remove(d.data(), 12));
pchunks.disable_peek(true);
CU_ASSERT(!pchunks.peeking);
CU_ASSERT(20 == pchunks.rleft());
CU_ASSERT(20 == pchunks.rleft_buffered());
CU_ASSERT(20 == pchunks.remove(d.data(), d.size()));
CU_ASSERT(std::equal(std::begin(b) + 12, std::end(b), std::begin(d)));
CU_ASSERT(0 == pchunks.rleft());
CU_ASSERT(0 == pchunks.rleft_buffered());
}
void test_peek_memchunks_disable_peek_no_drain(void) {
MemchunkPool16 pool;
PeekMemchunks16 pchunks(&pool);
std::array<uint8_t, 32> b{{
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4',
'5', '6', '7', '8', '9', '0', '1',
}},
d;
pchunks.append(b.data(), b.size());
CU_ASSERT(12 == pchunks.remove(d.data(), 12));
pchunks.disable_peek(false);
CU_ASSERT(!pchunks.peeking);
CU_ASSERT(32 == pchunks.rleft());
CU_ASSERT(32 == pchunks.rleft_buffered());
CU_ASSERT(32 == pchunks.remove(d.data(), d.size()));
CU_ASSERT(std::equal(std::begin(b), std::end(b), std::begin(d)));
CU_ASSERT(0 == pchunks.rleft());
CU_ASSERT(0 == pchunks.rleft_buffered());
}
void test_peek_memchunks_reset(void) {
MemchunkPool16 pool;
PeekMemchunks16 pchunks(&pool);
std::array<uint8_t, 32> b{{
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4',
'5', '6', '7', '8', '9', '0', '1',
}},
d;
pchunks.append(b.data(), b.size());
CU_ASSERT(12 == pchunks.remove(d.data(), 12));
pchunks.disable_peek(true);
pchunks.reset();
CU_ASSERT(0 == pchunks.rleft());
CU_ASSERT(0 == pchunks.rleft_buffered());
CU_ASSERT(nullptr == pchunks.cur);
CU_ASSERT(nullptr == pchunks.cur_pos);
CU_ASSERT(nullptr == pchunks.cur_last);
CU_ASSERT(pchunks.peeking);
}
} // namespace nghttp2 } // namespace nghttp2

View File

@@ -36,6 +36,11 @@ void test_memchunks_append(void);
void test_memchunks_drain(void); void test_memchunks_drain(void);
void test_memchunks_riovec(void); void test_memchunks_riovec(void);
void test_memchunks_recycle(void); void test_memchunks_recycle(void);
void test_memchunks_reset(void);
void test_peek_memchunks_append(void);
void test_peek_memchunks_disable_peek_drain(void);
void test_peek_memchunks_disable_peek_no_drain(void);
void test_peek_memchunks_reset(void);
} // namespace nghttp2 } // namespace nghttp2

View File

@@ -172,7 +172,16 @@ int main(int argc, char *argv[]) {
!CU_add_test(pSuite, "memchunk_drain", nghttp2::test_memchunks_drain) || !CU_add_test(pSuite, "memchunk_drain", nghttp2::test_memchunks_drain) ||
!CU_add_test(pSuite, "memchunk_riovec", nghttp2::test_memchunks_riovec) || !CU_add_test(pSuite, "memchunk_riovec", nghttp2::test_memchunks_riovec) ||
!CU_add_test(pSuite, "memchunk_recycle", !CU_add_test(pSuite, "memchunk_recycle",
nghttp2::test_memchunks_recycle)) { nghttp2::test_memchunks_recycle) ||
!CU_add_test(pSuite, "memchunk_reset", nghttp2::test_memchunks_reset) ||
!CU_add_test(pSuite, "peek_memchunk_append",
nghttp2::test_peek_memchunks_append) ||
!CU_add_test(pSuite, "peek_memchunk_disable_peek_drain",
nghttp2::test_peek_memchunks_disable_peek_drain) ||
!CU_add_test(pSuite, "peek_memchunk_disable_peek_no_drain",
nghttp2::test_peek_memchunks_disable_peek_no_drain) ||
!CU_add_test(pSuite, "peek_memchunk_reset",
nghttp2::test_peek_memchunks_reset)) {
CU_cleanup_registry(); CU_cleanup_registry();
return CU_get_error(); return CU_get_error();
} }

View File

@@ -775,7 +775,7 @@ void memcached_get_ticket_key_cb(struct ev_loop *loop, ev_timer *w,
auto key = TicketKey(); auto key = TicketKey();
key.cipher = get_config()->tls_ticket_key_cipher; key.cipher = get_config()->tls_ticket_key_cipher;
key.hmac = EVP_sha256(); key.hmac = EVP_sha256();
key.hmac_keylen = EVP_MD_size(key.hmac); key.hmac_keylen = hmac_keylen;
std::copy_n(p, key.data.name.size(), key.data.name.data()); std::copy_n(p, key.data.name.size(), key.data.name.data());
p += key.data.name.size(); p += key.data.name.size();

View File

@@ -358,7 +358,8 @@ int ClientHandler::upstream_http1_connhd_read() {
ClientHandler::ClientHandler(Worker *worker, int fd, SSL *ssl, ClientHandler::ClientHandler(Worker *worker, int fd, SSL *ssl,
const char *ipaddr, const char *port) const char *ipaddr, const char *port)
: conn_(worker->get_loop(), fd, ssl, get_config()->upstream_write_timeout, : conn_(worker->get_loop(), fd, ssl, worker->get_mcpool(),
get_config()->upstream_write_timeout,
get_config()->upstream_read_timeout, get_config()->write_rate, get_config()->upstream_read_timeout, get_config()->write_rate,
get_config()->write_burst, get_config()->read_rate, get_config()->write_burst, get_config()->read_rate,
get_config()->read_burst, writecb, readcb, timeoutcb, this), get_config()->read_burst, writecb, readcb, timeoutcb, this),
@@ -849,6 +850,4 @@ ev_io *ClientHandler::get_wev() { return &conn_.wev; }
Worker *ClientHandler::get_worker() const { return worker_; } Worker *ClientHandler::get_worker() const { return worker_; }
Connection *ClientHandler::get_connection() { return &conn_; }
} // namespace shrpx } // namespace shrpx

View File

@@ -130,8 +130,6 @@ public:
void signal_write(); void signal_write();
ev_io *get_wev(); ev_io *get_wev();
Connection *get_connection();
private: private:
Connection conn_; Connection conn_;
ev_timer reneg_shutdown_timer_; ev_timer reneg_shutdown_timer_;

View File

@@ -40,12 +40,13 @@ using namespace nghttp2;
namespace shrpx { namespace shrpx {
Connection::Connection(struct ev_loop *loop, int fd, SSL *ssl, Connection::Connection(struct ev_loop *loop, int fd, SSL *ssl,
ev_tstamp write_timeout, ev_tstamp read_timeout, MemchunkPool *mcpool, ev_tstamp write_timeout,
size_t write_rate, size_t write_burst, size_t read_rate, ev_tstamp read_timeout, size_t write_rate,
size_t read_burst, IOCb writecb, IOCb readcb, size_t write_burst, size_t read_rate, size_t read_burst,
TimerCb timeoutcb, void *data) IOCb writecb, IOCb readcb, TimerCb timeoutcb, void *data)
: tls{}, wlimit(loop, &wev, write_rate, write_burst), : tls{DefaultMemchunks(mcpool), DefaultPeekMemchunks(mcpool)},
rlimit(loop, &rev, read_rate, read_burst, ssl), writecb(writecb), wlimit(loop, &wev, write_rate, write_burst),
rlimit(loop, &rev, read_rate, read_burst, this), writecb(writecb),
readcb(readcb), timeoutcb(timeoutcb), loop(loop), data(data), fd(fd) { readcb(readcb), timeoutcb(timeoutcb), loop(loop), data(data), fd(fd) {
ev_io_init(&wev, writecb, fd, EV_WRITE); ev_io_init(&wev, writecb, fd, EV_WRITE);
@@ -77,22 +78,18 @@ Connection::~Connection() {
} }
void Connection::disconnect() { void Connection::disconnect() {
ev_timer_stop(loop, &rt);
ev_timer_stop(loop, &wt);
rlimit.stopw();
wlimit.stopw();
if (tls.ssl) { if (tls.ssl) {
SSL_set_shutdown(tls.ssl, SSL_RECEIVED_SHUTDOWN); SSL_set_shutdown(tls.ssl, SSL_RECEIVED_SHUTDOWN);
ERR_clear_error(); ERR_clear_error();
if (tls.cached_session) { if (tls.cached_session) {
SSL_SESSION_free(tls.cached_session); SSL_SESSION_free(tls.cached_session);
tls.cached_session = nullptr;
} }
if (tls.cached_session_lookup_req) { if (tls.cached_session_lookup_req) {
tls.cached_session_lookup_req->canceled = true; tls.cached_session_lookup_req->canceled = true;
tls.cached_session_lookup_req = nullptr;
} }
// To reuse SSL/TLS session, we have to shutdown, and don't free // To reuse SSL/TLS session, we have to shutdown, and don't free
@@ -102,7 +99,15 @@ void Connection::disconnect() {
tls.ssl = nullptr; tls.ssl = nullptr;
} }
tls = {tls.ssl}; tls.wbuf.reset();
tls.rbuf.reset();
tls.last_write_idle = 0.;
tls.warmup_writelen = 0;
tls.last_writelen = 0;
tls.last_readlen = 0;
tls.handshake_state = 0;
tls.initial_handshake_done = false;
tls.reneg_started = false;
} }
if (fd != -1) { if (fd != -1) {
@@ -110,24 +115,19 @@ void Connection::disconnect() {
close(fd); close(fd);
fd = -1; fd = -1;
} }
// Stop watchers here because they could be activated in
// SSL_shutdown().
ev_timer_stop(loop, &rt);
ev_timer_stop(loop, &wt);
rlimit.stopw();
wlimit.stopw();
} }
namespace { void Connection::prepare_client_handshake() { SSL_set_connect_state(tls.ssl); }
void allocate_buffer(Connection *conn) {
conn->tls.rb = make_unique<Buffer<16_k>>();
conn->tls.wb = make_unique<Buffer<16_k>>();
}
} // namespace
void Connection::prepare_client_handshake() { void Connection::prepare_server_handshake() { SSL_set_accept_state(tls.ssl); }
SSL_set_connect_state(tls.ssl);
allocate_buffer(this);
}
void Connection::prepare_server_handshake() {
SSL_set_accept_state(tls.ssl);
allocate_buffer(this);
}
// BIO implementation is inspired by openldap implementation: // BIO implementation is inspired by openldap implementation:
// http://www.openldap.org/devel/cvsweb.cgi/~checkout~/libraries/libldap/tls_o.c // http://www.openldap.org/devel/cvsweb.cgi/~checkout~/libraries/libldap/tls_o.c
@@ -138,27 +138,26 @@ int shrpx_bio_write(BIO *b, const char *buf, int len) {
} }
auto conn = static_cast<Connection *>(b->ptr); auto conn = static_cast<Connection *>(b->ptr);
auto &wb = conn->tls.wb; auto &wbuf = conn->tls.wbuf;
BIO_clear_retry_flags(b); BIO_clear_retry_flags(b);
if (conn->tls.initial_handshake_done) { if (conn->tls.initial_handshake_done) {
// After handshake finished, send |buf| of length |len| to the // After handshake finished, send |buf| of length |len| to the
// socket directly. // socket directly.
if (wb && wb->rleft()) { if (wbuf.rleft()) {
auto nwrite = conn->write_clear(wb->pos, wb->rleft()); std::array<struct iovec, 4> iov;
auto iovcnt = wbuf.riovec(iov.data(), iov.size());
auto nwrite = conn->writev_clear(iov.data(), iovcnt);
if (nwrite < 0) { if (nwrite < 0) {
return -1; return -1;
} }
wb->drain(nwrite); wbuf.drain(nwrite);
if (wb->rleft()) { if (wbuf.rleft()) {
BIO_set_retry_write(b); BIO_set_retry_write(b);
return -1; return -1;
} }
// Here delete TLS write buffer
wb.reset();
} }
auto nwrite = conn->write_clear(buf, len); auto nwrite = conn->write_clear(buf, len);
if (nwrite < 0) { if (nwrite < 0) {
@@ -173,16 +172,9 @@ int shrpx_bio_write(BIO *b, const char *buf, int len) {
return nwrite; return nwrite;
} }
auto nwrite = std::min(static_cast<size_t>(len), wb->wleft()); wbuf.append(buf, len);
if (nwrite == 0) { return len;
BIO_set_retry_write(b);
return -1;
}
wb->write(buf, nwrite);
return nwrite;
} }
} // namespace } // namespace
@@ -193,11 +185,11 @@ int shrpx_bio_read(BIO *b, char *buf, int len) {
} }
auto conn = static_cast<Connection *>(b->ptr); auto conn = static_cast<Connection *>(b->ptr);
auto &rb = conn->tls.rb; auto &rbuf = conn->tls.rbuf;
BIO_clear_retry_flags(b); BIO_clear_retry_flags(b);
if (conn->tls.initial_handshake_done && !rb) { if (conn->tls.initial_handshake_done && rbuf.rleft() == 0) {
auto nread = conn->read_clear(buf, len); auto nread = conn->read_clear(buf, len);
if (nread < 0) { if (nread < 0) {
return -1; return -1;
@@ -209,22 +201,12 @@ int shrpx_bio_read(BIO *b, char *buf, int len) {
return nread; return nread;
} }
auto nread = std::min(static_cast<size_t>(len), rb->rleft()); if (rbuf.rleft() == 0) {
if (nread == 0) {
if (conn->tls.initial_handshake_done) {
rb.reset();
}
BIO_set_retry_read(b); BIO_set_retry_read(b);
return -1; return -1;
} }
std::copy_n(rb->pos, nread, buf); return rbuf.remove(buf, len);
rb->drain(nread);
return nread;
} }
} // namespace } // namespace
@@ -287,42 +269,59 @@ void Connection::set_ssl(SSL *ssl) {
bio->ptr = this; bio->ptr = this;
SSL_set_bio(tls.ssl, bio, bio); SSL_set_bio(tls.ssl, bio, bio);
SSL_set_app_data(tls.ssl, this); SSL_set_app_data(tls.ssl, this);
rlimit.set_ssl(tls.ssl);
} }
namespace {
// We should buffer at least full encrypted TLS record here.
// Theoretically, peer can send client hello in several TLS records,
// which could exeed this limit, but it is not portable, and we don't
// have to handle such exotic behaviour.
bool read_buffer_full(DefaultPeekMemchunks &rbuf) {
return rbuf.rleft_buffered() >= 20_k;
}
} // namespace
int Connection::tls_handshake() { int Connection::tls_handshake() {
wlimit.stopw(); wlimit.stopw();
ev_timer_stop(loop, &wt); ev_timer_stop(loop, &wt);
auto nread = read_clear(tls.rb->last, tls.rb->wleft()); if (ev_is_active(&rev)) {
if (nread < 0) { std::array<uint8_t, 8_k> buf;
if (LOG_ENABLED(INFO)) { auto nread = read_clear(buf.data(), buf.size());
LOG(INFO) << "tls: handshake read error"; if (nread < 0) {
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "tls: handshake read error";
}
return -1;
}
tls.rbuf.append(buf.data(), nread);
if (read_buffer_full(tls.rbuf)) {
rlimit.stopw();
} }
return -1;
} }
tls.rb->write(nread);
switch (tls.handshake_state) { switch (tls.handshake_state) {
case TLS_CONN_WAIT_FOR_SESSION_CACHE: case TLS_CONN_WAIT_FOR_SESSION_CACHE:
if (tls.rb->wleft() == 0) {
// Input buffer is full. Disable read until cache is returned
rlimit.stopw();
ev_timer_stop(loop, &rt);
}
return SHRPX_ERR_INPROGRESS; return SHRPX_ERR_INPROGRESS;
case TLS_CONN_GOT_SESSION_CACHE: { case TLS_CONN_GOT_SESSION_CACHE: {
// Use the same trick invented by @kazuho in h2o project // Use the same trick invented by @kazuho in h2o project.
tls.wb->reset();
tls.rb->pos = tls.rb->begin(); // Discard all outgoing data.
tls.wbuf.reset();
// Rewind buffered incoming data to replay client hello.
tls.rbuf.disable_peek(false);
auto ssl_ctx = SSL_get_SSL_CTX(tls.ssl); auto ssl_ctx = SSL_get_SSL_CTX(tls.ssl);
auto ssl_opts = SSL_get_options(tls.ssl);
SSL_free(tls.ssl); SSL_free(tls.ssl);
auto ssl = ssl::create_server_ssl(ssl_ctx, nullptr); auto ssl = ssl::create_ssl(ssl_ctx);
if (!ssl) { if (!ssl) {
return -1; return -1;
} }
if (ssl_opts & SSL_OP_NO_TICKET) {
SSL_set_options(ssl, SSL_OP_NO_TICKET);
}
set_ssl(ssl); set_ssl(ssl);
@@ -342,6 +341,13 @@ int Connection::tls_handshake() {
auto err = SSL_get_error(tls.ssl, rv); auto err = SSL_get_error(tls.ssl, rv);
switch (err) { switch (err) {
case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_READ:
if (read_buffer_full(tls.rbuf)) {
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "tls: handshake message is too large";
}
return -1;
}
break;
case SSL_ERROR_WANT_WRITE: case SSL_ERROR_WANT_WRITE:
break; break;
default: default:
@@ -359,20 +365,33 @@ int Connection::tls_handshake() {
return SHRPX_ERR_INPROGRESS; return SHRPX_ERR_INPROGRESS;
} }
if (tls.wb->rleft()) { if (tls.wbuf.rleft()) {
auto nwrite = write_clear(tls.wb->pos, tls.wb->rleft()); // First write indicates that resumption stuff has done.
if (tls.handshake_state != TLS_CONN_WRITE_STARTED) {
tls.handshake_state = TLS_CONN_WRITE_STARTED;
// If peek has already disabled, this is noop.
tls.rbuf.disable_peek(true);
}
std::array<struct iovec, 4> iov;
auto iovcnt = tls.wbuf.riovec(iov.data(), iov.size());
auto nwrite = writev_clear(iov.data(), iovcnt);
if (nwrite < 0) { if (nwrite < 0) {
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
LOG(INFO) << "tls: handshake write error"; LOG(INFO) << "tls: handshake write error";
} }
return -1; return -1;
} }
tls.wb->drain(nwrite); tls.wbuf.drain(nwrite);
if (tls.wbuf.rleft()) {
wlimit.startw();
ev_timer_again(loop, &wt);
}
} }
if (tls.wb->rleft()) { if (!read_buffer_full(tls.rbuf)) {
wlimit.startw(); // We may have stopped reading
ev_timer_again(loop, &wt); rlimit.startw();
} }
if (rv != 1) { if (rv != 1) {
@@ -384,6 +403,15 @@ int Connection::tls_handshake() {
tls.initial_handshake_done = true; tls.initial_handshake_done = true;
// We have to start read watcher, since later stage of code expects
// this.
rlimit.startw();
// We may have whole request in tls.rbuf. This means that we don't
// get notified further read event. This is especially true for
// HTTP/1.1.
handle_tls_pending_read();
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
LOG(INFO) << "SSL/TLS handshake completed"; LOG(INFO) << "SSL/TLS handshake completed";
if (SSL_session_reused(tls.ssl)) { if (SSL_session_reused(tls.ssl)) {
@@ -463,8 +491,8 @@ ssize_t Connection::write_tls(const void *data, size_t len) {
return SHRPX_ERR_NETWORK; return SHRPX_ERR_NETWORK;
case SSL_ERROR_WANT_WRITE: case SSL_ERROR_WANT_WRITE:
tls.last_writelen = len; tls.last_writelen = len;
wlimit.startw(); // starting write watcher and timer is done in write_clear via
ev_timer_again(loop, &wt); // bio.
return 0; return 0;
default: default:
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {

View File

@@ -35,7 +35,7 @@
#include "shrpx_rate_limit.h" #include "shrpx_rate_limit.h"
#include "shrpx_error.h" #include "shrpx_error.h"
#include "buffer.h" #include "memchunk.h"
namespace shrpx { namespace shrpx {
@@ -46,9 +46,12 @@ enum {
TLS_CONN_WAIT_FOR_SESSION_CACHE, TLS_CONN_WAIT_FOR_SESSION_CACHE,
TLS_CONN_GOT_SESSION_CACHE, TLS_CONN_GOT_SESSION_CACHE,
TLS_CONN_CANCEL_SESSION_CACHE, TLS_CONN_CANCEL_SESSION_CACHE,
TLS_CONN_WRITE_STARTED,
}; };
struct TLSConnection { struct TLSConnection {
DefaultMemchunks wbuf;
DefaultPeekMemchunks rbuf;
SSL *ssl; SSL *ssl;
SSL_SESSION *cached_session; SSL_SESSION *cached_session;
MemcachedRequest *cached_session_lookup_req; MemcachedRequest *cached_session_lookup_req;
@@ -61,8 +64,6 @@ struct TLSConnection {
int handshake_state; int handshake_state;
bool initial_handshake_done; bool initial_handshake_done;
bool reneg_started; bool reneg_started;
std::unique_ptr<Buffer<16_k>> rb;
std::unique_ptr<Buffer<16_k>> wb;
}; };
template <typename T> using EVCb = void (*)(struct ev_loop *, T *, int); template <typename T> using EVCb = void (*)(struct ev_loop *, T *, int);
@@ -71,10 +72,10 @@ using IOCb = EVCb<ev_io>;
using TimerCb = EVCb<ev_timer>; using TimerCb = EVCb<ev_timer>;
struct Connection { struct Connection {
Connection(struct ev_loop *loop, int fd, SSL *ssl, ev_tstamp write_timeout, Connection(struct ev_loop *loop, int fd, SSL *ssl, MemchunkPool *mcpool,
ev_tstamp read_timeout, size_t write_rate, size_t write_burst, ev_tstamp write_timeout, ev_tstamp read_timeout, size_t write_rate,
size_t read_rate, size_t read_burst, IOCb writecb, IOCb readcb, size_t write_burst, size_t read_rate, size_t read_burst,
TimerCb timeoutcb, void *data); IOCb writecb, IOCb readcb, TimerCb timeoutcb, void *data);
~Connection(); ~Connection();
void disconnect(); void disconnect();

View File

@@ -144,7 +144,8 @@ void writecb(struct ev_loop *loop, ev_io *w, int revents) {
Http2Session::Http2Session(struct ev_loop *loop, SSL_CTX *ssl_ctx, Http2Session::Http2Session(struct ev_loop *loop, SSL_CTX *ssl_ctx,
ConnectBlocker *connect_blocker, Worker *worker, ConnectBlocker *connect_blocker, Worker *worker,
size_t group, size_t idx) size_t group, size_t idx)
: conn_(loop, -1, nullptr, get_config()->downstream_write_timeout, : conn_(loop, -1, nullptr, worker->get_mcpool(),
get_config()->downstream_write_timeout,
get_config()->downstream_read_timeout, 0, 0, 0, 0, writecb, readcb, get_config()->downstream_read_timeout, 0, 0, 0, 0, writecb, readcb,
timeoutcb, this), timeoutcb, this),
worker_(worker), connect_blocker_(connect_blocker), ssl_ctx_(ssl_ctx), worker_(worker), connect_blocker_(connect_blocker), ssl_ctx_(ssl_ctx),
@@ -323,7 +324,7 @@ int Http2Session::initiate_connection() {
// We are establishing TLS connection. If conn_.tls.ssl, we may // We are establishing TLS connection. If conn_.tls.ssl, we may
// reuse the previous session. // reuse the previous session.
if (!conn_.tls.ssl) { if (!conn_.tls.ssl) {
auto ssl = ssl::create_client_ssl(ssl_ctx_); auto ssl = ssl::create_ssl(ssl_ctx_);
if (!ssl) { if (!ssl) {
return -1; return -1;
} }

View File

@@ -112,7 +112,7 @@ void connectcb(struct ev_loop *loop, ev_io *w, int revents) {
HttpDownstreamConnection::HttpDownstreamConnection( HttpDownstreamConnection::HttpDownstreamConnection(
DownstreamConnectionPool *dconn_pool, size_t group, struct ev_loop *loop) DownstreamConnectionPool *dconn_pool, size_t group, struct ev_loop *loop)
: DownstreamConnection(dconn_pool), : DownstreamConnection(dconn_pool),
conn_(loop, -1, nullptr, get_config()->downstream_write_timeout, conn_(loop, -1, nullptr, nullptr, get_config()->downstream_write_timeout,
get_config()->downstream_read_timeout, 0, 0, 0, 0, connectcb, get_config()->downstream_read_timeout, 0, 0, 0, 0, connectcb,
readcb, timeoutcb, this), readcb, timeoutcb, this),
ioctrl_(&conn_.rlimit), response_htp_{0}, group_(group), addr_idx_(0), ioctrl_(&conn_.rlimit), response_htp_{0}, group_(group), addr_idx_(0),

View File

@@ -92,7 +92,7 @@ constexpr ev_tstamp read_timeout = 10.;
MemcachedConnection::MemcachedConnection(const Address *addr, MemcachedConnection::MemcachedConnection(const Address *addr,
struct ev_loop *loop) struct ev_loop *loop)
: conn_(loop, -1, nullptr, write_timeout, read_timeout, 0, 0, 0, 0, : conn_(loop, -1, nullptr, nullptr, write_timeout, read_timeout, 0, 0, 0, 0,
connectcb, readcb, timeoutcb, this), connectcb, readcb, timeoutcb, this),
parse_state_{}, addr_(addr), sendsum_(0), connected_(false) {} parse_state_{}, addr_(addr), sendsum_(0), connected_(false) {}
@@ -403,6 +403,7 @@ int MemcachedConnection::parse_packet() {
return 0; return 0;
} }
#undef DEFAULT_WR_IOVCNT
#define DEFAULT_WR_IOVCNT 128 #define DEFAULT_WR_IOVCNT 128
#if defined(IOV_MAX) && IOV_MAX < DEFAULT_WR_IOVCNT #if defined(IOV_MAX) && IOV_MAX < DEFAULT_WR_IOVCNT

View File

@@ -26,6 +26,8 @@
#include <limits> #include <limits>
#include "shrpx_connection.h"
namespace shrpx { namespace shrpx {
namespace { namespace {
@@ -36,9 +38,9 @@ void regencb(struct ev_loop *loop, ev_timer *w, int revents) {
} // namespace } // namespace
RateLimit::RateLimit(struct ev_loop *loop, ev_io *w, size_t rate, size_t burst, RateLimit::RateLimit(struct ev_loop *loop, ev_io *w, size_t rate, size_t burst,
SSL *ssl) Connection *conn)
: w_(w), loop_(loop), ssl_(ssl), rate_(rate), burst_(burst), avail_(burst), : w_(w), loop_(loop), conn_(conn), rate_(rate), burst_(burst),
startw_req_(false) { avail_(burst), startw_req_(false) {
ev_timer_init(&t_, regencb, 0., 1.); ev_timer_init(&t_, regencb, 0., 1.);
t_.data = this; t_.data = this;
if (rate_ > 0) { if (rate_ > 0) {
@@ -97,7 +99,8 @@ void RateLimit::stopw() {
} }
void RateLimit::handle_tls_pending_read() { void RateLimit::handle_tls_pending_read() {
if (!ssl_ || SSL_pending(ssl_) == 0) { if (!conn_ || !conn_->tls.ssl ||
(SSL_pending(conn_->tls.ssl) == 0 && conn_->tls.rbuf.rleft() == 0)) {
return; return;
} }
@@ -106,6 +109,4 @@ void RateLimit::handle_tls_pending_read() {
ev_feed_event(loop_, w_, EV_READ); ev_feed_event(loop_, w_, EV_READ);
} }
void RateLimit::set_ssl(SSL *ssl) { ssl_ = ssl; }
} // namespace shrpx } // namespace shrpx

View File

@@ -33,28 +33,30 @@
namespace shrpx { namespace shrpx {
struct Connection;
class RateLimit { class RateLimit {
public: public:
// We need |ssl| object to check that it has unread decrypted bytes. // We need |conn| object to check that it has unread bytes for TLS
// connection.
RateLimit(struct ev_loop *loop, ev_io *w, size_t rate, size_t burst, RateLimit(struct ev_loop *loop, ev_io *w, size_t rate, size_t burst,
SSL *ssl = nullptr); Connection *conn = nullptr);
~RateLimit(); ~RateLimit();
size_t avail() const; size_t avail() const;
void drain(size_t n); void drain(size_t n);
void regen(); void regen();
void startw(); void startw();
void stopw(); void stopw();
// Feeds event if ssl_ object has unread decrypted bytes. This is // Feeds event if conn_->tls object has unread bytes. This is
// required since it is buffered in ssl_ object, io event is not // required since it is buffered in conn_->tls object, io event is
// generated unless new incoming data is received. // not generated unless new incoming data is received.
void handle_tls_pending_read(); void handle_tls_pending_read();
void set_ssl(SSL *ssl);
private: private:
ev_timer t_; ev_timer t_;
ev_io *w_; ev_io *w_;
struct ev_loop *loop_; struct ev_loop *loop_;
SSL *ssl_; Connection *conn_;
size_t rate_; size_t rate_;
size_t burst_; size_t burst_;
size_t avail_; size_t avail_;

View File

@@ -134,7 +134,8 @@ int ssl_pem_passwd_cb(char *buf, int size, int rwflag, void *user_data) {
namespace { namespace {
int servername_callback(SSL *ssl, int *al, void *arg) { int servername_callback(SSL *ssl, int *al, void *arg) {
auto handler = static_cast<ClientHandler *>(SSL_get_app_data(ssl)); auto conn = static_cast<Connection *>(SSL_get_app_data(ssl));
auto handler = static_cast<ClientHandler *>(conn->data);
auto worker = handler->get_worker(); auto worker = handler->get_worker();
auto cert_tree = worker->get_cert_lookup_tree(); auto cert_tree = worker->get_cert_lookup_tree();
if (cert_tree) { if (cert_tree) {
@@ -190,7 +191,8 @@ constexpr char MEMCACHED_SESSION_CACHE_KEY_PREFIX[] =
namespace { namespace {
int tls_session_new_cb(SSL *ssl, SSL_SESSION *session) { int tls_session_new_cb(SSL *ssl, SSL_SESSION *session) {
auto handler = static_cast<ClientHandler *>(SSL_get_app_data(ssl)); auto conn = static_cast<Connection *>(SSL_get_app_data(ssl));
auto handler = static_cast<ClientHandler *>(conn->data);
auto worker = handler->get_worker(); auto worker = handler->get_worker();
auto dispatcher = worker->get_session_cache_memcached_dispatcher(); auto dispatcher = worker->get_session_cache_memcached_dispatcher();
@@ -236,10 +238,10 @@ int tls_session_new_cb(SSL *ssl, SSL_SESSION *session) {
namespace { namespace {
SSL_SESSION *tls_session_get_cb(SSL *ssl, unsigned char *id, int idlen, SSL_SESSION *tls_session_get_cb(SSL *ssl, unsigned char *id, int idlen,
int *copy) { int *copy) {
auto handler = static_cast<ClientHandler *>(SSL_get_app_data(ssl)); auto conn = static_cast<Connection *>(SSL_get_app_data(ssl));
auto handler = static_cast<ClientHandler *>(conn->data);
auto worker = handler->get_worker(); auto worker = handler->get_worker();
auto dispatcher = worker->get_session_cache_memcached_dispatcher(); auto dispatcher = worker->get_session_cache_memcached_dispatcher();
auto conn = handler->get_connection();
if (conn->tls.cached_session) { if (conn->tls.cached_session) {
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
@@ -309,7 +311,8 @@ SSL_SESSION *tls_session_get_cb(SSL *ssl, unsigned char *id, int idlen,
namespace { namespace {
int ticket_key_cb(SSL *ssl, unsigned char *key_name, unsigned char *iv, int ticket_key_cb(SSL *ssl, unsigned char *key_name, unsigned char *iv,
EVP_CIPHER_CTX *ctx, HMAC_CTX *hctx, int enc) { EVP_CIPHER_CTX *ctx, HMAC_CTX *hctx, int enc) {
auto handler = static_cast<ClientHandler *>(SSL_get_app_data(ssl)); auto conn = static_cast<Connection *>(SSL_get_app_data(ssl));
auto handler = static_cast<ClientHandler *>(conn->data);
auto worker = handler->get_worker(); auto worker = handler->get_worker();
auto ticket_keys = worker->get_ticket_keys(); auto ticket_keys = worker->get_ticket_keys();
@@ -385,7 +388,6 @@ void info_callback(const SSL *ssl, int where, int ret) {
if (where & SSL_CB_HANDSHAKE_START) { if (where & SSL_CB_HANDSHAKE_START) {
auto conn = static_cast<Connection *>(SSL_get_app_data(ssl)); auto conn = static_cast<Connection *>(SSL_get_app_data(ssl));
if (conn && conn->tls.initial_handshake_done) { if (conn && conn->tls.initial_handshake_done) {
// We only set SSL_get_app_data for ClientHandler for now.
auto handler = static_cast<ClientHandler *>(conn->data); auto handler = static_cast<ClientHandler *>(conn->data);
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
CLOG(INFO, handler) << "TLS renegotiation started"; CLOG(INFO, handler) << "TLS renegotiation started";
@@ -695,7 +697,6 @@ SSL_CTX *create_ssl_client_context() {
return ssl_ctx; return ssl_ctx;
} }
namespace {
SSL *create_ssl(SSL_CTX *ssl_ctx) { SSL *create_ssl(SSL_CTX *ssl_ctx) {
auto ssl = SSL_new(ssl_ctx); auto ssl = SSL_new(ssl_ctx);
if (!ssl) { if (!ssl) {
@@ -706,23 +707,6 @@ SSL *create_ssl(SSL_CTX *ssl_ctx) {
return ssl; return ssl;
} }
} // namespace
SSL *create_server_ssl(SSL_CTX *ssl_ctx, Worker *worker) {
auto ssl = create_ssl(ssl_ctx);
if (!ssl) {
return nullptr;
}
// Disable TLS session ticket if we don't have working ticket keys.
if (worker && !worker->get_ticket_keys()) {
SSL_set_options(ssl, SSL_OP_NO_TICKET);
}
return ssl;
}
SSL *create_client_ssl(SSL_CTX *ssl_ctx) { return create_ssl(ssl_ctx); }
ClientHandler *accept_connection(Worker *worker, int fd, sockaddr *addr, ClientHandler *accept_connection(Worker *worker, int fd, sockaddr *addr,
int addrlen) { int addrlen) {
@@ -746,10 +730,15 @@ ClientHandler *accept_connection(Worker *worker, int fd, sockaddr *addr,
SSL *ssl = nullptr; SSL *ssl = nullptr;
auto ssl_ctx = worker->get_sv_ssl_ctx(); auto ssl_ctx = worker->get_sv_ssl_ctx();
if (ssl_ctx) { if (ssl_ctx) {
ssl = create_server_ssl(ssl_ctx, worker); ssl = create_ssl(ssl_ctx);
if (!ssl) { if (!ssl) {
return nullptr; return nullptr;
} }
// Disable TLS session ticket if we don't have working ticket
// keys.
if (!worker->get_ticket_keys()) {
SSL_set_options(ssl, SSL_OP_NO_TICKET);
}
} }
return new ClientHandler(worker, fd, ssl, host, service); return new ClientHandler(worker, fd, ssl, host, service);

View File

@@ -172,8 +172,7 @@ SSL_CTX *setup_client_ssl_context();
// this function returns nullptr. // this function returns nullptr.
CertLookupTree *create_cert_lookup_tree(); CertLookupTree *create_cert_lookup_tree();
SSL *create_server_ssl(SSL_CTX *ssl_ctx, Worker *worker); SSL *create_ssl(SSL_CTX *ssl_ctx);
SSL *create_client_ssl(SSL_CTX *ssl_ctx);
} // namespace ssl } // namespace ssl

View File

@@ -1158,7 +1158,7 @@ uint64_t get_uint64(const uint8_t *data) {
n += static_cast<uint64_t>(data[1]) << 48; n += static_cast<uint64_t>(data[1]) << 48;
n += static_cast<uint64_t>(data[2]) << 40; n += static_cast<uint64_t>(data[2]) << 40;
n += static_cast<uint64_t>(data[3]) << 32; n += static_cast<uint64_t>(data[3]) << 32;
n += data[4] << 24; n += static_cast<uint64_t>(data[4]) << 24;
n += data[5] << 16; n += data[5] << 16;
n += data[6] << 8; n += data[6] << 8;
n += data[7]; n += data[7];

View File

@@ -394,12 +394,22 @@ void test_util_localtime_date(void) {
} }
void test_util_get_uint64(void) { void test_util_get_uint64(void) {
auto v = std::array<unsigned char, 8>{ {
{0x01, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xab, 0xbc}}; auto v = std::array<unsigned char, 8>{
{0x01, 0x12, 0x34, 0x56, 0xff, 0x9a, 0xab, 0xbc}};
auto n = util::get_uint64(v.data()); auto n = util::get_uint64(v.data());
CU_ASSERT(0x01123456789aabbcULL == n); CU_ASSERT(0x01123456ff9aabbcULL == n);
}
{
auto v = std::array<unsigned char, 8>{
{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}};
auto n = util::get_uint64(v.data());
CU_ASSERT(0xffffffffffffffffULL == n);
}
} }
} // namespace shrpx } // namespace shrpx