Merge pull request #4 from tatsuhiro-t/master
Merging tatsuhiro-t:master -> nshoemaker:master
This commit is contained in:
commit
e2808dcd96
|
@ -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])
|
||||
|
|
|
@ -223,7 +223,7 @@ $(APIDOC): apidoc.stamp
|
|||
fi
|
||||
|
||||
clean-local:
|
||||
-rm $(APIDOCS)
|
||||
-rm -f $(APIDOCS)
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
|
||||
html-local: apiref.rst
|
||||
|
|
|
@ -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
|
||||
.
|
||||
|
|
|
@ -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
|
||||
.
|
||||
|
|
|
@ -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
|
||||
.
|
||||
|
|
|
@ -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
|
||||
.
|
||||
|
|
|
@ -91,6 +91,8 @@ OPTIONS = [
|
|||
"header-field-buffer",
|
||||
"max-header-fields",
|
||||
"include",
|
||||
"tls-ticket-cipher",
|
||||
"host-rewrite",
|
||||
"conf",
|
||||
]
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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 \
|
||||
|
|
228
src/h2load.cc
228
src/h2load.cc
|
@ -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);
|
||||
|
|
14
src/h2load.h
14
src/h2load.h
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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) ||
|
||||
|
|
136
src/shrpx.cc
136
src/shrpx.cc
|
@ -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);
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 += "\", ";
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
33
src/timegm.c
33
src/timegm.c
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue