summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 19:37:08 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 19:37:08 +0000
commitd710a65c8b50bc3d4d0920dc6e865296f42edd5e (patch)
treed3bf9843448af9398b55f49a50a194bbaacd724e /src
parentInitial commit. (diff)
downloadnghttp2-d710a65c8b50bc3d4d0920dc6e865296f42edd5e.tar.xz
nghttp2-d710a65c8b50bc3d4d0920dc6e865296f42edd5e.zip
Adding upstream version 1.59.0.upstream/1.59.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src')
-rw-r--r--src/.gitignore13
-rw-r--r--src/CMakeLists.txt243
-rw-r--r--src/HtmlParser.cc217
-rw-r--r--src/HtmlParser.h94
-rw-r--r--src/HttpServer.cc2248
-rw-r--r--src/HttpServer.h253
-rw-r--r--src/Makefile.am257
-rw-r--r--src/allocator.h273
-rw-r--r--src/app_helper.cc518
-rw-r--r--src/app_helper.h98
-rw-r--r--src/base64.h225
-rw-r--r--src/base64_test.cc121
-rw-r--r--src/base64_test.h39
-rw-r--r--src/buffer.h78
-rw-r--r--src/buffer_test.cc78
-rw-r--r--src/buffer_test.h38
-rw-r--r--src/ca-config.json17
-rw-r--r--src/ca.nghttp2.org-key.pem27
-rw-r--r--src/ca.nghttp2.org.csr17
-rw-r--r--src/ca.nghttp2.org.csr.json17
-rw-r--r--src/ca.nghttp2.org.pem22
-rw-r--r--src/comp_helper.c133
-rw-r--r--src/comp_helper.h57
-rw-r--r--src/deflatehd.cc450
-rw-r--r--src/h2load.cc3292
-rw-r--r--src/h2load.h510
-rw-r--r--src/h2load_http1_session.cc306
-rw-r--r--src/h2load_http1_session.h60
-rw-r--r--src/h2load_http2_session.cc314
-rw-r--r--src/h2load_http2_session.h54
-rw-r--r--src/h2load_http3_session.cc474
-rw-r--r--src/h2load_http3_session.h84
-rw-r--r--src/h2load_quic.cc844
-rw-r--r--src/h2load_quic.h38
-rw-r--r--src/h2load_session.h59
-rw-r--r--src/http-parser.patch28
-rw-r--r--src/http2.cc2096
-rw-r--r--src/http2.h457
-rw-r--r--src/http2_test.cc1249
-rw-r--r--src/http2_test.h54
-rw-r--r--src/http3.cc206
-rw-r--r--src/http3.h123
-rw-r--r--src/inflatehd.cc289
-rw-r--r--src/libevent_util.cc162
-rw-r--r--src/libevent_util.h75
-rw-r--r--src/memchunk.h664
-rw-r--r--src/memchunk_test.cc340
-rw-r--r--src/memchunk_test.h47
-rw-r--r--src/network.h67
-rw-r--r--src/nghttp.cc3115
-rw-r--r--src/nghttp.h310
-rw-r--r--src/nghttp2_config.h32
-rw-r--r--src/nghttp2_gzip.c87
-rw-r--r--src/nghttp2_gzip.h122
-rw-r--r--src/nghttp2_gzip_test.c111
-rw-r--r--src/nghttp2_gzip_test.h42
-rw-r--r--src/nghttpd.cc502
-rw-r--r--src/quic.cc60
-rw-r--r--src/quic.h56
-rw-r--r--src/shrpx-unittest.cc246
-rw-r--r--src/shrpx.cc5329
-rw-r--r--src/shrpx.h58
-rw-r--r--src/shrpx_accept_handler.cc111
-rw-r--r--src/shrpx_accept_handler.h54
-rw-r--r--src/shrpx_api_downstream_connection.cc478
-rw-r--r--src/shrpx_api_downstream_connection.h114
-rw-r--r--src/shrpx_client_handler.cc1703
-rw-r--r--src/shrpx_client_handler.h236
-rw-r--r--src/shrpx_config.cc4694
-rw-r--r--src/shrpx_config.h1450
-rw-r--r--src/shrpx_config_test.cc249
-rw-r--r--src/shrpx_config_test.h42
-rw-r--r--src/shrpx_connect_blocker.cc143
-rw-r--r--src/shrpx_connect_blocker.h86
-rw-r--r--src/shrpx_connection.cc1275
-rw-r--r--src/shrpx_connection.h203
-rw-r--r--src/shrpx_connection_handler.cc1319
-rw-r--r--src/shrpx_connection_handler.h322
-rw-r--r--src/shrpx_dns_resolver.cc353
-rw-r--r--src/shrpx_dns_resolver.h118
-rw-r--r--src/shrpx_dns_tracker.cc328
-rw-r--r--src/shrpx_dns_tracker.h121
-rw-r--r--src/shrpx_downstream.cc1189
-rw-r--r--src/shrpx_downstream.h628
-rw-r--r--src/shrpx_downstream_connection.cc48
-rw-r--r--src/shrpx_downstream_connection.h81
-rw-r--r--src/shrpx_downstream_connection_pool.cc66
-rw-r--r--src/shrpx_downstream_connection_pool.h53
-rw-r--r--src/shrpx_downstream_queue.cc175
-rw-r--r--src/shrpx_downstream_queue.h116
-rw-r--r--src/shrpx_downstream_test.cc231
-rw-r--r--src/shrpx_downstream_test.h44
-rw-r--r--src/shrpx_dual_dns_resolver.cc93
-rw-r--r--src/shrpx_dual_dns_resolver.h69
-rw-r--r--src/shrpx_error.h47
-rw-r--r--src/shrpx_exec.cc138
-rw-r--r--src/shrpx_exec.h47
-rw-r--r--src/shrpx_health_monitor_downstream_connection.cc116
-rw-r--r--src/shrpx_health_monitor_downstream_connection.h64
-rw-r--r--src/shrpx_http.cc280
-rw-r--r--src/shrpx_http.h96
-rw-r--r--src/shrpx_http2_downstream_connection.cc621
-rw-r--r--src/shrpx_http2_downstream_connection.h88
-rw-r--r--src/shrpx_http2_session.cc2426
-rw-r--r--src/shrpx_http2_session.h296
-rw-r--r--src/shrpx_http2_upstream.cc2404
-rw-r--r--src/shrpx_http2_upstream.h150
-rw-r--r--src/shrpx_http3_upstream.cc2906
-rw-r--r--src/shrpx_http3_upstream.h193
-rw-r--r--src/shrpx_http_downstream_connection.cc1617
-rw-r--r--src/shrpx_http_downstream_connection.h124
-rw-r--r--src/shrpx_http_test.cc168
-rw-r--r--src/shrpx_http_test.h42
-rw-r--r--src/shrpx_https_upstream.cc1582
-rw-r--r--src/shrpx_https_upstream.h113
-rw-r--r--src/shrpx_io_control.cc66
-rw-r--r--src/shrpx_io_control.h58
-rw-r--r--src/shrpx_live_check.cc792
-rw-r--r--src/shrpx_live_check.h125
-rw-r--r--src/shrpx_log.cc1008
-rw-r--r--src/shrpx_log.h318
-rw-r--r--src/shrpx_log_config.cc127
-rw-r--r--src/shrpx_log_config.h79
-rw-r--r--src/shrpx_memcached_connection.cc777
-rw-r--r--src/shrpx_memcached_connection.h155
-rw-r--r--src/shrpx_memcached_dispatcher.cc53
-rw-r--r--src/shrpx_memcached_dispatcher.h63
-rw-r--r--src/shrpx_memcached_request.h59
-rw-r--r--src/shrpx_memcached_result.h50
-rw-r--r--src/shrpx_mruby.cc238
-rw-r--r--src/shrpx_mruby.h89
-rw-r--r--src/shrpx_mruby_module.cc113
-rw-r--r--src/shrpx_mruby_module.h52
-rw-r--r--src/shrpx_mruby_module_env.cc500
-rw-r--r--src/shrpx_mruby_module_env.h42
-rw-r--r--src/shrpx_mruby_module_request.cc367
-rw-r--r--src/shrpx_mruby_module_request.h42
-rw-r--r--src/shrpx_mruby_module_response.cc398
-rw-r--r--src/shrpx_mruby_module_response.h42
-rw-r--r--src/shrpx_null_downstream_connection.cc88
-rw-r--r--src/shrpx_null_downstream_connection.h68
-rw-r--r--src/shrpx_process.h37
-rw-r--r--src/shrpx_quic.cc393
-rw-r--r--src/shrpx_quic.h138
-rw-r--r--src/shrpx_quic_connection_handler.cc761
-rw-r--r--src/shrpx_quic_connection_handler.h142
-rw-r--r--src/shrpx_quic_listener.cc132
-rw-r--r--src/shrpx_quic_listener.h51
-rw-r--r--src/shrpx_rate_limit.cc123
-rw-r--r--src/shrpx_rate_limit.h68
-rw-r--r--src/shrpx_router.cc420
-rw-r--r--src/shrpx_router.h110
-rw-r--r--src/shrpx_router_test.cc184
-rw-r--r--src/shrpx_router_test.h40
-rw-r--r--src/shrpx_signal.cc138
-rw-r--r--src/shrpx_signal.h60
-rw-r--r--src/shrpx_tls.cc2465
-rw-r--r--src/shrpx_tls.h321
-rw-r--r--src/shrpx_tls_test.cc339
-rw-r--r--src/shrpx_tls_test.h42
-rw-r--r--src/shrpx_upstream.h112
-rw-r--r--src/shrpx_worker.cc1347
-rw-r--r--src/shrpx_worker.h480
-rw-r--r--src/shrpx_worker_process.cc701
-rw-r--r--src/shrpx_worker_process.h67
-rw-r--r--src/shrpx_worker_test.cc247
-rw-r--r--src/shrpx_worker_test.h38
-rw-r--r--src/ssl_compat.h48
-rw-r--r--src/template.h548
-rw-r--r--src/template_test.cc204
-rw-r--r--src/template_test.h39
-rw-r--r--src/test.example.com-key.pem27
-rw-r--r--src/test.example.com.csr17
-rw-r--r--src/test.example.com.csr.json14
-rw-r--r--src/test.example.com.pem23
-rw-r--r--src/test.nghttp2.org-key.pem27
-rw-r--r--src/test.nghttp2.org.csr19
-rw-r--r--src/test.nghttp2.org.csr.json19
-rw-r--r--src/test.nghttp2.org.pem24
-rw-r--r--src/testdata/Makefile.am27
-rw-r--r--src/testdata/ipaddr.crt10
-rw-r--r--src/testdata/nosan.crt9
-rw-r--r--src/testdata/nosan_ip.crt9
-rw-r--r--src/testdata/verify_hostname.crt10
-rw-r--r--src/timegm.c88
-rw-r--r--src/timegm.h49
-rw-r--r--src/tls.cc125
-rw-r--r--src/tls.h104
-rw-r--r--src/util.cc1796
-rw-r--r--src/util.h971
-rw-r--r--src/util_test.cc707
-rw-r--r--src/util_test.h75
-rw-r--r--src/xsi_strerror.c50
-rw-r--r--src/xsi_strerror.h55
194 files changed, 77024 insertions, 0 deletions
diff --git a/src/.gitignore b/src/.gitignore
new file mode 100644
index 0000000..27631a7
--- /dev/null
+++ b/src/.gitignore
@@ -0,0 +1,13 @@
+# programs
+deflatehd
+h2load
+inflatehd
+nghttp
+nghttpd
+nghttpx
+
+# build
+libnghttpx.a
+
+# tests
+nghttpx-unittest
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 0000000..201c5a2
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,243 @@
+file(GLOB c_sources *.c)
+set_source_files_properties(${c_sources} PROPERTIES
+ COMPILE_FLAGS "${WARNCFLAGS}")
+file(GLOB cxx_sources *.cc)
+set_source_files_properties(${cxx_sources} PROPERTIES
+ COMPILE_FLAGS "${WARNCXXFLAGS} ${CXX1XCXXFLAGS}")
+
+include_directories(
+ "${CMAKE_CURRENT_SOURCE_DIR}/includes"
+ "${CMAKE_CURRENT_SOURCE_DIR}/../third-party"
+ "${CMAKE_CURRENT_SOURCE_DIR}/../third-party/llhttp/include"
+
+ ${JEMALLOC_INCLUDE_DIRS}
+ ${LIBXML2_INCLUDE_DIRS}
+ ${LIBEV_INCLUDE_DIRS}
+ ${LIBNGHTTP3_INCLUDE_DIRS}
+ ${LIBNGTCP2_INCLUDE_DIRS}
+ ${LIBNGTCP2_CRYPTO_QUICTLS_INCLUDE_DIRS}
+ ${OPENSSL_INCLUDE_DIRS}
+ ${LIBCARES_INCLUDE_DIRS}
+ ${JANSSON_INCLUDE_DIRS}
+ ${ZLIB_INCLUDE_DIRS}
+ ${LIBBPF_INCLUDE_DIRS}
+)
+
+# XXX per-target?
+link_libraries(
+ nghttp2
+ ${JEMALLOC_LIBRARIES}
+ ${LIBXML2_LIBRARIES}
+ ${LIBEV_LIBRARIES}
+ ${LIBNGHTTP3_LIBRARIES}
+ ${LIBNGTCP2_LIBRARIES}
+ ${LIBNGTCP2_CRYPTO_QUICTLS_LIBRARIES}
+ ${OPENSSL_LIBRARIES}
+ ${LIBCARES_LIBRARIES}
+ ${JANSSON_LIBRARIES}
+ ${ZLIB_LIBRARIES}
+ ${APP_LIBRARIES}
+ ${LIBBPF_LIBRARIES}
+)
+
+if(ENABLE_APP)
+ set(HELPER_OBJECTS
+ util.cc
+ http2.cc timegm.c app_helper.cc nghttp2_gzip.c
+ )
+
+ # nghttp client
+ set(NGHTTP_SOURCES
+ ${HELPER_OBJECTS}
+ nghttp.cc
+ tls.cc
+ )
+ if(HAVE_LIBXML2)
+ list(APPEND NGHTTP_SOURCES HtmlParser.cc)
+ endif()
+
+ # nghttpd
+ set(NGHTTPD_SOURCES
+ ${HELPER_OBJECTS}
+ nghttpd.cc
+ tls.cc
+ HttpServer.cc
+ )
+
+ # h2load
+ set(H2LOAD_SOURCES
+ util.cc
+ http2.cc h2load.cc
+ timegm.c
+ tls.cc
+ h2load_http2_session.cc
+ h2load_http1_session.cc
+ )
+ if(ENABLE_HTTP3)
+ list(APPEND H2LOAD_SOURCES
+ h2load_http3_session.cc
+ h2load_quic.cc
+ quic.cc
+ )
+ endif()
+
+ # Common libnhttpx sources (used for nghttpx and unit tests)
+ set(NGHTTPX_SRCS
+ util.cc http2.cc timegm.c
+ app_helper.cc
+ tls.cc
+ shrpx_config.cc
+ shrpx_accept_handler.cc
+ shrpx_connection_handler.cc
+ shrpx_client_handler.cc
+ shrpx_http2_upstream.cc
+ shrpx_https_upstream.cc
+ shrpx_downstream.cc
+ shrpx_downstream_connection.cc
+ shrpx_http_downstream_connection.cc
+ shrpx_http2_downstream_connection.cc
+ shrpx_http2_session.cc
+ shrpx_downstream_queue.cc
+ shrpx_log.cc
+ shrpx_http.cc
+ shrpx_io_control.cc
+ shrpx_tls.cc
+ shrpx_worker.cc
+ shrpx_log_config.cc
+ shrpx_connect_blocker.cc
+ shrpx_live_check.cc
+ shrpx_downstream_connection_pool.cc
+ shrpx_rate_limit.cc
+ shrpx_connection.cc
+ shrpx_memcached_dispatcher.cc
+ shrpx_memcached_connection.cc
+ shrpx_worker_process.cc
+ shrpx_signal.cc
+ shrpx_router.cc
+ shrpx_api_downstream_connection.cc
+ shrpx_health_monitor_downstream_connection.cc
+ shrpx_null_downstream_connection.cc
+ shrpx_exec.cc
+ shrpx_dns_resolver.cc
+ shrpx_dual_dns_resolver.cc
+ shrpx_dns_tracker.cc
+ xsi_strerror.c
+ )
+ if(HAVE_MRUBY)
+ list(APPEND NGHTTPX_SRCS
+ shrpx_mruby.cc
+ shrpx_mruby_module.cc
+ shrpx_mruby_module_env.cc
+ shrpx_mruby_module_request.cc
+ shrpx_mruby_module_response.cc
+ )
+ endif()
+ if(ENABLE_HTTP3)
+ list(APPEND NGHTTPX_SRCS
+ shrpx_quic.cc
+ shrpx_quic_listener.cc
+ shrpx_quic_connection_handler.cc
+ shrpx_http3_upstream.cc
+ http3.cc
+ quic.cc
+ )
+ endif()
+ add_library(nghttpx_static STATIC ${NGHTTPX_SRCS})
+ set_target_properties(nghttpx_static PROPERTIES ARCHIVE_OUTPUT_NAME nghttpx)
+
+ set(NGHTTPX-bin_SOURCES
+ shrpx.cc
+ )
+
+ if(HAVE_SYSTEMD)
+ target_link_libraries(nghttpx_static ${SYSTEMD_LIBRARIES})
+ target_compile_definitions(nghttpx_static PUBLIC HAVE_LIBSYSTEMD)
+ target_include_directories(nghttpx_static PUBLIC ${SYSTEMD_INCLUDE_DIRS})
+ endif()
+
+ if(HAVE_MRUBY)
+ target_link_libraries(nghttpx_static mruby-lib)
+ endif()
+
+ if(HAVE_NEVERBLEED)
+ target_link_libraries(nghttpx_static neverbleed)
+ endif()
+
+
+ if(HAVE_CUNIT)
+ set(NGHTTPX_UNITTEST_SOURCES
+ shrpx-unittest.cc
+ shrpx_tls_test.cc
+ shrpx_downstream_test.cc
+ shrpx_config_test.cc
+ shrpx_worker_test.cc
+ shrpx_http_test.cc
+ shrpx_router_test.cc
+ http2_test.cc
+ util_test.cc
+ nghttp2_gzip_test.c
+ nghttp2_gzip.c
+ buffer_test.cc
+ memchunk_test.cc
+ template_test.cc
+ base64_test.cc
+ )
+ add_executable(nghttpx-unittest EXCLUDE_FROM_ALL
+ ${NGHTTPX_UNITTEST_SOURCES}
+ $<TARGET_OBJECTS:llhttp>
+ $<TARGET_OBJECTS:url-parser>
+ )
+ target_include_directories(nghttpx-unittest PRIVATE ${CUNIT_INCLUDE_DIRS})
+ target_compile_definitions(nghttpx-unittest
+ PRIVATE "-DNGHTTP2_SRC_DIR=\"${CMAKE_SOURCE_DIR}/src\""
+ )
+ target_link_libraries(nghttpx-unittest nghttpx_static ${CUNIT_LIBRARIES})
+ if(HAVE_MRUBY)
+ target_link_libraries(nghttpx-unittest mruby-lib)
+ endif()
+ if(HAVE_NEVERBLEED)
+ target_link_libraries(nghttpx-unittest neverbleed)
+ endif()
+
+ add_test(nghttpx-unittest nghttpx-unittest)
+ add_dependencies(check nghttpx-unittest)
+ endif()
+
+ add_executable(nghttp ${NGHTTP_SOURCES} $<TARGET_OBJECTS:llhttp>
+ $<TARGET_OBJECTS:url-parser>
+ )
+ add_executable(nghttpd ${NGHTTPD_SOURCES} $<TARGET_OBJECTS:llhttp>
+ $<TARGET_OBJECTS:url-parser>
+ )
+ add_executable(nghttpx ${NGHTTPX-bin_SOURCES} $<TARGET_OBJECTS:llhttp>
+ $<TARGET_OBJECTS:url-parser>
+ )
+ target_compile_definitions(nghttpx PRIVATE
+ "-DPKGDATADIR=\"${PKGDATADIR}\""
+ "-DPKGLIBDIR=\"${PKGLIBDIR}\""
+ )
+ target_link_libraries(nghttpx nghttpx_static)
+ add_executable(h2load ${H2LOAD_SOURCES} $<TARGET_OBJECTS:llhttp>
+ $<TARGET_OBJECTS:url-parser>
+ )
+
+ install(TARGETS nghttp nghttpd nghttpx h2load
+ RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
+endif()
+
+if(ENABLE_HPACK_TOOLS)
+ set(inflatehd_SOURCES
+ inflatehd.cc
+ comp_helper.c
+ )
+ set(deflatehd_SOURCES
+ deflatehd.cc
+ comp_helper.c
+ util.cc
+ timegm.c
+ )
+ add_executable(inflatehd ${inflatehd_SOURCES})
+ add_executable(deflatehd ${deflatehd_SOURCES})
+ install(TARGETS inflatehd deflatehd
+ RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
+endif()
diff --git a/src/HtmlParser.cc b/src/HtmlParser.cc
new file mode 100644
index 0000000..591c4c7
--- /dev/null
+++ b/src/HtmlParser.cc
@@ -0,0 +1,217 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "HtmlParser.h"
+
+#include <libxml/uri.h>
+
+#include "util.h"
+
+namespace nghttp2 {
+
+ParserData::ParserData(const std::string &base_uri)
+ : base_uri(base_uri), inside_head(0) {}
+
+HtmlParser::HtmlParser(const std::string &base_uri)
+ : base_uri_(base_uri), parser_ctx_(nullptr), parser_data_(base_uri) {}
+
+HtmlParser::~HtmlParser() { htmlFreeParserCtxt(parser_ctx_); }
+
+namespace {
+StringRef get_attr(const xmlChar **attrs, const StringRef &name) {
+ if (attrs == nullptr) {
+ return StringRef{};
+ }
+ for (; *attrs; attrs += 2) {
+ if (util::strieq(StringRef{attrs[0], strlen(reinterpret_cast<const char *>(
+ attrs[0]))},
+ name)) {
+ return StringRef{attrs[1],
+ strlen(reinterpret_cast<const char *>(attrs[1]))};
+ }
+ }
+ return StringRef{};
+}
+} // namespace
+
+namespace {
+ResourceType
+get_resource_type_for_preload_as(const StringRef &attribute_value) {
+ if (util::strieq_l("image", attribute_value)) {
+ return REQ_IMG;
+ } else if (util::strieq_l("style", attribute_value)) {
+ return REQ_CSS;
+ } else if (util::strieq_l("script", attribute_value)) {
+ return REQ_UNBLOCK_JS;
+ } else {
+ return REQ_OTHERS;
+ }
+}
+} // namespace
+
+namespace {
+void add_link(ParserData *parser_data, const StringRef &uri,
+ ResourceType res_type) {
+ auto u = xmlBuildURI(
+ reinterpret_cast<const xmlChar *>(uri.c_str()),
+ reinterpret_cast<const xmlChar *>(parser_data->base_uri.c_str()));
+ if (u) {
+ parser_data->links.push_back(
+ std::make_pair(reinterpret_cast<char *>(u), res_type));
+ free(u);
+ }
+}
+} // namespace
+
+namespace {
+void start_element_func(void *user_data, const xmlChar *src_name,
+ const xmlChar **attrs) {
+ auto parser_data = static_cast<ParserData *>(user_data);
+ auto name =
+ StringRef{src_name, strlen(reinterpret_cast<const char *>(src_name))};
+ if (util::strieq_l("head", name)) {
+ ++parser_data->inside_head;
+ }
+ if (util::strieq_l("link", name)) {
+ auto rel_attr = get_attr(attrs, StringRef::from_lit("rel"));
+ auto href_attr = get_attr(attrs, StringRef::from_lit("href"));
+ if (rel_attr.empty() || href_attr.empty()) {
+ return;
+ }
+ if (util::strieq_l("shortcut icon", rel_attr)) {
+ add_link(parser_data, href_attr, REQ_OTHERS);
+ } else if (util::strieq_l("stylesheet", rel_attr)) {
+ add_link(parser_data, href_attr, REQ_CSS);
+ } else if (util::strieq_l("preload", rel_attr)) {
+ auto as_attr = get_attr(attrs, StringRef::from_lit("as"));
+ if (as_attr.empty()) {
+ return;
+ }
+ add_link(parser_data, href_attr,
+ get_resource_type_for_preload_as(as_attr));
+ }
+ } else if (util::strieq_l("img", name)) {
+ auto src_attr = get_attr(attrs, StringRef::from_lit("src"));
+ if (src_attr.empty()) {
+ return;
+ }
+ add_link(parser_data, src_attr, REQ_IMG);
+ } else if (util::strieq_l("script", name)) {
+ auto src_attr = get_attr(attrs, StringRef::from_lit("src"));
+ if (src_attr.empty()) {
+ return;
+ }
+ if (parser_data->inside_head) {
+ add_link(parser_data, src_attr, REQ_JS);
+ } else {
+ add_link(parser_data, src_attr, REQ_UNBLOCK_JS);
+ }
+ }
+}
+} // namespace
+
+namespace {
+void end_element_func(void *user_data, const xmlChar *name) {
+ auto parser_data = static_cast<ParserData *>(user_data);
+ if (util::strieq_l(
+ "head",
+ StringRef{name, strlen(reinterpret_cast<const char *>(name))})) {
+ --parser_data->inside_head;
+ }
+}
+} // namespace
+
+namespace {
+xmlSAXHandler saxHandler = {
+ nullptr, // internalSubsetSAXFunc
+ nullptr, // isStandaloneSAXFunc
+ nullptr, // hasInternalSubsetSAXFunc
+ nullptr, // hasExternalSubsetSAXFunc
+ nullptr, // resolveEntitySAXFunc
+ nullptr, // getEntitySAXFunc
+ nullptr, // entityDeclSAXFunc
+ nullptr, // notationDeclSAXFunc
+ nullptr, // attributeDeclSAXFunc
+ nullptr, // elementDeclSAXFunc
+ nullptr, // unparsedEntityDeclSAXFunc
+ nullptr, // setDocumentLocatorSAXFunc
+ nullptr, // startDocumentSAXFunc
+ nullptr, // endDocumentSAXFunc
+ &start_element_func, // startElementSAXFunc
+ &end_element_func, // endElementSAXFunc
+ nullptr, // referenceSAXFunc
+ nullptr, // charactersSAXFunc
+ nullptr, // ignorableWhitespaceSAXFunc
+ nullptr, // processingInstructionSAXFunc
+ nullptr, // commentSAXFunc
+ nullptr, // warningSAXFunc
+ nullptr, // errorSAXFunc
+ nullptr, // fatalErrorSAXFunc
+ nullptr, // getParameterEntitySAXFunc
+ nullptr, // cdataBlockSAXFunc
+ nullptr, // externalSubsetSAXFunc
+ 0, // unsigned int initialized
+ nullptr, // void * _private
+ nullptr, // startElementNsSAX2Func
+ nullptr, // endElementNsSAX2Func
+ nullptr, // xmlStructuredErrorFunc
+};
+} // namespace
+
+int HtmlParser::parse_chunk(const char *chunk, size_t size, int fin) {
+ if (!parser_ctx_) {
+ parser_ctx_ =
+ htmlCreatePushParserCtxt(&saxHandler, &parser_data_, chunk, size,
+ base_uri_.c_str(), XML_CHAR_ENCODING_NONE);
+ if (!parser_ctx_) {
+ return -1;
+ } else {
+ if (fin) {
+ return parse_chunk_internal(nullptr, 0, fin);
+ } else {
+ return 0;
+ }
+ }
+ } else {
+ return parse_chunk_internal(chunk, size, fin);
+ }
+}
+
+int HtmlParser::parse_chunk_internal(const char *chunk, size_t size, int fin) {
+ int rv = htmlParseChunk(parser_ctx_, chunk, size, fin);
+ if (rv == 0) {
+ return 0;
+ } else {
+ return -1;
+ }
+}
+
+const std::vector<std::pair<std::string, ResourceType>> &
+HtmlParser::get_links() const {
+ return parser_data_.links;
+}
+
+void HtmlParser::clear_links() { parser_data_.links.clear(); }
+
+} // namespace nghttp2
diff --git a/src/HtmlParser.h b/src/HtmlParser.h
new file mode 100644
index 0000000..1e84688
--- /dev/null
+++ b/src/HtmlParser.h
@@ -0,0 +1,94 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef HTML_PARSER_H
+#define HTML_PARSER_H
+
+#include "nghttp2_config.h"
+
+#include <vector>
+#include <string>
+
+#ifdef HAVE_LIBXML2
+
+# include <libxml/HTMLparser.h>
+
+#endif // HAVE_LIBXML2
+
+namespace nghttp2 {
+
+enum ResourceType {
+ REQ_CSS = 1,
+ REQ_JS,
+ REQ_UNBLOCK_JS,
+ REQ_IMG,
+ REQ_OTHERS,
+};
+
+struct ParserData {
+ std::string base_uri;
+ std::vector<std::pair<std::string, ResourceType>> links;
+ // > 0 if we are inside "head" element.
+ int inside_head;
+ ParserData(const std::string &base_uri);
+};
+
+#ifdef HAVE_LIBXML2
+
+class HtmlParser {
+public:
+ HtmlParser(const std::string &base_uri);
+ ~HtmlParser();
+ int parse_chunk(const char *chunk, size_t size, int fin);
+ const std::vector<std::pair<std::string, ResourceType>> &get_links() const;
+ void clear_links();
+
+private:
+ int parse_chunk_internal(const char *chunk, size_t size, int fin);
+
+ std::string base_uri_;
+ htmlParserCtxtPtr parser_ctx_;
+ ParserData parser_data_;
+};
+
+#else // !HAVE_LIBXML2
+
+class HtmlParser {
+public:
+ HtmlParser(const std::string &base_uri) {}
+ int parse_chunk(const char *chunk, size_t size, int fin) { return 0; }
+ const std::vector<std::pair<std::string, ResourceType>> &get_links() const {
+ return links_;
+ }
+ void clear_links() {}
+
+private:
+ std::vector<std::pair<std::string, ResourceType>> links_;
+};
+
+#endif // !HAVE_LIBXML2
+
+} // namespace nghttp2
+
+#endif // HTML_PARSER_H
diff --git a/src/HttpServer.cc b/src/HttpServer.cc
new file mode 100644
index 0000000..0385cd0
--- /dev/null
+++ b/src/HttpServer.cc
@@ -0,0 +1,2248 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "HttpServer.h"
+
+#include <sys/stat.h>
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif // HAVE_SYS_SOCKET_H
+#ifdef HAVE_NETDB_H
+# include <netdb.h>
+#endif // HAVE_NETDB_H
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+#ifdef HAVE_FCNTL_H
+# include <fcntl.h>
+#endif // HAVE_FCNTL_H
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif // HAVE_NETINET_IN_H
+#include <netinet/tcp.h>
+#ifdef HAVE_ARPA_INET_H
+# include <arpa/inet.h>
+#endif // HAVE_ARPA_INET_H
+
+#include <cassert>
+#include <set>
+#include <iostream>
+#include <thread>
+#include <mutex>
+#include <deque>
+
+#include "ssl_compat.h"
+
+#include <openssl/err.h>
+#include <openssl/dh.h>
+#if OPENSSL_3_0_0_API
+# include <openssl/decoder.h>
+#endif // OPENSSL_3_0_0_API
+
+#include <zlib.h>
+
+#include "app_helper.h"
+#include "http2.h"
+#include "util.h"
+#include "tls.h"
+#include "template.h"
+
+#ifndef O_BINARY
+# define O_BINARY (0)
+#endif // O_BINARY
+
+using namespace std::chrono_literals;
+
+namespace nghttp2 {
+
+namespace {
+// TODO could be constexpr
+constexpr auto DEFAULT_HTML = StringRef::from_lit("index.html");
+constexpr auto NGHTTPD_SERVER =
+ StringRef::from_lit("nghttpd nghttp2/" NGHTTP2_VERSION);
+} // namespace
+
+namespace {
+void delete_handler(Http2Handler *handler) {
+ handler->remove_self();
+ delete handler;
+}
+} // namespace
+
+namespace {
+void print_session_id(int64_t id) { std::cout << "[id=" << id << "] "; }
+} // namespace
+
+Config::Config()
+ : mime_types_file("/etc/mime.types"),
+ stream_read_timeout(1_min),
+ stream_write_timeout(1_min),
+ data_ptr(nullptr),
+ padding(0),
+ num_worker(1),
+ max_concurrent_streams(100),
+ header_table_size(-1),
+ encoder_header_table_size(-1),
+ window_bits(-1),
+ connection_window_bits(-1),
+ port(0),
+ verbose(false),
+ daemon(false),
+ verify_client(false),
+ no_tls(false),
+ error_gzip(false),
+ early_response(false),
+ hexdump(false),
+ echo_upload(false),
+ no_content_length(false),
+ ktls(false),
+ no_rfc7540_pri(false) {}
+
+Config::~Config() {}
+
+namespace {
+void stream_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ int rv;
+ auto stream = static_cast<Stream *>(w->data);
+ auto hd = stream->handler;
+ auto config = hd->get_config();
+
+ ev_timer_stop(hd->get_loop(), &stream->rtimer);
+ ev_timer_stop(hd->get_loop(), &stream->wtimer);
+
+ if (config->verbose) {
+ print_session_id(hd->session_id());
+ print_timer();
+ std::cout << " timeout stream_id=" << stream->stream_id << std::endl;
+ }
+
+ hd->submit_rst_stream(stream, NGHTTP2_INTERNAL_ERROR);
+
+ rv = hd->on_write();
+ if (rv == -1) {
+ delete_handler(hd);
+ }
+}
+} // namespace
+
+namespace {
+void add_stream_read_timeout(Stream *stream) {
+ auto hd = stream->handler;
+ ev_timer_again(hd->get_loop(), &stream->rtimer);
+}
+} // namespace
+
+namespace {
+void add_stream_read_timeout_if_pending(Stream *stream) {
+ auto hd = stream->handler;
+ if (ev_is_active(&stream->rtimer)) {
+ ev_timer_again(hd->get_loop(), &stream->rtimer);
+ }
+}
+} // namespace
+
+namespace {
+void add_stream_write_timeout(Stream *stream) {
+ auto hd = stream->handler;
+ ev_timer_again(hd->get_loop(), &stream->wtimer);
+}
+} // namespace
+
+namespace {
+void remove_stream_read_timeout(Stream *stream) {
+ auto hd = stream->handler;
+ ev_timer_stop(hd->get_loop(), &stream->rtimer);
+}
+} // namespace
+
+namespace {
+void remove_stream_write_timeout(Stream *stream) {
+ auto hd = stream->handler;
+ ev_timer_stop(hd->get_loop(), &stream->wtimer);
+}
+} // namespace
+
+namespace {
+void fill_callback(nghttp2_session_callbacks *callbacks, const Config *config);
+} // namespace
+
+namespace {
+constexpr ev_tstamp RELEASE_FD_TIMEOUT = 2.;
+} // namespace
+
+namespace {
+void release_fd_cb(struct ev_loop *loop, ev_timer *w, int revents);
+} // namespace
+
+namespace {
+constexpr auto FILE_ENTRY_MAX_AGE = 10s;
+} // namespace
+
+namespace {
+constexpr size_t FILE_ENTRY_EVICT_THRES = 2048;
+} // namespace
+
+namespace {
+bool need_validation_file_entry(
+ const FileEntry *ent, const std::chrono::steady_clock::time_point &now) {
+ return ent->last_valid + FILE_ENTRY_MAX_AGE < now;
+}
+} // namespace
+
+namespace {
+bool validate_file_entry(FileEntry *ent,
+ const std::chrono::steady_clock::time_point &now) {
+ struct stat stbuf;
+ int rv;
+
+ rv = fstat(ent->fd, &stbuf);
+ if (rv != 0) {
+ ent->stale = true;
+ return false;
+ }
+
+ if (stbuf.st_nlink == 0 || ent->mtime != stbuf.st_mtime) {
+ ent->stale = true;
+ return false;
+ }
+
+ ent->mtime = stbuf.st_mtime;
+ ent->last_valid = now;
+
+ return true;
+}
+} // namespace
+
+class Sessions {
+public:
+ Sessions(HttpServer *sv, struct ev_loop *loop, const Config *config,
+ SSL_CTX *ssl_ctx)
+ : sv_(sv),
+ loop_(loop),
+ config_(config),
+ ssl_ctx_(ssl_ctx),
+ callbacks_(nullptr),
+ option_(nullptr),
+ next_session_id_(1),
+ tstamp_cached_(ev_now(loop)),
+ cached_date_(util::http_date(tstamp_cached_)) {
+ nghttp2_session_callbacks_new(&callbacks_);
+
+ fill_callback(callbacks_, config_);
+
+ nghttp2_option_new(&option_);
+
+ if (config_->encoder_header_table_size != -1) {
+ nghttp2_option_set_max_deflate_dynamic_table_size(
+ option_, config_->encoder_header_table_size);
+ }
+
+ ev_timer_init(&release_fd_timer_, release_fd_cb, 0., RELEASE_FD_TIMEOUT);
+ release_fd_timer_.data = this;
+ }
+ ~Sessions() {
+ ev_timer_stop(loop_, &release_fd_timer_);
+ for (auto handler : handlers_) {
+ delete handler;
+ }
+ nghttp2_option_del(option_);
+ nghttp2_session_callbacks_del(callbacks_);
+ }
+ void add_handler(Http2Handler *handler) { handlers_.insert(handler); }
+ void remove_handler(Http2Handler *handler) {
+ handlers_.erase(handler);
+ if (handlers_.empty() && !fd_cache_.empty()) {
+ ev_timer_again(loop_, &release_fd_timer_);
+ }
+ }
+ SSL_CTX *get_ssl_ctx() const { return ssl_ctx_; }
+ SSL *ssl_session_new(int fd) {
+ SSL *ssl = SSL_new(ssl_ctx_);
+ if (!ssl) {
+ std::cerr << "SSL_new() failed" << std::endl;
+ return nullptr;
+ }
+ if (SSL_set_fd(ssl, fd) == 0) {
+ std::cerr << "SSL_set_fd() failed" << std::endl;
+ SSL_free(ssl);
+ return nullptr;
+ }
+ return ssl;
+ }
+ const Config *get_config() const { return config_; }
+ struct ev_loop *get_loop() const { return loop_; }
+ int64_t get_next_session_id() {
+ auto session_id = next_session_id_;
+ if (next_session_id_ == std::numeric_limits<int64_t>::max()) {
+ next_session_id_ = 1;
+ } else {
+ ++next_session_id_;
+ }
+ return session_id;
+ }
+ const nghttp2_session_callbacks *get_callbacks() const { return callbacks_; }
+ const nghttp2_option *get_option() const { return option_; }
+ void accept_connection(int fd) {
+ util::make_socket_nodelay(fd);
+ SSL *ssl = nullptr;
+ if (ssl_ctx_) {
+ ssl = ssl_session_new(fd);
+ if (!ssl) {
+ close(fd);
+ return;
+ }
+ }
+ auto handler =
+ std::make_unique<Http2Handler>(this, fd, ssl, get_next_session_id());
+ if (!ssl) {
+ if (handler->connection_made() != 0) {
+ return;
+ }
+ }
+ add_handler(handler.release());
+ }
+ void update_cached_date() { cached_date_ = util::http_date(tstamp_cached_); }
+ const std::string &get_cached_date() {
+ auto t = ev_now(loop_);
+ if (t != tstamp_cached_) {
+ tstamp_cached_ = t;
+ update_cached_date();
+ }
+ return cached_date_;
+ }
+ FileEntry *get_cached_fd(const std::string &path) {
+ auto range = fd_cache_.equal_range(path);
+ if (range.first == range.second) {
+ return nullptr;
+ }
+
+ auto now = std::chrono::steady_clock::now();
+
+ for (auto it = range.first; it != range.second;) {
+ auto &ent = (*it).second;
+ if (ent->stale) {
+ ++it;
+ continue;
+ }
+ if (need_validation_file_entry(ent.get(), now) &&
+ !validate_file_entry(ent.get(), now)) {
+ if (ent->usecount == 0) {
+ fd_cache_lru_.remove(ent.get());
+ close(ent->fd);
+ it = fd_cache_.erase(it);
+ continue;
+ }
+ ++it;
+ continue;
+ }
+
+ fd_cache_lru_.remove(ent.get());
+ fd_cache_lru_.append(ent.get());
+
+ ++ent->usecount;
+ return ent.get();
+ }
+ return nullptr;
+ }
+ FileEntry *cache_fd(const std::string &path, const FileEntry &ent) {
+#ifdef HAVE_STD_MAP_EMPLACE
+ auto rv = fd_cache_.emplace(path, std::make_unique<FileEntry>(ent));
+#else // !HAVE_STD_MAP_EMPLACE
+ // for gcc-4.7
+ auto rv = fd_cache_.insert(
+ std::make_pair(path, std::make_unique<FileEntry>(ent)));
+#endif // !HAVE_STD_MAP_EMPLACE
+ auto &res = (*rv).second;
+ res->it = rv;
+ fd_cache_lru_.append(res.get());
+
+ while (fd_cache_.size() > FILE_ENTRY_EVICT_THRES) {
+ auto ent = fd_cache_lru_.head;
+ if (ent->usecount) {
+ break;
+ }
+ fd_cache_lru_.remove(ent);
+ close(ent->fd);
+ fd_cache_.erase(ent->it);
+ }
+
+ return res.get();
+ }
+ void release_fd(FileEntry *target) {
+ --target->usecount;
+
+ if (target->usecount == 0 && target->stale) {
+ fd_cache_lru_.remove(target);
+ close(target->fd);
+ fd_cache_.erase(target->it);
+ return;
+ }
+
+ // We use timer to close file descriptor and delete the entry from
+ // cache. The timer will be started when there is no handler.
+ }
+ void release_unused_fd() {
+ for (auto i = std::begin(fd_cache_); i != std::end(fd_cache_);) {
+ auto &ent = (*i).second;
+ if (ent->usecount != 0) {
+ ++i;
+ continue;
+ }
+
+ fd_cache_lru_.remove(ent.get());
+ close(ent->fd);
+ i = fd_cache_.erase(i);
+ }
+ }
+ const HttpServer *get_server() const { return sv_; }
+ bool handlers_empty() const { return handlers_.empty(); }
+
+private:
+ std::set<Http2Handler *> handlers_;
+ // cache for file descriptors to read file.
+ std::multimap<std::string, std::unique_ptr<FileEntry>> fd_cache_;
+ DList<FileEntry> fd_cache_lru_;
+ HttpServer *sv_;
+ struct ev_loop *loop_;
+ const Config *config_;
+ SSL_CTX *ssl_ctx_;
+ nghttp2_session_callbacks *callbacks_;
+ nghttp2_option *option_;
+ ev_timer release_fd_timer_;
+ int64_t next_session_id_;
+ ev_tstamp tstamp_cached_;
+ std::string cached_date_;
+};
+
+namespace {
+void release_fd_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto sessions = static_cast<Sessions *>(w->data);
+
+ ev_timer_stop(loop, w);
+
+ if (!sessions->handlers_empty()) {
+ return;
+ }
+
+ sessions->release_unused_fd();
+}
+} // namespace
+
+Stream::Stream(Http2Handler *handler, int32_t stream_id)
+ : balloc(1024, 1024),
+ header{},
+ handler(handler),
+ file_ent(nullptr),
+ body_length(0),
+ body_offset(0),
+ header_buffer_size(0),
+ stream_id(stream_id),
+ echo_upload(false) {
+ auto config = handler->get_config();
+ ev_timer_init(&rtimer, stream_timeout_cb, 0., config->stream_read_timeout);
+ ev_timer_init(&wtimer, stream_timeout_cb, 0., config->stream_write_timeout);
+ rtimer.data = this;
+ wtimer.data = this;
+}
+
+Stream::~Stream() {
+ if (file_ent != nullptr) {
+ auto sessions = handler->get_sessions();
+ sessions->release_fd(file_ent);
+ }
+
+ auto &rcbuf = header.rcbuf;
+ nghttp2_rcbuf_decref(rcbuf.method);
+ nghttp2_rcbuf_decref(rcbuf.scheme);
+ nghttp2_rcbuf_decref(rcbuf.authority);
+ nghttp2_rcbuf_decref(rcbuf.host);
+ nghttp2_rcbuf_decref(rcbuf.path);
+ nghttp2_rcbuf_decref(rcbuf.ims);
+ nghttp2_rcbuf_decref(rcbuf.expect);
+
+ auto loop = handler->get_loop();
+ ev_timer_stop(loop, &rtimer);
+ ev_timer_stop(loop, &wtimer);
+}
+
+namespace {
+void on_session_closed(Http2Handler *hd, int64_t session_id) {
+ if (hd->get_config()->verbose) {
+ print_session_id(session_id);
+ print_timer();
+ std::cout << " closed" << std::endl;
+ }
+}
+} // namespace
+
+namespace {
+void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ int rv;
+ auto hd = static_cast<Http2Handler *>(w->data);
+ hd->terminate_session(NGHTTP2_SETTINGS_TIMEOUT);
+ rv = hd->on_write();
+ if (rv == -1) {
+ delete_handler(hd);
+ }
+}
+} // namespace
+
+namespace {
+void readcb(struct ev_loop *loop, ev_io *w, int revents) {
+ int rv;
+ auto handler = static_cast<Http2Handler *>(w->data);
+
+ rv = handler->on_read();
+ if (rv == -1) {
+ delete_handler(handler);
+ }
+}
+} // namespace
+
+namespace {
+void writecb(struct ev_loop *loop, ev_io *w, int revents) {
+ int rv;
+ auto handler = static_cast<Http2Handler *>(w->data);
+
+ rv = handler->on_write();
+ if (rv == -1) {
+ delete_handler(handler);
+ }
+}
+} // namespace
+
+Http2Handler::Http2Handler(Sessions *sessions, int fd, SSL *ssl,
+ int64_t session_id)
+ : session_id_(session_id),
+ session_(nullptr),
+ sessions_(sessions),
+ ssl_(ssl),
+ data_pending_(nullptr),
+ data_pendinglen_(0),
+ fd_(fd) {
+ ev_timer_init(&settings_timerev_, settings_timeout_cb, 10., 0.);
+ ev_io_init(&wev_, writecb, fd, EV_WRITE);
+ ev_io_init(&rev_, readcb, fd, EV_READ);
+
+ settings_timerev_.data = this;
+ wev_.data = this;
+ rev_.data = this;
+
+ auto loop = sessions_->get_loop();
+ ev_io_start(loop, &rev_);
+
+ if (ssl) {
+ SSL_set_accept_state(ssl);
+ read_ = &Http2Handler::tls_handshake;
+ write_ = &Http2Handler::tls_handshake;
+ } else {
+ read_ = &Http2Handler::read_clear;
+ write_ = &Http2Handler::write_clear;
+ }
+}
+
+Http2Handler::~Http2Handler() {
+ on_session_closed(this, session_id_);
+ nghttp2_session_del(session_);
+ if (ssl_) {
+ SSL_set_shutdown(ssl_, SSL_get_shutdown(ssl_) | SSL_RECEIVED_SHUTDOWN);
+ ERR_clear_error();
+ SSL_shutdown(ssl_);
+ }
+ auto loop = sessions_->get_loop();
+ ev_timer_stop(loop, &settings_timerev_);
+ ev_io_stop(loop, &rev_);
+ ev_io_stop(loop, &wev_);
+ if (ssl_) {
+ SSL_free(ssl_);
+ }
+ shutdown(fd_, SHUT_WR);
+ close(fd_);
+}
+
+void Http2Handler::remove_self() { sessions_->remove_handler(this); }
+
+struct ev_loop *Http2Handler::get_loop() const { return sessions_->get_loop(); }
+
+Http2Handler::WriteBuf *Http2Handler::get_wb() { return &wb_; }
+
+void Http2Handler::start_settings_timer() {
+ ev_timer_start(sessions_->get_loop(), &settings_timerev_);
+}
+
+int Http2Handler::fill_wb() {
+ if (data_pending_) {
+ auto n = std::min(wb_.wleft(), data_pendinglen_);
+ wb_.write(data_pending_, n);
+ if (n < data_pendinglen_) {
+ data_pending_ += n;
+ data_pendinglen_ -= n;
+ return 0;
+ }
+
+ data_pending_ = nullptr;
+ data_pendinglen_ = 0;
+ }
+
+ for (;;) {
+ const uint8_t *data;
+ auto datalen = nghttp2_session_mem_send(session_, &data);
+
+ if (datalen < 0) {
+ std::cerr << "nghttp2_session_mem_send() returned error: "
+ << nghttp2_strerror(datalen) << std::endl;
+ return -1;
+ }
+ if (datalen == 0) {
+ break;
+ }
+ auto n = wb_.write(data, datalen);
+ if (n < static_cast<decltype(n)>(datalen)) {
+ data_pending_ = data + n;
+ data_pendinglen_ = datalen - n;
+ break;
+ }
+ }
+ return 0;
+}
+
+int Http2Handler::read_clear() {
+ int rv;
+ std::array<uint8_t, 8_k> buf;
+
+ ssize_t nread;
+ while ((nread = read(fd_, buf.data(), buf.size())) == -1 && errno == EINTR)
+ ;
+ if (nread == -1) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ return write_(*this);
+ }
+ return -1;
+ }
+ if (nread == 0) {
+ return -1;
+ }
+
+ if (get_config()->hexdump) {
+ util::hexdump(stdout, buf.data(), nread);
+ }
+
+ rv = nghttp2_session_mem_recv(session_, buf.data(), nread);
+ if (rv < 0) {
+ if (rv != NGHTTP2_ERR_BAD_CLIENT_MAGIC) {
+ std::cerr << "nghttp2_session_mem_recv() returned error: "
+ << nghttp2_strerror(rv) << std::endl;
+ }
+ return -1;
+ }
+
+ return write_(*this);
+}
+
+int Http2Handler::write_clear() {
+ auto loop = sessions_->get_loop();
+ for (;;) {
+ if (wb_.rleft() > 0) {
+ ssize_t nwrite;
+ while ((nwrite = write(fd_, wb_.pos, wb_.rleft())) == -1 &&
+ errno == EINTR)
+ ;
+ if (nwrite == -1) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ ev_io_start(loop, &wev_);
+ return 0;
+ }
+ return -1;
+ }
+ wb_.drain(nwrite);
+ continue;
+ }
+ wb_.reset();
+ if (fill_wb() != 0) {
+ return -1;
+ }
+ if (wb_.rleft() == 0) {
+ break;
+ }
+ }
+
+ if (wb_.rleft() == 0) {
+ ev_io_stop(loop, &wev_);
+ } else {
+ ev_io_start(loop, &wev_);
+ }
+
+ if (nghttp2_session_want_read(session_) == 0 &&
+ nghttp2_session_want_write(session_) == 0 && wb_.rleft() == 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int Http2Handler::tls_handshake() {
+ ev_io_stop(sessions_->get_loop(), &wev_);
+
+ ERR_clear_error();
+
+ auto rv = SSL_do_handshake(ssl_);
+
+ if (rv <= 0) {
+ auto err = SSL_get_error(ssl_, rv);
+ switch (err) {
+ case SSL_ERROR_WANT_READ:
+ return 0;
+ case SSL_ERROR_WANT_WRITE:
+ ev_io_start(sessions_->get_loop(), &wev_);
+ return 0;
+ default:
+ return -1;
+ }
+ }
+
+ if (sessions_->get_config()->verbose) {
+ std::cerr << "SSL/TLS handshake completed" << std::endl;
+ }
+
+ if (verify_alpn_result() != 0) {
+ return -1;
+ }
+
+ read_ = &Http2Handler::read_tls;
+ write_ = &Http2Handler::write_tls;
+
+ if (connection_made() != 0) {
+ return -1;
+ }
+
+ if (sessions_->get_config()->verbose) {
+ if (SSL_session_reused(ssl_)) {
+ std::cerr << "SSL/TLS session reused" << std::endl;
+ }
+ }
+
+ return 0;
+}
+
+int Http2Handler::read_tls() {
+ std::array<uint8_t, 8_k> buf;
+
+ ERR_clear_error();
+
+ auto rv = SSL_read(ssl_, buf.data(), buf.size());
+
+ if (rv <= 0) {
+ auto err = SSL_get_error(ssl_, rv);
+ switch (err) {
+ case SSL_ERROR_WANT_READ:
+ return write_(*this);
+ case SSL_ERROR_WANT_WRITE:
+ // renegotiation started
+ return -1;
+ default:
+ return -1;
+ }
+ }
+
+ auto nread = rv;
+
+ if (get_config()->hexdump) {
+ util::hexdump(stdout, buf.data(), nread);
+ }
+
+ rv = nghttp2_session_mem_recv(session_, buf.data(), nread);
+ if (rv < 0) {
+ if (rv != NGHTTP2_ERR_BAD_CLIENT_MAGIC) {
+ std::cerr << "nghttp2_session_mem_recv() returned error: "
+ << nghttp2_strerror(rv) << std::endl;
+ }
+ return -1;
+ }
+
+ return write_(*this);
+}
+
+int Http2Handler::write_tls() {
+ auto loop = sessions_->get_loop();
+
+ ERR_clear_error();
+
+ for (;;) {
+ if (wb_.rleft() > 0) {
+ auto rv = SSL_write(ssl_, wb_.pos, wb_.rleft());
+
+ if (rv <= 0) {
+ auto err = SSL_get_error(ssl_, rv);
+ switch (err) {
+ case SSL_ERROR_WANT_READ:
+ // renegotiation started
+ return -1;
+ case SSL_ERROR_WANT_WRITE:
+ ev_io_start(sessions_->get_loop(), &wev_);
+ return 0;
+ default:
+ return -1;
+ }
+ }
+
+ wb_.drain(rv);
+ continue;
+ }
+ wb_.reset();
+ if (fill_wb() != 0) {
+ return -1;
+ }
+ if (wb_.rleft() == 0) {
+ break;
+ }
+ }
+
+ if (wb_.rleft() == 0) {
+ ev_io_stop(loop, &wev_);
+ } else {
+ ev_io_start(loop, &wev_);
+ }
+
+ if (nghttp2_session_want_read(session_) == 0 &&
+ nghttp2_session_want_write(session_) == 0 && wb_.rleft() == 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int Http2Handler::on_read() { return read_(*this); }
+
+int Http2Handler::on_write() { return write_(*this); }
+
+int Http2Handler::connection_made() {
+ int r;
+
+ r = nghttp2_session_server_new2(&session_, sessions_->get_callbacks(), this,
+ sessions_->get_option());
+
+ if (r != 0) {
+ return r;
+ }
+
+ auto config = sessions_->get_config();
+ std::array<nghttp2_settings_entry, 4> entry;
+ size_t niv = 1;
+
+ entry[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
+ entry[0].value = config->max_concurrent_streams;
+
+ if (config->header_table_size >= 0) {
+ entry[niv].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
+ entry[niv].value = config->header_table_size;
+ ++niv;
+ }
+
+ if (config->window_bits != -1) {
+ entry[niv].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+ entry[niv].value = (1 << config->window_bits) - 1;
+ ++niv;
+ }
+
+ if (config->no_rfc7540_pri) {
+ entry[niv].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES;
+ entry[niv].value = 1;
+ ++niv;
+ }
+
+ r = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, entry.data(), niv);
+ if (r != 0) {
+ return r;
+ }
+
+ if (config->connection_window_bits != -1) {
+ r = nghttp2_session_set_local_window_size(
+ session_, NGHTTP2_FLAG_NONE, 0,
+ (1 << config->connection_window_bits) - 1);
+ if (r != 0) {
+ return r;
+ }
+ }
+
+ if (ssl_ && !nghttp2::tls::check_http2_requirement(ssl_)) {
+ terminate_session(NGHTTP2_INADEQUATE_SECURITY);
+ }
+
+ return on_write();
+}
+
+int Http2Handler::verify_alpn_result() {
+ const unsigned char *next_proto = nullptr;
+ unsigned int next_proto_len;
+ // Check the negotiated protocol in ALPN
+ SSL_get0_alpn_selected(ssl_, &next_proto, &next_proto_len);
+ if (next_proto) {
+ auto proto = StringRef{next_proto, next_proto_len};
+ if (sessions_->get_config()->verbose) {
+ std::cout << "The negotiated protocol: " << proto << std::endl;
+ }
+ if (util::check_h2_is_selected(proto)) {
+ return 0;
+ }
+ }
+ if (sessions_->get_config()->verbose) {
+ std::cerr << "Client did not advertise HTTP/2 protocol."
+ << " (nghttp2 expects " << NGHTTP2_PROTO_VERSION_ID << ")"
+ << std::endl;
+ }
+ return -1;
+}
+
+int Http2Handler::submit_file_response(const StringRef &status, Stream *stream,
+ time_t last_modified, off_t file_length,
+ const std::string *content_type,
+ nghttp2_data_provider *data_prd) {
+ std::string last_modified_str;
+ auto nva = make_array(http2::make_nv_ls_nocopy(":status", status),
+ http2::make_nv_ls_nocopy("server", NGHTTPD_SERVER),
+ http2::make_nv_ll("cache-control", "max-age=3600"),
+ http2::make_nv_ls("date", sessions_->get_cached_date()),
+ http2::make_nv_ll("", ""), http2::make_nv_ll("", ""),
+ http2::make_nv_ll("", ""), http2::make_nv_ll("", ""));
+ size_t nvlen = 4;
+ if (!get_config()->no_content_length) {
+ nva[nvlen++] = http2::make_nv_ls_nocopy(
+ "content-length",
+ util::make_string_ref_uint(stream->balloc, file_length));
+ }
+ if (last_modified != 0) {
+ last_modified_str = util::http_date(last_modified);
+ nva[nvlen++] = http2::make_nv_ls("last-modified", last_modified_str);
+ }
+ if (content_type) {
+ nva[nvlen++] = http2::make_nv_ls("content-type", *content_type);
+ }
+ auto &trailer_names = get_config()->trailer_names;
+ if (!trailer_names.empty()) {
+ nva[nvlen++] = http2::make_nv_ls_nocopy("trailer", trailer_names);
+ }
+ return nghttp2_submit_response(session_, stream->stream_id, nva.data(), nvlen,
+ data_prd);
+}
+
+int Http2Handler::submit_response(const StringRef &status, int32_t stream_id,
+ const HeaderRefs &headers,
+ nghttp2_data_provider *data_prd) {
+ auto nva = std::vector<nghttp2_nv>();
+ nva.reserve(4 + headers.size());
+ nva.push_back(http2::make_nv_ls_nocopy(":status", status));
+ nva.push_back(http2::make_nv_ls_nocopy("server", NGHTTPD_SERVER));
+ nva.push_back(http2::make_nv_ls("date", sessions_->get_cached_date()));
+
+ if (data_prd) {
+ auto &trailer_names = get_config()->trailer_names;
+ if (!trailer_names.empty()) {
+ nva.push_back(http2::make_nv_ls_nocopy("trailer", trailer_names));
+ }
+ }
+
+ for (auto &nv : headers) {
+ nva.push_back(http2::make_nv_nocopy(nv.name, nv.value, nv.no_index));
+ }
+ int r = nghttp2_submit_response(session_, stream_id, nva.data(), nva.size(),
+ data_prd);
+ return r;
+}
+
+int Http2Handler::submit_response(const StringRef &status, int32_t stream_id,
+ nghttp2_data_provider *data_prd) {
+ auto nva = make_array(http2::make_nv_ls_nocopy(":status", status),
+ http2::make_nv_ls_nocopy("server", NGHTTPD_SERVER),
+ http2::make_nv_ls("date", sessions_->get_cached_date()),
+ http2::make_nv_ll("", ""));
+ size_t nvlen = 3;
+
+ if (data_prd) {
+ auto &trailer_names = get_config()->trailer_names;
+ if (!trailer_names.empty()) {
+ nva[nvlen++] = http2::make_nv_ls_nocopy("trailer", trailer_names);
+ }
+ }
+
+ return nghttp2_submit_response(session_, stream_id, nva.data(), nvlen,
+ data_prd);
+}
+
+int Http2Handler::submit_non_final_response(const std::string &status,
+ int32_t stream_id) {
+ auto nva = make_array(http2::make_nv_ls(":status", status));
+ return nghttp2_submit_headers(session_, NGHTTP2_FLAG_NONE, stream_id, nullptr,
+ nva.data(), nva.size(), nullptr);
+}
+
+int Http2Handler::submit_push_promise(Stream *stream,
+ const StringRef &push_path) {
+ auto authority = stream->header.authority;
+
+ if (authority.empty()) {
+ authority = stream->header.host;
+ }
+
+ auto scheme = get_config()->no_tls ? StringRef::from_lit("http")
+ : StringRef::from_lit("https");
+
+ auto nva = make_array(http2::make_nv_ll(":method", "GET"),
+ http2::make_nv_ls_nocopy(":path", push_path),
+ http2::make_nv_ls_nocopy(":scheme", scheme),
+ http2::make_nv_ls_nocopy(":authority", authority));
+
+ auto promised_stream_id = nghttp2_submit_push_promise(
+ session_, NGHTTP2_FLAG_END_HEADERS, stream->stream_id, nva.data(),
+ nva.size(), nullptr);
+
+ if (promised_stream_id < 0) {
+ return promised_stream_id;
+ }
+
+ auto promised_stream = std::make_unique<Stream>(this, promised_stream_id);
+
+ auto &promised_header = promised_stream->header;
+ promised_header.method = StringRef::from_lit("GET");
+ promised_header.path = push_path;
+ promised_header.scheme = scheme;
+ promised_header.authority =
+ make_string_ref(promised_stream->balloc, authority);
+
+ add_stream(promised_stream_id, std::move(promised_stream));
+
+ return 0;
+}
+
+int Http2Handler::submit_rst_stream(Stream *stream, uint32_t error_code) {
+ remove_stream_read_timeout(stream);
+ remove_stream_write_timeout(stream);
+
+ return nghttp2_submit_rst_stream(session_, NGHTTP2_FLAG_NONE,
+ stream->stream_id, error_code);
+}
+
+void Http2Handler::add_stream(int32_t stream_id,
+ std::unique_ptr<Stream> stream) {
+ id2stream_[stream_id] = std::move(stream);
+}
+
+void Http2Handler::remove_stream(int32_t stream_id) {
+ id2stream_.erase(stream_id);
+}
+
+Stream *Http2Handler::get_stream(int32_t stream_id) {
+ auto itr = id2stream_.find(stream_id);
+ if (itr == std::end(id2stream_)) {
+ return nullptr;
+ } else {
+ return (*itr).second.get();
+ }
+}
+
+int64_t Http2Handler::session_id() const { return session_id_; }
+
+Sessions *Http2Handler::get_sessions() const { return sessions_; }
+
+const Config *Http2Handler::get_config() const {
+ return sessions_->get_config();
+}
+
+void Http2Handler::remove_settings_timer() {
+ ev_timer_stop(sessions_->get_loop(), &settings_timerev_);
+}
+
+void Http2Handler::terminate_session(uint32_t error_code) {
+ nghttp2_session_terminate_session(session_, error_code);
+}
+
+ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id,
+ uint8_t *buf, size_t length, uint32_t *data_flags,
+ nghttp2_data_source *source, void *user_data) {
+ int rv;
+ auto hd = static_cast<Http2Handler *>(user_data);
+ auto stream = hd->get_stream(stream_id);
+
+ auto nread = std::min(stream->body_length - stream->body_offset,
+ static_cast<int64_t>(length));
+
+ *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY;
+
+ if (nread == 0 || stream->body_length == stream->body_offset + nread) {
+ *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+
+ auto config = hd->get_config();
+ if (!config->trailer.empty()) {
+ std::vector<nghttp2_nv> nva;
+ nva.reserve(config->trailer.size());
+ for (auto &kv : config->trailer) {
+ nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index));
+ }
+ rv = nghttp2_submit_trailer(session, stream_id, nva.data(), nva.size());
+ if (rv != 0) {
+ if (nghttp2_is_fatal(rv)) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ } else {
+ *data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
+ }
+ }
+
+ if (nghttp2_session_get_stream_remote_close(session, stream_id) == 0) {
+ remove_stream_read_timeout(stream);
+ remove_stream_write_timeout(stream);
+
+ hd->submit_rst_stream(stream, NGHTTP2_NO_ERROR);
+ }
+ }
+
+ return nread;
+}
+
+namespace {
+void prepare_status_response(Stream *stream, Http2Handler *hd, int status) {
+ auto sessions = hd->get_sessions();
+ auto status_page = sessions->get_server()->get_status_page(status);
+ auto file_ent = &status_page->file_ent;
+
+ // we don't set stream->file_ent since we don't want to expire it.
+ stream->body_length = file_ent->length;
+ nghttp2_data_provider data_prd;
+ data_prd.source.fd = file_ent->fd;
+ data_prd.read_callback = file_read_callback;
+
+ HeaderRefs headers;
+ headers.reserve(2);
+ headers.emplace_back(StringRef::from_lit("content-type"),
+ StringRef::from_lit("text/html; charset=UTF-8"));
+ headers.emplace_back(
+ StringRef::from_lit("content-length"),
+ util::make_string_ref_uint(stream->balloc, file_ent->length));
+ hd->submit_response(StringRef{status_page->status}, stream->stream_id,
+ headers, &data_prd);
+}
+} // namespace
+
+namespace {
+void prepare_echo_response(Stream *stream, Http2Handler *hd) {
+ auto length = lseek(stream->file_ent->fd, 0, SEEK_END);
+ if (length == -1) {
+ hd->submit_rst_stream(stream, NGHTTP2_INTERNAL_ERROR);
+ return;
+ }
+ stream->body_length = length;
+ if (lseek(stream->file_ent->fd, 0, SEEK_SET) == -1) {
+ hd->submit_rst_stream(stream, NGHTTP2_INTERNAL_ERROR);
+ return;
+ }
+ nghttp2_data_provider data_prd;
+ data_prd.source.fd = stream->file_ent->fd;
+ data_prd.read_callback = file_read_callback;
+
+ HeaderRefs headers;
+ headers.emplace_back(StringRef::from_lit("nghttpd-response"),
+ StringRef::from_lit("echo"));
+ if (!hd->get_config()->no_content_length) {
+ headers.emplace_back(StringRef::from_lit("content-length"),
+ util::make_string_ref_uint(stream->balloc, length));
+ }
+
+ hd->submit_response(StringRef::from_lit("200"), stream->stream_id, headers,
+ &data_prd);
+}
+} // namespace
+
+namespace {
+bool prepare_upload_temp_store(Stream *stream, Http2Handler *hd) {
+ auto sessions = hd->get_sessions();
+
+ char tempfn[] = "/tmp/nghttpd.temp.XXXXXX";
+ auto fd = mkstemp(tempfn);
+ if (fd == -1) {
+ return false;
+ }
+ unlink(tempfn);
+ // Ordinary request never start with "echo:". The length is 0 for
+ // now. We will update it when we get whole request body.
+ auto path = std::string("echo:") + tempfn;
+ stream->file_ent =
+ sessions->cache_fd(path, FileEntry(path, 0, 0, fd, nullptr, {}, true));
+ stream->echo_upload = true;
+ return true;
+}
+} // namespace
+
+namespace {
+void prepare_redirect_response(Stream *stream, Http2Handler *hd,
+ const StringRef &path, int status) {
+ auto scheme = stream->header.scheme;
+
+ auto authority = stream->header.authority;
+ if (authority.empty()) {
+ authority = stream->header.host;
+ }
+
+ auto location = concat_string_ref(
+ stream->balloc, scheme, StringRef::from_lit("://"), authority, path);
+
+ auto headers = HeaderRefs{{StringRef::from_lit("location"), location}};
+
+ auto sessions = hd->get_sessions();
+ auto status_page = sessions->get_server()->get_status_page(status);
+
+ hd->submit_response(StringRef{status_page->status}, stream->stream_id,
+ headers, nullptr);
+}
+} // namespace
+
+namespace {
+void prepare_response(Stream *stream, Http2Handler *hd,
+ bool allow_push = true) {
+ int rv;
+ auto reqpath = stream->header.path;
+ if (reqpath.empty()) {
+ prepare_status_response(stream, hd, 405);
+ return;
+ }
+
+ auto ims = stream->header.ims;
+
+ time_t last_mod = 0;
+ bool last_mod_found = false;
+ if (!ims.empty()) {
+ last_mod_found = true;
+ last_mod = util::parse_http_date(ims);
+ }
+
+ StringRef raw_path, raw_query;
+ auto query_pos = std::find(std::begin(reqpath), std::end(reqpath), '?');
+ if (query_pos != std::end(reqpath)) {
+ // Do not response to this request to allow clients to test timeouts.
+ if (util::streq_l("nghttpd_do_not_respond_to_req=yes",
+ StringRef{query_pos, std::end(reqpath)})) {
+ return;
+ }
+ raw_path = StringRef{std::begin(reqpath), query_pos};
+ raw_query = StringRef{query_pos, std::end(reqpath)};
+ } else {
+ raw_path = reqpath;
+ }
+
+ auto sessions = hd->get_sessions();
+
+ StringRef path;
+ if (std::find(std::begin(raw_path), std::end(raw_path), '%') ==
+ std::end(raw_path)) {
+ path = raw_path;
+ } else {
+ path = util::percent_decode(stream->balloc, raw_path);
+ }
+
+ path = http2::path_join(stream->balloc, StringRef{}, StringRef{}, path,
+ StringRef{});
+
+ if (std::find(std::begin(path), std::end(path), '\\') != std::end(path)) {
+ if (stream->file_ent) {
+ sessions->release_fd(stream->file_ent);
+ stream->file_ent = nullptr;
+ }
+ prepare_status_response(stream, hd, 404);
+ return;
+ }
+
+ if (!hd->get_config()->push.empty()) {
+ auto push_itr = hd->get_config()->push.find(path.str());
+ if (allow_push && push_itr != std::end(hd->get_config()->push)) {
+ for (auto &push_path : (*push_itr).second) {
+ rv = hd->submit_push_promise(stream, StringRef{push_path});
+ if (rv != 0) {
+ std::cerr << "nghttp2_submit_push_promise() returned error: "
+ << nghttp2_strerror(rv) << std::endl;
+ }
+ }
+ }
+ }
+
+ std::string file_path;
+ {
+ auto len = hd->get_config()->htdocs.size() + path.size();
+
+ auto trailing_slash = path[path.size() - 1] == '/';
+ if (trailing_slash) {
+ len += DEFAULT_HTML.size();
+ }
+
+ file_path.resize(len);
+
+ auto p = &file_path[0];
+
+ auto &htdocs = hd->get_config()->htdocs;
+ p = std::copy(std::begin(htdocs), std::end(htdocs), p);
+ p = std::copy(std::begin(path), std::end(path), p);
+ if (trailing_slash) {
+ std::copy(std::begin(DEFAULT_HTML), std::end(DEFAULT_HTML), p);
+ }
+ }
+
+ if (stream->echo_upload) {
+ assert(stream->file_ent);
+ prepare_echo_response(stream, hd);
+ return;
+ }
+
+ auto file_ent = sessions->get_cached_fd(file_path);
+
+ if (file_ent == nullptr) {
+ int file = open(file_path.c_str(), O_RDONLY | O_BINARY);
+ if (file == -1) {
+ prepare_status_response(stream, hd, 404);
+
+ return;
+ }
+
+ struct stat buf;
+
+ if (fstat(file, &buf) == -1) {
+ close(file);
+ prepare_status_response(stream, hd, 404);
+
+ return;
+ }
+
+ if (buf.st_mode & S_IFDIR) {
+ close(file);
+
+ auto reqpath = concat_string_ref(stream->balloc, raw_path,
+ StringRef::from_lit("/"), raw_query);
+
+ prepare_redirect_response(stream, hd, reqpath, 301);
+
+ return;
+ }
+
+ const std::string *content_type = nullptr;
+
+ auto ext = file_path.c_str() + file_path.size() - 1;
+ for (; file_path.c_str() < ext && *ext != '.' && *ext != '/'; --ext)
+ ;
+ if (*ext == '.') {
+ ++ext;
+
+ const auto &mime_types = hd->get_config()->mime_types;
+ auto content_type_itr = mime_types.find(ext);
+ if (content_type_itr != std::end(mime_types)) {
+ content_type = &(*content_type_itr).second;
+ }
+ }
+
+ file_ent = sessions->cache_fd(
+ file_path, FileEntry(file_path, buf.st_size, buf.st_mtime, file,
+ content_type, std::chrono::steady_clock::now()));
+ }
+
+ stream->file_ent = file_ent;
+
+ if (last_mod_found && file_ent->mtime <= last_mod) {
+ hd->submit_response(StringRef::from_lit("304"), stream->stream_id, nullptr);
+
+ return;
+ }
+
+ auto method = stream->header.method;
+ if (method == StringRef::from_lit("HEAD")) {
+ hd->submit_file_response(StringRef::from_lit("200"), stream,
+ file_ent->mtime, file_ent->length,
+ file_ent->content_type, nullptr);
+ return;
+ }
+
+ stream->body_length = file_ent->length;
+
+ nghttp2_data_provider data_prd;
+
+ data_prd.source.fd = file_ent->fd;
+ data_prd.read_callback = file_read_callback;
+
+ hd->submit_file_response(StringRef::from_lit("200"), stream, file_ent->mtime,
+ file_ent->length, file_ent->content_type, &data_prd);
+}
+} // namespace
+
+namespace {
+int on_header_callback2(nghttp2_session *session, const nghttp2_frame *frame,
+ nghttp2_rcbuf *name, nghttp2_rcbuf *value,
+ uint8_t flags, void *user_data) {
+ auto hd = static_cast<Http2Handler *>(user_data);
+
+ auto namebuf = nghttp2_rcbuf_get_buf(name);
+ auto valuebuf = nghttp2_rcbuf_get_buf(value);
+
+ if (hd->get_config()->verbose) {
+ print_session_id(hd->session_id());
+ verbose_on_header_callback(session, frame, namebuf.base, namebuf.len,
+ valuebuf.base, valuebuf.len, flags, user_data);
+ }
+ if (frame->hd.type != NGHTTP2_HEADERS ||
+ frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
+ return 0;
+ }
+ auto stream = hd->get_stream(frame->hd.stream_id);
+ if (!stream) {
+ return 0;
+ }
+
+ if (stream->header_buffer_size + namebuf.len + valuebuf.len > 64_k) {
+ hd->submit_rst_stream(stream, NGHTTP2_INTERNAL_ERROR);
+ return 0;
+ }
+
+ stream->header_buffer_size += namebuf.len + valuebuf.len;
+
+ auto token = http2::lookup_token(namebuf.base, namebuf.len);
+
+ auto &header = stream->header;
+
+ switch (token) {
+ case http2::HD__METHOD:
+ header.method = StringRef{valuebuf.base, valuebuf.len};
+ header.rcbuf.method = value;
+ nghttp2_rcbuf_incref(value);
+ break;
+ case http2::HD__SCHEME:
+ header.scheme = StringRef{valuebuf.base, valuebuf.len};
+ header.rcbuf.scheme = value;
+ nghttp2_rcbuf_incref(value);
+ break;
+ case http2::HD__AUTHORITY:
+ header.authority = StringRef{valuebuf.base, valuebuf.len};
+ header.rcbuf.authority = value;
+ nghttp2_rcbuf_incref(value);
+ break;
+ case http2::HD_HOST:
+ header.host = StringRef{valuebuf.base, valuebuf.len};
+ header.rcbuf.host = value;
+ nghttp2_rcbuf_incref(value);
+ break;
+ case http2::HD__PATH:
+ header.path = StringRef{valuebuf.base, valuebuf.len};
+ header.rcbuf.path = value;
+ nghttp2_rcbuf_incref(value);
+ break;
+ case http2::HD_IF_MODIFIED_SINCE:
+ header.ims = StringRef{valuebuf.base, valuebuf.len};
+ header.rcbuf.ims = value;
+ nghttp2_rcbuf_incref(value);
+ break;
+ case http2::HD_EXPECT:
+ header.expect = StringRef{valuebuf.base, valuebuf.len};
+ header.rcbuf.expect = value;
+ nghttp2_rcbuf_incref(value);
+ break;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int on_begin_headers_callback(nghttp2_session *session,
+ const nghttp2_frame *frame, void *user_data) {
+ auto hd = static_cast<Http2Handler *>(user_data);
+
+ if (frame->hd.type != NGHTTP2_HEADERS ||
+ frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
+ return 0;
+ }
+
+ auto stream = std::make_unique<Stream>(hd, frame->hd.stream_id);
+
+ add_stream_read_timeout(stream.get());
+
+ hd->add_stream(frame->hd.stream_id, std::move(stream));
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int hd_on_frame_recv_callback(nghttp2_session *session,
+ const nghttp2_frame *frame, void *user_data) {
+ auto hd = static_cast<Http2Handler *>(user_data);
+ if (hd->get_config()->verbose) {
+ print_session_id(hd->session_id());
+ verbose_on_frame_recv_callback(session, frame, user_data);
+ }
+ switch (frame->hd.type) {
+ case NGHTTP2_DATA: {
+ // TODO Handle POST
+ auto stream = hd->get_stream(frame->hd.stream_id);
+ if (!stream) {
+ return 0;
+ }
+
+ if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+ remove_stream_read_timeout(stream);
+ if (stream->echo_upload || !hd->get_config()->early_response) {
+ prepare_response(stream, hd);
+ }
+ } else {
+ add_stream_read_timeout(stream);
+ }
+
+ break;
+ }
+ case NGHTTP2_HEADERS: {
+ auto stream = hd->get_stream(frame->hd.stream_id);
+ if (!stream) {
+ return 0;
+ }
+
+ if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
+
+ auto expect100 = stream->header.expect;
+
+ if (util::strieq_l("100-continue", expect100)) {
+ hd->submit_non_final_response("100", frame->hd.stream_id);
+ }
+
+ auto method = stream->header.method;
+ if (hd->get_config()->echo_upload &&
+ (method == StringRef::from_lit("POST") ||
+ method == StringRef::from_lit("PUT"))) {
+ if (!prepare_upload_temp_store(stream, hd)) {
+ hd->submit_rst_stream(stream, NGHTTP2_INTERNAL_ERROR);
+ return 0;
+ }
+ } else if (hd->get_config()->early_response) {
+ prepare_response(stream, hd);
+ }
+ }
+
+ if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+ remove_stream_read_timeout(stream);
+ if (stream->echo_upload || !hd->get_config()->early_response) {
+ prepare_response(stream, hd);
+ }
+ } else {
+ add_stream_read_timeout(stream);
+ }
+
+ break;
+ }
+ case NGHTTP2_SETTINGS:
+ if (frame->hd.flags & NGHTTP2_FLAG_ACK) {
+ hd->remove_settings_timer();
+ }
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+int hd_on_frame_send_callback(nghttp2_session *session,
+ const nghttp2_frame *frame, void *user_data) {
+ auto hd = static_cast<Http2Handler *>(user_data);
+
+ if (hd->get_config()->verbose) {
+ print_session_id(hd->session_id());
+ verbose_on_frame_send_callback(session, frame, user_data);
+ }
+
+ switch (frame->hd.type) {
+ case NGHTTP2_DATA:
+ case NGHTTP2_HEADERS: {
+ auto stream = hd->get_stream(frame->hd.stream_id);
+
+ if (!stream) {
+ return 0;
+ }
+
+ if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+ remove_stream_write_timeout(stream);
+ } else if (std::min(nghttp2_session_get_stream_remote_window_size(
+ session, frame->hd.stream_id),
+ nghttp2_session_get_remote_window_size(session)) <= 0) {
+ // If stream is blocked by flow control, enable write timeout.
+ add_stream_read_timeout_if_pending(stream);
+ add_stream_write_timeout(stream);
+ } else {
+ add_stream_read_timeout_if_pending(stream);
+ remove_stream_write_timeout(stream);
+ }
+
+ break;
+ }
+ case NGHTTP2_SETTINGS: {
+ if (frame->hd.flags & NGHTTP2_FLAG_ACK) {
+ return 0;
+ }
+
+ hd->start_settings_timer();
+
+ break;
+ }
+ case NGHTTP2_PUSH_PROMISE: {
+ auto promised_stream_id = frame->push_promise.promised_stream_id;
+ auto promised_stream = hd->get_stream(promised_stream_id);
+ auto stream = hd->get_stream(frame->hd.stream_id);
+
+ if (!stream || !promised_stream) {
+ return 0;
+ }
+
+ add_stream_read_timeout_if_pending(stream);
+ add_stream_write_timeout(stream);
+
+ prepare_response(promised_stream, hd, /*allow_push */ false);
+ }
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+int send_data_callback(nghttp2_session *session, nghttp2_frame *frame,
+ const uint8_t *framehd, size_t length,
+ nghttp2_data_source *source, void *user_data) {
+ auto hd = static_cast<Http2Handler *>(user_data);
+ auto wb = hd->get_wb();
+ auto padlen = frame->data.padlen;
+ auto stream = hd->get_stream(frame->hd.stream_id);
+
+ if (wb->wleft() < 9 + length + padlen) {
+ return NGHTTP2_ERR_WOULDBLOCK;
+ }
+
+ int fd = source->fd;
+
+ auto p = wb->last;
+
+ p = std::copy_n(framehd, 9, p);
+
+ if (padlen) {
+ *p++ = padlen - 1;
+ }
+
+ while (length) {
+ ssize_t nread;
+ while ((nread = pread(fd, p, length, stream->body_offset)) == -1 &&
+ errno == EINTR)
+ ;
+
+ if (nread == -1) {
+ remove_stream_read_timeout(stream);
+ remove_stream_write_timeout(stream);
+
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+
+ stream->body_offset += nread;
+ length -= nread;
+ p += nread;
+ }
+
+ if (padlen) {
+ std::fill(p, p + padlen - 1, 0);
+ p += padlen - 1;
+ }
+
+ wb->last = p;
+
+ return 0;
+}
+} // namespace
+
+namespace {
+ssize_t select_padding_callback(nghttp2_session *session,
+ const nghttp2_frame *frame, size_t max_payload,
+ void *user_data) {
+ auto hd = static_cast<Http2Handler *>(user_data);
+ return std::min(max_payload, frame->hd.length + hd->get_config()->padding);
+}
+} // namespace
+
+namespace {
+int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
+ int32_t stream_id, const uint8_t *data,
+ size_t len, void *user_data) {
+ auto hd = static_cast<Http2Handler *>(user_data);
+ auto stream = hd->get_stream(stream_id);
+
+ if (!stream) {
+ return 0;
+ }
+
+ if (stream->echo_upload) {
+ assert(stream->file_ent);
+ while (len) {
+ ssize_t n;
+ while ((n = write(stream->file_ent->fd, data, len)) == -1 &&
+ errno == EINTR)
+ ;
+ if (n == -1) {
+ hd->submit_rst_stream(stream, NGHTTP2_INTERNAL_ERROR);
+ return 0;
+ }
+ len -= n;
+ data += n;
+ }
+ }
+ // TODO Handle POST
+
+ add_stream_read_timeout(stream);
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
+ uint32_t error_code, void *user_data) {
+ auto hd = static_cast<Http2Handler *>(user_data);
+ hd->remove_stream(stream_id);
+ if (hd->get_config()->verbose) {
+ print_session_id(hd->session_id());
+ print_timer();
+ printf(" stream_id=%d closed\n", stream_id);
+ fflush(stdout);
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+void fill_callback(nghttp2_session_callbacks *callbacks, const Config *config) {
+ nghttp2_session_callbacks_set_on_stream_close_callback(
+ callbacks, on_stream_close_callback);
+
+ nghttp2_session_callbacks_set_on_frame_recv_callback(
+ callbacks, hd_on_frame_recv_callback);
+
+ nghttp2_session_callbacks_set_on_frame_send_callback(
+ callbacks, hd_on_frame_send_callback);
+
+ if (config->verbose) {
+ nghttp2_session_callbacks_set_on_invalid_frame_recv_callback(
+ callbacks, verbose_on_invalid_frame_recv_callback);
+
+ nghttp2_session_callbacks_set_error_callback2(callbacks,
+ verbose_error_callback);
+ }
+
+ nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
+ callbacks, on_data_chunk_recv_callback);
+
+ nghttp2_session_callbacks_set_on_header_callback2(callbacks,
+ on_header_callback2);
+
+ nghttp2_session_callbacks_set_on_begin_headers_callback(
+ callbacks, on_begin_headers_callback);
+
+ nghttp2_session_callbacks_set_send_data_callback(callbacks,
+ send_data_callback);
+
+ if (config->padding) {
+ nghttp2_session_callbacks_set_select_padding_callback(
+ callbacks, select_padding_callback);
+ }
+}
+} // namespace
+
+struct ClientInfo {
+ int fd;
+};
+
+struct Worker {
+ std::unique_ptr<Sessions> sessions;
+ ev_async w;
+ // protects q
+ std::mutex m;
+ std::deque<ClientInfo> q;
+};
+
+namespace {
+void worker_acceptcb(struct ev_loop *loop, ev_async *w, int revents) {
+ auto worker = static_cast<Worker *>(w->data);
+ auto &sessions = worker->sessions;
+
+ std::deque<ClientInfo> q;
+ {
+ std::lock_guard<std::mutex> lock(worker->m);
+ q.swap(worker->q);
+ }
+
+ for (const auto &c : q) {
+ sessions->accept_connection(c.fd);
+ }
+}
+} // namespace
+
+namespace {
+void run_worker(Worker *worker) {
+ auto loop = worker->sessions->get_loop();
+
+ ev_run(loop, 0);
+}
+} // namespace
+
+namespace {
+int get_ev_loop_flags() {
+ if (ev_supported_backends() & ~ev_recommended_backends() & EVBACKEND_KQUEUE) {
+ return ev_recommended_backends() | EVBACKEND_KQUEUE;
+ }
+
+ return 0;
+}
+} // namespace
+
+class AcceptHandler {
+public:
+ AcceptHandler(HttpServer *sv, Sessions *sessions, const Config *config)
+ : sessions_(sessions), config_(config), next_worker_(0) {
+ if (config_->num_worker == 1) {
+ return;
+ }
+ for (size_t i = 0; i < config_->num_worker; ++i) {
+ if (config_->verbose) {
+ std::cerr << "spawning thread #" << i << std::endl;
+ }
+ auto worker = std::make_unique<Worker>();
+ auto loop = ev_loop_new(get_ev_loop_flags());
+ worker->sessions = std::make_unique<Sessions>(sv, loop, config_,
+ sessions_->get_ssl_ctx());
+ ev_async_init(&worker->w, worker_acceptcb);
+ worker->w.data = worker.get();
+ ev_async_start(loop, &worker->w);
+
+ auto t = std::thread(run_worker, worker.get());
+ t.detach();
+ workers_.push_back(std::move(worker));
+ }
+ }
+ void accept_connection(int fd) {
+ if (config_->num_worker == 1) {
+ sessions_->accept_connection(fd);
+ return;
+ }
+
+ // Dispatch client to the one of the worker threads, in a round
+ // robin manner.
+ auto &worker = workers_[next_worker_];
+ if (next_worker_ == config_->num_worker - 1) {
+ next_worker_ = 0;
+ } else {
+ ++next_worker_;
+ }
+ {
+ std::lock_guard<std::mutex> lock(worker->m);
+ worker->q.push_back({fd});
+ }
+ ev_async_send(worker->sessions->get_loop(), &worker->w);
+ }
+
+private:
+ std::vector<std::unique_ptr<Worker>> workers_;
+ Sessions *sessions_;
+ const Config *config_;
+ // In multi threading mode, this points to the next thread that
+ // client will be dispatched.
+ size_t next_worker_;
+};
+
+namespace {
+void acceptcb(struct ev_loop *loop, ev_io *w, int revents);
+} // namespace
+
+class ListenEventHandler {
+public:
+ ListenEventHandler(Sessions *sessions, int fd,
+ std::shared_ptr<AcceptHandler> acceptor)
+ : acceptor_(std::move(acceptor)), sessions_(sessions), fd_(fd) {
+ ev_io_init(&w_, acceptcb, fd, EV_READ);
+ w_.data = this;
+ ev_io_start(sessions_->get_loop(), &w_);
+ }
+ void accept_connection() {
+ for (;;) {
+#ifdef HAVE_ACCEPT4
+ auto fd = accept4(fd_, nullptr, nullptr, SOCK_NONBLOCK);
+#else // !HAVE_ACCEPT4
+ auto fd = accept(fd_, nullptr, nullptr);
+#endif // !HAVE_ACCEPT4
+ if (fd == -1) {
+ break;
+ }
+#ifndef HAVE_ACCEPT4
+ util::make_socket_nonblocking(fd);
+#endif // !HAVE_ACCEPT4
+ acceptor_->accept_connection(fd);
+ }
+ }
+
+private:
+ ev_io w_;
+ std::shared_ptr<AcceptHandler> acceptor_;
+ Sessions *sessions_;
+ int fd_;
+};
+
+namespace {
+void acceptcb(struct ev_loop *loop, ev_io *w, int revents) {
+ auto handler = static_cast<ListenEventHandler *>(w->data);
+ handler->accept_connection();
+}
+} // namespace
+
+namespace {
+FileEntry make_status_body(int status, uint16_t port) {
+ BlockAllocator balloc(1024, 1024);
+
+ auto status_string = http2::stringify_status(balloc, status);
+ auto reason_pharase = http2::get_reason_phrase(status);
+
+ std::string body;
+ body = "<html><head><title>";
+ body += status_string;
+ body += ' ';
+ body += reason_pharase;
+ body += "</title></head><body><h1>";
+ body += status_string;
+ body += ' ';
+ body += reason_pharase;
+ body += "</h1><hr><address>";
+ body += NGHTTPD_SERVER;
+ body += " at port ";
+ body += util::utos(port);
+ body += "</address>";
+ body += "</body></html>";
+
+ char tempfn[] = "/tmp/nghttpd.temp.XXXXXX";
+ int fd = mkstemp(tempfn);
+ if (fd == -1) {
+ auto error = errno;
+ std::cerr << "Could not open status response body file: errno=" << error;
+ assert(0);
+ }
+ unlink(tempfn);
+ ssize_t nwrite;
+ while ((nwrite = write(fd, body.c_str(), body.size())) == -1 &&
+ errno == EINTR)
+ ;
+ if (nwrite == -1) {
+ auto error = errno;
+ std::cerr << "Could not write status response body into file: errno="
+ << error;
+ assert(0);
+ }
+
+ return FileEntry(util::utos(status), nwrite, 0, fd, nullptr, {});
+}
+} // namespace
+
+// index into HttpServer::status_pages_
+enum {
+ IDX_200,
+ IDX_301,
+ IDX_400,
+ IDX_404,
+ IDX_405,
+};
+
+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)},
+ {"400", make_status_body(400, config_->port)},
+ {"404", make_status_body(404, config_->port)},
+ {"405", make_status_body(405, config_->port)},
+ };
+}
+
+namespace {
+int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) {
+ // We don't verify the client certificate. Just request it for the
+ // testing purpose.
+ return 1;
+}
+} // namespace
+
+namespace {
+int start_listen(HttpServer *sv, struct ev_loop *loop, Sessions *sessions,
+ const Config *config) {
+ int r;
+ bool ok = false;
+ const char *addr = nullptr;
+
+ std::shared_ptr<AcceptHandler> acceptor;
+ auto service = util::utos(config->port);
+
+ addrinfo hints{};
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_PASSIVE;
+#ifdef AI_ADDRCONFIG
+ hints.ai_flags |= AI_ADDRCONFIG;
+#endif // AI_ADDRCONFIG
+
+ if (!config->address.empty()) {
+ addr = config->address.c_str();
+ }
+
+ addrinfo *res, *rp;
+ r = getaddrinfo(addr, service.c_str(), &hints, &res);
+ if (r != 0) {
+ std::cerr << "getaddrinfo() failed: " << gai_strerror(r) << std::endl;
+ return -1;
+ }
+
+ for (rp = res; rp; rp = rp->ai_next) {
+ int fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if (fd == -1) {
+ continue;
+ }
+ int val = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ close(fd);
+ continue;
+ }
+ (void)util::make_socket_nonblocking(fd);
+#ifdef IPV6_V6ONLY
+ if (rp->ai_family == AF_INET6) {
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ close(fd);
+ continue;
+ }
+ }
+#endif // IPV6_V6ONLY
+ if (bind(fd, rp->ai_addr, rp->ai_addrlen) == 0 && listen(fd, 1000) == 0) {
+ if (!acceptor) {
+ acceptor = std::make_shared<AcceptHandler>(sv, sessions, config);
+ }
+ new ListenEventHandler(sessions, fd, acceptor);
+
+ if (config->verbose) {
+ std::string s = util::numeric_name(rp->ai_addr, rp->ai_addrlen);
+ std::cout << (rp->ai_family == AF_INET ? "IPv4" : "IPv6") << ": listen "
+ << s << ":" << config->port << std::endl;
+ }
+ ok = true;
+ continue;
+ } else {
+ std::cerr << strerror(errno) << std::endl;
+ }
+ close(fd);
+ }
+ freeaddrinfo(res);
+
+ if (!ok) {
+ return -1;
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+int alpn_select_proto_cb(SSL *ssl, const unsigned char **out,
+ unsigned char *outlen, const unsigned char *in,
+ unsigned int inlen, void *arg) {
+ auto config = static_cast<HttpServer *>(arg)->get_config();
+ if (config->verbose) {
+ std::cout << "[ALPN] client offers:" << std::endl;
+ }
+ if (config->verbose) {
+ for (unsigned int i = 0; i < inlen; i += in[i] + 1) {
+ std::cout << " * ";
+ std::cout.write(reinterpret_cast<const char *>(&in[i + 1]), in[i]);
+ std::cout << std::endl;
+ }
+ }
+ if (!util::select_h2(out, outlen, in, inlen)) {
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+ return SSL_TLSEXT_ERR_OK;
+}
+} // namespace
+
+int HttpServer::run() {
+ SSL_CTX *ssl_ctx = nullptr;
+ std::vector<unsigned char> next_proto;
+
+ if (!config_->no_tls) {
+ ssl_ctx = SSL_CTX_new(TLS_server_method());
+ if (!ssl_ctx) {
+ std::cerr << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+ return -1;
+ }
+
+ auto ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) |
+ SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION |
+ SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION |
+ SSL_OP_SINGLE_ECDH_USE | SSL_OP_NO_TICKET |
+ SSL_OP_CIPHER_SERVER_PREFERENCE;
+
+#ifdef SSL_OP_ENABLE_KTLS
+ if (config_->ktls) {
+ ssl_opts |= SSL_OP_ENABLE_KTLS;
+ }
+#endif // SSL_OP_ENABLE_KTLS
+
+ SSL_CTX_set_options(ssl_ctx, ssl_opts);
+ SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
+ SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
+
+ if (nghttp2::tls::ssl_ctx_set_proto_versions(
+ ssl_ctx, nghttp2::tls::NGHTTP2_TLS_MIN_VERSION,
+ nghttp2::tls::NGHTTP2_TLS_MAX_VERSION) != 0) {
+ std::cerr << "Could not set TLS versions" << std::endl;
+ return -1;
+ }
+
+ if (SSL_CTX_set_cipher_list(ssl_ctx, tls::DEFAULT_CIPHER_LIST) == 0) {
+ std::cerr << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+ return -1;
+ }
+
+ const unsigned char sid_ctx[] = "nghttpd";
+ 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);
+
+#ifndef OPENSSL_NO_EC
+ if (SSL_CTX_set1_curves_list(ssl_ctx, "P-256") != 1) {
+ std::cerr << "SSL_CTX_set1_curves_list failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ return -1;
+ }
+#endif // OPENSSL_NO_EC
+
+ if (!config_->dh_param_file.empty()) {
+ // Read DH parameters from file
+ auto bio = BIO_new_file(config_->dh_param_file.c_str(), "rb");
+ if (bio == nullptr) {
+ std::cerr << "BIO_new_file() failed: "
+ << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+ return -1;
+ }
+
+#if OPENSSL_3_0_0_API
+ EVP_PKEY *dh = nullptr;
+ auto dctx = OSSL_DECODER_CTX_new_for_pkey(
+ &dh, "PEM", nullptr, "DH", OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS,
+ nullptr, nullptr);
+
+ if (!OSSL_DECODER_from_bio(dctx, bio)) {
+ std::cerr << "OSSL_DECODER_from_bio() failed: "
+ << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+ return -1;
+ }
+
+ if (SSL_CTX_set0_tmp_dh_pkey(ssl_ctx, dh) != 1) {
+ std::cerr << "SSL_CTX_set0_tmp_dh_pkey failed: "
+ << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+ return -1;
+ }
+#else // !OPENSSL_3_0_0_API
+ auto dh = PEM_read_bio_DHparams(bio, nullptr, nullptr, nullptr);
+
+ if (dh == nullptr) {
+ std::cerr << "PEM_read_bio_DHparams() failed: "
+ << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+ return -1;
+ }
+
+ SSL_CTX_set_tmp_dh(ssl_ctx, dh);
+ DH_free(dh);
+#endif // !OPENSSL_3_0_0_API
+ BIO_free(bio);
+ }
+
+ if (SSL_CTX_use_PrivateKey_file(ssl_ctx, config_->private_key_file.c_str(),
+ SSL_FILETYPE_PEM) != 1) {
+ std::cerr << "SSL_CTX_use_PrivateKey_file failed." << std::endl;
+ return -1;
+ }
+ if (SSL_CTX_use_certificate_chain_file(ssl_ctx,
+ config_->cert_file.c_str()) != 1) {
+ std::cerr << "SSL_CTX_use_certificate_file failed." << std::endl;
+ return -1;
+ }
+ if (SSL_CTX_check_private_key(ssl_ctx) != 1) {
+ std::cerr << "SSL_CTX_check_private_key failed." << std::endl;
+ return -1;
+ }
+ if (config_->verify_client) {
+ SSL_CTX_set_verify(ssl_ctx,
+ SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE |
+ SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
+ verify_callback);
+ }
+
+ next_proto = util::get_default_alpn();
+
+ // ALPN selection callback
+ SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, this);
+ }
+
+ auto loop = EV_DEFAULT;
+
+ Sessions sessions(this, loop, config_, ssl_ctx);
+ if (start_listen(this, loop, &sessions, config_) != 0) {
+ std::cerr << "Could not listen" << std::endl;
+ if (ssl_ctx) {
+ SSL_CTX_free(ssl_ctx);
+ }
+ return -1;
+ }
+
+ ev_run(loop, 0);
+
+ SSL_CTX_free(ssl_ctx);
+
+ return 0;
+}
+
+const Config *HttpServer::get_config() const { return config_; }
+
+const StatusPage *HttpServer::get_status_page(int status) const {
+ switch (status) {
+ case 200:
+ return &status_pages_[IDX_200];
+ case 301:
+ return &status_pages_[IDX_301];
+ case 400:
+ return &status_pages_[IDX_400];
+ case 404:
+ return &status_pages_[IDX_404];
+ case 405:
+ return &status_pages_[IDX_405];
+ default:
+ assert(0);
+ }
+ return nullptr;
+}
+
+} // namespace nghttp2
diff --git a/src/HttpServer.h b/src/HttpServer.h
new file mode 100644
index 0000000..949bd1f
--- /dev/null
+++ b/src/HttpServer.h
@@ -0,0 +1,253 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef HTTP_SERVER_H
+#define HTTP_SERVER_H
+
+#include "nghttp2_config.h"
+
+#include <sys/types.h>
+
+#include <cinttypes>
+#include <cstdlib>
+
+#include <string>
+#include <vector>
+#include <map>
+#include <memory>
+
+#include <openssl/ssl.h>
+
+#include <ev.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "http2.h"
+#include "buffer.h"
+#include "template.h"
+#include "allocator.h"
+
+namespace nghttp2 {
+
+struct Config {
+ std::map<std::string, std::vector<std::string>> push;
+ std::map<std::string, std::string> mime_types;
+ Headers trailer;
+ std::string trailer_names;
+ std::string htdocs;
+ std::string host;
+ std::string private_key_file;
+ std::string cert_file;
+ std::string dh_param_file;
+ std::string address;
+ std::string mime_types_file;
+ ev_tstamp stream_read_timeout;
+ ev_tstamp stream_write_timeout;
+ void *data_ptr;
+ size_t padding;
+ size_t num_worker;
+ size_t max_concurrent_streams;
+ ssize_t header_table_size;
+ ssize_t encoder_header_table_size;
+ int window_bits;
+ int connection_window_bits;
+ uint16_t port;
+ bool verbose;
+ bool daemon;
+ bool verify_client;
+ bool no_tls;
+ bool error_gzip;
+ bool early_response;
+ bool hexdump;
+ bool echo_upload;
+ bool no_content_length;
+ bool ktls;
+ bool no_rfc7540_pri;
+ Config();
+ ~Config();
+};
+
+class Http2Handler;
+
+struct FileEntry {
+ FileEntry(std::string path, int64_t length, int64_t mtime, int fd,
+ const std::string *content_type,
+ const std::chrono::steady_clock::time_point &last_valid,
+ bool stale = false)
+ : path(std::move(path)),
+ length(length),
+ mtime(mtime),
+ last_valid(last_valid),
+ content_type(content_type),
+ dlnext(nullptr),
+ dlprev(nullptr),
+ fd(fd),
+ usecount(1),
+ stale(stale) {}
+ std::string path;
+ std::multimap<std::string, std::unique_ptr<FileEntry>>::iterator it;
+ int64_t length;
+ int64_t mtime;
+ std::chrono::steady_clock::time_point last_valid;
+ const std::string *content_type;
+ FileEntry *dlnext, *dlprev;
+ int fd;
+ int usecount;
+ bool stale;
+};
+
+struct RequestHeader {
+ StringRef method;
+ StringRef scheme;
+ StringRef authority;
+ StringRef host;
+ StringRef path;
+ StringRef ims;
+ StringRef expect;
+
+ struct {
+ nghttp2_rcbuf *method;
+ nghttp2_rcbuf *scheme;
+ nghttp2_rcbuf *authority;
+ nghttp2_rcbuf *host;
+ nghttp2_rcbuf *path;
+ nghttp2_rcbuf *ims;
+ nghttp2_rcbuf *expect;
+ } rcbuf;
+};
+
+struct Stream {
+ BlockAllocator balloc;
+ RequestHeader header;
+ Http2Handler *handler;
+ FileEntry *file_ent;
+ ev_timer rtimer;
+ ev_timer wtimer;
+ int64_t body_length;
+ int64_t body_offset;
+ // Total amount of bytes (sum of name and value length) used in
+ // headers.
+ size_t header_buffer_size;
+ int32_t stream_id;
+ bool echo_upload;
+ Stream(Http2Handler *handler, int32_t stream_id);
+ ~Stream();
+};
+
+class Sessions;
+
+class Http2Handler {
+public:
+ Http2Handler(Sessions *sessions, int fd, SSL *ssl, int64_t session_id);
+ ~Http2Handler();
+
+ void remove_self();
+ void start_settings_timer();
+ int on_read();
+ int on_write();
+ int connection_made();
+ int verify_alpn_result();
+
+ int submit_file_response(const StringRef &status, Stream *stream,
+ time_t last_modified, off_t file_length,
+ const std::string *content_type,
+ nghttp2_data_provider *data_prd);
+
+ int submit_response(const StringRef &status, int32_t stream_id,
+ nghttp2_data_provider *data_prd);
+
+ int submit_response(const StringRef &status, int32_t stream_id,
+ const HeaderRefs &headers,
+ nghttp2_data_provider *data_prd);
+
+ int submit_non_final_response(const std::string &status, int32_t stream_id);
+
+ int submit_push_promise(Stream *stream, const StringRef &push_path);
+
+ int submit_rst_stream(Stream *stream, uint32_t error_code);
+
+ void add_stream(int32_t stream_id, std::unique_ptr<Stream> stream);
+ void remove_stream(int32_t stream_id);
+ Stream *get_stream(int32_t stream_id);
+ int64_t session_id() const;
+ Sessions *get_sessions() const;
+ const Config *get_config() const;
+ void remove_settings_timer();
+ void terminate_session(uint32_t error_code);
+
+ int fill_wb();
+
+ int read_clear();
+ int write_clear();
+ int tls_handshake();
+ int read_tls();
+ int write_tls();
+
+ struct ev_loop *get_loop() const;
+
+ using WriteBuf = Buffer<64_k>;
+
+ WriteBuf *get_wb();
+
+private:
+ ev_io wev_;
+ ev_io rev_;
+ ev_timer settings_timerev_;
+ std::map<int32_t, std::unique_ptr<Stream>> id2stream_;
+ WriteBuf wb_;
+ std::function<int(Http2Handler &)> read_, write_;
+ int64_t session_id_;
+ nghttp2_session *session_;
+ Sessions *sessions_;
+ SSL *ssl_;
+ const uint8_t *data_pending_;
+ size_t data_pendinglen_;
+ int fd_;
+};
+
+struct StatusPage {
+ std::string status;
+ FileEntry file_ent;
+};
+
+class HttpServer {
+public:
+ HttpServer(const Config *config);
+ int listen();
+ int run();
+ const Config *get_config() const;
+ const StatusPage *get_status_page(int status) const;
+
+private:
+ std::vector<StatusPage> status_pages_;
+ const Config *config_;
+};
+
+ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id,
+ uint8_t *buf, size_t length, uint32_t *data_flags,
+ nghttp2_data_source *source, void *user_data);
+
+} // namespace nghttp2
+
+#endif // HTTP_SERVER_H
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..f112ac2
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,257 @@
+# nghttp2 - HTTP/2 C Library
+
+# Copyright (c) 2012 Tatsuhiro Tsujikawa
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+SUBDIRS = testdata
+
+EXTRA_DIST = \
+ CMakeLists.txt \
+ test.example.com.pem \
+ test.nghttp2.org.pem
+
+bin_PROGRAMS =
+check_PROGRAMS =
+TESTS =
+
+AM_CFLAGS = $(WARNCFLAGS)
+AM_CXXFLAGS = $(WARNCXXFLAGS) $(CXX1XCXXFLAGS)
+AM_CPPFLAGS = \
+ -DPKGDATADIR='"$(pkgdatadir)"' \
+ -DPKGLIBDIR='"$(pkglibdir)"' \
+ -I$(top_srcdir)/lib/includes \
+ -I$(top_builddir)/lib/includes \
+ -I$(top_srcdir)/lib \
+ -I$(top_srcdir)/third-party \
+ -I$(top_srcdir)/third-party/llhttp/include \
+ @JEMALLOC_CFLAGS@ \
+ @LIBXML2_CFLAGS@ \
+ @LIBEV_CFLAGS@ \
+ @LIBNGHTTP3_CFLAGS@ \
+ @LIBNGTCP2_CRYPTO_QUICTLS_CFLAGS@ \
+ @LIBNGTCP2_CRYPTO_BORINGSSL_CFLAGS@ \
+ @LIBNGTCP2_CFLAGS@ \
+ @OPENSSL_CFLAGS@ \
+ @LIBCARES_CFLAGS@ \
+ @JANSSON_CFLAGS@ \
+ @LIBBPF_CFLAGS@ \
+ @ZLIB_CFLAGS@ \
+ @EXTRA_DEFS@ \
+ @DEFS@
+AM_LDFLAGS = @LIBTOOL_LDFLAGS@
+
+LDADD = $(top_builddir)/lib/libnghttp2.la \
+ $(top_builddir)/third-party/liburl-parser.la \
+ $(top_builddir)/third-party/libllhttp.la \
+ @JEMALLOC_LIBS@ \
+ @LIBXML2_LIBS@ \
+ @LIBEV_LIBS@ \
+ @LIBNGHTTP3_LIBS@ \
+ @LIBNGTCP2_CRYPTO_QUICTLS_LIBS@ \
+ @LIBNGTCP2_CRYPTO_BORINGSSL_LIBS@ \
+ @LIBNGTCP2_LIBS@ \
+ @OPENSSL_LIBS@ \
+ @LIBCARES_LIBS@ \
+ @SYSTEMD_LIBS@ \
+ @JANSSON_LIBS@ \
+ @LIBBPF_LIBS@ \
+ @ZLIB_LIBS@ \
+ @APPLDFLAGS@
+
+if ENABLE_APP
+
+bin_PROGRAMS += nghttp nghttpd nghttpx
+
+HELPER_OBJECTS = util.cc \
+ http2.cc timegm.c app_helper.cc nghttp2_gzip.c
+HELPER_HFILES = util.h \
+ http2.h timegm.h app_helper.h nghttp2_config.h \
+ nghttp2_gzip.h network.h
+
+HTML_PARSER_OBJECTS =
+HTML_PARSER_HFILES = HtmlParser.h
+
+if HAVE_LIBXML2
+HTML_PARSER_OBJECTS += HtmlParser.cc
+endif # HAVE_LIBXML2
+
+nghttp_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} nghttp.cc nghttp.h \
+ ${HTML_PARSER_OBJECTS} ${HTML_PARSER_HFILES} \
+ tls.cc tls.h ssl_compat.h
+
+nghttpd_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} nghttpd.cc \
+ tls.cc tls.h ssl_compat.h \
+ HttpServer.cc HttpServer.h
+
+bin_PROGRAMS += h2load
+
+h2load_SOURCES = util.cc util.h \
+ http2.cc http2.h h2load.cc h2load.h \
+ timegm.c timegm.h \
+ tls.cc tls.h ssl_compat.h \
+ h2load_session.h \
+ h2load_http2_session.cc h2load_http2_session.h \
+ h2load_http1_session.cc h2load_http1_session.h
+
+if ENABLE_HTTP3
+h2load_SOURCES += \
+ h2load_http3_session.cc h2load_http3_session.h \
+ h2load_quic.cc h2load_quic.h \
+ quic.cc quic.h
+endif # ENABLE_HTTP3
+
+NGHTTPX_SRCS = \
+ util.cc util.h http2.cc http2.h timegm.c timegm.h base64.h \
+ app_helper.cc app_helper.h \
+ tls.cc tls.h ssl_compat.h \
+ shrpx_config.cc shrpx_config.h \
+ shrpx_error.h \
+ shrpx_accept_handler.cc shrpx_accept_handler.h \
+ shrpx_connection_handler.cc shrpx_connection_handler.h \
+ shrpx_client_handler.cc shrpx_client_handler.h \
+ shrpx_upstream.h \
+ shrpx_http2_upstream.cc shrpx_http2_upstream.h \
+ shrpx_https_upstream.cc shrpx_https_upstream.h \
+ shrpx_downstream.cc shrpx_downstream.h \
+ shrpx_downstream_connection.cc shrpx_downstream_connection.h \
+ shrpx_http_downstream_connection.cc shrpx_http_downstream_connection.h \
+ shrpx_http2_downstream_connection.cc shrpx_http2_downstream_connection.h \
+ shrpx_http2_session.cc shrpx_http2_session.h \
+ shrpx_downstream_queue.cc shrpx_downstream_queue.h \
+ shrpx_log.cc shrpx_log.h \
+ shrpx_http.cc shrpx_http.h \
+ shrpx_io_control.cc shrpx_io_control.h \
+ shrpx_tls.cc shrpx_tls.h \
+ shrpx_worker.cc shrpx_worker.h \
+ shrpx_log_config.cc shrpx_log_config.h \
+ shrpx_connect_blocker.cc shrpx_connect_blocker.h \
+ shrpx_live_check.cc shrpx_live_check.h \
+ shrpx_downstream_connection_pool.cc shrpx_downstream_connection_pool.h \
+ shrpx_rate_limit.cc shrpx_rate_limit.h \
+ shrpx_connection.cc shrpx_connection.h \
+ shrpx_memcached_dispatcher.cc shrpx_memcached_dispatcher.h \
+ shrpx_memcached_connection.cc shrpx_memcached_connection.h \
+ shrpx_memcached_request.h \
+ shrpx_memcached_result.h \
+ shrpx_worker_process.cc shrpx_worker_process.h \
+ shrpx_process.h \
+ shrpx_signal.cc shrpx_signal.h \
+ shrpx_router.cc shrpx_router.h \
+ shrpx_api_downstream_connection.cc shrpx_api_downstream_connection.h \
+ shrpx_health_monitor_downstream_connection.cc \
+ shrpx_health_monitor_downstream_connection.h \
+ shrpx_null_downstream_connection.cc shrpx_null_downstream_connection.h \
+ shrpx_exec.cc shrpx_exec.h \
+ shrpx_dns_resolver.cc shrpx_dns_resolver.h \
+ shrpx_dual_dns_resolver.cc shrpx_dual_dns_resolver.h \
+ shrpx_dns_tracker.cc shrpx_dns_tracker.h \
+ buffer.h memchunk.h template.h allocator.h \
+ xsi_strerror.c xsi_strerror.h
+
+if HAVE_MRUBY
+NGHTTPX_SRCS += \
+ shrpx_mruby.cc shrpx_mruby.h \
+ shrpx_mruby_module.cc shrpx_mruby_module.h \
+ shrpx_mruby_module_env.cc shrpx_mruby_module_env.h \
+ shrpx_mruby_module_request.cc shrpx_mruby_module_request.h \
+ shrpx_mruby_module_response.cc shrpx_mruby_module_response.h
+endif # HAVE_MRUBY
+
+if ENABLE_HTTP3
+NGHTTPX_SRCS += \
+ shrpx_quic.cc shrpx_quic.h \
+ shrpx_quic_listener.cc shrpx_quic_listener.h \
+ shrpx_quic_connection_handler.cc shrpx_quic_connection_handler.h \
+ shrpx_http3_upstream.cc shrpx_http3_upstream.h \
+ http3.cc http3.h \
+ quic.cc quic.h
+endif # ENABLE_HTTP3
+
+noinst_LIBRARIES = libnghttpx.a
+libnghttpx_a_SOURCES = ${NGHTTPX_SRCS}
+libnghttpx_a_CPPFLAGS = ${AM_CPPFLAGS}
+
+nghttpx_SOURCES = shrpx.cc shrpx.h
+nghttpx_CPPFLAGS = ${libnghttpx_a_CPPFLAGS}
+nghttpx_LDADD = libnghttpx.a ${LDADD}
+
+if HAVE_MRUBY
+libnghttpx_a_CPPFLAGS += \
+ -I${top_srcdir}/third-party/mruby/include @LIBMRUBY_CFLAGS@
+nghttpx_LDADD += -L${top_builddir}/third-party/mruby/build/lib @LIBMRUBY_LIBS@
+endif # HAVE_MRUBY
+
+if HAVE_NEVERBLEED
+libnghttpx_a_CPPFLAGS += -I${top_srcdir}/third-party/neverbleed
+nghttpx_LDADD += ${top_builddir}/third-party/libneverbleed.la
+endif # HAVE_NEVERBLEED
+
+if HAVE_CUNIT
+check_PROGRAMS += nghttpx-unittest
+nghttpx_unittest_SOURCES = shrpx-unittest.cc \
+ shrpx_tls_test.cc shrpx_tls_test.h \
+ shrpx_downstream_test.cc shrpx_downstream_test.h \
+ shrpx_config_test.cc shrpx_config_test.h \
+ shrpx_worker_test.cc shrpx_worker_test.h \
+ shrpx_http_test.cc shrpx_http_test.h \
+ shrpx_router_test.cc shrpx_router_test.h \
+ http2_test.cc http2_test.h \
+ util_test.cc util_test.h \
+ nghttp2_gzip_test.c nghttp2_gzip_test.h \
+ nghttp2_gzip.c nghttp2_gzip.h \
+ buffer_test.cc buffer_test.h \
+ memchunk_test.cc memchunk_test.h \
+ template_test.cc template_test.h \
+ base64_test.cc base64_test.h
+nghttpx_unittest_CPPFLAGS = ${AM_CPPFLAGS} \
+ -DNGHTTP2_SRC_DIR=\"$(top_srcdir)/src\"
+nghttpx_unittest_LDADD = libnghttpx.a ${LDADD} @CUNIT_LIBS@ @TESTLDADD@
+
+if HAVE_MRUBY
+nghttpx_unittest_CPPFLAGS += \
+ -I${top_srcdir}/third-party/mruby/include @LIBMRUBY_CFLAGS@
+nghttpx_unittest_LDADD += \
+ -L${top_builddir}/third-party/mruby/build/lib @LIBMRUBY_LIBS@
+endif # HAVE_MRUBY
+
+if HAVE_NEVERBLEED
+nghttpx_unittest_CPPFLAGS += -I${top_srcdir}/third-party/neverbleed
+nghttpx_unittest_LDADD += ${top_builddir}/third-party/libneverbleed.la
+endif # HAVE_NEVERBLEED
+
+TESTS += nghttpx-unittest
+endif # HAVE_CUNIT
+
+endif # ENABLE_APP
+
+if ENABLE_HPACK_TOOLS
+
+bin_PROGRAMS += inflatehd deflatehd
+
+HPACK_TOOLS_COMMON_SRCS = \
+ comp_helper.c comp_helper.h \
+ util.cc util.h \
+ timegm.c timegm.h
+
+inflatehd_SOURCES = inflatehd.cc $(HPACK_TOOLS_COMMON_SRCS)
+
+deflatehd_SOURCES = deflatehd.cc $(HPACK_TOOLS_COMMON_SRCS)
+
+endif # ENABLE_HPACK_TOOLS
diff --git a/src/allocator.h b/src/allocator.h
new file mode 100644
index 0000000..97b9a41
--- /dev/null
+++ b/src/allocator.h
@@ -0,0 +1,273 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef ALLOCATOR_H
+#define ALLOCATOR_H
+
+#include "nghttp2_config.h"
+
+#ifndef _WIN32
+# include <sys/uio.h>
+#endif // !_WIN32
+
+#include <cassert>
+#include <utility>
+
+#include "template.h"
+
+namespace nghttp2 {
+
+struct MemBlock {
+ // The next MemBlock to chain them. This is for book keeping
+ // purpose to free them later.
+ MemBlock *next;
+ // begin is the pointer to the beginning of buffer. last is the
+ // location of next write. end is the one beyond of the end of the
+ // buffer.
+ uint8_t *begin, *last, *end;
+};
+
+// BlockAllocator allocates memory block with given size at once, and
+// cuts the region from it when allocation is requested. If the
+// requested size is larger than given threshold (plus small internal
+// overhead), it will be allocated in a distinct buffer on demand.
+// The |isolation_threshold| must be less than or equal to
+// |block_size|.
+struct BlockAllocator {
+ BlockAllocator(size_t block_size, size_t isolation_threshold)
+ : retain(nullptr),
+ head(nullptr),
+ block_size(block_size),
+ isolation_threshold(std::min(block_size, isolation_threshold)) {
+ assert(isolation_threshold <= block_size);
+ }
+
+ ~BlockAllocator() { reset(); }
+
+ BlockAllocator(BlockAllocator &&other) noexcept
+ : retain{std::exchange(other.retain, nullptr)},
+ head{std::exchange(other.head, nullptr)},
+ block_size(other.block_size),
+ isolation_threshold(other.isolation_threshold) {}
+
+ BlockAllocator &operator=(BlockAllocator &&other) noexcept {
+ reset();
+
+ retain = std::exchange(other.retain, nullptr);
+ head = std::exchange(other.head, nullptr);
+ block_size = other.block_size;
+ isolation_threshold = other.isolation_threshold;
+
+ return *this;
+ }
+
+ BlockAllocator(const BlockAllocator &) = delete;
+ BlockAllocator &operator=(const BlockAllocator &) = delete;
+
+ void reset() {
+ for (auto mb = retain; mb;) {
+ auto next = mb->next;
+ delete[] reinterpret_cast<uint8_t *>(mb);
+ mb = next;
+ }
+
+ retain = nullptr;
+ head = nullptr;
+ }
+
+ MemBlock *alloc_mem_block(size_t size) {
+ auto block = new uint8_t[sizeof(MemBlock) + size];
+ auto mb = reinterpret_cast<MemBlock *>(block);
+
+ mb->next = retain;
+ mb->begin = mb->last = block + sizeof(MemBlock);
+ mb->end = mb->begin + size;
+ retain = mb;
+ return mb;
+ }
+
+ void *alloc(size_t size) {
+ if (size + sizeof(size_t) >= isolation_threshold) {
+ auto len = std::max(static_cast<size_t>(16), size);
+ // We will store the allocated size in size_t field.
+ auto mb = alloc_mem_block(len + sizeof(size_t));
+ auto sp = reinterpret_cast<size_t *>(mb->begin);
+ *sp = len;
+ mb->last = mb->end;
+ return mb->begin + sizeof(size_t);
+ }
+
+ if (!head ||
+ head->end - head->last < static_cast<ssize_t>(size + sizeof(size_t))) {
+ head = alloc_mem_block(block_size);
+ }
+
+ // We will store the allocated size in size_t field.
+ auto res = head->last + sizeof(size_t);
+ auto sp = reinterpret_cast<size_t *>(head->last);
+ *sp = size;
+
+ head->last = reinterpret_cast<uint8_t *>(
+ (reinterpret_cast<intptr_t>(res + size) + 0xf) & ~0xf);
+
+ return res;
+ }
+
+ // Returns allocated size for memory pointed by |ptr|. We assume
+ // that |ptr| was returned from alloc() or realloc().
+ size_t get_alloc_length(void *ptr) {
+ return *reinterpret_cast<size_t *>(static_cast<uint8_t *>(ptr) -
+ sizeof(size_t));
+ }
+
+ // Allocates memory of at least |size| bytes. If |ptr| is nullptr,
+ // this is equivalent to alloc(size). If |ptr| is not nullptr,
+ // obtain the allocated size for |ptr|, assuming that |ptr| was
+ // returned from alloc() or realloc(). If the allocated size is
+ // greater than or equal to size, |ptr| is returned. Otherwise,
+ // allocates at least |size| bytes of memory, and the original
+ // content pointed by |ptr| is copied to the newly allocated memory.
+ void *realloc(void *ptr, size_t size) {
+ if (!ptr) {
+ return alloc(size);
+ }
+ auto alloclen = get_alloc_length(ptr);
+ auto p = reinterpret_cast<uint8_t *>(ptr);
+ if (size <= alloclen) {
+ return ptr;
+ }
+
+ auto nalloclen = std::max(size + 1, alloclen * 2);
+
+ auto res = alloc(nalloclen);
+ std::copy_n(p, alloclen, static_cast<uint8_t *>(res));
+
+ return res;
+ }
+
+ // This holds live memory block to free them in dtor.
+ MemBlock *retain;
+ // Current memory block to use.
+ MemBlock *head;
+ // size of single memory block
+ size_t block_size;
+ // if allocation greater or equal to isolation_threshold bytes is
+ // requested, allocate dedicated block.
+ size_t isolation_threshold;
+};
+
+// Makes a copy of |src|. The resulting string will be
+// NULL-terminated.
+template <typename BlockAllocator>
+StringRef make_string_ref(BlockAllocator &alloc, const StringRef &src) {
+ auto dst = static_cast<uint8_t *>(alloc.alloc(src.size() + 1));
+ auto p = dst;
+ p = std::copy(std::begin(src), std::end(src), p);
+ *p = '\0';
+ return StringRef{dst, src.size()};
+}
+
+// private function used in concat_string_ref. this is the base
+// function of concat_string_ref_count().
+inline constexpr size_t concat_string_ref_count(size_t acc) { return acc; }
+
+// private function used in concat_string_ref. This function counts
+// the sum of length of given arguments. The calculated length is
+// accumulated, and passed to the next function.
+template <typename... Args>
+constexpr size_t concat_string_ref_count(size_t acc, const StringRef &value,
+ Args &&...args) {
+ return concat_string_ref_count(acc + value.size(),
+ std::forward<Args>(args)...);
+}
+
+// private function used in concat_string_ref. this is the base
+// function of concat_string_ref_copy().
+inline uint8_t *concat_string_ref_copy(uint8_t *p) { return p; }
+
+// private function used in concat_string_ref. This function copies
+// given strings into |p|. |p| is incremented by the copied length,
+// and returned. In the end, return value points to the location one
+// beyond the last byte written.
+template <typename... Args>
+uint8_t *concat_string_ref_copy(uint8_t *p, const StringRef &value,
+ Args &&...args) {
+ p = std::copy(std::begin(value), std::end(value), p);
+ return concat_string_ref_copy(p, std::forward<Args>(args)...);
+}
+
+// Returns the string which is the concatenation of |args| in the
+// given order. The resulting string will be NULL-terminated.
+template <typename BlockAllocator, typename... Args>
+StringRef concat_string_ref(BlockAllocator &alloc, Args &&...args) {
+ size_t len = concat_string_ref_count(0, std::forward<Args>(args)...);
+ auto dst = static_cast<uint8_t *>(alloc.alloc(len + 1));
+ auto p = dst;
+ p = concat_string_ref_copy(p, std::forward<Args>(args)...);
+ *p = '\0';
+ return StringRef{dst, len};
+}
+
+// Returns the string which is the concatenation of |value| and |args|
+// in the given order. The resulting string will be NULL-terminated.
+// This function assumes that the pointer value value.c_str() was
+// obtained from alloc.alloc() or alloc.realloc(), and attempts to use
+// unused memory region by using alloc.realloc(). If value is empty,
+// then just call concat_string_ref().
+template <typename BlockAllocator, typename... Args>
+StringRef realloc_concat_string_ref(BlockAllocator &alloc,
+ const StringRef &value, Args &&...args) {
+ if (value.empty()) {
+ return concat_string_ref(alloc, std::forward<Args>(args)...);
+ }
+
+ auto len =
+ value.size() + concat_string_ref_count(0, std::forward<Args>(args)...);
+ auto dst = static_cast<uint8_t *>(
+ alloc.realloc(const_cast<uint8_t *>(value.byte()), len + 1));
+ auto p = dst + value.size();
+ p = concat_string_ref_copy(p, std::forward<Args>(args)...);
+ *p = '\0';
+
+ return StringRef{dst, len};
+}
+
+struct ByteRef {
+ // The pointer to the beginning of the buffer.
+ uint8_t *base;
+ // The length of the buffer.
+ size_t len;
+};
+
+// Makes a buffer with given size. The resulting byte string might
+// not be NULL-terminated.
+template <typename BlockAllocator>
+ByteRef make_byte_ref(BlockAllocator &alloc, size_t size) {
+ auto dst = static_cast<uint8_t *>(alloc.alloc(size));
+ return {dst, size};
+}
+
+} // namespace nghttp2
+
+#endif // ALLOCATOR_H
diff --git a/src/app_helper.cc b/src/app_helper.cc
new file mode 100644
index 0000000..ef92762
--- /dev/null
+++ b/src/app_helper.cc
@@ -0,0 +1,518 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include <sys/types.h>
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif // HAVE_SYS_SOCKET_H
+#ifdef HAVE_NETDB_H
+# include <netdb.h>
+#endif // HAVE_NETDB_H
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+#ifdef HAVE_FCNTL_H
+# include <fcntl.h>
+#endif // HAVE_FCNTL_H
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif // HAVE_NETINET_IN_H
+#include <netinet/tcp.h>
+#include <poll.h>
+
+#include <cassert>
+#include <cstdio>
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <string>
+#include <iostream>
+#include <string>
+#include <set>
+#include <iomanip>
+#include <fstream>
+
+#include <zlib.h>
+
+#include "app_helper.h"
+#include "util.h"
+#include "http2.h"
+#include "template.h"
+
+namespace nghttp2 {
+
+namespace {
+const char *strsettingsid(int32_t id) {
+ switch (id) {
+ case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE:
+ return "SETTINGS_HEADER_TABLE_SIZE";
+ case NGHTTP2_SETTINGS_ENABLE_PUSH:
+ return "SETTINGS_ENABLE_PUSH";
+ case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS:
+ return "SETTINGS_MAX_CONCURRENT_STREAMS";
+ case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE:
+ return "SETTINGS_INITIAL_WINDOW_SIZE";
+ case NGHTTP2_SETTINGS_MAX_FRAME_SIZE:
+ return "SETTINGS_MAX_FRAME_SIZE";
+ case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE:
+ return "SETTINGS_MAX_HEADER_LIST_SIZE";
+ case NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL:
+ return "SETTINGS_ENABLE_CONNECT_PROTOCOL";
+ case NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES:
+ return "SETTINGS_NO_RFC7540_PRIORITIES";
+ default:
+ return "UNKNOWN";
+ }
+}
+} // namespace
+
+namespace {
+std::string strframetype(uint8_t type) {
+ switch (type) {
+ case NGHTTP2_DATA:
+ return "DATA";
+ case NGHTTP2_HEADERS:
+ return "HEADERS";
+ case NGHTTP2_PRIORITY:
+ return "PRIORITY";
+ case NGHTTP2_RST_STREAM:
+ return "RST_STREAM";
+ case NGHTTP2_SETTINGS:
+ return "SETTINGS";
+ case NGHTTP2_PUSH_PROMISE:
+ return "PUSH_PROMISE";
+ case NGHTTP2_PING:
+ return "PING";
+ case NGHTTP2_GOAWAY:
+ return "GOAWAY";
+ case NGHTTP2_WINDOW_UPDATE:
+ return "WINDOW_UPDATE";
+ case NGHTTP2_ALTSVC:
+ return "ALTSVC";
+ case NGHTTP2_ORIGIN:
+ return "ORIGIN";
+ case NGHTTP2_PRIORITY_UPDATE:
+ return "PRIORITY_UPDATE";
+ }
+
+ std::string s = "extension(0x";
+ s += util::format_hex(&type, 1);
+ s += ')';
+
+ return s;
+};
+} // namespace
+
+namespace {
+bool color_output = false;
+} // namespace
+
+void set_color_output(bool f) { color_output = f; }
+
+namespace {
+FILE *outfile = stdout;
+} // namespace
+
+void set_output(FILE *file) { outfile = file; }
+
+namespace {
+void print_frame_attr_indent() { fprintf(outfile, " "); }
+} // namespace
+
+namespace {
+const char *ansi_esc(const char *code) { return color_output ? code : ""; }
+} // namespace
+
+namespace {
+const char *ansi_escend() { return color_output ? "\033[0m" : ""; }
+} // namespace
+
+namespace {
+void print_nv(nghttp2_nv *nv) {
+ fprintf(outfile, "%s%s%s: %s\n", ansi_esc("\033[1;34m"), nv->name,
+ ansi_escend(), nv->value);
+}
+} // namespace
+namespace {
+void print_nv(nghttp2_nv *nva, size_t nvlen) {
+ auto end = nva + nvlen;
+ for (; nva != end; ++nva) {
+ print_frame_attr_indent();
+
+ print_nv(nva);
+ }
+}
+} // namespace
+
+void print_timer() {
+ auto millis = get_timer();
+ fprintf(outfile, "%s[%3ld.%03ld]%s", ansi_esc("\033[33m"),
+ (long int)(millis.count() / 1000), (long int)(millis.count() % 1000),
+ ansi_escend());
+}
+
+namespace {
+void print_frame_hd(const nghttp2_frame_hd &hd) {
+ fprintf(outfile, "<length=%zu, flags=0x%02x, stream_id=%d>\n", hd.length,
+ hd.flags, hd.stream_id);
+}
+} // namespace
+
+namespace {
+void print_flags(const nghttp2_frame_hd &hd) {
+ std::string s;
+ switch (hd.type) {
+ case NGHTTP2_DATA:
+ if (hd.flags & NGHTTP2_FLAG_END_STREAM) {
+ s += "END_STREAM";
+ }
+ if (hd.flags & NGHTTP2_FLAG_PADDED) {
+ if (!s.empty()) {
+ s += " | ";
+ }
+ s += "PADDED";
+ }
+ break;
+ case NGHTTP2_HEADERS:
+ if (hd.flags & NGHTTP2_FLAG_END_STREAM) {
+ s += "END_STREAM";
+ }
+ if (hd.flags & NGHTTP2_FLAG_END_HEADERS) {
+ if (!s.empty()) {
+ s += " | ";
+ }
+ s += "END_HEADERS";
+ }
+ if (hd.flags & NGHTTP2_FLAG_PADDED) {
+ if (!s.empty()) {
+ s += " | ";
+ }
+ s += "PADDED";
+ }
+ if (hd.flags & NGHTTP2_FLAG_PRIORITY) {
+ if (!s.empty()) {
+ s += " | ";
+ }
+ s += "PRIORITY";
+ }
+
+ break;
+ case NGHTTP2_PRIORITY:
+ break;
+ case NGHTTP2_SETTINGS:
+ if (hd.flags & NGHTTP2_FLAG_ACK) {
+ s += "ACK";
+ }
+ break;
+ case NGHTTP2_PUSH_PROMISE:
+ if (hd.flags & NGHTTP2_FLAG_END_HEADERS) {
+ s += "END_HEADERS";
+ }
+ if (hd.flags & NGHTTP2_FLAG_PADDED) {
+ if (!s.empty()) {
+ s += " | ";
+ }
+ s += "PADDED";
+ }
+ break;
+ case NGHTTP2_PING:
+ if (hd.flags & NGHTTP2_FLAG_ACK) {
+ s += "ACK";
+ }
+ break;
+ }
+ fprintf(outfile, "; %s\n", s.c_str());
+}
+} // namespace
+
+enum print_type { PRINT_SEND, PRINT_RECV };
+
+namespace {
+const char *frame_name_ansi_esc(print_type ptype) {
+ return ansi_esc(ptype == PRINT_SEND ? "\033[1;35m" : "\033[1;36m");
+}
+} // namespace
+
+namespace {
+void print_frame(print_type ptype, const nghttp2_frame *frame) {
+ fprintf(outfile, "%s%s%s frame ", frame_name_ansi_esc(ptype),
+ strframetype(frame->hd.type).c_str(), ansi_escend());
+ print_frame_hd(frame->hd);
+ if (frame->hd.flags) {
+ print_frame_attr_indent();
+ print_flags(frame->hd);
+ }
+ switch (frame->hd.type) {
+ case NGHTTP2_DATA:
+ if (frame->data.padlen > 0) {
+ print_frame_attr_indent();
+ fprintf(outfile, "(padlen=%zu)\n", frame->data.padlen);
+ }
+ break;
+ case NGHTTP2_HEADERS:
+ print_frame_attr_indent();
+ fprintf(outfile, "(padlen=%zu", frame->headers.padlen);
+ if (frame->hd.flags & NGHTTP2_FLAG_PRIORITY) {
+ fprintf(outfile, ", dep_stream_id=%d, weight=%u, exclusive=%d",
+ frame->headers.pri_spec.stream_id, frame->headers.pri_spec.weight,
+ frame->headers.pri_spec.exclusive);
+ }
+ fprintf(outfile, ")\n");
+ switch (frame->headers.cat) {
+ case NGHTTP2_HCAT_REQUEST:
+ print_frame_attr_indent();
+ fprintf(outfile, "; Open new stream\n");
+ break;
+ case NGHTTP2_HCAT_RESPONSE:
+ print_frame_attr_indent();
+ fprintf(outfile, "; First response header\n");
+ break;
+ case NGHTTP2_HCAT_PUSH_RESPONSE:
+ print_frame_attr_indent();
+ fprintf(outfile, "; First push response header\n");
+ break;
+ default:
+ break;
+ }
+ print_nv(frame->headers.nva, frame->headers.nvlen);
+ break;
+ case NGHTTP2_PRIORITY:
+ print_frame_attr_indent();
+
+ fprintf(outfile, "(dep_stream_id=%d, weight=%u, exclusive=%d)\n",
+ frame->priority.pri_spec.stream_id, frame->priority.pri_spec.weight,
+ frame->priority.pri_spec.exclusive);
+
+ break;
+ case NGHTTP2_RST_STREAM:
+ print_frame_attr_indent();
+ fprintf(outfile, "(error_code=%s(0x%02x))\n",
+ nghttp2_http2_strerror(frame->rst_stream.error_code),
+ frame->rst_stream.error_code);
+ break;
+ case NGHTTP2_SETTINGS:
+ print_frame_attr_indent();
+ fprintf(outfile, "(niv=%lu)\n",
+ static_cast<unsigned long>(frame->settings.niv));
+ for (size_t i = 0; i < frame->settings.niv; ++i) {
+ print_frame_attr_indent();
+ fprintf(outfile, "[%s(0x%02x):%u]\n",
+ strsettingsid(frame->settings.iv[i].settings_id),
+ frame->settings.iv[i].settings_id, frame->settings.iv[i].value);
+ }
+ break;
+ case NGHTTP2_PUSH_PROMISE:
+ print_frame_attr_indent();
+ fprintf(outfile, "(padlen=%zu, promised_stream_id=%d)\n",
+ frame->push_promise.padlen, frame->push_promise.promised_stream_id);
+ print_nv(frame->push_promise.nva, frame->push_promise.nvlen);
+ break;
+ case NGHTTP2_PING:
+ print_frame_attr_indent();
+ fprintf(outfile, "(opaque_data=%s)\n",
+ util::format_hex(frame->ping.opaque_data, 8).c_str());
+ break;
+ case NGHTTP2_GOAWAY:
+ print_frame_attr_indent();
+ fprintf(outfile,
+ "(last_stream_id=%d, error_code=%s(0x%02x), "
+ "opaque_data(%u)=[%s])\n",
+ frame->goaway.last_stream_id,
+ nghttp2_http2_strerror(frame->goaway.error_code),
+ frame->goaway.error_code,
+ static_cast<unsigned int>(frame->goaway.opaque_data_len),
+ util::ascii_dump(frame->goaway.opaque_data,
+ frame->goaway.opaque_data_len)
+ .c_str());
+ break;
+ case NGHTTP2_WINDOW_UPDATE:
+ print_frame_attr_indent();
+ fprintf(outfile, "(window_size_increment=%d)\n",
+ frame->window_update.window_size_increment);
+ break;
+ case NGHTTP2_ALTSVC: {
+ auto altsvc = static_cast<nghttp2_ext_altsvc *>(frame->ext.payload);
+ print_frame_attr_indent();
+ fprintf(outfile, "(origin=[%.*s], altsvc_field_value=[%.*s])\n",
+ static_cast<int>(altsvc->origin_len), altsvc->origin,
+ static_cast<int>(altsvc->field_value_len), altsvc->field_value);
+ break;
+ }
+ case NGHTTP2_ORIGIN: {
+ auto origin = static_cast<nghttp2_ext_origin *>(frame->ext.payload);
+ for (size_t i = 0; i < origin->nov; ++i) {
+ auto ent = &origin->ov[i];
+ print_frame_attr_indent();
+ fprintf(outfile, "[%.*s]\n", (int)ent->origin_len, ent->origin);
+ }
+ break;
+ }
+ case NGHTTP2_PRIORITY_UPDATE: {
+ auto priority_update =
+ static_cast<nghttp2_ext_priority_update *>(frame->ext.payload);
+ print_frame_attr_indent();
+ fprintf(outfile,
+ "(prioritized_stream_id=%d, priority_field_value=[%.*s])\n",
+ priority_update->stream_id,
+ static_cast<int>(priority_update->field_value_len),
+ priority_update->field_value);
+ break;
+ }
+ default:
+ break;
+ }
+}
+} // namespace
+
+int verbose_on_header_callback(nghttp2_session *session,
+ const nghttp2_frame *frame, const uint8_t *name,
+ size_t namelen, const uint8_t *value,
+ size_t valuelen, uint8_t flags,
+ void *user_data) {
+ nghttp2_nv nv = {const_cast<uint8_t *>(name), const_cast<uint8_t *>(value),
+ namelen, valuelen};
+
+ print_timer();
+ fprintf(outfile, " recv (stream_id=%d", frame->hd.stream_id);
+ if (flags & NGHTTP2_NV_FLAG_NO_INDEX) {
+ fprintf(outfile, ", sensitive");
+ }
+ fprintf(outfile, ") ");
+
+ print_nv(&nv);
+ fflush(outfile);
+
+ return 0;
+}
+
+int verbose_on_frame_recv_callback(nghttp2_session *session,
+ const nghttp2_frame *frame,
+ void *user_data) {
+ print_timer();
+ fprintf(outfile, " recv ");
+ print_frame(PRINT_RECV, frame);
+ fflush(outfile);
+ return 0;
+}
+
+int verbose_on_invalid_frame_recv_callback(nghttp2_session *session,
+ const nghttp2_frame *frame,
+ int lib_error_code,
+ void *user_data) {
+ print_timer();
+ fprintf(outfile, " [INVALID; error=%s] recv ",
+ nghttp2_strerror(lib_error_code));
+ print_frame(PRINT_RECV, frame);
+ fflush(outfile);
+ return 0;
+}
+
+int verbose_on_frame_send_callback(nghttp2_session *session,
+ const nghttp2_frame *frame,
+ void *user_data) {
+ print_timer();
+ fprintf(outfile, " send ");
+ print_frame(PRINT_SEND, frame);
+ fflush(outfile);
+ return 0;
+}
+
+int verbose_on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
+ int32_t stream_id, const uint8_t *data,
+ size_t len, void *user_data) {
+ print_timer();
+ auto srecv =
+ nghttp2_session_get_stream_effective_recv_data_length(session, stream_id);
+ auto crecv = nghttp2_session_get_effective_recv_data_length(session);
+
+ fprintf(outfile,
+ " recv (stream_id=%d, length=%zu, srecv=%d, crecv=%d) DATA\n",
+ stream_id, len, srecv, crecv);
+ fflush(outfile);
+
+ return 0;
+}
+
+int verbose_error_callback(nghttp2_session *session, int lib_error_code,
+ const char *msg, size_t len, void *user_data) {
+ print_timer();
+ fprintf(outfile, " [ERROR] %.*s\n", (int)len, msg);
+ fflush(outfile);
+
+ return 0;
+}
+
+namespace {
+std::chrono::steady_clock::time_point base_tv;
+} // namespace
+
+void reset_timer() { base_tv = std::chrono::steady_clock::now(); }
+
+std::chrono::milliseconds get_timer() {
+ return time_delta(std::chrono::steady_clock::now(), base_tv);
+}
+
+std::chrono::steady_clock::time_point get_time() {
+ return std::chrono::steady_clock::now();
+}
+
+ssize_t deflate_data(uint8_t *out, size_t outlen, const uint8_t *in,
+ size_t inlen) {
+ int rv;
+ z_stream zst{};
+ uint8_t temp_out[8_k];
+ auto temp_outlen = sizeof(temp_out);
+
+ rv = deflateInit2(&zst, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 9,
+ Z_DEFAULT_STRATEGY);
+
+ if (rv != Z_OK) {
+ return -1;
+ }
+
+ zst.avail_in = inlen;
+ zst.next_in = (uint8_t *)in;
+ zst.avail_out = temp_outlen;
+ zst.next_out = temp_out;
+
+ rv = deflate(&zst, Z_FINISH);
+
+ deflateEnd(&zst);
+
+ if (rv != Z_STREAM_END) {
+ return -1;
+ }
+
+ temp_outlen -= zst.avail_out;
+
+ if (temp_outlen > outlen) {
+ return -1;
+ }
+
+ memcpy(out, temp_out, temp_outlen);
+
+ return temp_outlen;
+}
+
+} // namespace nghttp2
diff --git a/src/app_helper.h b/src/app_helper.h
new file mode 100644
index 0000000..5424054
--- /dev/null
+++ b/src/app_helper.h
@@ -0,0 +1,98 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef APP_HELPER_H
+#define APP_HELPER_H
+
+#include "nghttp2_config.h"
+
+#include <cinttypes>
+#include <cstdlib>
+#ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+#endif // HAVE_SYS_TIME_H
+#include <poll.h>
+
+#include <map>
+#include <chrono>
+
+#include <nghttp2/nghttp2.h>
+
+namespace nghttp2 {
+
+int verbose_on_header_callback(nghttp2_session *session,
+ const nghttp2_frame *frame, const uint8_t *name,
+ size_t namelen, const uint8_t *value,
+ size_t valuelen, uint8_t flags, void *user_data);
+
+int verbose_on_frame_recv_callback(nghttp2_session *session,
+ const nghttp2_frame *frame, void *user_data);
+
+int verbose_on_invalid_frame_recv_callback(nghttp2_session *session,
+ const nghttp2_frame *frame,
+ int lib_error_code, void *user_data);
+
+int verbose_on_frame_send_callback(nghttp2_session *session,
+ const nghttp2_frame *frame, void *user_data);
+
+int verbose_on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
+ int32_t stream_id, const uint8_t *data,
+ size_t len, void *user_data);
+
+int verbose_error_callback(nghttp2_session *session, int lib_error_code,
+ const char *msg, size_t len, void *user_data);
+
+// Returns difference between |a| and |b| in milliseconds, assuming
+// |a| is more recent than |b|.
+template <typename TimePoint>
+std::chrono::milliseconds time_delta(const TimePoint &a, const TimePoint &b) {
+ return std::chrono::duration_cast<std::chrono::milliseconds>(a - b);
+}
+
+// Resets timer
+void reset_timer();
+
+// Returns the duration since timer reset.
+std::chrono::milliseconds get_timer();
+
+// Returns current time point.
+std::chrono::steady_clock::time_point get_time();
+
+void print_timer();
+
+// Setting true will print characters with ANSI color escape codes
+// when printing HTTP2 frames. This function changes a static
+// variable.
+void set_color_output(bool f);
+
+// Set output file when printing HTTP2 frames. By default, stdout is
+// used.
+void set_output(FILE *file);
+
+ssize_t deflate_data(uint8_t *out, size_t outlen, const uint8_t *in,
+ size_t inlen);
+
+} // namespace nghttp2
+
+#endif // APP_HELPER_H
diff --git a/src/base64.h b/src/base64.h
new file mode 100644
index 0000000..1bd51af
--- /dev/null
+++ b/src/base64.h
@@ -0,0 +1,225 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef BASE64_H
+#define BASE64_H
+
+#include "nghttp2_config.h"
+
+#include <string>
+
+#include "template.h"
+#include "allocator.h"
+
+namespace nghttp2 {
+
+namespace base64 {
+
+namespace {
+constexpr char B64_CHARS[] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+ 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/',
+};
+} // namespace
+
+template <typename InputIt> std::string encode(InputIt first, InputIt last) {
+ std::string res;
+ size_t len = last - first;
+ if (len == 0) {
+ return res;
+ }
+ size_t r = len % 3;
+ res.resize((len + 2) / 3 * 4);
+ auto j = last - r;
+ auto p = std::begin(res);
+ while (first != j) {
+ uint32_t n = static_cast<uint8_t>(*first++) << 16;
+ n += static_cast<uint8_t>(*first++) << 8;
+ n += static_cast<uint8_t>(*first++);
+ *p++ = B64_CHARS[n >> 18];
+ *p++ = B64_CHARS[(n >> 12) & 0x3fu];
+ *p++ = B64_CHARS[(n >> 6) & 0x3fu];
+ *p++ = B64_CHARS[n & 0x3fu];
+ }
+
+ if (r == 2) {
+ uint32_t n = static_cast<uint8_t>(*first++) << 16;
+ n += static_cast<uint8_t>(*first++) << 8;
+ *p++ = B64_CHARS[n >> 18];
+ *p++ = B64_CHARS[(n >> 12) & 0x3fu];
+ *p++ = B64_CHARS[(n >> 6) & 0x3fu];
+ *p++ = '=';
+ } else if (r == 1) {
+ uint32_t n = static_cast<uint8_t>(*first++) << 16;
+ *p++ = B64_CHARS[n >> 18];
+ *p++ = B64_CHARS[(n >> 12) & 0x3fu];
+ *p++ = '=';
+ *p++ = '=';
+ }
+ return res;
+}
+
+constexpr size_t encode_length(size_t n) { return (n + 2) / 3 * 4; }
+
+template <typename InputIt, typename OutputIt>
+OutputIt encode(InputIt first, InputIt last, OutputIt d_first) {
+ size_t len = last - first;
+ if (len == 0) {
+ return d_first;
+ }
+ auto r = len % 3;
+ auto j = last - r;
+ auto p = d_first;
+ while (first != j) {
+ uint32_t n = static_cast<uint8_t>(*first++) << 16;
+ n += static_cast<uint8_t>(*first++) << 8;
+ n += static_cast<uint8_t>(*first++);
+ *p++ = B64_CHARS[n >> 18];
+ *p++ = B64_CHARS[(n >> 12) & 0x3fu];
+ *p++ = B64_CHARS[(n >> 6) & 0x3fu];
+ *p++ = B64_CHARS[n & 0x3fu];
+ }
+
+ switch (r) {
+ case 2: {
+ uint32_t n = static_cast<uint8_t>(*first++) << 16;
+ n += static_cast<uint8_t>(*first++) << 8;
+ *p++ = B64_CHARS[n >> 18];
+ *p++ = B64_CHARS[(n >> 12) & 0x3fu];
+ *p++ = B64_CHARS[(n >> 6) & 0x3fu];
+ *p++ = '=';
+ break;
+ }
+ case 1: {
+ uint32_t n = static_cast<uint8_t>(*first++) << 16;
+ *p++ = B64_CHARS[n >> 18];
+ *p++ = B64_CHARS[(n >> 12) & 0x3fu];
+ *p++ = '=';
+ *p++ = '=';
+ break;
+ }
+ }
+ return p;
+}
+
+template <typename InputIt>
+InputIt next_decode_input(InputIt first, InputIt last, const int *tbl) {
+ for (; first != last; ++first) {
+ if (tbl[static_cast<size_t>(*first)] != -1 || *first == '=') {
+ break;
+ }
+ }
+ return first;
+}
+
+template <typename InputIt, typename OutputIt>
+OutputIt decode(InputIt first, InputIt last, OutputIt d_first) {
+ static constexpr int INDEX_TABLE[] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57,
+ 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6,
+ 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
+ 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1};
+ assert(std::distance(first, last) % 4 == 0);
+ auto p = d_first;
+ for (; first != last;) {
+ uint32_t n = 0;
+ for (int i = 1; i <= 4; ++i, ++first) {
+ auto idx = INDEX_TABLE[static_cast<size_t>(*first)];
+ if (idx == -1) {
+ if (i <= 2) {
+ return d_first;
+ }
+ if (i == 3) {
+ if (*first == '=' && *(first + 1) == '=' && first + 2 == last) {
+ *p++ = n >> 16;
+ return p;
+ }
+ return d_first;
+ }
+ if (*first == '=' && first + 1 == last) {
+ *p++ = n >> 16;
+ *p++ = n >> 8 & 0xffu;
+ return p;
+ }
+ return d_first;
+ }
+
+ n += idx << (24 - i * 6);
+ }
+
+ *p++ = n >> 16;
+ *p++ = n >> 8 & 0xffu;
+ *p++ = n & 0xffu;
+ }
+
+ return p;
+}
+
+template <typename InputIt> std::string decode(InputIt first, InputIt last) {
+ auto len = std::distance(first, last);
+ if (len % 4 != 0) {
+ return "";
+ }
+ std::string res;
+ res.resize(len / 4 * 3);
+
+ res.erase(decode(first, last, std::begin(res)), std::end(res));
+
+ return res;
+}
+
+template <typename InputIt>
+StringRef decode(BlockAllocator &balloc, InputIt first, InputIt last) {
+ auto len = std::distance(first, last);
+ if (len % 4 != 0) {
+ return StringRef::from_lit("");
+ }
+ auto iov = make_byte_ref(balloc, len / 4 * 3 + 1);
+ auto p = iov.base;
+
+ p = decode(first, last, p);
+ *p = '\0';
+
+ return StringRef{iov.base, p};
+}
+
+} // namespace base64
+
+} // namespace nghttp2
+
+#endif // BASE64_H
diff --git a/src/base64_test.cc b/src/base64_test.cc
new file mode 100644
index 0000000..4324bd7
--- /dev/null
+++ b/src/base64_test.cc
@@ -0,0 +1,121 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "base64_test.h"
+
+#include <cstring>
+#include <iostream>
+
+#include <CUnit/CUnit.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "base64.h"
+
+namespace nghttp2 {
+
+void test_base64_encode(void) {
+ {
+ std::string in = "\xff";
+ auto out = base64::encode(std::begin(in), std::end(in));
+ CU_ASSERT("/w==" == out);
+ }
+ {
+ std::string in = "\xff\xfe";
+ auto out = base64::encode(std::begin(in), std::end(in));
+ CU_ASSERT("//4=" == out);
+ }
+ {
+ std::string in = "\xff\xfe\xfd";
+ auto out = base64::encode(std::begin(in), std::end(in));
+ CU_ASSERT("//79" == out);
+ }
+ {
+ std::string in = "\xff\xfe\xfd\xfc";
+ auto out = base64::encode(std::begin(in), std::end(in));
+ CU_ASSERT("//79/A==" == out);
+ }
+}
+
+void test_base64_decode(void) {
+ BlockAllocator balloc(4096, 4096);
+ {
+ std::string in = "/w==";
+ auto out = base64::decode(std::begin(in), std::end(in));
+ CU_ASSERT("\xff" == out);
+ CU_ASSERT("\xff" == base64::decode(balloc, std::begin(in), std::end(in)));
+ }
+ {
+ std::string in = "//4=";
+ auto out = base64::decode(std::begin(in), std::end(in));
+ CU_ASSERT("\xff\xfe" == out);
+ CU_ASSERT("\xff\xfe" ==
+ base64::decode(balloc, std::begin(in), std::end(in)));
+ }
+ {
+ std::string in = "//79";
+ auto out = base64::decode(std::begin(in), std::end(in));
+ CU_ASSERT("\xff\xfe\xfd" == out);
+ CU_ASSERT("\xff\xfe\xfd" ==
+ base64::decode(balloc, std::begin(in), std::end(in)));
+ }
+ {
+ std::string in = "//79/A==";
+ auto out = base64::decode(std::begin(in), std::end(in));
+ CU_ASSERT("\xff\xfe\xfd\xfc" == out);
+ CU_ASSERT("\xff\xfe\xfd\xfc" ==
+ base64::decode(balloc, std::begin(in), std::end(in)));
+ }
+ {
+ // we check the number of valid input must be multiples of 4
+ std::string in = "//79=";
+ auto out = base64::decode(std::begin(in), std::end(in));
+ CU_ASSERT("" == out);
+ CU_ASSERT("" == base64::decode(balloc, std::begin(in), std::end(in)));
+ }
+ {
+ // ending invalid character at the boundary of multiples of 4 is
+ // bad
+ std::string in = "bmdodHRw\n";
+ auto out = base64::decode(std::begin(in), std::end(in));
+ CU_ASSERT("" == out);
+ CU_ASSERT("" == base64::decode(balloc, std::begin(in), std::end(in)));
+ }
+ {
+ // after seeing '=', subsequent input must be also '='.
+ std::string in = "//79/A=A";
+ auto out = base64::decode(std::begin(in), std::end(in));
+ CU_ASSERT("" == out);
+ CU_ASSERT("" == base64::decode(balloc, std::begin(in), std::end(in)));
+ }
+ {
+ // additional '=' at the end is bad
+ std::string in = "//79/A======";
+ auto out = base64::decode(std::begin(in), std::end(in));
+ CU_ASSERT("" == out);
+ CU_ASSERT("" == base64::decode(balloc, std::begin(in), std::end(in)));
+ }
+}
+
+} // namespace nghttp2
diff --git a/src/base64_test.h b/src/base64_test.h
new file mode 100644
index 0000000..8bdb84f
--- /dev/null
+++ b/src/base64_test.h
@@ -0,0 +1,39 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef BASE64_TEST_H
+#define BASE64_TEST_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+namespace nghttp2 {
+
+void test_base64_encode(void);
+void test_base64_decode(void);
+
+} // namespace nghttp2
+
+#endif // BASE64_TEST_H
diff --git a/src/buffer.h b/src/buffer.h
new file mode 100644
index 0000000..1921edf
--- /dev/null
+++ b/src/buffer.h
@@ -0,0 +1,78 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef BUFFER_H
+#define BUFFER_H
+
+#include "nghttp2_config.h"
+
+#include <cstring>
+#include <algorithm>
+#include <array>
+
+namespace nghttp2 {
+
+template <size_t N> struct Buffer {
+ Buffer() : pos(std::begin(buf)), last(pos) {}
+ // Returns the number of bytes to read.
+ size_t rleft() const { return last - pos; }
+ // Returns the number of bytes this buffer can store.
+ size_t wleft() const { return std::end(buf) - last; }
+ // Writes up to min(wleft(), |count|) bytes from buffer pointed by
+ // |src|. Returns number of bytes written.
+ size_t write(const void *src, size_t count) {
+ count = std::min(count, wleft());
+ auto p = static_cast<const uint8_t *>(src);
+ last = std::copy_n(p, count, last);
+ return count;
+ }
+ size_t write(size_t count) {
+ count = std::min(count, wleft());
+ last += count;
+ return count;
+ }
+ // Drains min(rleft(), |count|) bytes from start of the buffer.
+ size_t drain(size_t count) {
+ count = std::min(count, rleft());
+ pos += count;
+ return count;
+ }
+ size_t drain_reset(size_t count) {
+ count = std::min(count, rleft());
+ std::copy(pos + count, last, std::begin(buf));
+ last = std::begin(buf) + (last - (pos + count));
+ pos = std::begin(buf);
+ return count;
+ }
+ void reset() { pos = last = std::begin(buf); }
+ uint8_t *begin() { return std::begin(buf); }
+ uint8_t &operator[](size_t n) { return buf[n]; }
+ const uint8_t &operator[](size_t n) const { return buf[n]; }
+ std::array<uint8_t, N> buf;
+ uint8_t *pos, *last;
+};
+
+} // namespace nghttp2
+
+#endif // BUFFER_H
diff --git a/src/buffer_test.cc b/src/buffer_test.cc
new file mode 100644
index 0000000..38688ed
--- /dev/null
+++ b/src/buffer_test.cc
@@ -0,0 +1,78 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "buffer_test.h"
+
+#include <cstring>
+#include <iostream>
+#include <tuple>
+
+#include <CUnit/CUnit.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "buffer.h"
+
+namespace nghttp2 {
+
+void test_buffer_write(void) {
+ Buffer<16> b;
+ CU_ASSERT(0 == b.rleft());
+ CU_ASSERT(16 == b.wleft());
+
+ b.write("012", 3);
+
+ CU_ASSERT(3 == b.rleft());
+ CU_ASSERT(13 == b.wleft());
+ CU_ASSERT(b.pos == std::begin(b.buf));
+
+ b.drain(3);
+
+ CU_ASSERT(0 == b.rleft());
+ CU_ASSERT(13 == b.wleft());
+ CU_ASSERT(3 == b.pos - std::begin(b.buf));
+
+ auto n = b.write("0123456789ABCDEF", 16);
+
+ CU_ASSERT(n == 13);
+
+ CU_ASSERT(13 == b.rleft());
+ CU_ASSERT(0 == b.wleft());
+ CU_ASSERT(3 == b.pos - std::begin(b.buf));
+ CU_ASSERT(0 == memcmp(b.pos, "0123456789ABC", 13));
+
+ b.reset();
+
+ CU_ASSERT(0 == b.rleft());
+ CU_ASSERT(16 == b.wleft());
+ CU_ASSERT(b.pos == std::begin(b.buf));
+
+ b.write(5);
+
+ CU_ASSERT(5 == b.rleft());
+ CU_ASSERT(11 == b.wleft());
+ CU_ASSERT(b.pos == std::begin(b.buf));
+}
+
+} // namespace nghttp2
diff --git a/src/buffer_test.h b/src/buffer_test.h
new file mode 100644
index 0000000..6789aa3
--- /dev/null
+++ b/src/buffer_test.h
@@ -0,0 +1,38 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef BUFFER_TEST_H
+#define BUFFER_TEST_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+namespace nghttp2 {
+
+void test_buffer_write(void);
+
+} // namespace nghttp2
+
+#endif // BUFFER_TEST_H
diff --git a/src/ca-config.json b/src/ca-config.json
new file mode 100644
index 0000000..5788d07
--- /dev/null
+++ b/src/ca-config.json
@@ -0,0 +1,17 @@
+{
+ "signing": {
+ "default": {
+ "expiry": "87600h"
+ },
+ "profiles": {
+ "server": {
+ "expiry": "87600h",
+ "usages": [
+ "signing",
+ "key encipherment",
+ "server auth"
+ ]
+ }
+ }
+ }
+}
diff --git a/src/ca.nghttp2.org-key.pem b/src/ca.nghttp2.org-key.pem
new file mode 100644
index 0000000..6ce8707
--- /dev/null
+++ b/src/ca.nghttp2.org-key.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEA1kVkF8QSUwW/HV9EFRPSMoiOVmYwB8vqKDtT0d6MFiKAM8/Y
+JFUq2uKlUydgT4IPE7PATvVcIj3GtL9XzPhscqYO/S0Y7scyTE2VAPmtz+StPWf2
+wZ1IQR09HrnDTc44KvYGZpefBZkD9UjbmJ9a1ZmJjJiMr3hTnKE/sxZ2+dMsnMZX
+N822cfaHyTN+T0+Tyw5vBBboCDsZzxmf+9FFIDJNs3NL34cR8EZRhpfaegapH8bt
+OJ+D+RZ2kg7E/YYkGcS6NodvTjSUFCFHpWjHCfTFhn/owBIAooCdWorh6dc8Q72l
+AodwNLXS8uuPgPqM5s4Cz57m7Zgs4OilNmIdawIDAQABAoIBAQCwqLtygLye6KD+
+RXorapEmCsJX5553/x6Klwdvg+25ni5XCWjp47IWj0DBQzi7tL5bfxrxvod8z7QR
+d6SbIMLA77px8Ima7G7CzEAqcrBkM+TFOP8P+G4HCWVH/N5SOtDCUt9KHH4Grna9
+95jdx5yreRAX8/oh/bHp9GRBcicbpwYMVWOnjTE2seEUYQOpdpYdP4bOPUvAju0l
+mwmy2/dDGmbibktN3sdHEhDodKu+Znv7nFZo0jzhlyoXse653WcvaQeZZYuojvSe
+Sr92DvPp7UaYrb4KvT7ujXiPavSV2m/4EmGtyqevUf2dZ6sfMXZjmXsjWz9txhWp
+4BgbHyHRAoGBAPqyuNj2CDD3FE7N3Hxyba8d+ZtsVUNawjq2gwOvT9NLsMstOGyH
+OCc1v4W6Sq4w1wo4nIJyY8kNZwtReaTHOPZlDgBhVvk/x8eLBu+QTMRyocRt1LoD
+8HyKxWSAnYTtCh/GUEQ37amIqvOJ5GNL+25WDzevLa5kMYWG743uxEupAoGBANrN
+c/fVxepvP0GISlLpL3aZCFGAjMrq3xUYcf/w4wPoMq6AdpIPeRVBmJ1/Uqw1FkV8
+NRKJNPE2YcMuv8iMeQlacoPd34KT9ob80EYVlMwAkeC0NK+FfiM/UteR0wB49gmi
+ugX9YlJytOP9aUgPvEGT6l+XtgGC44W1TQWe62zzAoGBAKZenNU+0UjNb6isbToZ
+Jjkkh1Vhm2PLg0I7hM6ZNTxf6r+rDtrXEajTvnocmxrmRo796r+W8immv09/jl6P
+53l8rsIJ1xIqBYai+MNa29cyy6/zw0x++MVtwnlj8SUZubJEhVgAVbRAglKEnBBZ
+iE48xnSJyKMG0uZuGePzJEmhAoGBAIOHJcNBumum3DuklikpC+MbMyjrQbdpYRjp
+TP4x7AWZO34ysxQyQPNKL1feBfCHKRA0DiNKX4zwx+vw2lDQQKIiwNwMMCPqljOn
+HfxDVOMdJJQTP+iTMrQ1iLMVceXC0QQR0glvu/8b/SlgWD19WAmDxUwZgst9xw/F
+YLuUQKmJAoGAREeTugd4hc0U/YV/BQQjSCLhl11EtVry/oQMHj8KZpIJhP7tj8lw
+hSE0+z04oMhiTeq55PYKQkTo5l6V4PW0zfpEwlKEEm0erab1G9Ddh7us47XFcKLl
+Rmk192EVZ0lQuzftsYv7dzRLiAR7yDFXwD1ELIK/uPkwBtu7wtHlq+M=
+-----END RSA PRIVATE KEY-----
diff --git a/src/ca.nghttp2.org.csr b/src/ca.nghttp2.org.csr
new file mode 100644
index 0000000..37ee560
--- /dev/null
+++ b/src/ca.nghttp2.org.csr
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIICwjCCAaoCAQAwXjELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUx
+ITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEXMBUGA1UEAxMOY2Eu
+bmdodHRwMi5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDWRWQX
+xBJTBb8dX0QVE9IyiI5WZjAHy+ooO1PR3owWIoAzz9gkVSra4qVTJ2BPgg8Ts8BO
+9VwiPca0v1fM+Gxypg79LRjuxzJMTZUA+a3P5K09Z/bBnUhBHT0eucNNzjgq9gZm
+l58FmQP1SNuYn1rVmYmMmIyveFOcoT+zFnb50yycxlc3zbZx9ofJM35PT5PLDm8E
+FugIOxnPGZ/70UUgMk2zc0vfhxHwRlGGl9p6Bqkfxu04n4P5FnaSDsT9hiQZxLo2
+h29ONJQUIUelaMcJ9MWGf+jAEgCigJ1aiuHp1zxDvaUCh3A0tdLy64+A+ozmzgLP
+nubtmCzg6KU2Yh1rAgMBAAGgHzAdBgkqhkiG9w0BCQ4xEDAOMAwGA1UdEwQFMAMB
+Af8wDQYJKoZIhvcNAQELBQADggEBACI5v8GbOXKv38h9/tuGEwJ9uxpYEljgGt8h
+QL5lwfEifh/7A8b39b9JEzWk5hnMRCOb8J6Jc3/6nmVgtKkQ+Mceupqpwsp1gT/v
+uUoAkJE03Iuja9zLhHmy74oZ7LWOQrZ1T7Z0eGQ+5u+LBZiPKnKxmkLCQoUPTbc4
+NQ9BbKhr8OaoJ4DDvJnszcL7to6kih7SkdoNZsq4zB0/ai/cPhvoVgkYfbLH2++D
+Tcs7TqU2L7gKzqXUtHeAKM2y81ewL7QTrcYzgiW86s3NmquxZG5pq0mjD+P4BYLc
+MOdnCxKbBuE/1R29pa6+JKgc46jOa2yRgv5+8rXkkpu53Ke3FGc=
+-----END CERTIFICATE REQUEST-----
diff --git a/src/ca.nghttp2.org.csr.json b/src/ca.nghttp2.org.csr.json
new file mode 100644
index 0000000..69d9c6a
--- /dev/null
+++ b/src/ca.nghttp2.org.csr.json
@@ -0,0 +1,17 @@
+{
+ "CN": "ca.nghttp2.org",
+ "key": {
+ "algo": "rsa",
+ "size": 2048
+ },
+ "ca": {
+ "expiry": "87600h"
+ },
+ "names": [
+ {
+ "C": "AU",
+ "ST": "Some-State",
+ "O": "Internet Widgits Pty Ltd"
+ }
+ ]
+}
diff --git a/src/ca.nghttp2.org.pem b/src/ca.nghttp2.org.pem
new file mode 100644
index 0000000..e50acfc
--- /dev/null
+++ b/src/ca.nghttp2.org.pem
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDrTCCApWgAwIBAgIUe4dvx8haIjsT3ZpNCMrl62Xk6E0wDQYJKoZIhvcNAQEL
+BQAwXjELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoT
+GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEXMBUGA1UEAxMOY2EubmdodHRwMi5v
+cmcwHhcNMTYwNjI1MDkzMzAwWhcNMjYwNjIzMDkzMzAwWjBeMQswCQYDVQQGEwJB
+VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0
+cyBQdHkgTHRkMRcwFQYDVQQDEw5jYS5uZ2h0dHAyLm9yZzCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBANZFZBfEElMFvx1fRBUT0jKIjlZmMAfL6ig7U9He
+jBYigDPP2CRVKtripVMnYE+CDxOzwE71XCI9xrS/V8z4bHKmDv0tGO7HMkxNlQD5
+rc/krT1n9sGdSEEdPR65w03OOCr2BmaXnwWZA/VI25ifWtWZiYyYjK94U5yhP7MW
+dvnTLJzGVzfNtnH2h8kzfk9Pk8sObwQW6Ag7Gc8Zn/vRRSAyTbNzS9+HEfBGUYaX
+2noGqR/G7Tifg/kWdpIOxP2GJBnEujaHb040lBQhR6Voxwn0xYZ/6MASAKKAnVqK
+4enXPEO9pQKHcDS10vLrj4D6jObOAs+e5u2YLODopTZiHWsCAwEAAaNjMGEwDgYD
+VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNA5xVR1Zcax
+RJL9VC6pzuLmvduGMB8GA1UdIwQYMBaAFNA5xVR1ZcaxRJL9VC6pzuLmvduGMA0G
+CSqGSIb3DQEBCwUAA4IBAQCmdVfn/hUyEdvkKG7svg5d8o6BENOj8695KtWmzJjK
+zxH8J5Vy3mn89XrHQ+BOYXCDPyhs0aDS8aq3Z+HY0n9z1oAicyGzlVwZQQNX3YId
+Y2vcf7qu/2ATm/1S+mebE1/EXMUlWISKKUYXjggCwFgjDhH87Ai+A8MKScVdmqgL
+Hf+fRSzH3ToW7BCXlRl5bPAq2g+v1ALYc8wU9cT1MYm4dqAXh870LGFyUpaSWmFr
+TtX1DXBTgLp62syNlDthAvGigYFDtCa4cDM2vdTD9wpec2V9EKpfVqiRDDuYjUVX
+UXl27MvkNWnEBKCIoNv5abWXpZVG2zQdEMmUOkVuAXUC
+-----END CERTIFICATE-----
diff --git a/src/comp_helper.c b/src/comp_helper.c
new file mode 100644
index 0000000..98db08a
--- /dev/null
+++ b/src/comp_helper.c
@@ -0,0 +1,133 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "comp_helper.h"
+#include <string.h>
+
+static void dump_val(json_t *jent, const char *key, uint8_t *val, size_t len) {
+ json_object_set_new(jent, key, json_pack("s#", val, len));
+}
+
+#define NGHTTP2_HD_ENTRY_OVERHEAD 32
+
+json_t *dump_deflate_header_table(nghttp2_hd_deflater *deflater) {
+ json_t *obj, *entries;
+ size_t i;
+ size_t len = nghttp2_hd_deflate_get_num_table_entries(deflater);
+
+ obj = json_object();
+ entries = json_array();
+ /* The first index of dynamic table is 62 */
+ for (i = 62; i <= len; ++i) {
+ const nghttp2_nv *nv = nghttp2_hd_deflate_get_table_entry(deflater, i);
+ json_t *outent = json_object();
+ json_object_set_new(outent, "index", json_integer((json_int_t)i));
+ dump_val(outent, "name", nv->name, nv->namelen);
+ dump_val(outent, "value", nv->value, nv->valuelen);
+ json_object_set_new(outent, "size",
+ json_integer((json_int_t)(nv->namelen + nv->valuelen +
+ NGHTTP2_HD_ENTRY_OVERHEAD)));
+ json_array_append_new(entries, outent);
+ }
+ json_object_set_new(obj, "entries", entries);
+ json_object_set_new(
+ obj, "size",
+ json_integer(
+ (json_int_t)nghttp2_hd_deflate_get_dynamic_table_size(deflater)));
+ json_object_set_new(
+ obj, "max_size",
+ json_integer(
+ (json_int_t)nghttp2_hd_deflate_get_max_dynamic_table_size(deflater)));
+
+ return obj;
+}
+
+json_t *dump_inflate_header_table(nghttp2_hd_inflater *inflater) {
+ json_t *obj, *entries;
+ size_t i;
+ size_t len = nghttp2_hd_inflate_get_num_table_entries(inflater);
+
+ obj = json_object();
+ entries = json_array();
+ /* The first index of dynamic table is 62 */
+ for (i = 62; i <= len; ++i) {
+ const nghttp2_nv *nv = nghttp2_hd_inflate_get_table_entry(inflater, i);
+ json_t *outent = json_object();
+ json_object_set_new(outent, "index", json_integer((json_int_t)i));
+ dump_val(outent, "name", nv->name, nv->namelen);
+ dump_val(outent, "value", nv->value, nv->valuelen);
+ json_object_set_new(outent, "size",
+ json_integer((json_int_t)(nv->namelen + nv->valuelen +
+ NGHTTP2_HD_ENTRY_OVERHEAD)));
+ json_array_append_new(entries, outent);
+ }
+ json_object_set_new(obj, "entries", entries);
+ json_object_set_new(
+ obj, "size",
+ json_integer(
+ (json_int_t)nghttp2_hd_inflate_get_dynamic_table_size(inflater)));
+ json_object_set_new(
+ obj, "max_size",
+ json_integer(
+ (json_int_t)nghttp2_hd_inflate_get_max_dynamic_table_size(inflater)));
+
+ return obj;
+}
+
+json_t *dump_header(const uint8_t *name, size_t namelen, const uint8_t *value,
+ size_t valuelen) {
+ json_t *nv_pair = json_object();
+ char *cname = malloc(namelen + 1);
+ if (cname == NULL) {
+ return NULL;
+ }
+ memcpy(cname, name, namelen);
+ cname[namelen] = '\0';
+ json_object_set_new(nv_pair, cname, json_pack("s#", value, valuelen));
+ free(cname);
+ return nv_pair;
+}
+
+json_t *dump_headers(const nghttp2_nv *nva, size_t nvlen) {
+ json_t *headers;
+ size_t i;
+
+ headers = json_array();
+ for (i = 0; i < nvlen; ++i) {
+ json_array_append_new(headers, dump_header(nva[i].name, nva[i].namelen,
+ nva[i].value, nva[i].valuelen));
+ }
+ return headers;
+}
+
+void output_json_header(void) {
+ printf("{\n"
+ " \"cases\":\n"
+ " [\n");
+}
+
+void output_json_footer(void) {
+ printf(" ]\n"
+ "}\n");
+}
diff --git a/src/comp_helper.h b/src/comp_helper.h
new file mode 100644
index 0000000..131ed21
--- /dev/null
+++ b/src/comp_helper.h
@@ -0,0 +1,57 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_COMP_HELPER_H
+#define NGHTTP2_COMP_HELPER_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <jansson.h>
+
+#include <nghttp2/nghttp2.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+json_t *dump_deflate_header_table(nghttp2_hd_deflater *deflater);
+
+json_t *dump_inflate_header_table(nghttp2_hd_inflater *inflater);
+
+json_t *dump_header(const uint8_t *name, size_t namelen, const uint8_t *value,
+ size_t vlauelen);
+
+json_t *dump_headers(const nghttp2_nv *nva, size_t nvlen);
+
+void output_json_header(void);
+
+void output_json_footer(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* NGHTTP2_COMP_HELPER_H */
diff --git a/src/deflatehd.cc b/src/deflatehd.cc
new file mode 100644
index 0000000..7dcfccf
--- /dev/null
+++ b/src/deflatehd.cc
@@ -0,0 +1,450 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+#include <getopt.h>
+
+#include <cstdio>
+#include <cstring>
+#include <cassert>
+#include <cerrno>
+#include <cstdlib>
+#include <vector>
+#include <iostream>
+
+#include <jansson.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "template.h"
+#include "comp_helper.h"
+#include "util.h"
+
+namespace nghttp2 {
+
+typedef struct {
+ size_t table_size;
+ size_t deflate_table_size;
+ int http1text;
+ int dump_header_table;
+} deflate_config;
+
+static deflate_config config;
+
+static size_t input_sum;
+static size_t output_sum;
+
+static char to_hex_digit(uint8_t n) {
+ if (n > 9) {
+ return n - 10 + 'a';
+ }
+ return n + '0';
+}
+
+static void to_hex(char *dest, const uint8_t *src, size_t len) {
+ size_t i;
+ for (i = 0; i < len; ++i) {
+ *dest++ = to_hex_digit(src[i] >> 4);
+ *dest++ = to_hex_digit(src[i] & 0xf);
+ }
+}
+
+static void output_to_json(nghttp2_hd_deflater *deflater, const uint8_t *buf,
+ size_t buflen, size_t inputlen,
+ const std::vector<nghttp2_nv> &nva, int seq) {
+ auto hex = std::vector<char>(buflen * 2);
+ auto obj = json_object();
+ auto comp_ratio = inputlen == 0 ? 0.0 : (double)buflen / inputlen * 100;
+
+ json_object_set_new(obj, "seq", json_integer(seq));
+ json_object_set_new(obj, "input_length", json_integer(inputlen));
+ json_object_set_new(obj, "output_length", json_integer(buflen));
+ json_object_set_new(obj, "percentage_of_original_size",
+ json_real(comp_ratio));
+
+ if (buflen == 0) {
+ json_object_set_new(obj, "wire", json_string(""));
+ } else {
+ to_hex(hex.data(), buf, buflen);
+ json_object_set_new(obj, "wire", json_pack("s#", hex.data(), hex.size()));
+ }
+ json_object_set_new(obj, "headers", dump_headers(nva.data(), nva.size()));
+ if (seq == 0) {
+ // We only change the header table size only once at the beginning
+ json_object_set_new(obj, "header_table_size",
+ json_integer(config.table_size));
+ }
+ if (config.dump_header_table) {
+ json_object_set_new(obj, "header_table",
+ dump_deflate_header_table(deflater));
+ }
+ json_dumpf(obj, stdout, JSON_PRESERVE_ORDER | JSON_INDENT(2));
+ printf("\n");
+ json_decref(obj);
+}
+
+static void deflate_hd(nghttp2_hd_deflater *deflater,
+ const std::vector<nghttp2_nv> &nva, size_t inputlen,
+ int seq) {
+ ssize_t rv;
+ std::array<uint8_t, 64_k> buf;
+
+ rv = nghttp2_hd_deflate_hd(deflater, buf.data(), buf.size(),
+ (nghttp2_nv *)nva.data(), nva.size());
+ if (rv < 0) {
+ fprintf(stderr, "deflate failed with error code %zd at %d\n", rv, seq);
+ exit(EXIT_FAILURE);
+ }
+
+ input_sum += inputlen;
+ output_sum += rv;
+
+ output_to_json(deflater, buf.data(), rv, inputlen, nva, seq);
+}
+
+static int deflate_hd_json(json_t *obj, nghttp2_hd_deflater *deflater,
+ int seq) {
+ size_t inputlen = 0;
+
+ auto js = json_object_get(obj, "headers");
+ if (js == nullptr) {
+ fprintf(stderr, "'headers' key is missing at %d\n", seq);
+ return -1;
+ }
+ if (!json_is_array(js)) {
+ fprintf(stderr, "The value of 'headers' key must be an array at %d\n", seq);
+ return -1;
+ }
+
+ auto len = json_array_size(js);
+ auto nva = std::vector<nghttp2_nv>(len);
+
+ for (size_t i = 0; i < len; ++i) {
+ auto nv_pair = json_array_get(js, i);
+ const char *name;
+ json_t *value;
+
+ if (!json_is_object(nv_pair) || json_object_size(nv_pair) != 1) {
+ fprintf(stderr, "bad formatted name/value pair object at %d\n", seq);
+ return -1;
+ }
+
+ json_object_foreach(nv_pair, name, value) {
+ nva[i].name = (uint8_t *)name;
+ nva[i].namelen = strlen(name);
+
+ if (!json_is_string(value)) {
+ fprintf(stderr, "value is not string at %d\n", seq);
+ return -1;
+ }
+
+ nva[i].value = (uint8_t *)json_string_value(value);
+ nva[i].valuelen = strlen(json_string_value(value));
+
+ nva[i].flags = NGHTTP2_NV_FLAG_NONE;
+ }
+
+ inputlen += nva[i].namelen + nva[i].valuelen;
+ }
+
+ deflate_hd(deflater, nva, inputlen, seq);
+
+ return 0;
+}
+
+static nghttp2_hd_deflater *init_deflater() {
+ nghttp2_hd_deflater *deflater;
+ nghttp2_hd_deflate_new(&deflater, config.deflate_table_size);
+ if (config.table_size != NGHTTP2_DEFAULT_HEADER_TABLE_SIZE) {
+ nghttp2_hd_deflate_change_table_size(deflater, config.table_size);
+ }
+ return deflater;
+}
+
+static void deinit_deflater(nghttp2_hd_deflater *deflater) {
+ nghttp2_hd_deflate_del(deflater);
+}
+
+static int perform(void) {
+ json_error_t error;
+
+ auto json = json_loadf(stdin, 0, &error);
+
+ if (json == nullptr) {
+ fprintf(stderr, "JSON loading failed\n");
+ exit(EXIT_FAILURE);
+ }
+
+ auto cases = json_object_get(json, "cases");
+
+ if (cases == nullptr) {
+ fprintf(stderr, "Missing 'cases' key in root object\n");
+ exit(EXIT_FAILURE);
+ }
+
+ if (!json_is_array(cases)) {
+ fprintf(stderr, "'cases' must be JSON array\n");
+ exit(EXIT_FAILURE);
+ }
+
+ auto deflater = init_deflater();
+ output_json_header();
+ auto len = json_array_size(cases);
+
+ for (size_t i = 0; i < len; ++i) {
+ auto obj = json_array_get(cases, i);
+ if (!json_is_object(obj)) {
+ fprintf(stderr, "Unexpected JSON type at %zu. It should be object.\n", i);
+ continue;
+ }
+ if (deflate_hd_json(obj, deflater, i) != 0) {
+ continue;
+ }
+ if (i + 1 < len) {
+ printf(",\n");
+ }
+ }
+ output_json_footer();
+ deinit_deflater(deflater);
+ json_decref(json);
+ return 0;
+}
+
+static int perform_from_http1text(void) {
+ char line[1 << 14];
+ int seq = 0;
+
+ auto deflater = init_deflater();
+ output_json_header();
+ for (;;) {
+ std::vector<nghttp2_nv> nva;
+ int end = 0;
+ size_t inputlen = 0;
+
+ for (;;) {
+ char *rv = fgets(line, sizeof(line), stdin);
+ char *val, *val_end;
+ if (rv == nullptr) {
+ end = 1;
+ break;
+ } else if (line[0] == '\n') {
+ break;
+ }
+
+ nva.emplace_back();
+ auto &nv = nva.back();
+
+ val = strchr(line + 1, ':');
+ if (val == nullptr) {
+ fprintf(stderr, "Bad HTTP/1 header field format at %d.\n", seq);
+ exit(EXIT_FAILURE);
+ }
+ *val = '\0';
+ ++val;
+ for (; *val && (*val == ' ' || *val == '\t'); ++val)
+ ;
+ for (val_end = val; *val_end && (*val_end != '\r' && *val_end != '\n');
+ ++val_end)
+ ;
+ *val_end = '\0';
+
+ nv.namelen = strlen(line);
+ nv.valuelen = strlen(val);
+ nv.name = (uint8_t *)strdup(line);
+ nv.value = (uint8_t *)strdup(val);
+ nv.flags = NGHTTP2_NV_FLAG_NONE;
+
+ inputlen += nv.namelen + nv.valuelen;
+ }
+
+ if (!end) {
+ if (seq > 0) {
+ printf(",\n");
+ }
+ deflate_hd(deflater, nva, inputlen, seq);
+ }
+
+ for (auto &nv : nva) {
+ free(nv.name);
+ free(nv.value);
+ }
+
+ if (end)
+ break;
+ ++seq;
+ }
+ output_json_footer();
+ deinit_deflater(deflater);
+ return 0;
+}
+
+static void print_help(void) {
+ std::cout << R"(HPACK HTTP/2 header encoder
+Usage: deflatehd [OPTIONS] < INPUT
+
+Reads JSON data or HTTP/1-style header fields from stdin and outputs
+deflated header block in JSON array.
+
+For the JSON input, the root JSON object must contain "context" key,
+which indicates which compression context is used. If it is
+"request", request compression context is used. Otherwise, response
+compression context is used. The value of "cases" key contains the
+sequence of input header set. They share the same compression context
+and are processed in the order they appear. Each item in the sequence
+is a JSON object and it must have at least "headers" key. Its value
+is an array of a JSON object containing exactly one name/value pair.
+
+Example:
+{
+ "context": "request",
+ "cases":
+ [
+ {
+ "headers": [
+ { ":method": "GET" },
+ { ":path": "/" }
+ ]
+ },
+ {
+ "headers": [
+ { ":method": "POST" },
+ { ":path": "/" }
+ ]
+ }
+ ]
+}
+
+With -t option, the program can accept more familiar HTTP/1 style
+header field block. Each header set must be followed by one empty
+line:
+
+Example:
+
+:method: GET
+:scheme: https
+:path: /
+
+:method: POST
+user-agent: nghttp2
+
+The output of this program can be used as input for inflatehd.
+
+OPTIONS:
+ -t, --http1text Use HTTP/1 style header field text as input.
+ Each header set is delimited by single empty
+ line.
+ -s, --table-size=<N>
+ Set dynamic table size. In the HPACK
+ specification, this value is denoted by
+ SETTINGS_HEADER_TABLE_SIZE.
+ Default: 4096
+ -S, --deflate-table-size=<N>
+ Use first N bytes of dynamic header table
+ buffer.
+ Default: 4096
+ -d, --dump-header-table
+ Output dynamic header table.)"
+ << std::endl;
+}
+
+constexpr static struct option long_options[] = {
+ {"http1text", no_argument, nullptr, 't'},
+ {"table-size", required_argument, nullptr, 's'},
+ {"deflate-table-size", required_argument, nullptr, 'S'},
+ {"dump-header-table", no_argument, nullptr, 'd'},
+ {nullptr, 0, nullptr, 0}};
+
+int main(int argc, char **argv) {
+ config.table_size = 4_k;
+ config.deflate_table_size = 4_k;
+ config.http1text = 0;
+ config.dump_header_table = 0;
+ while (1) {
+ int option_index = 0;
+ int c = getopt_long(argc, argv, "S:dhs:t", long_options, &option_index);
+ if (c == -1) {
+ break;
+ }
+ switch (c) {
+ case 'h':
+ print_help();
+ exit(EXIT_SUCCESS);
+ case 't':
+ // --http1text
+ config.http1text = 1;
+ break;
+ case 's': {
+ // --table-size
+ auto n = util::parse_uint(optarg);
+ if (n == -1) {
+ fprintf(stderr, "-s: Bad option value\n");
+ exit(EXIT_FAILURE);
+ }
+ config.table_size = n;
+ break;
+ }
+ case 'S': {
+ // --deflate-table-size
+ auto n = util::parse_uint(optarg);
+ if (n == -1) {
+ fprintf(stderr, "-S: Bad option value\n");
+ exit(EXIT_FAILURE);
+ }
+ config.deflate_table_size = n;
+ break;
+ }
+ case 'd':
+ // --dump-header-table
+ config.dump_header_table = 1;
+ break;
+ case '?':
+ exit(EXIT_FAILURE);
+ default:
+ break;
+ }
+ }
+ if (config.http1text) {
+ perform_from_http1text();
+ } else {
+ perform();
+ }
+
+ auto comp_ratio = input_sum == 0 ? 0.0 : (double)output_sum / input_sum;
+
+ fprintf(stderr, "Overall: input=%zu output=%zu ratio=%.02f\n", input_sum,
+ output_sum, comp_ratio);
+ return 0;
+}
+
+} // namespace nghttp2
+
+int main(int argc, char **argv) {
+ return nghttp2::run_app(nghttp2::main, argc, argv);
+}
diff --git a/src/h2load.cc b/src/h2load.cc
new file mode 100644
index 0000000..0f07610
--- /dev/null
+++ b/src/h2load.cc
@@ -0,0 +1,3292 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "h2load.h"
+
+#include <getopt.h>
+#include <signal.h>
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif // HAVE_NETINET_IN_H
+#include <netinet/tcp.h>
+#include <sys/stat.h>
+#ifdef HAVE_FCNTL_H
+# include <fcntl.h>
+#endif // HAVE_FCNTL_H
+#include <sys/mman.h>
+#include <netinet/udp.h>
+
+#include <cstdio>
+#include <cassert>
+#include <cstdlib>
+#include <iostream>
+#include <iomanip>
+#include <fstream>
+#include <chrono>
+#include <thread>
+#include <future>
+#include <random>
+
+#include <openssl/err.h>
+
+#ifdef ENABLE_HTTP3
+# ifdef HAVE_LIBNGTCP2_CRYPTO_QUICTLS
+# include <ngtcp2/ngtcp2_crypto_quictls.h>
+# endif // HAVE_LIBNGTCP2_CRYPTO_QUICTLS
+# ifdef HAVE_LIBNGTCP2_CRYPTO_BORINGSSL
+# include <ngtcp2/ngtcp2_crypto_boringssl.h>
+# endif // HAVE_LIBNGTCP2_CRYPTO_BORINGSSL
+#endif // ENABLE_HTTP3
+
+#include "url-parser/url_parser.h"
+
+#include "h2load_http1_session.h"
+#include "h2load_http2_session.h"
+#ifdef ENABLE_HTTP3
+# include "h2load_http3_session.h"
+# include "h2load_quic.h"
+#endif // ENABLE_HTTP3
+#include "tls.h"
+#include "http2.h"
+#include "util.h"
+#include "template.h"
+#include "ssl_compat.h"
+
+#ifndef O_BINARY
+# define O_BINARY (0)
+#endif // O_BINARY
+
+using namespace nghttp2;
+
+namespace h2load {
+
+namespace {
+bool recorded(const std::chrono::steady_clock::time_point &t) {
+ return std::chrono::steady_clock::duration::zero() != t.time_since_epoch();
+}
+} // namespace
+
+namespace {
+std::ofstream keylog_file;
+void keylog_callback(const SSL *ssl, const char *line) {
+ keylog_file.write(line, strlen(line));
+ keylog_file.put('\n');
+ keylog_file.flush();
+}
+} // namespace
+
+Config::Config()
+ : ciphers(tls::DEFAULT_CIPHER_LIST),
+ tls13_ciphers("TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_"
+ "CHACHA20_POLY1305_SHA256:TLS_AES_128_CCM_SHA256"),
+ groups("X25519:P-256:P-384:P-521"),
+ data_length(-1),
+ data(nullptr),
+ addrs(nullptr),
+ nreqs(1),
+ nclients(1),
+ nthreads(1),
+ max_concurrent_streams(1),
+ window_bits(30),
+ connection_window_bits(30),
+ max_frame_size(16_k),
+ rate(0),
+ rate_period(1.0),
+ duration(0.0),
+ warm_up_time(0.0),
+ conn_active_timeout(0.),
+ conn_inactivity_timeout(0.),
+ no_tls_proto(PROTO_HTTP2),
+ header_table_size(4_k),
+ encoder_header_table_size(4_k),
+ data_fd(-1),
+ log_fd(-1),
+ qlog_file_base(),
+ port(0),
+ default_port(0),
+ connect_to_port(0),
+ verbose(false),
+ timing_script(false),
+ base_uri_unix(false),
+ unix_addr{},
+ rps(0.),
+ no_udp_gso(false),
+ max_udp_payload_size(0),
+ ktls(false) {}
+
+Config::~Config() {
+ if (addrs) {
+ if (base_uri_unix) {
+ delete addrs;
+ } else {
+ freeaddrinfo(addrs);
+ }
+ }
+
+ if (data_fd != -1) {
+ close(data_fd);
+ }
+}
+
+bool Config::is_rate_mode() const { return (this->rate != 0); }
+bool Config::is_timing_based_mode() const { return (this->duration > 0); }
+bool Config::has_base_uri() const { return (!this->base_uri.empty()); }
+bool Config::rps_enabled() const { return this->rps > 0.0; }
+bool Config::is_quic() const {
+#ifdef ENABLE_HTTP3
+ return !alpn_list.empty() &&
+ (alpn_list[0] == NGHTTP3_ALPN_H3 || alpn_list[0] == "\x5h3-29");
+#else // !ENABLE_HTTP3
+ return false;
+#endif // !ENABLE_HTTP3
+}
+Config config;
+
+namespace {
+constexpr size_t MAX_SAMPLES = 1000000;
+} // namespace
+
+Stats::Stats(size_t req_todo, size_t nclients)
+ : req_todo(req_todo),
+ req_started(0),
+ req_done(0),
+ req_success(0),
+ req_status_success(0),
+ req_failed(0),
+ req_error(0),
+ req_timedout(0),
+ bytes_total(0),
+ bytes_head(0),
+ bytes_head_decomp(0),
+ bytes_body(0),
+ status(),
+ udp_dgram_recv(0),
+ udp_dgram_sent(0) {}
+
+Stream::Stream() : req_stat{}, status_success(-1) {}
+
+namespace {
+std::random_device rd;
+} // namespace
+
+namespace {
+std::mt19937 gen(rd());
+} // namespace
+
+namespace {
+void sampling_init(Sampling &smp, size_t max_samples) {
+ smp.n = 0;
+ smp.max_samples = max_samples;
+}
+} // namespace
+
+namespace {
+void writecb(struct ev_loop *loop, ev_io *w, int revents) {
+ auto client = static_cast<Client *>(w->data);
+ client->restart_timeout();
+ auto rv = client->do_write();
+ if (rv == Client::ERR_CONNECT_FAIL) {
+ client->disconnect();
+ // Try next address
+ client->current_addr = nullptr;
+ rv = client->connect();
+ if (rv != 0) {
+ client->fail();
+ client->worker->free_client(client);
+ delete client;
+ return;
+ }
+ return;
+ }
+ if (rv != 0) {
+ client->fail();
+ client->worker->free_client(client);
+ delete client;
+ }
+}
+} // namespace
+
+namespace {
+void readcb(struct ev_loop *loop, ev_io *w, int revents) {
+ auto client = static_cast<Client *>(w->data);
+ client->restart_timeout();
+ if (client->do_read() != 0) {
+ if (client->try_again_or_fail() == 0) {
+ return;
+ }
+ client->worker->free_client(client);
+ delete client;
+ return;
+ }
+ client->signal_write();
+}
+} // namespace
+
+namespace {
+// Called every rate_period when rate mode is being used
+void rate_period_timeout_w_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto worker = static_cast<Worker *>(w->data);
+ auto nclients_per_second = worker->rate;
+ auto conns_remaining = worker->nclients - worker->nconns_made;
+ auto nclients = std::min(nclients_per_second, conns_remaining);
+
+ for (size_t i = 0; i < nclients; ++i) {
+ auto req_todo = worker->nreqs_per_client;
+ if (worker->nreqs_rem > 0) {
+ ++req_todo;
+ --worker->nreqs_rem;
+ }
+ auto client =
+ std::make_unique<Client>(worker->next_client_id++, worker, req_todo);
+
+ ++worker->nconns_made;
+
+ if (client->connect() != 0) {
+ std::cerr << "client could not connect to host" << std::endl;
+ client->fail();
+ } else {
+ if (worker->config->is_timing_based_mode()) {
+ worker->clients.push_back(client.release());
+ } else {
+ client.release();
+ }
+ }
+ worker->report_rate_progress();
+ }
+ if (!worker->config->is_timing_based_mode()) {
+ if (worker->nconns_made >= worker->nclients) {
+ ev_timer_stop(worker->loop, w);
+ }
+ } else {
+ // To check whether all created clients are pushed correctly
+ assert(worker->nclients == worker->clients.size());
+ }
+}
+} // namespace
+
+namespace {
+// Called when the duration for infinite number of requests are over
+void duration_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto worker = static_cast<Worker *>(w->data);
+
+ worker->current_phase = Phase::DURATION_OVER;
+
+ std::cout << "Main benchmark duration is over for thread #" << worker->id
+ << ". Stopping all clients." << std::endl;
+ worker->stop_all_clients();
+ std::cout << "Stopped all clients for thread #" << worker->id << std::endl;
+}
+} // namespace
+
+namespace {
+// Called when the warmup duration for infinite number of requests are over
+void warmup_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto worker = static_cast<Worker *>(w->data);
+
+ std::cout << "Warm-up phase is over for thread #" << worker->id << "."
+ << std::endl;
+ std::cout << "Main benchmark duration is started for thread #" << worker->id
+ << "." << std::endl;
+ assert(worker->stats.req_started == 0);
+ assert(worker->stats.req_done == 0);
+
+ for (auto client : worker->clients) {
+ if (client) {
+ assert(client->req_todo == 0);
+ assert(client->req_left == 1);
+ assert(client->req_inflight == 0);
+ assert(client->req_started == 0);
+ assert(client->req_done == 0);
+
+ client->record_client_start_time();
+ client->clear_connect_times();
+ client->record_connect_start_time();
+ }
+ }
+
+ worker->current_phase = Phase::MAIN_DURATION;
+
+ ev_timer_start(worker->loop, &worker->duration_watcher);
+}
+} // namespace
+
+namespace {
+void rps_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto client = static_cast<Client *>(w->data);
+ auto &session = client->session;
+
+ assert(!config.timing_script);
+
+ if (client->req_left == 0) {
+ ev_timer_stop(loop, w);
+ return;
+ }
+
+ auto now = std::chrono::steady_clock::now();
+ auto d = now - client->rps_duration_started;
+ auto n = static_cast<size_t>(
+ round(std::chrono::duration<double>(d).count() * config.rps));
+ client->rps_req_pending += n;
+ client->rps_duration_started +=
+ util::duration_from(static_cast<double>(n) / config.rps);
+
+ if (client->rps_req_pending == 0) {
+ return;
+ }
+
+ auto nreq = session->max_concurrent_streams() - client->rps_req_inflight;
+ if (nreq == 0) {
+ return;
+ }
+
+ nreq = config.is_timing_based_mode() ? std::max(nreq, client->req_left)
+ : std::min(nreq, client->req_left);
+ nreq = std::min(nreq, client->rps_req_pending);
+
+ client->rps_req_inflight += nreq;
+ client->rps_req_pending -= nreq;
+
+ for (; nreq > 0; --nreq) {
+ if (client->submit_request() != 0) {
+ client->process_request_failure();
+ break;
+ }
+ }
+
+ client->signal_write();
+}
+} // namespace
+
+namespace {
+// Called when an a connection has been inactive for a set period of time
+// or a fixed amount of time after all requests have been made on a
+// connection
+void conn_timeout_cb(EV_P_ ev_timer *w, int revents) {
+ auto client = static_cast<Client *>(w->data);
+
+ ev_timer_stop(client->worker->loop, &client->conn_inactivity_watcher);
+ ev_timer_stop(client->worker->loop, &client->conn_active_watcher);
+
+ if (util::check_socket_connected(client->fd)) {
+ client->timeout();
+ }
+}
+} // namespace
+
+namespace {
+bool check_stop_client_request_timeout(Client *client, ev_timer *w) {
+ if (client->req_left == 0) {
+ // no more requests to make, stop timer
+ ev_timer_stop(client->worker->loop, w);
+ return true;
+ }
+
+ return false;
+}
+} // namespace
+
+namespace {
+void client_request_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto client = static_cast<Client *>(w->data);
+
+ if (client->streams.size() >= (size_t)config.max_concurrent_streams) {
+ ev_timer_stop(client->worker->loop, w);
+ return;
+ }
+
+ if (client->submit_request() != 0) {
+ ev_timer_stop(client->worker->loop, w);
+ client->process_request_failure();
+ return;
+ }
+ client->signal_write();
+
+ if (check_stop_client_request_timeout(client, w)) {
+ return;
+ }
+
+ auto duration =
+ config.timings[client->reqidx] - config.timings[client->reqidx - 1];
+
+ while (duration < std::chrono::duration<double>(1e-9)) {
+ if (client->submit_request() != 0) {
+ ev_timer_stop(client->worker->loop, w);
+ client->process_request_failure();
+ return;
+ }
+ client->signal_write();
+ if (check_stop_client_request_timeout(client, w)) {
+ return;
+ }
+
+ duration =
+ config.timings[client->reqidx] - config.timings[client->reqidx - 1];
+ }
+
+ client->request_timeout_watcher.repeat = util::ev_tstamp_from(duration);
+ ev_timer_again(client->worker->loop, &client->request_timeout_watcher);
+}
+} // namespace
+
+Client::Client(uint32_t id, Worker *worker, size_t req_todo)
+ : wb(&worker->mcpool),
+ cstat{},
+ worker(worker),
+ ssl(nullptr),
+#ifdef ENABLE_HTTP3
+ quic{},
+#endif // ENABLE_HTTP3
+ next_addr(config.addrs),
+ current_addr(nullptr),
+ reqidx(0),
+ state(CLIENT_IDLE),
+ req_todo(req_todo),
+ req_left(req_todo),
+ req_inflight(0),
+ req_started(0),
+ req_done(0),
+ id(id),
+ fd(-1),
+ local_addr{},
+ new_connection_requested(false),
+ final(false),
+ rps_req_pending(0),
+ rps_req_inflight(0) {
+ if (req_todo == 0) { // this means infinite number of requests are to be made
+ // This ensures that number of requests are unbounded
+ // Just a positive number is fine, we chose the first positive number
+ req_left = 1;
+ }
+ ev_io_init(&wev, writecb, 0, EV_WRITE);
+ ev_io_init(&rev, readcb, 0, EV_READ);
+
+ wev.data = this;
+ rev.data = this;
+
+ ev_timer_init(&conn_inactivity_watcher, conn_timeout_cb, 0.,
+ worker->config->conn_inactivity_timeout);
+ conn_inactivity_watcher.data = this;
+
+ ev_timer_init(&conn_active_watcher, conn_timeout_cb,
+ worker->config->conn_active_timeout, 0.);
+ conn_active_watcher.data = this;
+
+ ev_timer_init(&request_timeout_watcher, client_request_timeout_cb, 0., 0.);
+ request_timeout_watcher.data = this;
+
+ ev_timer_init(&rps_watcher, rps_cb, 0., 0.);
+ rps_watcher.data = this;
+
+#ifdef ENABLE_HTTP3
+ ev_timer_init(&quic.pkt_timer, quic_pkt_timeout_cb, 0., 0.);
+ quic.pkt_timer.data = this;
+
+ if (config.is_quic()) {
+ quic.tx.data = std::make_unique<uint8_t[]>(64_k);
+ }
+
+ ngtcp2_ccerr_default(&quic.last_error);
+#endif // ENABLE_HTTP3
+}
+
+Client::~Client() {
+ disconnect();
+
+#ifdef ENABLE_HTTP3
+ if (config.is_quic()) {
+ quic_free();
+ }
+#endif // ENABLE_HTTP3
+
+ if (ssl) {
+ SSL_free(ssl);
+ }
+
+ worker->sample_client_stat(&cstat);
+ ++worker->client_smp.n;
+}
+
+int Client::do_read() { return readfn(*this); }
+int Client::do_write() { return writefn(*this); }
+
+int Client::make_socket(addrinfo *addr) {
+ int rv;
+
+ if (config.is_quic()) {
+#ifdef ENABLE_HTTP3
+ fd = util::create_nonblock_udp_socket(addr->ai_family);
+ if (fd == -1) {
+ return -1;
+ }
+
+# ifdef UDP_GRO
+ int val = 1;
+ if (setsockopt(fd, IPPROTO_UDP, UDP_GRO, &val, sizeof(val)) != 0) {
+ std::cerr << "setsockopt UDP_GRO failed" << std::endl;
+ return -1;
+ }
+# endif // UDP_GRO
+
+ rv = util::bind_any_addr_udp(fd, addr->ai_family);
+ if (rv != 0) {
+ close(fd);
+ fd = -1;
+ return -1;
+ }
+
+ socklen_t addrlen = sizeof(local_addr.su.storage);
+ rv = getsockname(fd, &local_addr.su.sa, &addrlen);
+ if (rv == -1) {
+ return -1;
+ }
+ local_addr.len = addrlen;
+
+ if (quic_init(&local_addr.su.sa, local_addr.len, addr->ai_addr,
+ addr->ai_addrlen) != 0) {
+ std::cerr << "quic_init failed" << std::endl;
+ return -1;
+ }
+#endif // ENABLE_HTTP3
+ } else {
+ fd = util::create_nonblock_socket(addr->ai_family);
+ if (fd == -1) {
+ return -1;
+ }
+ if (config.scheme == "https") {
+ if (!ssl) {
+ ssl = SSL_new(worker->ssl_ctx);
+ }
+
+ SSL_set_connect_state(ssl);
+ }
+ }
+
+ if (ssl && !util::numeric_host(config.host.c_str())) {
+ SSL_set_tlsext_host_name(ssl, config.host.c_str());
+ }
+
+ if (config.is_quic()) {
+ return 0;
+ }
+
+ rv = ::connect(fd, addr->ai_addr, addr->ai_addrlen);
+ if (rv != 0 && errno != EINPROGRESS) {
+ if (ssl) {
+ SSL_free(ssl);
+ ssl = nullptr;
+ }
+ close(fd);
+ fd = -1;
+ return -1;
+ }
+ return 0;
+}
+
+int Client::connect() {
+ int rv;
+
+ if (!worker->config->is_timing_based_mode() ||
+ worker->current_phase == Phase::MAIN_DURATION) {
+ record_client_start_time();
+ clear_connect_times();
+ record_connect_start_time();
+ } else if (worker->current_phase == Phase::INITIAL_IDLE) {
+ worker->current_phase = Phase::WARM_UP;
+ std::cout << "Warm-up started for thread #" << worker->id << "."
+ << std::endl;
+ ev_timer_start(worker->loop, &worker->warmup_watcher);
+ }
+
+ if (worker->config->conn_inactivity_timeout > 0.) {
+ ev_timer_again(worker->loop, &conn_inactivity_watcher);
+ }
+
+ if (current_addr) {
+ rv = make_socket(current_addr);
+ if (rv == -1) {
+ return -1;
+ }
+ } else {
+ addrinfo *addr = nullptr;
+ while (next_addr) {
+ addr = next_addr;
+ next_addr = next_addr->ai_next;
+ rv = make_socket(addr);
+ if (rv == 0) {
+ break;
+ }
+ }
+
+ if (fd == -1) {
+ return -1;
+ }
+
+ assert(addr);
+
+ current_addr = addr;
+ }
+
+ ev_io_set(&rev, fd, EV_READ);
+ ev_io_set(&wev, fd, EV_WRITE);
+
+ ev_io_start(worker->loop, &wev);
+
+ if (config.is_quic()) {
+#ifdef ENABLE_HTTP3
+ ev_io_start(worker->loop, &rev);
+
+ readfn = &Client::read_quic;
+ writefn = &Client::write_quic;
+#endif // ENABLE_HTTP3
+ } else {
+ writefn = &Client::connected;
+ }
+
+ return 0;
+}
+
+void Client::timeout() {
+ process_timedout_streams();
+
+ disconnect();
+}
+
+void Client::restart_timeout() {
+ if (worker->config->conn_inactivity_timeout > 0.) {
+ ev_timer_again(worker->loop, &conn_inactivity_watcher);
+ }
+}
+
+int Client::try_again_or_fail() {
+ disconnect();
+
+ if (new_connection_requested) {
+ new_connection_requested = false;
+
+ if (req_left) {
+
+ if (worker->current_phase == Phase::MAIN_DURATION) {
+ // At the moment, we don't have a facility to re-start request
+ // already in in-flight. Make them fail.
+ worker->stats.req_failed += req_inflight;
+ worker->stats.req_error += req_inflight;
+
+ req_inflight = 0;
+ }
+
+ // Keep using current address
+ if (connect() == 0) {
+ return 0;
+ }
+ std::cerr << "client could not connect to host" << std::endl;
+ }
+ }
+
+ process_abandoned_streams();
+
+ return -1;
+}
+
+void Client::fail() {
+ disconnect();
+
+ process_abandoned_streams();
+}
+
+void Client::disconnect() {
+ record_client_end_time();
+
+#ifdef ENABLE_HTTP3
+ if (config.is_quic()) {
+ quic_close_connection();
+ }
+#endif // ENABLE_HTTP3
+
+#ifdef ENABLE_HTTP3
+ ev_timer_stop(worker->loop, &quic.pkt_timer);
+#endif // ENABLE_HTTP3
+ ev_timer_stop(worker->loop, &conn_inactivity_watcher);
+ ev_timer_stop(worker->loop, &conn_active_watcher);
+ ev_timer_stop(worker->loop, &rps_watcher);
+ ev_timer_stop(worker->loop, &request_timeout_watcher);
+ streams.clear();
+ session.reset();
+ wb.reset();
+ state = CLIENT_IDLE;
+ ev_io_stop(worker->loop, &wev);
+ ev_io_stop(worker->loop, &rev);
+ if (ssl) {
+ if (config.is_quic()) {
+ SSL_free(ssl);
+ ssl = nullptr;
+ } else {
+ SSL_set_shutdown(ssl, SSL_get_shutdown(ssl) | SSL_RECEIVED_SHUTDOWN);
+ ERR_clear_error();
+
+ if (SSL_shutdown(ssl) != 1) {
+ SSL_free(ssl);
+ ssl = nullptr;
+ }
+ }
+ }
+ if (fd != -1) {
+ shutdown(fd, SHUT_WR);
+ close(fd);
+ fd = -1;
+ }
+
+ final = false;
+}
+
+int Client::submit_request() {
+ if (session->submit_request() != 0) {
+ return -1;
+ }
+
+ if (worker->current_phase != Phase::MAIN_DURATION) {
+ return 0;
+ }
+
+ ++worker->stats.req_started;
+ ++req_started;
+ ++req_inflight;
+ if (!worker->config->is_timing_based_mode()) {
+ --req_left;
+ }
+ // if an active timeout is set and this is the last request to be submitted
+ // on this connection, start the active timeout.
+ if (worker->config->conn_active_timeout > 0. && req_left == 0) {
+ ev_timer_start(worker->loop, &conn_active_watcher);
+ }
+
+ return 0;
+}
+
+void Client::process_timedout_streams() {
+ if (worker->current_phase != Phase::MAIN_DURATION) {
+ return;
+ }
+
+ for (auto &p : streams) {
+ auto &req_stat = p.second.req_stat;
+ if (!req_stat.completed) {
+ req_stat.stream_close_time = std::chrono::steady_clock::now();
+ }
+ }
+
+ worker->stats.req_timedout += req_inflight;
+
+ process_abandoned_streams();
+}
+
+void Client::process_abandoned_streams() {
+ if (worker->current_phase != Phase::MAIN_DURATION) {
+ return;
+ }
+
+ auto req_abandoned = req_inflight + req_left;
+
+ worker->stats.req_failed += req_abandoned;
+ worker->stats.req_error += req_abandoned;
+
+ req_inflight = 0;
+ req_left = 0;
+}
+
+void Client::process_request_failure() {
+ if (worker->current_phase != Phase::MAIN_DURATION) {
+ return;
+ }
+
+ worker->stats.req_failed += req_left;
+ worker->stats.req_error += req_left;
+
+ req_left = 0;
+
+ if (req_inflight == 0) {
+ terminate_session();
+ }
+ std::cout << "Process Request Failure:" << worker->stats.req_failed
+ << std::endl;
+}
+
+namespace {
+void print_server_tmp_key(SSL *ssl) {
+ EVP_PKEY *key;
+
+ if (!SSL_get_server_tmp_key(ssl, &key)) {
+ return;
+ }
+
+ auto key_del = defer(EVP_PKEY_free, key);
+
+ std::cout << "Server Temp Key: ";
+
+ auto pkey_id = EVP_PKEY_id(key);
+ switch (pkey_id) {
+ case EVP_PKEY_RSA:
+ std::cout << "RSA " << EVP_PKEY_bits(key) << " bits" << std::endl;
+ break;
+ case EVP_PKEY_DH:
+ std::cout << "DH " << EVP_PKEY_bits(key) << " bits" << std::endl;
+ break;
+ case EVP_PKEY_EC: {
+#if OPENSSL_3_0_0_API
+ std::array<char, 64> curve_name;
+ const char *cname;
+ if (!EVP_PKEY_get_utf8_string_param(key, "group", curve_name.data(),
+ curve_name.size(), nullptr)) {
+ cname = "<unknown>";
+ } else {
+ cname = curve_name.data();
+ }
+#else // !OPENSSL_3_0_0_API
+ auto ec = EVP_PKEY_get1_EC_KEY(key);
+ auto ec_del = defer(EC_KEY_free, ec);
+ auto nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec));
+ auto cname = EC_curve_nid2nist(nid);
+ if (!cname) {
+ cname = OBJ_nid2sn(nid);
+ }
+#endif // !OPENSSL_3_0_0_API
+
+ std::cout << "ECDH " << cname << " " << EVP_PKEY_bits(key) << " bits"
+ << std::endl;
+ break;
+ }
+ default:
+ std::cout << OBJ_nid2sn(pkey_id) << " " << EVP_PKEY_bits(key) << " bits"
+ << std::endl;
+ break;
+ }
+}
+} // namespace
+
+void Client::report_tls_info() {
+ if (worker->id == 0 && !worker->tls_info_report_done) {
+ worker->tls_info_report_done = true;
+ auto cipher = SSL_get_current_cipher(ssl);
+ std::cout << "TLS Protocol: " << tls::get_tls_protocol(ssl) << "\n"
+ << "Cipher: " << SSL_CIPHER_get_name(cipher) << std::endl;
+ print_server_tmp_key(ssl);
+ }
+}
+
+void Client::report_app_info() {
+ if (worker->id == 0 && !worker->app_info_report_done) {
+ worker->app_info_report_done = true;
+ std::cout << "Application protocol: " << selected_proto << std::endl;
+ }
+}
+
+void Client::terminate_session() {
+#ifdef ENABLE_HTTP3
+ if (config.is_quic()) {
+ quic.close_requested = true;
+ }
+#endif // ENABLE_HTTP3
+ if (session) {
+ session->terminate();
+ }
+ // http1 session needs writecb to tear down session.
+ signal_write();
+}
+
+void Client::on_request(int32_t stream_id) { streams[stream_id] = Stream(); }
+
+void Client::on_header(int32_t stream_id, const uint8_t *name, size_t namelen,
+ const uint8_t *value, size_t valuelen) {
+ auto itr = streams.find(stream_id);
+ if (itr == std::end(streams)) {
+ return;
+ }
+ auto &stream = (*itr).second;
+
+ if (worker->current_phase != Phase::MAIN_DURATION) {
+ // If the stream is for warm-up phase, then mark as a success
+ // But we do not update the count for 2xx, 3xx, etc status codes
+ // Same has been done in on_status_code function
+ stream.status_success = 1;
+ return;
+ }
+
+ if (stream.status_success == -1 && namelen == 7 &&
+ util::streq_l(":status", name, namelen)) {
+ int status = 0;
+ for (size_t i = 0; i < valuelen; ++i) {
+ if ('0' <= value[i] && value[i] <= '9') {
+ status *= 10;
+ status += value[i] - '0';
+ if (status > 999) {
+ stream.status_success = 0;
+ return;
+ }
+ } else {
+ break;
+ }
+ }
+
+ if (status < 200) {
+ return;
+ }
+
+ stream.req_stat.status = status;
+ if (status >= 200 && status < 300) {
+ ++worker->stats.status[2];
+ stream.status_success = 1;
+ } else if (status < 400) {
+ ++worker->stats.status[3];
+ stream.status_success = 1;
+ } else if (status < 600) {
+ ++worker->stats.status[status / 100];
+ stream.status_success = 0;
+ } else {
+ stream.status_success = 0;
+ }
+ }
+}
+
+void Client::on_status_code(int32_t stream_id, uint16_t status) {
+ auto itr = streams.find(stream_id);
+ if (itr == std::end(streams)) {
+ return;
+ }
+ auto &stream = (*itr).second;
+
+ if (worker->current_phase != Phase::MAIN_DURATION) {
+ stream.status_success = 1;
+ return;
+ }
+
+ stream.req_stat.status = status;
+ if (status >= 200 && status < 300) {
+ ++worker->stats.status[2];
+ stream.status_success = 1;
+ } else if (status < 400) {
+ ++worker->stats.status[3];
+ stream.status_success = 1;
+ } else if (status < 600) {
+ ++worker->stats.status[status / 100];
+ stream.status_success = 0;
+ } else {
+ stream.status_success = 0;
+ }
+}
+
+void Client::on_stream_close(int32_t stream_id, bool success, bool final) {
+ if (worker->current_phase == Phase::MAIN_DURATION) {
+ if (req_inflight > 0) {
+ --req_inflight;
+ }
+ auto req_stat = get_req_stat(stream_id);
+ if (!req_stat) {
+ return;
+ }
+
+ req_stat->stream_close_time = std::chrono::steady_clock::now();
+ if (success) {
+ req_stat->completed = true;
+ ++worker->stats.req_success;
+ ++cstat.req_success;
+
+ if (streams[stream_id].status_success == 1) {
+ ++worker->stats.req_status_success;
+ } else {
+ ++worker->stats.req_failed;
+ }
+
+ worker->sample_req_stat(req_stat);
+
+ // Count up in successful cases only
+ ++worker->request_times_smp.n;
+ } else {
+ ++worker->stats.req_failed;
+ ++worker->stats.req_error;
+ }
+ ++worker->stats.req_done;
+ ++req_done;
+
+ if (worker->config->log_fd != -1) {
+ auto start = std::chrono::duration_cast<std::chrono::microseconds>(
+ req_stat->request_wall_time.time_since_epoch());
+ auto delta = std::chrono::duration_cast<std::chrono::microseconds>(
+ req_stat->stream_close_time - req_stat->request_time);
+
+ std::array<uint8_t, 256> buf;
+ auto p = std::begin(buf);
+ p = util::utos(p, start.count());
+ *p++ = '\t';
+ if (success) {
+ p = util::utos(p, req_stat->status);
+ } else {
+ *p++ = '-';
+ *p++ = '1';
+ }
+ *p++ = '\t';
+ p = util::utos(p, delta.count());
+ *p++ = '\n';
+
+ auto nwrite = static_cast<size_t>(std::distance(std::begin(buf), p));
+ assert(nwrite <= buf.size());
+ while (write(worker->config->log_fd, buf.data(), nwrite) == -1 &&
+ errno == EINTR)
+ ;
+ }
+ }
+
+ worker->report_progress();
+ streams.erase(stream_id);
+ if (req_left == 0 && req_inflight == 0) {
+ terminate_session();
+ return;
+ }
+
+ if (!final && req_left > 0) {
+ if (config.timing_script) {
+ if (!ev_is_active(&request_timeout_watcher)) {
+ ev_feed_event(worker->loop, &request_timeout_watcher, EV_TIMER);
+ }
+ } else if (!config.rps_enabled()) {
+ if (submit_request() != 0) {
+ process_request_failure();
+ }
+ } else if (rps_req_pending) {
+ --rps_req_pending;
+ if (submit_request() != 0) {
+ process_request_failure();
+ }
+ } else {
+ assert(rps_req_inflight);
+ --rps_req_inflight;
+ }
+ }
+}
+
+RequestStat *Client::get_req_stat(int32_t stream_id) {
+ auto it = streams.find(stream_id);
+ if (it == std::end(streams)) {
+ return nullptr;
+ }
+
+ return &(*it).second.req_stat;
+}
+
+int Client::connection_made() {
+ if (ssl) {
+ report_tls_info();
+
+ const unsigned char *next_proto = nullptr;
+ unsigned int next_proto_len;
+
+ SSL_get0_alpn_selected(ssl, &next_proto, &next_proto_len);
+
+ if (next_proto) {
+ auto proto = StringRef{next_proto, next_proto_len};
+ if (config.is_quic()) {
+#ifdef ENABLE_HTTP3
+ assert(session);
+ if (!util::streq(StringRef{&NGHTTP3_ALPN_H3[1]}, proto) &&
+ !util::streq_l("h3-29", proto)) {
+ return -1;
+ }
+#endif // ENABLE_HTTP3
+ } else if (util::check_h2_is_selected(proto)) {
+ session = std::make_unique<Http2Session>(this);
+ } else if (util::streq(NGHTTP2_H1_1, proto)) {
+ session = std::make_unique<Http1Session>(this);
+ }
+
+ // Just assign next_proto to selected_proto anyway to show the
+ // negotiation result.
+ selected_proto = proto.str();
+ } else if (config.is_quic()) {
+ std::cerr << "QUIC requires ALPN negotiation" << std::endl;
+ return -1;
+ } else {
+ std::cout << "No protocol negotiated. Fallback behaviour may be activated"
+ << std::endl;
+
+ for (const auto &proto : config.alpn_list) {
+ if (util::streq(NGHTTP2_H1_1_ALPN, StringRef{proto})) {
+ std::cout << "Server does not support ALPN. Falling back to HTTP/1.1."
+ << std::endl;
+ session = std::make_unique<Http1Session>(this);
+ selected_proto = NGHTTP2_H1_1.str();
+ break;
+ }
+ }
+ }
+
+ if (!selected_proto.empty()) {
+ report_app_info();
+ }
+
+ if (!session) {
+ std::cout
+ << "No supported protocol was negotiated. Supported protocols were:"
+ << std::endl;
+ for (const auto &proto : config.alpn_list) {
+ std::cout << proto.substr(1) << std::endl;
+ }
+ disconnect();
+ return -1;
+ }
+ } else {
+ switch (config.no_tls_proto) {
+ case Config::PROTO_HTTP2:
+ session = std::make_unique<Http2Session>(this);
+ selected_proto = NGHTTP2_CLEARTEXT_PROTO_VERSION_ID;
+ break;
+ case Config::PROTO_HTTP1_1:
+ session = std::make_unique<Http1Session>(this);
+ selected_proto = NGHTTP2_H1_1.str();
+ break;
+ default:
+ // unreachable
+ assert(0);
+ }
+
+ report_app_info();
+ }
+
+ state = CLIENT_CONNECTED;
+
+ session->on_connect();
+
+ record_connect_time();
+
+ if (config.rps_enabled()) {
+ rps_watcher.repeat = std::max(0.01, 1. / config.rps);
+ ev_timer_again(worker->loop, &rps_watcher);
+ rps_duration_started = std::chrono::steady_clock::now();
+ }
+
+ if (config.rps_enabled()) {
+ assert(req_left);
+
+ ++rps_req_inflight;
+
+ if (submit_request() != 0) {
+ process_request_failure();
+ }
+ } else if (!config.timing_script) {
+ auto nreq = config.is_timing_based_mode()
+ ? std::max(req_left, session->max_concurrent_streams())
+ : std::min(req_left, session->max_concurrent_streams());
+
+ for (; nreq > 0; --nreq) {
+ if (submit_request() != 0) {
+ process_request_failure();
+ break;
+ }
+ }
+ } else {
+
+ auto duration = config.timings[reqidx];
+
+ while (duration < std::chrono::duration<double>(1e-9)) {
+ if (submit_request() != 0) {
+ process_request_failure();
+ break;
+ }
+ duration = config.timings[reqidx];
+ if (reqidx == 0) {
+ // if reqidx wraps around back to 0, we uses up all lines and
+ // should break
+ break;
+ }
+ }
+
+ if (duration >= std::chrono::duration<double>(1e-9)) {
+ // double check since we may have break due to reqidx wraps
+ // around back to 0
+ request_timeout_watcher.repeat = util::ev_tstamp_from(duration);
+ ev_timer_again(worker->loop, &request_timeout_watcher);
+ }
+ }
+ signal_write();
+
+ return 0;
+}
+
+int Client::on_read(const uint8_t *data, size_t len) {
+ auto rv = session->on_read(data, len);
+ if (rv != 0) {
+ return -1;
+ }
+ if (worker->current_phase == Phase::MAIN_DURATION) {
+ worker->stats.bytes_total += len;
+ }
+ signal_write();
+ return 0;
+}
+
+int Client::on_write() {
+ if (wb.rleft() >= BACKOFF_WRITE_BUFFER_THRES) {
+ return 0;
+ }
+
+ if (session->on_write() != 0) {
+ return -1;
+ }
+ return 0;
+}
+
+int Client::read_clear() {
+ uint8_t buf[8_k];
+
+ for (;;) {
+ ssize_t nread;
+ while ((nread = read(fd, buf, sizeof(buf))) == -1 && errno == EINTR)
+ ;
+ if (nread == -1) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ return 0;
+ }
+ return -1;
+ }
+
+ if (nread == 0) {
+ return -1;
+ }
+
+ if (on_read(buf, nread) != 0) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int Client::write_clear() {
+ std::array<struct iovec, 2> iov;
+
+ for (;;) {
+ if (on_write() != 0) {
+ return -1;
+ }
+
+ auto iovcnt = wb.riovec(iov.data(), iov.size());
+
+ if (iovcnt == 0) {
+ break;
+ }
+
+ ssize_t nwrite;
+ while ((nwrite = writev(fd, iov.data(), iovcnt)) == -1 && errno == EINTR)
+ ;
+
+ if (nwrite == -1) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ ev_io_start(worker->loop, &wev);
+ return 0;
+ }
+ return -1;
+ }
+
+ wb.drain(nwrite);
+ }
+
+ ev_io_stop(worker->loop, &wev);
+
+ return 0;
+}
+
+int Client::connected() {
+ if (!util::check_socket_connected(fd)) {
+ return ERR_CONNECT_FAIL;
+ }
+ ev_io_start(worker->loop, &rev);
+ ev_io_stop(worker->loop, &wev);
+
+ if (ssl) {
+ SSL_set_fd(ssl, fd);
+
+ readfn = &Client::tls_handshake;
+ writefn = &Client::tls_handshake;
+
+ return do_write();
+ }
+
+ readfn = &Client::read_clear;
+ writefn = &Client::write_clear;
+
+ if (connection_made() != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int Client::tls_handshake() {
+ ERR_clear_error();
+
+ auto rv = SSL_do_handshake(ssl);
+
+ if (rv <= 0) {
+ auto err = SSL_get_error(ssl, rv);
+ switch (err) {
+ case SSL_ERROR_WANT_READ:
+ ev_io_stop(worker->loop, &wev);
+ return 0;
+ case SSL_ERROR_WANT_WRITE:
+ ev_io_start(worker->loop, &wev);
+ return 0;
+ default:
+ return -1;
+ }
+ }
+
+ ev_io_stop(worker->loop, &wev);
+
+ readfn = &Client::read_tls;
+ writefn = &Client::write_tls;
+
+ if (connection_made() != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int Client::read_tls() {
+ uint8_t buf[8_k];
+
+ ERR_clear_error();
+
+ for (;;) {
+ auto rv = SSL_read(ssl, buf, sizeof(buf));
+
+ if (rv <= 0) {
+ auto err = SSL_get_error(ssl, rv);
+ switch (err) {
+ case SSL_ERROR_WANT_READ:
+ return 0;
+ case SSL_ERROR_WANT_WRITE:
+ // renegotiation started
+ return -1;
+ default:
+ return -1;
+ }
+ }
+
+ if (on_read(buf, rv) != 0) {
+ return -1;
+ }
+ }
+}
+
+int Client::write_tls() {
+ ERR_clear_error();
+
+ struct iovec iov;
+
+ for (;;) {
+ if (on_write() != 0) {
+ return -1;
+ }
+
+ auto iovcnt = wb.riovec(&iov, 1);
+
+ if (iovcnt == 0) {
+ break;
+ }
+
+ auto rv = SSL_write(ssl, iov.iov_base, iov.iov_len);
+
+ if (rv <= 0) {
+ auto err = SSL_get_error(ssl, rv);
+ switch (err) {
+ case SSL_ERROR_WANT_READ:
+ // renegotiation started
+ return -1;
+ case SSL_ERROR_WANT_WRITE:
+ ev_io_start(worker->loop, &wev);
+ return 0;
+ default:
+ return -1;
+ }
+ }
+
+ wb.drain(rv);
+ }
+
+ ev_io_stop(worker->loop, &wev);
+
+ return 0;
+}
+
+#ifdef ENABLE_HTTP3
+// Returns 1 if sendmsg is blocked.
+int Client::write_udp(const sockaddr *addr, socklen_t addrlen,
+ const uint8_t *data, size_t datalen, size_t gso_size) {
+ iovec msg_iov;
+ msg_iov.iov_base = const_cast<uint8_t *>(data);
+ msg_iov.iov_len = datalen;
+
+ msghdr msg{};
+ msg.msg_name = const_cast<sockaddr *>(addr);
+ msg.msg_namelen = addrlen;
+ msg.msg_iov = &msg_iov;
+ msg.msg_iovlen = 1;
+
+# ifdef UDP_SEGMENT
+ std::array<uint8_t, CMSG_SPACE(sizeof(uint16_t))> msg_ctrl{};
+ if (gso_size && datalen > gso_size) {
+ msg.msg_control = msg_ctrl.data();
+ msg.msg_controllen = msg_ctrl.size();
+
+ auto cm = CMSG_FIRSTHDR(&msg);
+ cm->cmsg_level = SOL_UDP;
+ cm->cmsg_type = UDP_SEGMENT;
+ cm->cmsg_len = CMSG_LEN(sizeof(uint16_t));
+ uint16_t n = gso_size;
+ memcpy(CMSG_DATA(cm), &n, sizeof(n));
+ }
+# endif // UDP_SEGMENT
+
+ auto nwrite = sendmsg(fd, &msg, 0);
+ if (nwrite < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ return 1;
+ }
+
+ std::cerr << "sendmsg: errno=" << errno << std::endl;
+ } else {
+ ++worker->stats.udp_dgram_sent;
+ }
+
+ ev_io_stop(worker->loop, &wev);
+
+ return 0;
+}
+#endif // ENABLE_HTTP3
+
+void Client::record_request_time(RequestStat *req_stat) {
+ req_stat->request_time = std::chrono::steady_clock::now();
+ req_stat->request_wall_time = std::chrono::system_clock::now();
+}
+
+void Client::record_connect_start_time() {
+ cstat.connect_start_time = std::chrono::steady_clock::now();
+}
+
+void Client::record_connect_time() {
+ cstat.connect_time = std::chrono::steady_clock::now();
+}
+
+void Client::record_ttfb() {
+ if (recorded(cstat.ttfb)) {
+ return;
+ }
+
+ cstat.ttfb = std::chrono::steady_clock::now();
+}
+
+void Client::clear_connect_times() {
+ cstat.connect_start_time = std::chrono::steady_clock::time_point();
+ cstat.connect_time = std::chrono::steady_clock::time_point();
+ cstat.ttfb = std::chrono::steady_clock::time_point();
+}
+
+void Client::record_client_start_time() {
+ // Record start time only once at the very first connection is going
+ // to be made.
+ if (recorded(cstat.client_start_time)) {
+ return;
+ }
+
+ cstat.client_start_time = std::chrono::steady_clock::now();
+}
+
+void Client::record_client_end_time() {
+ // Unlike client_start_time, we overwrite client_end_time. This
+ // handles multiple connect/disconnect for HTTP/1.1 benchmark.
+ cstat.client_end_time = std::chrono::steady_clock::now();
+}
+
+void Client::signal_write() { ev_io_start(worker->loop, &wev); }
+
+void Client::try_new_connection() { new_connection_requested = true; }
+
+namespace {
+int get_ev_loop_flags() {
+ if (ev_supported_backends() & ~ev_recommended_backends() & EVBACKEND_KQUEUE) {
+ return ev_recommended_backends() | EVBACKEND_KQUEUE;
+ }
+
+ return 0;
+}
+} // namespace
+
+Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients,
+ size_t rate, size_t max_samples, Config *config)
+ : randgen(util::make_mt19937()),
+ stats(req_todo, nclients),
+ loop(ev_loop_new(get_ev_loop_flags())),
+ ssl_ctx(ssl_ctx),
+ config(config),
+ id(id),
+ tls_info_report_done(false),
+ app_info_report_done(false),
+ nconns_made(0),
+ nclients(nclients),
+ nreqs_per_client(req_todo / nclients),
+ nreqs_rem(req_todo % nclients),
+ rate(rate),
+ max_samples(max_samples),
+ next_client_id(0) {
+ if (!config->is_rate_mode() && !config->is_timing_based_mode()) {
+ progress_interval = std::max(static_cast<size_t>(1), req_todo / 10);
+ } else {
+ progress_interval = std::max(static_cast<size_t>(1), nclients / 10);
+ }
+
+ // Below timeout is not needed in case of timing-based benchmarking
+ // create timer that will go off every rate_period
+ ev_timer_init(&timeout_watcher, rate_period_timeout_w_cb, 0.,
+ config->rate_period);
+ timeout_watcher.data = this;
+
+ if (config->is_timing_based_mode()) {
+ stats.req_stats.reserve(std::max(req_todo, max_samples));
+ stats.client_stats.reserve(std::max(nclients, max_samples));
+ } else {
+ stats.req_stats.reserve(std::min(req_todo, max_samples));
+ stats.client_stats.reserve(std::min(nclients, max_samples));
+ }
+
+ sampling_init(request_times_smp, max_samples);
+ sampling_init(client_smp, max_samples);
+
+ ev_timer_init(&duration_watcher, duration_timeout_cb, config->duration, 0.);
+ duration_watcher.data = this;
+
+ ev_timer_init(&warmup_watcher, warmup_timeout_cb, config->warm_up_time, 0.);
+ warmup_watcher.data = this;
+
+ if (config->is_timing_based_mode()) {
+ current_phase = Phase::INITIAL_IDLE;
+ } else {
+ current_phase = Phase::MAIN_DURATION;
+ }
+}
+
+Worker::~Worker() {
+ ev_timer_stop(loop, &timeout_watcher);
+ ev_timer_stop(loop, &duration_watcher);
+ ev_timer_stop(loop, &warmup_watcher);
+ ev_loop_destroy(loop);
+}
+
+void Worker::stop_all_clients() {
+ for (auto client : clients) {
+ if (client) {
+ client->terminate_session();
+ }
+ }
+}
+
+void Worker::free_client(Client *deleted_client) {
+ for (auto &client : clients) {
+ if (client == deleted_client) {
+ client->req_todo = client->req_done;
+ stats.req_todo += client->req_todo;
+ auto index = &client - &clients[0];
+ clients[index] = nullptr;
+ return;
+ }
+ }
+}
+
+void Worker::run() {
+ if (!config->is_rate_mode() && !config->is_timing_based_mode()) {
+ for (size_t i = 0; i < nclients; ++i) {
+ auto req_todo = nreqs_per_client;
+ if (nreqs_rem > 0) {
+ ++req_todo;
+ --nreqs_rem;
+ }
+
+ auto client = std::make_unique<Client>(next_client_id++, this, req_todo);
+ if (client->connect() != 0) {
+ std::cerr << "client could not connect to host" << std::endl;
+ client->fail();
+ } else {
+ client.release();
+ }
+ }
+ } else if (config->is_rate_mode()) {
+ ev_timer_again(loop, &timeout_watcher);
+
+ // call callback so that we don't waste the first rate_period
+ rate_period_timeout_w_cb(loop, &timeout_watcher, 0);
+ } else {
+ // call the callback to start for one single time
+ rate_period_timeout_w_cb(loop, &timeout_watcher, 0);
+ }
+ ev_run(loop, 0);
+}
+
+namespace {
+template <typename Stats, typename Stat>
+void sample(Sampling &smp, Stats &stats, Stat *s) {
+ ++smp.n;
+ if (stats.size() < smp.max_samples) {
+ stats.push_back(*s);
+ return;
+ }
+ auto d = std::uniform_int_distribution<unsigned long>(0, smp.n - 1);
+ auto i = d(gen);
+ if (i < smp.max_samples) {
+ stats[i] = *s;
+ }
+}
+} // namespace
+
+void Worker::sample_req_stat(RequestStat *req_stat) {
+ sample(request_times_smp, stats.req_stats, req_stat);
+}
+
+void Worker::sample_client_stat(ClientStat *cstat) {
+ sample(client_smp, stats.client_stats, cstat);
+}
+
+void Worker::report_progress() {
+ if (id != 0 || config->is_rate_mode() || stats.req_done % progress_interval ||
+ config->is_timing_based_mode()) {
+ return;
+ }
+
+ std::cout << "progress: " << stats.req_done * 100 / stats.req_todo << "% done"
+ << std::endl;
+}
+
+void Worker::report_rate_progress() {
+ if (id != 0 || nconns_made % progress_interval) {
+ return;
+ }
+
+ std::cout << "progress: " << nconns_made * 100 / nclients
+ << "% of clients started" << std::endl;
+}
+
+namespace {
+// Returns percentage of number of samples within mean +/- sd.
+double within_sd(const std::vector<double> &samples, double mean, double sd) {
+ if (samples.size() == 0) {
+ return 0.0;
+ }
+ auto lower = mean - sd;
+ auto upper = mean + sd;
+ auto m = std::count_if(
+ std::begin(samples), std::end(samples),
+ [&lower, &upper](double t) { return lower <= t && t <= upper; });
+ return (m / static_cast<double>(samples.size())) * 100;
+}
+} // namespace
+
+namespace {
+// Computes statistics using |samples|. The min, max, mean, sd, and
+// percentage of number of samples within mean +/- sd are computed.
+// If |sampling| is true, this computes sample variance. Otherwise,
+// population variance.
+SDStat compute_time_stat(const std::vector<double> &samples,
+ bool sampling = false) {
+ if (samples.empty()) {
+ return {0.0, 0.0, 0.0, 0.0, 0.0};
+ }
+ // standard deviation calculated using Rapid calculation method:
+ // https://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods
+ double a = 0, q = 0;
+ size_t n = 0;
+ double sum = 0;
+ auto res = SDStat{std::numeric_limits<double>::max(),
+ std::numeric_limits<double>::min()};
+ for (const auto &t : samples) {
+ ++n;
+ res.min = std::min(res.min, t);
+ res.max = std::max(res.max, t);
+ sum += t;
+
+ auto na = a + (t - a) / n;
+ q += (t - a) * (t - na);
+ a = na;
+ }
+
+ assert(n > 0);
+ res.mean = sum / n;
+ res.sd = sqrt(q / (sampling && n > 1 ? n - 1 : n));
+ res.within_sd = within_sd(samples, res.mean, res.sd);
+
+ return res;
+}
+} // namespace
+
+namespace {
+SDStats
+process_time_stats(const std::vector<std::unique_ptr<Worker>> &workers) {
+ auto request_times_sampling = false;
+ auto client_times_sampling = false;
+ size_t nrequest_times = 0;
+ size_t nclient_times = 0;
+ for (const auto &w : workers) {
+ nrequest_times += w->stats.req_stats.size();
+ request_times_sampling = w->request_times_smp.n > w->stats.req_stats.size();
+
+ nclient_times += w->stats.client_stats.size();
+ client_times_sampling = w->client_smp.n > w->stats.client_stats.size();
+ }
+
+ std::vector<double> request_times;
+ request_times.reserve(nrequest_times);
+
+ std::vector<double> connect_times, ttfb_times, rps_values;
+ connect_times.reserve(nclient_times);
+ ttfb_times.reserve(nclient_times);
+ rps_values.reserve(nclient_times);
+
+ for (const auto &w : workers) {
+ for (const auto &req_stat : w->stats.req_stats) {
+ if (!req_stat.completed) {
+ continue;
+ }
+ request_times.push_back(
+ std::chrono::duration_cast<std::chrono::duration<double>>(
+ req_stat.stream_close_time - req_stat.request_time)
+ .count());
+ }
+
+ const auto &stat = w->stats;
+
+ for (const auto &cstat : stat.client_stats) {
+ if (recorded(cstat.client_start_time) &&
+ recorded(cstat.client_end_time)) {
+ auto t = std::chrono::duration_cast<std::chrono::duration<double>>(
+ cstat.client_end_time - cstat.client_start_time)
+ .count();
+ if (t > 1e-9) {
+ rps_values.push_back(cstat.req_success / t);
+ }
+ }
+
+ // We will get connect event before FFTB.
+ if (!recorded(cstat.connect_start_time) ||
+ !recorded(cstat.connect_time)) {
+ continue;
+ }
+
+ connect_times.push_back(
+ std::chrono::duration_cast<std::chrono::duration<double>>(
+ cstat.connect_time - cstat.connect_start_time)
+ .count());
+
+ if (!recorded(cstat.ttfb)) {
+ continue;
+ }
+
+ ttfb_times.push_back(
+ std::chrono::duration_cast<std::chrono::duration<double>>(
+ cstat.ttfb - cstat.connect_start_time)
+ .count());
+ }
+ }
+
+ return {compute_time_stat(request_times, request_times_sampling),
+ compute_time_stat(connect_times, client_times_sampling),
+ compute_time_stat(ttfb_times, client_times_sampling),
+ compute_time_stat(rps_values, client_times_sampling)};
+}
+} // namespace
+
+namespace {
+void resolve_host() {
+ if (config.base_uri_unix) {
+ auto res = std::make_unique<addrinfo>();
+ res->ai_family = config.unix_addr.sun_family;
+ res->ai_socktype = SOCK_STREAM;
+ res->ai_addrlen = sizeof(config.unix_addr);
+ res->ai_addr =
+ static_cast<struct sockaddr *>(static_cast<void *>(&config.unix_addr));
+
+ config.addrs = res.release();
+ return;
+ };
+
+ int rv;
+ addrinfo hints{}, *res;
+
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = 0;
+ hints.ai_flags = AI_ADDRCONFIG;
+
+ const auto &resolve_host =
+ config.connect_to_host.empty() ? config.host : config.connect_to_host;
+ auto port =
+ config.connect_to_port == 0 ? config.port : config.connect_to_port;
+
+ rv =
+ getaddrinfo(resolve_host.c_str(), util::utos(port).c_str(), &hints, &res);
+ if (rv != 0) {
+ std::cerr << "getaddrinfo() failed: " << gai_strerror(rv) << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ if (res == nullptr) {
+ std::cerr << "No address returned" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.addrs = res;
+}
+} // namespace
+
+namespace {
+std::string get_reqline(const char *uri, const http_parser_url &u) {
+ std::string reqline;
+
+ if (util::has_uri_field(u, UF_PATH)) {
+ reqline = util::get_uri_field(uri, u, UF_PATH).str();
+ } else {
+ reqline = "/";
+ }
+
+ if (util::has_uri_field(u, UF_QUERY)) {
+ reqline += '?';
+ reqline += util::get_uri_field(uri, u, UF_QUERY);
+ }
+
+ return reqline;
+}
+} // namespace
+
+namespace {
+constexpr char UNIX_PATH_PREFIX[] = "unix:";
+} // namespace
+
+namespace {
+bool parse_base_uri(const StringRef &base_uri) {
+ http_parser_url u{};
+ if (http_parser_parse_url(base_uri.c_str(), base_uri.size(), 0, &u) != 0 ||
+ !util::has_uri_field(u, UF_SCHEMA) || !util::has_uri_field(u, UF_HOST)) {
+ return false;
+ }
+
+ config.scheme = util::get_uri_field(base_uri.c_str(), u, UF_SCHEMA).str();
+ config.host = util::get_uri_field(base_uri.c_str(), u, UF_HOST).str();
+ config.default_port = util::get_default_port(base_uri.c_str(), u);
+ if (util::has_uri_field(u, UF_PORT)) {
+ config.port = u.port;
+ } else {
+ config.port = config.default_port;
+ }
+
+ return true;
+}
+} // namespace
+namespace {
+// 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;
+
+ if (first == last) {
+ std::cerr << "no URI available" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ if (!config.has_base_uri()) {
+
+ if (!parse_base_uri(StringRef{*first})) {
+ std::cerr << "invalid URI: " << *first << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ config.base_uri = *first;
+ }
+
+ for (; first != last; ++first) {
+ http_parser_url u{};
+
+ auto uri = (*first).c_str();
+
+ if (http_parser_parse_url(uri, (*first).size(), 0, &u) != 0) {
+ std::cerr << "invalid URI: " << uri << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ reqlines.push_back(get_reqline(uri, u));
+ }
+
+ return reqlines;
+}
+} // namespace
+
+namespace {
+std::vector<std::string> read_uri_from_file(std::istream &infile) {
+ std::vector<std::string> uris;
+ std::string line_uri;
+ while (std::getline(infile, line_uri)) {
+ uris.push_back(line_uri);
+ }
+
+ return uris;
+}
+} // namespace
+
+namespace {
+void read_script_from_file(
+ std::istream &infile,
+ std::vector<std::chrono::steady_clock::duration> &timings,
+ std::vector<std::string> &uris) {
+ std::string script_line;
+ int line_count = 0;
+ while (std::getline(infile, script_line)) {
+ line_count++;
+ if (script_line.empty()) {
+ std::cerr << "Empty line detected at line " << line_count
+ << ". Ignoring and continuing." << std::endl;
+ continue;
+ }
+
+ std::size_t pos = script_line.find("\t");
+ if (pos == std::string::npos) {
+ std::cerr << "Invalid line format detected, no tab character at line "
+ << line_count << ". \n\t" << script_line << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ const char *start = script_line.c_str();
+ char *end;
+ auto v = std::strtod(start, &end);
+
+ errno = 0;
+ if (v < 0.0 || !std::isfinite(v) || end == start || errno != 0) {
+ auto error = errno;
+ std::cerr << "Time value error at line " << line_count << ". \n\t"
+ << "value = " << script_line.substr(0, pos) << std::endl;
+ if (error != 0) {
+ std::cerr << "\t" << strerror(error) << std::endl;
+ }
+ exit(EXIT_FAILURE);
+ }
+
+ timings.emplace_back(
+ std::chrono::duration_cast<std::chrono::steady_clock::duration>(
+ std::chrono::duration<double, std::milli>(v)));
+ uris.push_back(script_line.substr(pos + 1, script_line.size()));
+ }
+}
+} // namespace
+
+namespace {
+std::unique_ptr<Worker> create_worker(uint32_t id, SSL_CTX *ssl_ctx,
+ size_t nreqs, size_t nclients,
+ size_t rate, size_t max_samples) {
+ std::stringstream rate_report;
+ if (config.is_rate_mode() && nclients > rate) {
+ rate_report << "Up to " << rate << " client(s) will be created every "
+ << util::duration_str(config.rate_period) << " ";
+ }
+
+ if (config.is_timing_based_mode()) {
+ std::cout << "spawning thread #" << id << ": " << nclients
+ << " total client(s). Timing-based test with "
+ << config.warm_up_time << "s of warm-up time and "
+ << config.duration << "s of main duration for measurements."
+ << std::endl;
+ } else {
+ std::cout << "spawning thread #" << id << ": " << nclients
+ << " total client(s). " << rate_report.str() << nreqs
+ << " total requests" << std::endl;
+ }
+
+ if (config.is_rate_mode()) {
+ return std::make_unique<Worker>(id, ssl_ctx, nreqs, nclients, rate,
+ max_samples, &config);
+ } else {
+ // Here rate is same as client because the rate_timeout callback
+ // will be called only once
+ return std::make_unique<Worker>(id, ssl_ctx, nreqs, nclients, nclients,
+ max_samples, &config);
+ }
+}
+} // namespace
+
+namespace {
+int parse_header_table_size(uint32_t &dst, const char *opt,
+ const char *optarg) {
+ auto n = util::parse_uint_with_unit(optarg);
+ if (n == -1) {
+ std::cerr << "--" << opt << ": Bad option value: " << optarg << std::endl;
+ return -1;
+ }
+ if (n > std::numeric_limits<uint32_t>::max()) {
+ std::cerr << "--" << opt
+ << ": Value too large. It should be less than or equal to "
+ << std::numeric_limits<uint32_t>::max() << std::endl;
+ return -1;
+ }
+
+ dst = n;
+
+ return 0;
+}
+} // namespace
+
+namespace {
+std::string make_http_authority(const Config &config) {
+ std::string host;
+
+ if (util::numeric_host(config.host.c_str(), AF_INET6)) {
+ host += '[';
+ host += config.host;
+ host += ']';
+ } else {
+ host = config.host;
+ }
+
+ if (config.port != config.default_port) {
+ host += ':';
+ host += util::utos(config.port);
+ }
+
+ return host;
+}
+} // namespace
+
+namespace {
+void print_version(std::ostream &out) {
+ out << "h2load nghttp2/" NGHTTP2_VERSION << std::endl;
+}
+} // namespace
+
+namespace {
+void print_usage(std::ostream &out) {
+ out << R"(Usage: h2load [OPTIONS]... [URI]...
+benchmarking tool for HTTP/2 server)"
+ << std::endl;
+}
+} // namespace
+
+namespace {
+constexpr char DEFAULT_ALPN_LIST[] = "h2,h2-16,h2-14,http/1.1";
+} // namespace
+
+namespace {
+void print_help(std::ostream &out) {
+ print_usage(out);
+
+ auto config = Config();
+
+ out << R"(
+ <URI> Specify URI to access. Multiple URIs can be specified.
+ 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
+ URIs, if present, are ignored. Those in the first URI
+ are used solely. Definition of a base URI overrides all
+ scheme, host or port values.
+Options:
+ -n, --requests=<N>
+ Number of requests across all clients. If it is used
+ with --timing-script-file option, this option specifies
+ the number of requests each client performs rather than
+ the number of requests across all clients. This option
+ is ignored if timing-based benchmarking is enabled (see
+ --duration option).
+ Default: )"
+ << config.nreqs << R"(
+ -c, --clients=<N>
+ Number of concurrent clients. With -r option, this
+ specifies the maximum number of connections to be made.
+ Default: )"
+ << config.nclients << R"(
+ -t, --threads=<N>
+ Number of native threads.
+ Default: )"
+ << config.nthreads << R"(
+ -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 <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
+ URIs, if present, are ignored. Those in the first URI
+ are used solely. Definition of a base URI overrides all
+ scheme, host or port values.
+ -m, --max-concurrent-streams=<N>
+ Max concurrent streams to issue per session. When
+ http/1.1 is used, this specifies the number of HTTP
+ pipelining requests in-flight.
+ Default: 1
+ -f, --max-frame-size=<SIZE>
+ Maximum frame size that the local endpoint is willing to
+ receive.
+ Default: )"
+ << util::utos_unit(config.max_frame_size) << R"(
+ -w, --window-bits=<N>
+ Sets the stream level initial window size to (2**<N>)-1.
+ For QUIC, <N> is capped to 26 (roughly 64MiB).
+ Default: )"
+ << config.window_bits << R"(
+ -W, --connection-window-bits=<N>
+ Sets the connection level initial window size to
+ (2**<N>)-1.
+ Default: )"
+ << config.connection_window_bits << R"(
+ -H, --header=<HEADER>
+ Add/Override a header to the requests.
+ --ciphers=<SUITE>
+ Set allowed cipher list for TLSv1.2 or earlier. The
+ format of the string is described in OpenSSL ciphers(1).
+ Default: )"
+ << config.ciphers << R"(
+ --tls13-ciphers=<SUITE>
+ Set allowed cipher list for TLSv1.3. The format of the
+ string is described in OpenSSL ciphers(1).
+ Default: )"
+ << config.tls13_ciphers << R"(
+ -p, --no-tls-proto=<PROTOID>
+ Specify ALPN identifier of the protocol to be used when
+ accessing http URI without SSL/TLS.
+ Available protocols: )"
+ << NGHTTP2_CLEARTEXT_PROTO_VERSION_ID << R"( and )" << NGHTTP2_H1_1 << R"(
+ Default: )"
+ << NGHTTP2_CLEARTEXT_PROTO_VERSION_ID << R"(
+ -d, --data=<PATH>
+ Post FILE to server. The request method is changed to
+ POST. For http/1.1 connection, if -d is used, the
+ maximum number of in-flight pipelined requests is set to
+ 1.
+ -r, --rate=<N>
+ Specifies the fixed rate at which connections are
+ created. The rate must be a positive integer,
+ representing the number of connections to be made per
+ rate period. The maximum number of connections to be
+ made is given in -c option. This rate will be
+ distributed among threads as evenly as possible. For
+ example, with -t2 and -r4, each thread gets 2
+ connections per period. 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. -r and -D are mutually exclusive.
+ --rate-period=<DURATION>
+ Specifies the time period between creating connections.
+ The period must be a positive number, representing the
+ length of the period in time. This option is ignored if
+ the rate option is not used. The default value for this
+ option is 1s.
+ -D, --duration=<DURATION>
+ Specifies the main duration for the measurements in case
+ of timing-based benchmarking. -D and -r are mutually
+ exclusive.
+ --warm-up-time=<DURATION>
+ Specifies the time period before starting the actual
+ measurements, in case of timing-based benchmarking.
+ Needs to provided along with -D option.
+ -T, --connection-active-timeout=<DURATION>
+ Specifies the maximum time that h2load is willing to
+ keep a connection open, regardless of the activity on
+ said connection. <DURATION> must be a positive integer,
+ specifying the amount of time to wait. When no timeout
+ value is set (either active or inactive), h2load will
+ keep a connection open indefinitely, waiting for a
+ response.
+ -N, --connection-inactivity-timeout=<DURATION>
+ Specifies the amount of time that h2load is willing to
+ wait to see activity on a given connection. <DURATION>
+ must be a positive integer, specifying the amount of
+ time to wait. When no timeout value is set (either
+ active or inactive), h2load will keep a connection open
+ indefinitely, waiting for a response.
+ --timing-script-file=<PATH>
+ Path of a file containing one or more lines separated by
+ EOLs. Each script line is composed of two tab-separated
+ fields. The first field represents the time offset from
+ the start of execution, expressed as a positive value of
+ milliseconds with microsecond resolution. The second
+ field represents the URI. This option will disable URIs
+ getting from command-line. If '-' is given as <PATH>,
+ script lines will be read from stdin. Script lines are
+ used in order for each client. If -n is given, it must
+ be less than or equal to the number of script lines,
+ larger values are clamped to the number of script lines.
+ If -n is not given, the number of requests will default
+ to the number of script lines. The scheme, host and
+ port defined in the first URI are used solely. Values
+ contained in other URIs, if present, are ignored.
+ Definition of a base URI overrides all scheme, host or
+ port values. --timing-script-file and --rps are
+ mutually exclusive.
+ -B, --base-uri=(<URI>|unix:<PATH>)
+ Specify URI from which the scheme, host and port will be
+ used for all requests. The base URI overrides all
+ values defined either at the command line or inside
+ input files. If argument starts with "unix:", then the
+ rest of the argument will be treated as UNIX domain
+ socket path. The connection is made through that path
+ instead of TCP. In this case, scheme is inferred from
+ the first URI appeared in the command line or inside
+ input files as usual.
+ --alpn-list=<LIST>
+ Comma delimited list of ALPN protocol identifier sorted
+ in the order of preference. That means most desirable
+ protocol comes first. The parameter must be delimited
+ by a single comma only and any white spaces are treated
+ as a part of protocol string.
+ Default: )"
+ << DEFAULT_ALPN_LIST << R"(
+ --h1 Short hand for --alpn-list=http/1.1
+ --no-tls-proto=http/1.1, which effectively force
+ http/1.1 for both http and https URI.
+ --header-table-size=<SIZE>
+ Specify decoder header table size.
+ Default: )"
+ << util::utos_unit(config.header_table_size) << R"(
+ --encoder-header-table-size=<SIZE>
+ Specify encoder header table size. The decoder (server)
+ specifies the maximum dynamic table size it accepts.
+ Then the negotiated dynamic table size is the minimum of
+ this option value and the value which server specified.
+ Default: )"
+ << util::utos_unit(config.encoder_header_table_size) << R"(
+ --log-file=<PATH>
+ Write per-request information to a file as tab-separated
+ columns: start time as microseconds since epoch; HTTP
+ status code; microseconds until end of response. More
+ columns may be added later. Rows are ordered by end-of-
+ response time when using one worker thread, but may
+ appear slightly out of order with multiple threads due
+ to buffering. Status code is -1 for failed streams.
+ --qlog-file-base=<PATH>
+ Enable qlog output and specify base file name for qlogs.
+ Qlog is emitted for each connection. For a given base
+ name "base", each output file name becomes
+ "base.M.N.sqlog" where M is worker ID and N is client ID
+ (e.g. "base.0.3.sqlog"). Only effective in QUIC runs.
+ --connect-to=<HOST>[:<PORT>]
+ Host and port to connect instead of using the authority
+ in <URI>.
+ --rps=<N> Specify request per second for each client. --rps and
+ --timing-script-file are mutually exclusive.
+ --groups=<GROUPS>
+ Specify the supported groups.
+ Default: )"
+ << config.groups << R"(
+ --no-udp-gso
+ Disable UDP GSO.
+ --max-udp-payload-size=<SIZE>
+ Specify the maximum outgoing UDP datagram payload size.
+ --ktls Enable ktls.
+ -v, --verbose
+ Output debug information.
+ --version Display version information and exit.
+ -h, --help Display this help and exit.
+
+--
+
+ The <SIZE> argument is an integer and an optional unit (e.g., 10K is
+ 10 * 1024). Units are K, M and G (powers of 1024).
+
+ The <DURATION> argument is an integer and an optional unit (e.g., 1s
+ is 1 second and 500ms is 500 milliseconds). Units are h, m, s or ms
+ (hours, minutes, seconds and milliseconds, respectively). If a unit
+ is omitted, a second is used as unit.)"
+ << std::endl;
+}
+} // namespace
+
+int main(int argc, char **argv) {
+ std::string datafile;
+ std::string logfile;
+ bool nreqs_set_manually = false;
+ while (1) {
+ static int flag = 0;
+ constexpr static option long_options[] = {
+ {"requests", required_argument, nullptr, 'n'},
+ {"clients", required_argument, nullptr, 'c'},
+ {"data", required_argument, nullptr, 'd'},
+ {"threads", required_argument, nullptr, 't'},
+ {"max-concurrent-streams", required_argument, nullptr, 'm'},
+ {"window-bits", required_argument, nullptr, 'w'},
+ {"max-frame-size", required_argument, nullptr, 'f'},
+ {"connection-window-bits", required_argument, nullptr, 'W'},
+ {"input-file", required_argument, nullptr, 'i'},
+ {"header", required_argument, nullptr, 'H'},
+ {"no-tls-proto", required_argument, nullptr, 'p'},
+ {"verbose", no_argument, nullptr, 'v'},
+ {"help", no_argument, nullptr, 'h'},
+ {"version", no_argument, &flag, 1},
+ {"ciphers", required_argument, &flag, 2},
+ {"rate", required_argument, nullptr, 'r'},
+ {"connection-active-timeout", required_argument, nullptr, 'T'},
+ {"connection-inactivity-timeout", required_argument, nullptr, 'N'},
+ {"duration", required_argument, nullptr, 'D'},
+ {"timing-script-file", required_argument, &flag, 3},
+ {"base-uri", required_argument, nullptr, 'B'},
+ {"npn-list", required_argument, &flag, 4},
+ {"rate-period", required_argument, &flag, 5},
+ {"h1", no_argument, &flag, 6},
+ {"header-table-size", required_argument, &flag, 7},
+ {"encoder-header-table-size", required_argument, &flag, 8},
+ {"warm-up-time", required_argument, &flag, 9},
+ {"log-file", required_argument, &flag, 10},
+ {"connect-to", required_argument, &flag, 11},
+ {"rps", required_argument, &flag, 12},
+ {"groups", required_argument, &flag, 13},
+ {"tls13-ciphers", required_argument, &flag, 14},
+ {"no-udp-gso", no_argument, &flag, 15},
+ {"qlog-file-base", required_argument, &flag, 16},
+ {"max-udp-payload-size", required_argument, &flag, 17},
+ {"ktls", no_argument, &flag, 18},
+ {"alpn-list", required_argument, &flag, 19},
+ {nullptr, 0, nullptr, 0}};
+ int option_index = 0;
+ auto c = getopt_long(argc, argv,
+ "hvW:c:d:m:n:p:t:w:f:H:i:r:T:N:D:B:", long_options,
+ &option_index);
+ if (c == -1) {
+ break;
+ }
+ switch (c) {
+ case 'n': {
+ auto n = util::parse_uint(optarg);
+ if (n == -1) {
+ std::cerr << "-n: bad option value: " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.nreqs = n;
+ nreqs_set_manually = true;
+ break;
+ }
+ case 'c': {
+ auto n = util::parse_uint(optarg);
+ if (n == -1) {
+ std::cerr << "-c: bad option value: " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.nclients = n;
+ break;
+ }
+ case 'd':
+ datafile = optarg;
+ break;
+ case 't': {
+#ifdef NOTHREADS
+ std::cerr << "-t: WARNING: Threading disabled at build time, "
+ << "no threads created." << std::endl;
+#else
+ auto n = util::parse_uint(optarg);
+ if (n == -1) {
+ std::cerr << "-t: bad option value: " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.nthreads = n;
+#endif // NOTHREADS
+ break;
+ }
+ case 'm': {
+ auto n = util::parse_uint(optarg);
+ if (n == -1) {
+ std::cerr << "-m: bad option value: " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.max_concurrent_streams = n;
+ break;
+ }
+ case 'w':
+ case 'W': {
+ auto n = util::parse_uint(optarg);
+ if (n == -1 || n > 30) {
+ std::cerr << "-" << static_cast<char>(c)
+ << ": specify the integer in the range [0, 30], inclusive"
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ if (c == 'w') {
+ config.window_bits = n;
+ } else {
+ config.connection_window_bits = n;
+ }
+ break;
+ }
+ case 'f': {
+ auto n = util::parse_uint_with_unit(optarg);
+ if (n == -1) {
+ std::cerr << "--max-frame-size: bad option value: " << optarg
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ if (static_cast<uint64_t>(n) < 16_k) {
+ std::cerr << "--max-frame-size: minimum 16384" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ if (static_cast<uint64_t>(n) > 16_m - 1) {
+ std::cerr << "--max-frame-size: maximum 16777215" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.max_frame_size = n;
+ break;
+ }
+ case 'H': {
+ char *header = optarg;
+ // Skip first possible ':' in the header name
+ char *value = strchr(optarg + 1, ':');
+ if (!value || (header[0] == ':' && header + 1 == value)) {
+ std::cerr << "-H: invalid header: " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ *value = 0;
+ value++;
+ while (isspace(*value)) {
+ value++;
+ }
+ if (*value == 0) {
+ // This could also be a valid case for suppressing a header
+ // similar to curl
+ std::cerr << "-H: invalid header - value missing: " << optarg
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ // Note that there is no processing currently to handle multiple
+ // message-header fields with the same field name
+ config.custom_headers.emplace_back(header, value);
+ util::inp_strlower(config.custom_headers.back().name);
+ break;
+ }
+ case 'i':
+ config.ifile = optarg;
+ break;
+ case 'p': {
+ auto proto = StringRef{optarg};
+ if (util::strieq(StringRef::from_lit(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID),
+ proto)) {
+ config.no_tls_proto = Config::PROTO_HTTP2;
+ } else if (util::strieq(NGHTTP2_H1_1, proto)) {
+ config.no_tls_proto = Config::PROTO_HTTP1_1;
+ } else {
+ std::cerr << "-p: unsupported protocol " << proto << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ break;
+ }
+ case 'r': {
+ auto n = util::parse_uint(optarg);
+ if (n == -1) {
+ std::cerr << "-r: bad option value: " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ if (n == 0) {
+ std::cerr << "-r: the rate at which connections are made "
+ << "must be positive." << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.rate = n;
+ break;
+ }
+ case 'T':
+ config.conn_active_timeout = util::parse_duration_with_unit(optarg);
+ if (!std::isfinite(config.conn_active_timeout)) {
+ std::cerr << "-T: bad value for the conn_active_timeout wait time: "
+ << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 'N':
+ config.conn_inactivity_timeout = util::parse_duration_with_unit(optarg);
+ if (!std::isfinite(config.conn_inactivity_timeout)) {
+ std::cerr << "-N: bad value for the conn_inactivity_timeout wait time: "
+ << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 'B': {
+ auto arg = StringRef{optarg};
+ config.base_uri = "";
+ config.base_uri_unix = false;
+
+ if (util::istarts_with_l(arg, UNIX_PATH_PREFIX)) {
+ // UNIX domain socket path
+ sockaddr_un un;
+
+ auto path = StringRef{std::begin(arg) + str_size(UNIX_PATH_PREFIX),
+ std::end(arg)};
+
+ if (path.size() == 0 || path.size() + 1 > sizeof(un.sun_path)) {
+ std::cerr << "--base-uri: invalid UNIX domain socket path: " << arg
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ config.base_uri_unix = true;
+
+ auto &unix_addr = config.unix_addr;
+ std::copy(std::begin(path), std::end(path), unix_addr.sun_path);
+ unix_addr.sun_path[path.size()] = '\0';
+ unix_addr.sun_family = AF_UNIX;
+
+ break;
+ }
+
+ if (!parse_base_uri(arg)) {
+ std::cerr << "--base-uri: invalid base URI: " << arg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ config.base_uri = arg.str();
+ break;
+ }
+ case 'D':
+ config.duration = util::parse_duration_with_unit(optarg);
+ if (!std::isfinite(config.duration)) {
+ std::cerr << "-D: value error " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 'v':
+ config.verbose = true;
+ break;
+ case 'h':
+ print_help(std::cout);
+ exit(EXIT_SUCCESS);
+ case '?':
+ util::show_candidates(argv[optind - 1], long_options);
+ exit(EXIT_FAILURE);
+ case 0:
+ switch (flag) {
+ case 1:
+ // version option
+ print_version(std::cout);
+ exit(EXIT_SUCCESS);
+ case 2:
+ // ciphers option
+ config.ciphers = optarg;
+ break;
+ case 3:
+ // timing-script option
+ config.ifile = optarg;
+ config.timing_script = true;
+ break;
+ case 5:
+ // rate-period
+ config.rate_period = util::parse_duration_with_unit(optarg);
+ if (!std::isfinite(config.rate_period)) {
+ std::cerr << "--rate-period: value error " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 6:
+ // --h1
+ config.alpn_list =
+ util::parse_config_str_list(StringRef::from_lit("http/1.1"));
+ config.no_tls_proto = Config::PROTO_HTTP1_1;
+ break;
+ case 7:
+ // --header-table-size
+ if (parse_header_table_size(config.header_table_size,
+ "header-table-size", optarg) != 0) {
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 8:
+ // --encoder-header-table-size
+ if (parse_header_table_size(config.encoder_header_table_size,
+ "encoder-header-table-size", optarg) != 0) {
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 9:
+ // --warm-up-time
+ config.warm_up_time = util::parse_duration_with_unit(optarg);
+ if (!std::isfinite(config.warm_up_time)) {
+ std::cerr << "--warm-up-time: value error " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 10:
+ // --log-file
+ logfile = optarg;
+ break;
+ case 11: {
+ // --connect-to
+ auto p = util::split_hostport(StringRef{optarg});
+ int64_t port = 0;
+ if (p.first.empty() ||
+ (!p.second.empty() && (port = util::parse_uint(p.second)) == -1)) {
+ std::cerr << "--connect-to: Invalid value " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.connect_to_host = p.first.str();
+ config.connect_to_port = port;
+ break;
+ }
+ case 12: {
+ char *end;
+ auto v = std::strtod(optarg, &end);
+ if (end == optarg || *end != '\0' || !std::isfinite(v) ||
+ 1. / v < 1e-6) {
+ std::cerr << "--rps: Invalid value " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.rps = v;
+ break;
+ }
+ case 13:
+ // --groups
+ config.groups = optarg;
+ break;
+ case 14:
+ // --tls13-ciphers
+ config.tls13_ciphers = optarg;
+ break;
+ case 15:
+ // --no-udp-gso
+ config.no_udp_gso = true;
+ break;
+ case 16:
+ // --qlog-file-base
+ config.qlog_file_base = optarg;
+ break;
+ case 17: {
+ // --max-udp-payload-size
+ auto n = util::parse_uint_with_unit(optarg);
+ if (n == -1) {
+ std::cerr << "--max-udp-payload-size: bad option value: " << optarg
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ if (static_cast<uint64_t>(n) > 64_k) {
+ std::cerr << "--max-udp-payload-size: must not exceed 65536"
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.max_udp_payload_size = n;
+ break;
+ }
+ case 18:
+ // --ktls
+ config.ktls = true;
+ break;
+ case 4:
+ // npn-list option
+ std::cerr << "--npn-list: deprecated. Use --alpn-list instead."
+ << std::endl;
+ // fall through
+ case 19:
+ // alpn-list option
+ config.alpn_list = util::parse_config_str_list(StringRef{optarg});
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (argc == optind) {
+ if (config.ifile.empty()) {
+ std::cerr << "no URI or input file given" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ if (config.nclients == 0) {
+ std::cerr << "-c: the number of clients must be strictly greater than 0."
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ if (config.alpn_list.empty()) {
+ config.alpn_list =
+ util::parse_config_str_list(StringRef::from_lit(DEFAULT_ALPN_LIST));
+ }
+
+ // serialize the APLN tokens
+ for (auto &proto : config.alpn_list) {
+ proto.insert(proto.begin(), static_cast<unsigned char>(proto.size()));
+ }
+
+ std::vector<std::string> reqlines;
+
+ if (config.ifile.empty()) {
+ std::vector<std::string> uris;
+ std::copy(&argv[optind], &argv[argc], std::back_inserter(uris));
+ reqlines = parse_uris(std::begin(uris), std::end(uris));
+ } else {
+ std::vector<std::string> uris;
+ if (!config.timing_script) {
+ if (config.ifile == "-") {
+ uris = read_uri_from_file(std::cin);
+ } else {
+ std::ifstream infile(config.ifile);
+ if (!infile) {
+ std::cerr << "cannot read input file: " << config.ifile << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ uris = read_uri_from_file(infile);
+ }
+ } else {
+ if (config.ifile == "-") {
+ read_script_from_file(std::cin, config.timings, uris);
+ } else {
+ std::ifstream infile(config.ifile);
+ if (!infile) {
+ std::cerr << "cannot read input file: " << config.ifile << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ read_script_from_file(infile, config.timings, uris);
+ }
+
+ if (nreqs_set_manually) {
+ if (config.nreqs > uris.size()) {
+ std::cerr << "-n: the number of requests must be less than or equal "
+ "to the number of timing script entries. Setting number "
+ "of requests to "
+ << uris.size() << std::endl;
+
+ config.nreqs = uris.size();
+ }
+ } else {
+ config.nreqs = uris.size();
+ }
+ }
+
+ reqlines = parse_uris(std::begin(uris), std::end(uris));
+ }
+
+ if (reqlines.empty()) {
+ std::cerr << "No URI given" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ if (config.is_timing_based_mode() && config.is_rate_mode()) {
+ std::cerr << "-r, -D: they are mutually exclusive." << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ if (config.timing_script && config.rps_enabled()) {
+ std::cerr << "--timing-script-file, --rps: they are mutually exclusive."
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ if (config.nreqs == 0 && !config.is_timing_based_mode()) {
+ std::cerr << "-n: the number of requests must be strictly greater than 0 "
+ "if timing-based test is not being run."
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ if (config.max_concurrent_streams == 0) {
+ std::cerr << "-m: the max concurrent streams must be strictly greater "
+ << "than 0." << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ if (config.nthreads == 0) {
+ std::cerr << "-t: the number of threads must be strictly greater than 0."
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ if (config.nthreads > std::thread::hardware_concurrency()) {
+ std::cerr << "-t: warning: the number of threads is greater than hardware "
+ << "cores." << std::endl;
+ }
+
+ // With timing script, we don't distribute config.nreqs to each
+ // client or thread.
+ if (!config.timing_script && config.nreqs < config.nclients &&
+ !config.is_timing_based_mode()) {
+ std::cerr << "-n, -c: the number of requests must be greater than or "
+ << "equal to the clients." << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ if (config.nclients < config.nthreads) {
+ std::cerr << "-c, -t: the number of clients must be greater than or equal "
+ << "to the number of threads." << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ if (config.is_timing_based_mode()) {
+ config.nreqs = 0;
+ }
+
+ if (config.is_rate_mode()) {
+ if (config.rate < config.nthreads) {
+ std::cerr << "-r, -t: the connection rate must be greater than or equal "
+ << "to the number of threads." << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ if (config.rate > config.nclients) {
+ std::cerr << "-r, -c: the connection rate must be smaller than or equal "
+ "to the number of clients."
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ if (!datafile.empty()) {
+ config.data_fd = open(datafile.c_str(), O_RDONLY | O_BINARY);
+ if (config.data_fd == -1) {
+ std::cerr << "-d: Could not open file " << datafile << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ struct stat data_stat;
+ if (fstat(config.data_fd, &data_stat) == -1) {
+ std::cerr << "-d: Could not stat file " << datafile << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.data_length = data_stat.st_size;
+ auto addr = mmap(nullptr, config.data_length, PROT_READ, MAP_SHARED,
+ config.data_fd, 0);
+ if (addr == MAP_FAILED) {
+ std::cerr << "-d: Could not mmap file " << datafile << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.data = static_cast<uint8_t *>(addr);
+ }
+
+ if (!logfile.empty()) {
+ config.log_fd = open(logfile.c_str(), O_WRONLY | O_CREAT | O_APPEND,
+ S_IRUSR | S_IWUSR | S_IRGRP);
+ if (config.log_fd == -1) {
+ std::cerr << "--log-file: Could not open file " << logfile << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ if (!config.qlog_file_base.empty() && !config.is_quic()) {
+ std::cerr << "Warning: --qlog-file-base: only effective in quic, ignoring."
+ << std::endl;
+ }
+
+ struct sigaction act {};
+ act.sa_handler = SIG_IGN;
+ sigaction(SIGPIPE, &act, nullptr);
+
+ auto ssl_ctx = SSL_CTX_new(TLS_client_method());
+ if (!ssl_ctx) {
+ std::cerr << "Failed to create SSL_CTX: "
+ << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ auto ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) |
+ SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION |
+ SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION;
+
+#ifdef SSL_OP_ENABLE_KTLS
+ if (config.ktls) {
+ ssl_opts |= SSL_OP_ENABLE_KTLS;
+ }
+#endif // SSL_OP_ENABLE_KTLS
+
+ SSL_CTX_set_options(ssl_ctx, ssl_opts);
+ SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
+ SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
+
+ if (config.is_quic()) {
+#ifdef ENABLE_HTTP3
+# ifdef HAVE_LIBNGTCP2_CRYPTO_QUICTLS
+ if (ngtcp2_crypto_quictls_configure_client_context(ssl_ctx) != 0) {
+ std::cerr << "ngtcp2_crypto_quictls_configure_client_context failed"
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+# endif // HAVE_LIBNGTCP2_CRYPTO_QUICTLS
+# ifdef HAVE_LIBNGTCP2_CRYPTO_BORINGSSL
+ if (ngtcp2_crypto_boringssl_configure_client_context(ssl_ctx) != 0) {
+ std::cerr << "ngtcp2_crypto_boringssl_configure_client_context failed"
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+# endif // HAVE_LIBNGTCP2_CRYPTO_BORINGSSL
+#endif // ENABLE_HTTP3
+ } else if (nghttp2::tls::ssl_ctx_set_proto_versions(
+ ssl_ctx, nghttp2::tls::NGHTTP2_TLS_MIN_VERSION,
+ nghttp2::tls::NGHTTP2_TLS_MAX_VERSION) != 0) {
+ std::cerr << "Could not set TLS versions" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ if (SSL_CTX_set_cipher_list(ssl_ctx, config.ciphers.c_str()) == 0) {
+ std::cerr << "SSL_CTX_set_cipher_list with " << config.ciphers
+ << " failed: " << ERR_error_string(ERR_get_error(), nullptr)
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+#if defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_LIBRESSL)
+ if (SSL_CTX_set_ciphersuites(ssl_ctx, config.tls13_ciphers.c_str()) == 0) {
+ std::cerr << "SSL_CTX_set_ciphersuites with " << config.tls13_ciphers
+ << " failed: " << ERR_error_string(ERR_get_error(), nullptr)
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+#endif // NGHTTP2_GENUINE_OPENSSL || NGHTTP2_OPENSSL_IS_LIBRESSL
+
+ if (SSL_CTX_set1_groups_list(ssl_ctx, config.groups.c_str()) != 1) {
+ std::cerr << "SSL_CTX_set1_groups_list failed" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ std::vector<unsigned char> proto_list;
+ for (const auto &proto : config.alpn_list) {
+ std::copy_n(proto.c_str(), proto.size(), std::back_inserter(proto_list));
+ }
+
+ SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size());
+
+ auto keylog_filename = getenv("SSLKEYLOGFILE");
+ if (keylog_filename) {
+ keylog_file.open(keylog_filename, std::ios_base::app);
+ if (keylog_file) {
+ SSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback);
+ }
+ }
+
+ std::string user_agent = "h2load nghttp2/" NGHTTP2_VERSION;
+ Headers shared_nva;
+ shared_nva.emplace_back(":scheme", config.scheme);
+ shared_nva.emplace_back(":authority", make_http_authority(config));
+ shared_nva.emplace_back(":method", config.data_fd == -1 ? "GET" : "POST");
+ shared_nva.emplace_back("user-agent", user_agent);
+
+ // list header fields that can be overridden.
+ auto override_hdrs = make_array<std::string>(":authority", ":host", ":method",
+ ":scheme", "user-agent");
+
+ for (auto &kv : config.custom_headers) {
+ if (std::find(std::begin(override_hdrs), std::end(override_hdrs),
+ kv.name) != std::end(override_hdrs)) {
+ // override header
+ for (auto &nv : shared_nva) {
+ if ((nv.name == ":authority" && kv.name == ":host") ||
+ (nv.name == kv.name)) {
+ nv.value = kv.value;
+ }
+ }
+ } else {
+ // add additional headers
+ shared_nva.push_back(kv);
+ }
+ }
+
+ std::string content_length_str;
+ if (config.data_fd != -1) {
+ content_length_str = util::utos(config.data_length);
+ }
+
+ auto method_it =
+ std::find_if(std::begin(shared_nva), std::end(shared_nva),
+ [](const Header &nv) { return nv.name == ":method"; });
+ assert(method_it != std::end(shared_nva));
+
+ config.h1reqs.reserve(reqlines.size());
+ config.nva.reserve(reqlines.size());
+
+ for (auto &req : reqlines) {
+ // For HTTP/1.1
+ auto h1req = (*method_it).value;
+ h1req += ' ';
+ h1req += req;
+ h1req += " HTTP/1.1\r\n";
+ for (auto &nv : shared_nva) {
+ if (nv.name == ":authority") {
+ h1req += "Host: ";
+ h1req += nv.value;
+ h1req += "\r\n";
+ continue;
+ }
+ if (nv.name[0] == ':') {
+ continue;
+ }
+ h1req += nv.name;
+ h1req += ": ";
+ h1req += nv.value;
+ h1req += "\r\n";
+ }
+
+ if (!content_length_str.empty()) {
+ h1req += "Content-Length: ";
+ h1req += content_length_str;
+ h1req += "\r\n";
+ }
+ h1req += "\r\n";
+
+ config.h1reqs.push_back(std::move(h1req));
+
+ // For nghttp2
+ std::vector<nghttp2_nv> nva;
+ // 2 for :path, and possible content-length
+ nva.reserve(2 + shared_nva.size());
+
+ nva.push_back(http2::make_nv_ls(":path", req));
+
+ for (auto &nv : shared_nva) {
+ nva.push_back(http2::make_nv(nv.name, nv.value, false));
+ }
+
+ if (!content_length_str.empty()) {
+ nva.push_back(http2::make_nv(StringRef::from_lit("content-length"),
+ StringRef{content_length_str}));
+ }
+
+ config.nva.push_back(std::move(nva));
+ }
+
+ // Don't DOS our server!
+ if (config.host == "nghttp2.org") {
+ std::cerr << "Using h2load against public server " << config.host
+ << " should be prohibited." << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ resolve_host();
+
+ std::cout << "starting benchmark..." << std::endl;
+
+ std::vector<std::unique_ptr<Worker>> workers;
+ workers.reserve(config.nthreads);
+
+#ifndef NOTHREADS
+ size_t nreqs_per_thread = 0;
+ ssize_t nreqs_rem = 0;
+
+ if (!config.timing_script) {
+ nreqs_per_thread = config.nreqs / config.nthreads;
+ nreqs_rem = config.nreqs % config.nthreads;
+ }
+
+ size_t nclients_per_thread = config.nclients / config.nthreads;
+ ssize_t nclients_rem = config.nclients % config.nthreads;
+
+ size_t rate_per_thread = config.rate / config.nthreads;
+ ssize_t rate_per_thread_rem = config.rate % config.nthreads;
+
+ size_t max_samples_per_thread =
+ std::max(static_cast<size_t>(256), MAX_SAMPLES / config.nthreads);
+
+ std::mutex mu;
+ std::condition_variable cv;
+ auto ready = false;
+
+ std::vector<std::future<void>> futures;
+ for (size_t i = 0; i < config.nthreads; ++i) {
+ auto rate = rate_per_thread;
+ if (rate_per_thread_rem > 0) {
+ --rate_per_thread_rem;
+ ++rate;
+ }
+ auto nclients = nclients_per_thread;
+ if (nclients_rem > 0) {
+ --nclients_rem;
+ ++nclients;
+ }
+
+ size_t nreqs;
+ if (config.timing_script) {
+ // With timing script, each client issues config.nreqs requests.
+ // We divide nreqs by number of clients in Worker ctor to
+ // distribute requests to those clients evenly, so multiply
+ // config.nreqs here by config.nclients.
+ nreqs = config.nreqs * nclients;
+ } else {
+ nreqs = nreqs_per_thread;
+ if (nreqs_rem > 0) {
+ --nreqs_rem;
+ ++nreqs;
+ }
+ }
+
+ workers.push_back(create_worker(i, ssl_ctx, nreqs, nclients, rate,
+ max_samples_per_thread));
+ auto &worker = workers.back();
+ futures.push_back(
+ std::async(std::launch::async, [&worker, &mu, &cv, &ready]() {
+ {
+ std::unique_lock<std::mutex> ulk(mu);
+ cv.wait(ulk, [&ready] { return ready; });
+ }
+ worker->run();
+ }));
+ }
+
+ {
+ std::lock_guard<std::mutex> lg(mu);
+ ready = true;
+ cv.notify_all();
+ }
+
+ auto start = std::chrono::steady_clock::now();
+
+ for (auto &fut : futures) {
+ fut.get();
+ }
+
+#else // NOTHREADS
+ auto rate = config.rate;
+ auto nclients = config.nclients;
+ auto nreqs =
+ config.timing_script ? config.nreqs * config.nclients : config.nreqs;
+
+ workers.push_back(
+ create_worker(0, ssl_ctx, nreqs, nclients, rate, MAX_SAMPLES));
+
+ auto start = std::chrono::steady_clock::now();
+
+ workers.back()->run();
+#endif // NOTHREADS
+
+ auto end = std::chrono::steady_clock::now();
+ auto duration =
+ std::chrono::duration_cast<std::chrono::microseconds>(end - start);
+
+ Stats stats(0, 0);
+ for (const auto &w : workers) {
+ const auto &s = w->stats;
+
+ stats.req_todo += s.req_todo;
+ stats.req_started += s.req_started;
+ stats.req_done += s.req_done;
+ stats.req_timedout += s.req_timedout;
+ stats.req_success += s.req_success;
+ stats.req_status_success += s.req_status_success;
+ stats.req_failed += s.req_failed;
+ stats.req_error += s.req_error;
+ stats.bytes_total += s.bytes_total;
+ stats.bytes_head += s.bytes_head;
+ stats.bytes_head_decomp += s.bytes_head_decomp;
+ stats.bytes_body += s.bytes_body;
+ stats.udp_dgram_recv += s.udp_dgram_recv;
+ stats.udp_dgram_sent += s.udp_dgram_sent;
+
+ for (size_t i = 0; i < stats.status.size(); ++i) {
+ stats.status[i] += s.status[i];
+ }
+ }
+
+ auto ts = process_time_stats(workers);
+
+ // Requests which have not been issued due to connection errors, are
+ // counted towards req_failed and req_error.
+ auto req_not_issued =
+ (stats.req_todo - stats.req_status_success - stats.req_failed);
+ stats.req_failed += req_not_issued;
+ stats.req_error += req_not_issued;
+
+ // UI is heavily inspired by weighttp[1] and wrk[2]
+ //
+ // [1] https://github.com/lighttpd/weighttp
+ // [2] https://github.com/wg/wrk
+ double rps = 0;
+ int64_t bps = 0;
+ if (duration.count() > 0) {
+ if (config.is_timing_based_mode()) {
+ // we only want to consider the main duration if warm-up is given
+ rps = stats.req_success / config.duration;
+ bps = stats.bytes_total / config.duration;
+ } else {
+ auto secd = std::chrono::duration_cast<
+ std::chrono::duration<double, std::chrono::seconds::period>>(
+ duration);
+ rps = stats.req_success / secd.count();
+ bps = stats.bytes_total / secd.count();
+ }
+ }
+
+ double header_space_savings = 0.;
+ if (stats.bytes_head_decomp > 0) {
+ header_space_savings =
+ 1. - static_cast<double>(stats.bytes_head) / stats.bytes_head_decomp;
+ }
+
+ std::cout << std::fixed << std::setprecision(2) << R"(
+finished in )"
+ << util::format_duration(duration) << ", " << rps << " req/s, "
+ << util::utos_funit(bps) << R"(B/s
+requests: )" << stats.req_todo
+ << " total, " << stats.req_started << " started, " << stats.req_done
+ << " done, " << stats.req_status_success << " succeeded, "
+ << stats.req_failed << " failed, " << stats.req_error
+ << " errored, " << stats.req_timedout << R"( timeout
+status codes: )"
+ << stats.status[2] << " 2xx, " << stats.status[3] << " 3xx, "
+ << stats.status[4] << " 4xx, " << stats.status[5] << R"( 5xx
+traffic: )" << util::utos_funit(stats.bytes_total)
+ << "B (" << stats.bytes_total << ") total, "
+ << util::utos_funit(stats.bytes_head) << "B (" << stats.bytes_head
+ << ") headers (space savings " << header_space_savings * 100
+ << "%), " << util::utos_funit(stats.bytes_body) << "B ("
+ << stats.bytes_body << R"() data)" << std::endl;
+#ifdef ENABLE_HTTP3
+ if (config.is_quic()) {
+ std::cout << "UDP datagram: " << stats.udp_dgram_sent << " sent, "
+ << stats.udp_dgram_recv << " received" << std::endl;
+ }
+#endif // ENABLE_HTTP3
+ std::cout
+ << R"( min max mean sd +/- sd
+time for request: )"
+ << std::setw(10) << util::format_duration(ts.request.min) << " "
+ << std::setw(10) << util::format_duration(ts.request.max) << " "
+ << std::setw(10) << util::format_duration(ts.request.mean) << " "
+ << std::setw(10) << util::format_duration(ts.request.sd) << std::setw(9)
+ << util::dtos(ts.request.within_sd) << "%"
+ << "\ntime for connect: " << std::setw(10)
+ << util::format_duration(ts.connect.min) << " " << std::setw(10)
+ << util::format_duration(ts.connect.max) << " " << std::setw(10)
+ << util::format_duration(ts.connect.mean) << " " << std::setw(10)
+ << util::format_duration(ts.connect.sd) << std::setw(9)
+ << util::dtos(ts.connect.within_sd) << "%"
+ << "\ntime to 1st byte: " << std::setw(10)
+ << util::format_duration(ts.ttfb.min) << " " << std::setw(10)
+ << util::format_duration(ts.ttfb.max) << " " << std::setw(10)
+ << util::format_duration(ts.ttfb.mean) << " " << std::setw(10)
+ << util::format_duration(ts.ttfb.sd) << std::setw(9)
+ << util::dtos(ts.ttfb.within_sd) << "%"
+ << "\nreq/s : " << std::setw(10) << ts.rps.min << " "
+ << std::setw(10) << ts.rps.max << " " << std::setw(10) << ts.rps.mean
+ << " " << std::setw(10) << ts.rps.sd << std::setw(9)
+ << util::dtos(ts.rps.within_sd) << "%" << std::endl;
+
+ SSL_CTX_free(ssl_ctx);
+
+ if (config.log_fd != -1) {
+ close(config.log_fd);
+ }
+
+ return 0;
+}
+
+} // namespace h2load
+
+int main(int argc, char **argv) { return h2load::main(argc, argv); }
diff --git a/src/h2load.h b/src/h2load.h
new file mode 100644
index 0000000..11bb54c
--- /dev/null
+++ b/src/h2load.h
@@ -0,0 +1,510 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef H2LOAD_H
+#define H2LOAD_H
+
+#include "nghttp2_config.h"
+
+#include <sys/types.h>
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif // HAVE_SYS_SOCKET_H
+#ifdef HAVE_NETDB_H
+# include <netdb.h>
+#endif // HAVE_NETDB_H
+#include <sys/un.h>
+
+#include <vector>
+#include <string>
+#include <unordered_map>
+#include <memory>
+#include <chrono>
+#include <array>
+
+#include <nghttp2/nghttp2.h>
+
+#ifdef ENABLE_HTTP3
+# include <ngtcp2/ngtcp2.h>
+# include <ngtcp2/ngtcp2_crypto.h>
+#endif // ENABLE_HTTP3
+
+#include <ev.h>
+
+#include <openssl/ssl.h>
+
+#include "http2.h"
+#ifdef ENABLE_HTTP3
+# include "quic.h"
+#endif // ENABLE_HTTP3
+#include "memchunk.h"
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace h2load {
+
+constexpr auto BACKOFF_WRITE_BUFFER_THRES = 16_k;
+
+class Session;
+struct Worker;
+
+struct Config {
+ std::vector<std::vector<nghttp2_nv>> nva;
+ std::vector<std::string> h1reqs;
+ std::vector<std::chrono::steady_clock::duration> timings;
+ nghttp2::Headers custom_headers;
+ std::string scheme;
+ std::string host;
+ std::string connect_to_host;
+ std::string ifile;
+ std::string ciphers;
+ std::string tls13_ciphers;
+ // supported groups (or curves).
+ std::string groups;
+ // length of upload data
+ int64_t data_length;
+ // memory mapped upload data
+ uint8_t *data;
+ addrinfo *addrs;
+ size_t nreqs;
+ size_t nclients;
+ size_t nthreads;
+ // The maximum number of concurrent streams per session.
+ ssize_t max_concurrent_streams;
+ size_t window_bits;
+ size_t connection_window_bits;
+ size_t max_frame_size;
+ // rate at which connections should be made
+ size_t rate;
+ ev_tstamp rate_period;
+ // amount of time for main measurements in timing-based test
+ ev_tstamp duration;
+ // amount of time to wait before starting measurements in timing-based test
+ ev_tstamp warm_up_time;
+ // amount of time to wait for activity on a given connection
+ ev_tstamp conn_active_timeout;
+ // amount of time to wait after the last request is made on a connection
+ ev_tstamp conn_inactivity_timeout;
+ enum { PROTO_HTTP2, PROTO_HTTP1_1 } no_tls_proto;
+ uint32_t header_table_size;
+ uint32_t encoder_header_table_size;
+ // file descriptor for upload data
+ int data_fd;
+ // file descriptor to write per-request stats to.
+ int log_fd;
+ // base file name of qlog output files
+ std::string qlog_file_base;
+ uint16_t port;
+ uint16_t default_port;
+ uint16_t connect_to_port;
+ bool verbose;
+ bool timing_script;
+ std::string base_uri;
+ // true if UNIX domain socket is used. In this case, base_uri is
+ // not used in usual way.
+ bool base_uri_unix;
+ // used when UNIX domain socket is used (base_uri_unix is true).
+ sockaddr_un unix_addr;
+ // list of supported ALPN protocol strings in the order of
+ // preference.
+ std::vector<std::string> alpn_list;
+ // The number of request per second for each client.
+ double rps;
+ // Disables GSO for UDP connections.
+ bool no_udp_gso;
+ // The maximum UDP datagram payload size to send.
+ size_t max_udp_payload_size;
+ // Enable ktls.
+ bool ktls;
+
+ Config();
+ ~Config();
+
+ bool is_rate_mode() const;
+ bool is_timing_based_mode() const;
+ bool has_base_uri() const;
+ bool rps_enabled() const;
+ bool is_quic() const;
+};
+
+struct RequestStat {
+ // time point when request was sent
+ std::chrono::steady_clock::time_point request_time;
+ // same, but in wall clock reference frame
+ std::chrono::system_clock::time_point request_wall_time;
+ // time point when stream was closed
+ std::chrono::steady_clock::time_point stream_close_time;
+ // upload data length sent so far
+ int64_t data_offset;
+ // HTTP status code
+ int status;
+ // true if stream was successfully closed. This means stream was
+ // not reset, but it does not mean HTTP level error (e.g., 404).
+ bool completed;
+};
+
+struct ClientStat {
+ // time client started (i.e., first connect starts)
+ std::chrono::steady_clock::time_point client_start_time;
+ // time client end (i.e., client somehow processed all requests it
+ // is responsible for, and disconnected)
+ std::chrono::steady_clock::time_point client_end_time;
+ // The number of requests completed successful, but not necessarily
+ // means successful HTTP status code.
+ size_t req_success;
+
+ // The following 3 numbers are overwritten each time when connection
+ // is made.
+
+ // time connect starts
+ std::chrono::steady_clock::time_point connect_start_time;
+ // time to connect
+ std::chrono::steady_clock::time_point connect_time;
+ // time to first byte (TTFB)
+ std::chrono::steady_clock::time_point ttfb;
+};
+
+struct SDStat {
+ // min, max, mean and sd (standard deviation)
+ double min, max, mean, sd;
+ // percentage of samples inside mean -/+ sd
+ double within_sd;
+};
+
+struct SDStats {
+ // time for request
+ SDStat request;
+ // time for connect
+ SDStat connect;
+ // time to first byte (TTFB)
+ SDStat ttfb;
+ // request per second for each client
+ SDStat rps;
+};
+
+struct Stats {
+ Stats(size_t req_todo, size_t nclients);
+ // The total number of requests
+ size_t req_todo;
+ // The number of requests issued so far
+ size_t req_started;
+ // The number of requests finished
+ size_t req_done;
+ // The number of requests completed successful, but not necessarily
+ // means successful HTTP status code.
+ size_t req_success;
+ // The number of requests marked as success. HTTP status code is
+ // also considered as success. This is subset of req_done.
+ size_t req_status_success;
+ // The number of requests failed. This is subset of req_done.
+ size_t req_failed;
+ // The number of requests failed due to network errors. This is
+ // subset of req_failed.
+ size_t req_error;
+ // The number of requests that failed due to timeout.
+ size_t req_timedout;
+ // The number of bytes received on the "wire". If SSL/TLS is used,
+ // this is the number of decrypted bytes the application received.
+ int64_t bytes_total;
+ // The number of bytes received for header fields. This is
+ // compressed version.
+ int64_t bytes_head;
+ // The number of bytes received for header fields after they are
+ // decompressed.
+ int64_t bytes_head_decomp;
+ // The number of bytes received in DATA frame.
+ int64_t bytes_body;
+ // The number of each HTTP status category, status[i] is status code
+ // in the range [i*100, (i+1)*100).
+ std::array<size_t, 6> status;
+ // The statistics per request
+ std::vector<RequestStat> req_stats;
+ // The statistics per client
+ std::vector<ClientStat> client_stats;
+ // The number of UDP datagrams received.
+ size_t udp_dgram_recv;
+ // The number of UDP datagrams sent.
+ size_t udp_dgram_sent;
+};
+
+enum ClientState { CLIENT_IDLE, CLIENT_CONNECTED };
+
+// This type tells whether the client is in warmup phase or not or is over
+enum class Phase {
+ INITIAL_IDLE, // Initial idle state before warm-up phase
+ WARM_UP, // Warm up phase when no measurements are done
+ MAIN_DURATION, // Main measurement phase; if timing-based
+ // test is not run, this is the default phase
+ DURATION_OVER // This phase occurs after the measurements are over
+};
+
+struct Client;
+
+// We use reservoir sampling method
+struct Sampling {
+ // maximum number of samples
+ size_t max_samples;
+ // number of samples seen, including discarded samples.
+ size_t n;
+};
+
+struct Worker {
+ MemchunkPool mcpool;
+ std::mt19937 randgen;
+ Stats stats;
+ Sampling request_times_smp;
+ Sampling client_smp;
+ struct ev_loop *loop;
+ SSL_CTX *ssl_ctx;
+ Config *config;
+ size_t progress_interval;
+ uint32_t id;
+ bool tls_info_report_done;
+ bool app_info_report_done;
+ size_t nconns_made;
+ // number of clients this worker handles
+ size_t nclients;
+ // number of requests each client issues
+ size_t nreqs_per_client;
+ // at most nreqs_rem clients get an extra request
+ size_t nreqs_rem;
+ size_t rate;
+ // maximum number of samples in this worker thread
+ size_t max_samples;
+ ev_timer timeout_watcher;
+ // The next client ID this worker assigns
+ uint32_t next_client_id;
+ // Keeps track of the current phase (for timing-based experiment) for the
+ // worker
+ Phase current_phase;
+ // We need to keep track of the clients in order to stop them when needed
+ std::vector<Client *> clients;
+ // This is only active when there is not a bounded number of requests
+ // specified
+ ev_timer duration_watcher;
+ ev_timer warmup_watcher;
+
+ Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t nreq_todo, size_t nclients,
+ size_t rate, size_t max_samples, Config *config);
+ ~Worker();
+ Worker(Worker &&o) = default;
+ void run();
+ void sample_req_stat(RequestStat *req_stat);
+ void sample_client_stat(ClientStat *cstat);
+ void report_progress();
+ void report_rate_progress();
+ // This function calls the destructors of all the clients.
+ void stop_all_clients();
+ // This function frees a client from the list of clients for this Worker.
+ void free_client(Client *);
+};
+
+struct Stream {
+ RequestStat req_stat;
+ int status_success;
+ Stream();
+};
+
+struct Client {
+ DefaultMemchunks wb;
+ std::unordered_map<int32_t, Stream> streams;
+ ClientStat cstat;
+ std::unique_ptr<Session> session;
+ ev_io wev;
+ ev_io rev;
+ std::function<int(Client &)> readfn, writefn;
+ Worker *worker;
+ SSL *ssl;
+#ifdef ENABLE_HTTP3
+ struct {
+ ngtcp2_crypto_conn_ref conn_ref;
+ ev_timer pkt_timer;
+ ngtcp2_conn *conn;
+ ngtcp2_ccerr last_error;
+ bool close_requested;
+ FILE *qlog_file;
+
+ struct {
+ bool send_blocked;
+ size_t num_blocked;
+ size_t num_blocked_sent;
+ struct {
+ Address remote_addr;
+ const uint8_t *data;
+ size_t datalen;
+ size_t gso_size;
+ } blocked[2];
+ std::unique_ptr<uint8_t[]> data;
+ } tx;
+ } quic;
+#endif // ENABLE_HTTP3
+ ev_timer request_timeout_watcher;
+ addrinfo *next_addr;
+ // Address for the current address. When try_new_connection() is
+ // used and current_addr is not nullptr, it is used instead of
+ // trying next address though next_addr. To try new address, set
+ // nullptr to current_addr before calling connect().
+ addrinfo *current_addr;
+ size_t reqidx;
+ ClientState state;
+ // The number of requests this client has to issue.
+ size_t req_todo;
+ // The number of requests left to issue
+ size_t req_left;
+ // The number of requests currently have started, but not abandoned
+ // or finished.
+ size_t req_inflight;
+ // The number of requests this client has issued so far.
+ size_t req_started;
+ // The number of requests this client has done so far.
+ size_t req_done;
+ // The client id per worker
+ uint32_t id;
+ int fd;
+ Address local_addr;
+ ev_timer conn_active_watcher;
+ ev_timer conn_inactivity_watcher;
+ std::string selected_proto;
+ bool new_connection_requested;
+ // true if the current connection will be closed, and no more new
+ // request cannot be processed.
+ bool final;
+ // rps_watcher is a timer to invoke callback periodically to
+ // generate a new request.
+ ev_timer rps_watcher;
+ // The timestamp that starts the period which contributes to the
+ // next request generation.
+ std::chrono::steady_clock::time_point rps_duration_started;
+ // The number of requests allowed by rps, but limited by stream
+ // concurrency.
+ size_t rps_req_pending;
+ // The number of in-flight streams. req_inflight has similar value
+ // but it only measures requests made during Phase::MAIN_DURATION.
+ // rps_req_inflight measures the number of requests in all phases,
+ // and it is only used if --rps is given.
+ size_t rps_req_inflight;
+
+ enum { ERR_CONNECT_FAIL = -100 };
+
+ Client(uint32_t id, Worker *worker, size_t req_todo);
+ ~Client();
+ int make_socket(addrinfo *addr);
+ int connect();
+ void disconnect();
+ void fail();
+ // Call this function when do_read() returns -1. This function
+ // tries to connect to the remote host again if it is requested. If
+ // so, this function returns 0, and this object should be retained.
+ // Otherwise, this function returns -1, and this object should be
+ // deleted.
+ int try_again_or_fail();
+ void timeout();
+ void restart_timeout();
+ int submit_request();
+ void process_request_failure();
+ void process_timedout_streams();
+ void process_abandoned_streams();
+ void report_tls_info();
+ void report_app_info();
+ void terminate_session();
+ // Asks client to create new connection, instead of just fail.
+ void try_new_connection();
+
+ int do_read();
+ int do_write();
+
+ // low-level I/O callback functions called by do_read/do_write
+ int connected();
+ int read_clear();
+ int write_clear();
+ int tls_handshake();
+ int read_tls();
+ int write_tls();
+
+ int on_read(const uint8_t *data, size_t len);
+ int on_write();
+
+ int connection_made();
+
+ void on_request(int32_t stream_id);
+ void on_header(int32_t stream_id, const uint8_t *name, size_t namelen,
+ const uint8_t *value, size_t valuelen);
+ void on_status_code(int32_t stream_id, uint16_t status);
+ // |success| == true means that the request/response was exchanged
+ // |successfully, but it does not mean response carried successful
+ // |HTTP status code.
+ void on_stream_close(int32_t stream_id, bool success, bool final = false);
+ // Returns RequestStat for |stream_id|. This function must be
+ // called after on_request(stream_id), and before
+ // on_stream_close(stream_id, ...). Otherwise, this will return
+ // nullptr.
+ RequestStat *get_req_stat(int32_t stream_id);
+
+ void record_request_time(RequestStat *req_stat);
+ void record_connect_start_time();
+ void record_connect_time();
+ void record_ttfb();
+ void clear_connect_times();
+ void record_client_start_time();
+ void record_client_end_time();
+
+ void signal_write();
+
+#ifdef ENABLE_HTTP3
+ // QUIC
+ int quic_init(const sockaddr *local_addr, socklen_t local_addrlen,
+ const sockaddr *remote_addr, socklen_t remote_addrlen);
+ void quic_free();
+ int read_quic();
+ int write_quic();
+ int write_udp(const sockaddr *addr, socklen_t addrlen, const uint8_t *data,
+ size_t datalen, size_t gso_size);
+ void on_send_blocked(const ngtcp2_addr &remote_addr, const uint8_t *data,
+ size_t datalen, size_t gso_size);
+ int send_blocked_packet();
+ void quic_close_connection();
+
+ int quic_handshake_completed();
+ int quic_recv_stream_data(uint32_t flags, int64_t stream_id,
+ const uint8_t *data, size_t datalen);
+ int quic_acked_stream_data_offset(int64_t stream_id, size_t datalen);
+ int quic_stream_close(int64_t stream_id, uint64_t app_error_code);
+ int quic_stream_reset(int64_t stream_id, uint64_t app_error_code);
+ int quic_stream_stop_sending(int64_t stream_id, uint64_t app_error_code);
+ int quic_extend_max_local_streams();
+ int quic_extend_max_stream_data(int64_t stream_id);
+
+ int quic_write_client_handshake(ngtcp2_encryption_level level,
+ const uint8_t *data, size_t datalen);
+ int quic_pkt_timeout();
+ void quic_restart_pkt_timer();
+ void quic_write_qlog(const void *data, size_t datalen);
+ int quic_make_http3_session();
+#endif // ENABLE_HTTP3
+};
+
+} // namespace h2load
+
+#endif // H2LOAD_H
diff --git a/src/h2load_http1_session.cc b/src/h2load_http1_session.cc
new file mode 100644
index 0000000..6bdcd04
--- /dev/null
+++ b/src/h2load_http1_session.cc
@@ -0,0 +1,306 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 British Broadcasting Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "h2load_http1_session.h"
+
+#include <cassert>
+#include <cerrno>
+
+#include "h2load.h"
+#include "util.h"
+#include "template.h"
+
+#include <iostream>
+#include <fstream>
+
+using namespace nghttp2;
+
+namespace h2load {
+
+namespace {
+// HTTP response message begin
+int htp_msg_begincb(llhttp_t *htp) {
+ auto session = static_cast<Http1Session *>(htp->data);
+
+ if (session->stream_resp_counter_ > session->stream_req_counter_) {
+ return -1;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+// HTTP response status code
+int htp_statuscb(llhttp_t *htp, const char *at, size_t length) {
+ auto session = static_cast<Http1Session *>(htp->data);
+ auto client = session->get_client();
+
+ if (htp->status_code / 100 == 1) {
+ return 0;
+ }
+
+ client->on_status_code(session->stream_resp_counter_, htp->status_code);
+
+ return 0;
+}
+} // namespace
+
+namespace {
+// HTTP response message complete
+int htp_msg_completecb(llhttp_t *htp) {
+ auto session = static_cast<Http1Session *>(htp->data);
+ auto client = session->get_client();
+
+ if (htp->status_code / 100 == 1) {
+ return 0;
+ }
+
+ client->final = llhttp_should_keep_alive(htp) == 0;
+ auto req_stat = client->get_req_stat(session->stream_resp_counter_);
+
+ assert(req_stat);
+
+ auto config = client->worker->config;
+ if (req_stat->data_offset >= config->data_length) {
+ client->on_stream_close(session->stream_resp_counter_, true, client->final);
+ }
+
+ session->stream_resp_counter_ += 2;
+
+ if (client->final) {
+ session->stream_req_counter_ = session->stream_resp_counter_;
+
+ // Connection is going down. If we have still request to do,
+ // create new connection and keep on doing the job.
+ if (client->req_left) {
+ client->try_new_connection();
+ }
+
+ return HPE_PAUSED;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int htp_hdr_keycb(llhttp_t *htp, const char *data, size_t len) {
+ auto session = static_cast<Http1Session *>(htp->data);
+ auto client = session->get_client();
+
+ client->worker->stats.bytes_head += len;
+ client->worker->stats.bytes_head_decomp += len;
+ return 0;
+}
+} // namespace
+
+namespace {
+int htp_hdr_valcb(llhttp_t *htp, const char *data, size_t len) {
+ auto session = static_cast<Http1Session *>(htp->data);
+ auto client = session->get_client();
+
+ client->worker->stats.bytes_head += len;
+ client->worker->stats.bytes_head_decomp += len;
+ return 0;
+}
+} // namespace
+
+namespace {
+int htp_hdrs_completecb(llhttp_t *htp) {
+ return !http2::expect_response_body(htp->status_code);
+}
+} // namespace
+
+namespace {
+int htp_body_cb(llhttp_t *htp, const char *data, size_t len) {
+ auto session = static_cast<Http1Session *>(htp->data);
+ auto client = session->get_client();
+
+ client->record_ttfb();
+ client->worker->stats.bytes_body += len;
+
+ return 0;
+}
+} // namespace
+
+namespace {
+constexpr llhttp_settings_t htp_hooks = {
+ htp_msg_begincb, // llhttp_cb on_message_begin;
+ nullptr, // llhttp_data_cb on_url;
+ htp_statuscb, // llhttp_data_cb on_status;
+ nullptr, // llhttp_data_cb on_method;
+ nullptr, // llhttp_data_cb on_version;
+ htp_hdr_keycb, // llhttp_data_cb on_header_field;
+ htp_hdr_valcb, // llhttp_data_cb on_header_value;
+ nullptr, // llhttp_data_cb on_chunk_extension_name;
+ nullptr, // llhttp_data_cb on_chunk_extension_value;
+ htp_hdrs_completecb, // llhttp_cb on_headers_complete;
+ htp_body_cb, // llhttp_data_cb on_body;
+ htp_msg_completecb, // llhttp_cb on_message_complete;
+ nullptr, // llhttp_cb on_url_complete;
+ nullptr, // llhttp_cb on_status_complete;
+ nullptr, // llhttp_cb on_method_complete;
+ nullptr, // llhttp_cb on_version_complete;
+ nullptr, // llhttp_cb on_header_field_complete;
+ nullptr, // llhttp_cb on_header_value_complete;
+ nullptr, // llhttp_cb on_chunk_extension_name_complete;
+ nullptr, // llhttp_cb on_chunk_extension_value_complete;
+ nullptr, // llhttp_cb on_chunk_header;
+ nullptr, // llhttp_cb on_chunk_complete;
+ nullptr, // llhttp_cb on_reset;
+};
+} // namespace
+
+Http1Session::Http1Session(Client *client)
+ : stream_req_counter_(1),
+ stream_resp_counter_(1),
+ client_(client),
+ htp_(),
+ complete_(false) {
+ llhttp_init(&htp_, HTTP_RESPONSE, &htp_hooks);
+ htp_.data = this;
+}
+
+Http1Session::~Http1Session() {}
+
+void Http1Session::on_connect() { client_->signal_write(); }
+
+int Http1Session::submit_request() {
+ auto config = client_->worker->config;
+ const auto &req = config->h1reqs[client_->reqidx];
+ client_->reqidx++;
+
+ if (client_->reqidx == config->h1reqs.size()) {
+ client_->reqidx = 0;
+ }
+
+ client_->on_request(stream_req_counter_);
+
+ auto req_stat = client_->get_req_stat(stream_req_counter_);
+
+ client_->record_request_time(req_stat);
+ client_->wb.append(req);
+
+ if (config->data_fd == -1 || config->data_length == 0) {
+ // increment for next request
+ stream_req_counter_ += 2;
+
+ return 0;
+ }
+
+ return on_write();
+}
+
+int Http1Session::on_read(const uint8_t *data, size_t len) {
+ auto htperr =
+ llhttp_execute(&htp_, reinterpret_cast<const char *>(data), len);
+ auto nread = htperr == HPE_OK
+ ? len
+ : static_cast<size_t>(reinterpret_cast<const uint8_t *>(
+ llhttp_get_error_pos(&htp_)) -
+ data);
+
+ if (client_->worker->config->verbose) {
+ std::cout.write(reinterpret_cast<const char *>(data), nread);
+ }
+
+ if (htperr == HPE_PAUSED) {
+ // pause is done only when connection: close is requested
+ return -1;
+ }
+
+ if (htperr != HPE_OK) {
+ std::cerr << "[ERROR] HTTP parse error: "
+ << "(" << llhttp_errno_name(htperr) << ") "
+ << llhttp_get_error_reason(&htp_) << std::endl;
+ return -1;
+ }
+
+ return 0;
+}
+
+int Http1Session::on_write() {
+ if (complete_) {
+ return -1;
+ }
+
+ auto config = client_->worker->config;
+ auto req_stat = client_->get_req_stat(stream_req_counter_);
+ if (!req_stat) {
+ return 0;
+ }
+
+ if (req_stat->data_offset < config->data_length) {
+ auto req_stat = client_->get_req_stat(stream_req_counter_);
+ auto &wb = client_->wb;
+
+ // TODO unfortunately, wb has no interface to use with read(2)
+ // family functions.
+ std::array<uint8_t, 16_k> buf;
+
+ ssize_t nread;
+ while ((nread = pread(config->data_fd, buf.data(), buf.size(),
+ req_stat->data_offset)) == -1 &&
+ errno == EINTR)
+ ;
+
+ if (nread == -1) {
+ return -1;
+ }
+
+ req_stat->data_offset += nread;
+
+ wb.append(buf.data(), nread);
+
+ if (client_->worker->config->verbose) {
+ std::cout << "[send " << nread << " byte(s)]" << std::endl;
+ }
+
+ if (req_stat->data_offset == config->data_length) {
+ // increment for next request
+ stream_req_counter_ += 2;
+
+ if (stream_resp_counter_ == stream_req_counter_) {
+ // Response has already been received
+ client_->on_stream_close(stream_resp_counter_ - 2, true,
+ client_->final);
+ }
+ }
+ }
+
+ return 0;
+}
+
+void Http1Session::terminate() { complete_ = true; }
+
+Client *Http1Session::get_client() { return client_; }
+
+size_t Http1Session::max_concurrent_streams() {
+ auto config = client_->worker->config;
+
+ return config->data_fd == -1 ? config->max_concurrent_streams : 1;
+}
+
+} // namespace h2load
diff --git a/src/h2load_http1_session.h b/src/h2load_http1_session.h
new file mode 100644
index 0000000..cc10f50
--- /dev/null
+++ b/src/h2load_http1_session.h
@@ -0,0 +1,60 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 British Broadcasting Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef H2LOAD_HTTP1_SESSION_H
+#define H2LOAD_HTTP1_SESSION_H
+
+#include "h2load_session.h"
+
+#include <nghttp2/nghttp2.h>
+
+#include "llhttp.h"
+
+namespace h2load {
+
+struct Client;
+
+class Http1Session : public Session {
+public:
+ Http1Session(Client *client);
+ virtual ~Http1Session();
+ virtual void on_connect();
+ virtual int submit_request();
+ virtual int on_read(const uint8_t *data, size_t len);
+ virtual int on_write();
+ virtual void terminate();
+ virtual size_t max_concurrent_streams();
+ Client *get_client();
+ int32_t stream_req_counter_;
+ int32_t stream_resp_counter_;
+
+private:
+ Client *client_;
+ llhttp_t htp_;
+ bool complete_;
+};
+
+} // namespace h2load
+
+#endif // H2LOAD_HTTP1_SESSION_H
diff --git a/src/h2load_http2_session.cc b/src/h2load_http2_session.cc
new file mode 100644
index 0000000..b058a33
--- /dev/null
+++ b/src/h2load_http2_session.cc
@@ -0,0 +1,314 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "h2load_http2_session.h"
+
+#include <cassert>
+#include <cerrno>
+#include <iostream>
+
+#include "h2load.h"
+#include "util.h"
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace h2load {
+
+Http2Session::Http2Session(Client *client)
+ : client_(client), session_(nullptr) {}
+
+Http2Session::~Http2Session() { nghttp2_session_del(session_); }
+
+namespace {
+int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
+ const uint8_t *name, size_t namelen,
+ const uint8_t *value, size_t valuelen, uint8_t flags,
+ void *user_data) {
+ auto client = static_cast<Client *>(user_data);
+ if (frame->hd.type != NGHTTP2_HEADERS) {
+ return 0;
+ }
+ client->on_header(frame->hd.stream_id, name, namelen, value, valuelen);
+ client->worker->stats.bytes_head_decomp += namelen + valuelen;
+
+ if (client->worker->config->verbose) {
+ std::cout << "[stream_id=" << frame->hd.stream_id << "] ";
+ std::cout.write(reinterpret_cast<const char *>(name), namelen);
+ std::cout << ": ";
+ std::cout.write(reinterpret_cast<const char *>(value), valuelen);
+ std::cout << "\n";
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame,
+ void *user_data) {
+ auto client = static_cast<Client *>(user_data);
+ switch (frame->hd.type) {
+ case NGHTTP2_HEADERS:
+ client->worker->stats.bytes_head +=
+ frame->hd.length - frame->headers.padlen -
+ ((frame->hd.flags & NGHTTP2_FLAG_PRIORITY) ? 5 : 0);
+ // fall through
+ case NGHTTP2_DATA:
+ if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+ client->record_ttfb();
+ }
+ break;
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
+ int32_t stream_id, const uint8_t *data,
+ size_t len, void *user_data) {
+ auto client = static_cast<Client *>(user_data);
+ client->record_ttfb();
+ client->worker->stats.bytes_body += len;
+ return 0;
+}
+} // namespace
+
+namespace {
+int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
+ uint32_t error_code, void *user_data) {
+ auto client = static_cast<Client *>(user_data);
+ client->on_stream_close(stream_id, error_code == NGHTTP2_NO_ERROR);
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int before_frame_send_callback(nghttp2_session *session,
+ const nghttp2_frame *frame, void *user_data) {
+ if (frame->hd.type != NGHTTP2_HEADERS ||
+ frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
+ return 0;
+ }
+
+ auto client = static_cast<Client *>(user_data);
+ auto req_stat = client->get_req_stat(frame->hd.stream_id);
+ assert(req_stat);
+ client->record_request_time(req_stat);
+
+ return 0;
+}
+} // namespace
+
+namespace {
+ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id,
+ uint8_t *buf, size_t length, uint32_t *data_flags,
+ nghttp2_data_source *source, void *user_data) {
+ auto client = static_cast<Client *>(user_data);
+ auto config = client->worker->config;
+ auto req_stat = client->get_req_stat(stream_id);
+ assert(req_stat);
+ ssize_t nread;
+ while ((nread = pread(config->data_fd, buf, length, req_stat->data_offset)) ==
+ -1 &&
+ errno == EINTR)
+ ;
+
+ if (nread == -1) {
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+
+ req_stat->data_offset += nread;
+
+ if (req_stat->data_offset == config->data_length) {
+ *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+ return nread;
+ }
+
+ if (req_stat->data_offset > config->data_length || nread == 0) {
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+
+ return nread;
+}
+
+} // namespace
+
+namespace {
+ssize_t send_callback(nghttp2_session *session, const uint8_t *data,
+ size_t length, int flags, void *user_data) {
+ auto client = static_cast<Client *>(user_data);
+ auto &wb = client->wb;
+
+ if (wb.rleft() >= BACKOFF_WRITE_BUFFER_THRES) {
+ return NGHTTP2_ERR_WOULDBLOCK;
+ }
+
+ return wb.append(data, length);
+}
+} // namespace
+
+void Http2Session::on_connect() {
+ int rv;
+
+ // This is required with --disable-assert.
+ (void)rv;
+
+ nghttp2_session_callbacks *callbacks;
+
+ nghttp2_session_callbacks_new(&callbacks);
+
+ auto callbacks_deleter = defer(nghttp2_session_callbacks_del, callbacks);
+
+ nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
+ on_frame_recv_callback);
+
+ nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
+ callbacks, on_data_chunk_recv_callback);
+
+ nghttp2_session_callbacks_set_on_stream_close_callback(
+ callbacks, on_stream_close_callback);
+
+ nghttp2_session_callbacks_set_on_header_callback(callbacks,
+ on_header_callback);
+
+ nghttp2_session_callbacks_set_before_frame_send_callback(
+ callbacks, before_frame_send_callback);
+
+ nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
+
+ nghttp2_option *opt;
+
+ rv = nghttp2_option_new(&opt);
+ assert(rv == 0);
+
+ auto config = client_->worker->config;
+
+ if (config->encoder_header_table_size != NGHTTP2_DEFAULT_HEADER_TABLE_SIZE) {
+ nghttp2_option_set_max_deflate_dynamic_table_size(
+ opt, config->encoder_header_table_size);
+ }
+
+ nghttp2_session_client_new2(&session_, callbacks, client_, opt);
+
+ nghttp2_option_del(opt);
+
+ std::array<nghttp2_settings_entry, 4> iv;
+ size_t niv = 2;
+ iv[0].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
+ iv[0].value = 0;
+ iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+ iv[1].value = (1 << config->window_bits) - 1;
+
+ if (config->header_table_size != NGHTTP2_DEFAULT_HEADER_TABLE_SIZE) {
+ iv[niv].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
+ iv[niv].value = config->header_table_size;
+ ++niv;
+ }
+ if (config->max_frame_size != 16_k) {
+ iv[niv].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE;
+ iv[niv].value = config->max_frame_size;
+ ++niv;
+ }
+
+ rv = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, iv.data(), niv);
+
+ assert(rv == 0);
+
+ auto connection_window = (1 << config->connection_window_bits) - 1;
+ nghttp2_session_set_local_window_size(session_, NGHTTP2_FLAG_NONE, 0,
+ connection_window);
+
+ client_->signal_write();
+}
+
+int Http2Session::submit_request() {
+ if (nghttp2_session_check_request_allowed(session_) == 0) {
+ return -1;
+ }
+
+ auto config = client_->worker->config;
+ auto &nva = config->nva[client_->reqidx++];
+
+ if (client_->reqidx == config->nva.size()) {
+ client_->reqidx = 0;
+ }
+
+ nghttp2_data_provider prd{{0}, file_read_callback};
+
+ auto stream_id =
+ nghttp2_submit_request(session_, nullptr, nva.data(), nva.size(),
+ config->data_fd == -1 ? nullptr : &prd, nullptr);
+ if (stream_id < 0) {
+ return -1;
+ }
+
+ client_->on_request(stream_id);
+
+ return 0;
+}
+
+int Http2Session::on_read(const uint8_t *data, size_t len) {
+ auto rv = nghttp2_session_mem_recv(session_, data, len);
+ if (rv < 0) {
+ return -1;
+ }
+
+ assert(static_cast<size_t>(rv) == len);
+
+ if (nghttp2_session_want_read(session_) == 0 &&
+ nghttp2_session_want_write(session_) == 0 && client_->wb.rleft() == 0) {
+ return -1;
+ }
+
+ client_->signal_write();
+
+ return 0;
+}
+
+int Http2Session::on_write() {
+ auto rv = nghttp2_session_send(session_);
+ if (rv != 0) {
+ return -1;
+ }
+
+ if (nghttp2_session_want_read(session_) == 0 &&
+ nghttp2_session_want_write(session_) == 0 && client_->wb.rleft() == 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+void Http2Session::terminate() {
+ nghttp2_session_terminate_session(session_, NGHTTP2_NO_ERROR);
+}
+
+size_t Http2Session::max_concurrent_streams() {
+ return (size_t)client_->worker->config->max_concurrent_streams;
+}
+
+} // namespace h2load
diff --git a/src/h2load_http2_session.h b/src/h2load_http2_session.h
new file mode 100644
index 0000000..1b9b9f7
--- /dev/null
+++ b/src/h2load_http2_session.h
@@ -0,0 +1,54 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef H2LOAD_HTTP2_SESSION_H
+#define H2LOAD_HTTP2_SESSION_H
+
+#include "h2load_session.h"
+
+#include <nghttp2/nghttp2.h>
+
+namespace h2load {
+
+struct Client;
+
+class Http2Session : public Session {
+public:
+ Http2Session(Client *client);
+ virtual ~Http2Session();
+ virtual void on_connect();
+ virtual int submit_request();
+ virtual int on_read(const uint8_t *data, size_t len);
+ virtual int on_write();
+ virtual void terminate();
+ virtual size_t max_concurrent_streams();
+
+private:
+ Client *client_;
+ nghttp2_session *session_;
+};
+
+} // namespace h2load
+
+#endif // H2LOAD_HTTP2_SESSION_H
diff --git a/src/h2load_http3_session.cc b/src/h2load_http3_session.cc
new file mode 100644
index 0000000..d491cba
--- /dev/null
+++ b/src/h2load_http3_session.cc
@@ -0,0 +1,474 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2019 nghttp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "h2load_http3_session.h"
+
+#include <iostream>
+
+#include <ngtcp2/ngtcp2.h>
+
+#include "h2load.h"
+
+namespace h2load {
+
+Http3Session::Http3Session(Client *client)
+ : client_(client), conn_(nullptr), npending_request_(0), reqidx_(0) {}
+
+Http3Session::~Http3Session() { nghttp3_conn_del(conn_); }
+
+void Http3Session::on_connect() {}
+
+int Http3Session::submit_request() {
+ if (npending_request_) {
+ ++npending_request_;
+ return 0;
+ }
+
+ auto config = client_->worker->config;
+ reqidx_ = client_->reqidx;
+
+ if (++client_->reqidx == config->nva.size()) {
+ client_->reqidx = 0;
+ }
+
+ auto stream_id = submit_request_internal();
+ if (stream_id < 0) {
+ if (stream_id == NGTCP2_ERR_STREAM_ID_BLOCKED) {
+ ++npending_request_;
+ return 0;
+ }
+ return -1;
+ }
+
+ return 0;
+}
+
+namespace {
+nghttp3_ssize read_data(nghttp3_conn *conn, int64_t stream_id, nghttp3_vec *vec,
+ size_t veccnt, uint32_t *pflags, void *user_data,
+ void *stream_user_data) {
+ auto s = static_cast<Http3Session *>(user_data);
+
+ s->read_data(vec, veccnt, pflags);
+
+ return 1;
+}
+} // namespace
+
+void Http3Session::read_data(nghttp3_vec *vec, size_t veccnt,
+ uint32_t *pflags) {
+ assert(veccnt > 0);
+
+ auto config = client_->worker->config;
+
+ vec[0].base = config->data;
+ vec[0].len = config->data_length;
+ *pflags |= NGHTTP3_DATA_FLAG_EOF;
+}
+
+int64_t Http3Session::submit_request_internal() {
+ int rv;
+ int64_t stream_id;
+
+ auto config = client_->worker->config;
+ auto &nva = config->nva[reqidx_];
+
+ rv = ngtcp2_conn_open_bidi_stream(client_->quic.conn, &stream_id, nullptr);
+ if (rv != 0) {
+ return rv;
+ }
+
+ nghttp3_data_reader dr{};
+ dr.read_data = h2load::read_data;
+
+ rv = nghttp3_conn_submit_request(
+ conn_, stream_id, reinterpret_cast<nghttp3_nv *>(nva.data()), nva.size(),
+ config->data_fd == -1 ? nullptr : &dr, nullptr);
+ if (rv != 0) {
+ return rv;
+ }
+
+ client_->on_request(stream_id);
+ auto req_stat = client_->get_req_stat(stream_id);
+ assert(req_stat);
+ client_->record_request_time(req_stat);
+
+ return stream_id;
+}
+
+int Http3Session::on_read(const uint8_t *data, size_t len) { return -1; }
+
+int Http3Session::on_write() { return -1; }
+
+void Http3Session::terminate() {}
+
+size_t Http3Session::max_concurrent_streams() {
+ return (size_t)client_->worker->config->max_concurrent_streams;
+}
+
+namespace {
+int stream_close(nghttp3_conn *conn, int64_t stream_id, uint64_t app_error_code,
+ void *user_data, void *stream_user_data) {
+ auto s = static_cast<Http3Session *>(user_data);
+ if (s->stream_close(stream_id, app_error_code) != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+int Http3Session::stream_close(int64_t stream_id, uint64_t app_error_code) {
+ if (!ngtcp2_is_bidi_stream(stream_id)) {
+ assert(!ngtcp2_conn_is_local_stream(client_->quic.conn, stream_id));
+ ngtcp2_conn_extend_max_streams_uni(client_->quic.conn, 1);
+ }
+ client_->on_stream_close(stream_id, app_error_code == NGHTTP3_H3_NO_ERROR);
+ return 0;
+}
+
+namespace {
+int end_stream(nghttp3_conn *conn, int64_t stream_id, void *user_data,
+ void *stream_user_data) {
+ auto s = static_cast<Http3Session *>(user_data);
+ if (s->end_stream(stream_id) != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+int Http3Session::end_stream(int64_t stream_id) {
+ client_->record_ttfb();
+
+ return 0;
+}
+
+namespace {
+int recv_data(nghttp3_conn *conn, int64_t stream_id, const uint8_t *data,
+ size_t datalen, void *user_data, void *stream_user_data) {
+ auto s = static_cast<Http3Session *>(user_data);
+ s->recv_data(stream_id, data, datalen);
+ return 0;
+}
+} // namespace
+
+void Http3Session::recv_data(int64_t stream_id, const uint8_t *data,
+ size_t datalen) {
+ client_->record_ttfb();
+ client_->worker->stats.bytes_body += datalen;
+ consume(stream_id, datalen);
+}
+
+namespace {
+int deferred_consume(nghttp3_conn *conn, int64_t stream_id, size_t nconsumed,
+ void *user_data, void *stream_user_data) {
+ auto s = static_cast<Http3Session *>(user_data);
+ s->consume(stream_id, nconsumed);
+ return 0;
+}
+} // namespace
+
+void Http3Session::consume(int64_t stream_id, size_t nconsumed) {
+ ngtcp2_conn_extend_max_stream_offset(client_->quic.conn, stream_id,
+ nconsumed);
+ ngtcp2_conn_extend_max_offset(client_->quic.conn, nconsumed);
+}
+
+namespace {
+int begin_headers(nghttp3_conn *conn, int64_t stream_id, void *user_data,
+ void *stream_user_data) {
+ auto s = static_cast<Http3Session *>(user_data);
+ s->begin_headers(stream_id);
+ return 0;
+}
+} // namespace
+
+void Http3Session::begin_headers(int64_t stream_id) {
+ auto payloadlen = nghttp3_conn_get_frame_payload_left(conn_, stream_id);
+ assert(payloadlen > 0);
+
+ client_->worker->stats.bytes_head += payloadlen;
+}
+
+namespace {
+int recv_header(nghttp3_conn *conn, int64_t stream_id, int32_t token,
+ nghttp3_rcbuf *name, nghttp3_rcbuf *value, uint8_t flags,
+ void *user_data, void *stream_user_data) {
+ auto s = static_cast<Http3Session *>(user_data);
+ auto k = nghttp3_rcbuf_get_buf(name);
+ auto v = nghttp3_rcbuf_get_buf(value);
+ s->recv_header(stream_id, &k, &v);
+ return 0;
+}
+} // namespace
+
+void Http3Session::recv_header(int64_t stream_id, const nghttp3_vec *name,
+ const nghttp3_vec *value) {
+ client_->on_header(stream_id, name->base, name->len, value->base, value->len);
+ client_->worker->stats.bytes_head_decomp += name->len + value->len;
+}
+
+namespace {
+int stop_sending(nghttp3_conn *conn, int64_t stream_id, uint64_t app_error_code,
+ void *user_data, void *stream_user_data) {
+ auto s = static_cast<Http3Session *>(user_data);
+ if (s->stop_sending(stream_id, app_error_code) != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+int Http3Session::stop_sending(int64_t stream_id, uint64_t app_error_code) {
+ auto rv = ngtcp2_conn_shutdown_stream_read(client_->quic.conn, 0, stream_id,
+ app_error_code);
+ if (rv != 0) {
+ std::cerr << "ngtcp2_conn_shutdown_stream_read: " << ngtcp2_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+ return 0;
+}
+
+namespace {
+int reset_stream(nghttp3_conn *conn, int64_t stream_id, uint64_t app_error_code,
+ void *user_data, void *stream_user_data) {
+ auto s = static_cast<Http3Session *>(user_data);
+ if (s->reset_stream(stream_id, app_error_code) != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+int Http3Session::reset_stream(int64_t stream_id, uint64_t app_error_code) {
+ auto rv = ngtcp2_conn_shutdown_stream_write(client_->quic.conn, 0, stream_id,
+ app_error_code);
+ if (rv != 0) {
+ std::cerr << "ngtcp2_conn_shutdown_stream_write: " << ngtcp2_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+ return 0;
+}
+
+int Http3Session::close_stream(int64_t stream_id, uint64_t app_error_code) {
+ auto rv = nghttp3_conn_close_stream(conn_, stream_id, app_error_code);
+ switch (rv) {
+ case 0:
+ return 0;
+ case NGHTTP3_ERR_STREAM_NOT_FOUND:
+ if (!ngtcp2_is_bidi_stream(stream_id)) {
+ assert(!ngtcp2_conn_is_local_stream(client_->quic.conn, stream_id));
+ ngtcp2_conn_extend_max_streams_uni(client_->quic.conn, 1);
+ }
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+int Http3Session::shutdown_stream_read(int64_t stream_id) {
+ auto rv = nghttp3_conn_shutdown_stream_read(conn_, stream_id);
+ if (rv != 0) {
+ return -1;
+ }
+ return 0;
+}
+
+int Http3Session::extend_max_local_streams() {
+ auto config = client_->worker->config;
+
+ for (; npending_request_; --npending_request_) {
+ auto stream_id = submit_request_internal();
+ if (stream_id < 0) {
+ if (stream_id == NGTCP2_ERR_STREAM_ID_BLOCKED) {
+ return 0;
+ }
+ return -1;
+ }
+
+ if (++reqidx_ == config->nva.size()) {
+ reqidx_ = 0;
+ }
+ }
+
+ return 0;
+}
+
+int Http3Session::init_conn() {
+ int rv;
+
+ assert(conn_ == nullptr);
+
+ if (ngtcp2_conn_get_streams_uni_left(client_->quic.conn) < 3) {
+ return -1;
+ }
+
+ nghttp3_callbacks callbacks{
+ nullptr, // acked_stream_data
+ h2load::stream_close,
+ h2load::recv_data,
+ h2load::deferred_consume,
+ h2load::begin_headers,
+ h2load::recv_header,
+ nullptr, // end_headers
+ nullptr, // begin_trailers
+ h2load::recv_header,
+ nullptr, // end_trailers
+ h2load::stop_sending,
+ h2load::end_stream,
+ h2load::reset_stream,
+ nullptr, // shutdown
+ };
+
+ auto config = client_->worker->config;
+
+ nghttp3_settings settings;
+ nghttp3_settings_default(&settings);
+ settings.qpack_max_dtable_capacity = config->header_table_size;
+ settings.qpack_blocked_streams = 100;
+
+ auto mem = nghttp3_mem_default();
+
+ rv = nghttp3_conn_client_new(&conn_, &callbacks, &settings, mem, this);
+ if (rv != 0) {
+ std::cerr << "nghttp3_conn_client_new: " << nghttp3_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ int64_t ctrl_stream_id;
+
+ rv =
+ ngtcp2_conn_open_uni_stream(client_->quic.conn, &ctrl_stream_id, nullptr);
+ if (rv != 0) {
+ std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ rv = nghttp3_conn_bind_control_stream(conn_, ctrl_stream_id);
+ if (rv != 0) {
+ std::cerr << "nghttp3_conn_bind_control_stream: " << nghttp3_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ int64_t qpack_enc_stream_id, qpack_dec_stream_id;
+
+ rv = ngtcp2_conn_open_uni_stream(client_->quic.conn, &qpack_enc_stream_id,
+ nullptr);
+ if (rv != 0) {
+ std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ rv = ngtcp2_conn_open_uni_stream(client_->quic.conn, &qpack_dec_stream_id,
+ nullptr);
+ if (rv != 0) {
+ std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ rv = nghttp3_conn_bind_qpack_streams(conn_, qpack_enc_stream_id,
+ qpack_dec_stream_id);
+ if (rv != 0) {
+ std::cerr << "nghttp3_conn_bind_qpack_streams: " << nghttp3_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ return 0;
+}
+
+ssize_t Http3Session::read_stream(uint32_t flags, int64_t stream_id,
+ const uint8_t *data, size_t datalen) {
+ auto nconsumed = nghttp3_conn_read_stream(
+ conn_, stream_id, data, datalen, flags & NGTCP2_STREAM_DATA_FLAG_FIN);
+ if (nconsumed < 0) {
+ std::cerr << "nghttp3_conn_read_stream: " << nghttp3_strerror(nconsumed)
+ << std::endl;
+ ngtcp2_ccerr_set_application_error(
+ &client_->quic.last_error,
+ nghttp3_err_infer_quic_app_error_code(nconsumed), nullptr, 0);
+ return -1;
+ }
+ return nconsumed;
+}
+
+ssize_t Http3Session::write_stream(int64_t &stream_id, int &fin,
+ nghttp3_vec *vec, size_t veccnt) {
+ auto sveccnt =
+ nghttp3_conn_writev_stream(conn_, &stream_id, &fin, vec, veccnt);
+ if (sveccnt < 0) {
+ ngtcp2_ccerr_set_application_error(
+ &client_->quic.last_error,
+ nghttp3_err_infer_quic_app_error_code(sveccnt), nullptr, 0);
+ return -1;
+ }
+ return sveccnt;
+}
+
+void Http3Session::block_stream(int64_t stream_id) {
+ nghttp3_conn_block_stream(conn_, stream_id);
+}
+
+int Http3Session::unblock_stream(int64_t stream_id) {
+ if (nghttp3_conn_unblock_stream(conn_, stream_id) != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+void Http3Session::shutdown_stream_write(int64_t stream_id) {
+ nghttp3_conn_shutdown_stream_write(conn_, stream_id);
+}
+
+int Http3Session::add_write_offset(int64_t stream_id, size_t ndatalen) {
+ auto rv = nghttp3_conn_add_write_offset(conn_, stream_id, ndatalen);
+ if (rv != 0) {
+ ngtcp2_ccerr_set_application_error(
+ &client_->quic.last_error, nghttp3_err_infer_quic_app_error_code(rv),
+ nullptr, 0);
+ return -1;
+ }
+ return 0;
+}
+
+int Http3Session::add_ack_offset(int64_t stream_id, size_t datalen) {
+ auto rv = nghttp3_conn_add_ack_offset(conn_, stream_id, datalen);
+ if (rv != 0) {
+ ngtcp2_ccerr_set_application_error(
+ &client_->quic.last_error, nghttp3_err_infer_quic_app_error_code(rv),
+ nullptr, 0);
+ return -1;
+ }
+ return 0;
+}
+
+} // namespace h2load
diff --git a/src/h2load_http3_session.h b/src/h2load_http3_session.h
new file mode 100644
index 0000000..8610417
--- /dev/null
+++ b/src/h2load_http3_session.h
@@ -0,0 +1,84 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2019 nghttp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef H2LOAD_HTTP3_SESSION_H
+#define H2LOAD_HTTP3_SESSION_H
+
+#include "h2load_session.h"
+
+#include <nghttp3/nghttp3.h>
+
+namespace h2load {
+
+struct Client;
+
+class Http3Session : public Session {
+public:
+ Http3Session(Client *client);
+ virtual ~Http3Session();
+ virtual void on_connect();
+ virtual int submit_request();
+ virtual int on_read(const uint8_t *data, size_t len);
+ virtual int on_write();
+ virtual void terminate();
+ virtual size_t max_concurrent_streams();
+
+ int init_conn();
+ int stream_close(int64_t stream_id, uint64_t app_error_code);
+ int end_stream(int64_t stream_id);
+ void recv_data(int64_t stream_id, const uint8_t *data, size_t datalen);
+ void consume(int64_t stream_id, size_t nconsumed);
+ void begin_headers(int64_t stream_id);
+ void recv_header(int64_t stream_id, const nghttp3_vec *name,
+ const nghttp3_vec *value);
+ int stop_sending(int64_t stream_id, uint64_t app_error_code);
+ int reset_stream(int64_t stream_id, uint64_t app_error_code);
+
+ int close_stream(int64_t stream_id, uint64_t app_error_code);
+ int shutdown_stream_read(int64_t stream_id);
+ int extend_max_local_streams();
+ int64_t submit_request_internal();
+
+ ssize_t read_stream(uint32_t flags, int64_t stream_id, const uint8_t *data,
+ size_t datalen);
+ ssize_t write_stream(int64_t &stream_id, int &fin, nghttp3_vec *vec,
+ size_t veccnt);
+ void block_stream(int64_t stream_id);
+ int unblock_stream(int64_t stream_id);
+ void shutdown_stream_write(int64_t stream_id);
+ int add_write_offset(int64_t stream_id, size_t ndatalen);
+ int add_ack_offset(int64_t stream_id, size_t datalen);
+
+ void read_data(nghttp3_vec *vec, size_t veccnt, uint32_t *pflags);
+
+private:
+ Client *client_;
+ nghttp3_conn *conn_;
+ size_t npending_request_;
+ size_t reqidx_;
+};
+
+} // namespace h2load
+
+#endif // H2LOAD_HTTP3_SESSION_H
diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc
new file mode 100644
index 0000000..e492a3e
--- /dev/null
+++ b/src/h2load_quic.cc
@@ -0,0 +1,844 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2019 nghttp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "h2load_quic.h"
+
+#include <netinet/udp.h>
+
+#include <iostream>
+
+#ifdef HAVE_LIBNGTCP2_CRYPTO_QUICTLS
+# include <ngtcp2/ngtcp2_crypto_quictls.h>
+#endif // HAVE_LIBNGTCP2_CRYPTO_QUICTLS
+#ifdef HAVE_LIBNGTCP2_CRYPTO_BORINGSSL
+# include <ngtcp2/ngtcp2_crypto_boringssl.h>
+#endif // HAVE_LIBNGTCP2_CRYPTO_BORINGSSL
+
+#include <openssl/err.h>
+#include <openssl/rand.h>
+
+#include "h2load_http3_session.h"
+
+namespace h2load {
+
+namespace {
+int handshake_completed(ngtcp2_conn *conn, void *user_data) {
+ auto c = static_cast<Client *>(user_data);
+
+ if (c->quic_handshake_completed() != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Client::quic_handshake_completed() { return connection_made(); }
+
+namespace {
+int recv_stream_data(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id,
+ uint64_t offset, const uint8_t *data, size_t datalen,
+ void *user_data, void *stream_user_data) {
+ auto c = static_cast<Client *>(user_data);
+ if (c->quic_recv_stream_data(flags, stream_id, data, datalen) != 0) {
+ // TODO Better to do this gracefully rather than
+ // NGTCP2_ERR_CALLBACK_FAILURE. Perhaps, call
+ // ngtcp2_conn_write_application_close() ?
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+int Client::quic_recv_stream_data(uint32_t flags, int64_t stream_id,
+ const uint8_t *data, size_t datalen) {
+ if (worker->current_phase == Phase::MAIN_DURATION) {
+ worker->stats.bytes_total += datalen;
+ }
+
+ auto s = static_cast<Http3Session *>(session.get());
+ auto nconsumed = s->read_stream(flags, stream_id, data, datalen);
+ if (nconsumed == -1) {
+ return -1;
+ }
+
+ ngtcp2_conn_extend_max_stream_offset(quic.conn, stream_id, nconsumed);
+ ngtcp2_conn_extend_max_offset(quic.conn, nconsumed);
+
+ return 0;
+}
+
+namespace {
+int acked_stream_data_offset(ngtcp2_conn *conn, int64_t stream_id,
+ uint64_t offset, uint64_t datalen, void *user_data,
+ void *stream_user_data) {
+ auto c = static_cast<Client *>(user_data);
+ if (c->quic_acked_stream_data_offset(stream_id, datalen) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+int Client::quic_acked_stream_data_offset(int64_t stream_id, size_t datalen) {
+ auto s = static_cast<Http3Session *>(session.get());
+ if (s->add_ack_offset(stream_id, datalen) != 0) {
+ return -1;
+ }
+ return 0;
+}
+
+namespace {
+int stream_close(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data) {
+ auto c = static_cast<Client *>(user_data);
+
+ if (!(flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)) {
+ app_error_code = NGHTTP3_H3_NO_ERROR;
+ }
+
+ if (c->quic_stream_close(stream_id, app_error_code) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+int Client::quic_stream_close(int64_t stream_id, uint64_t app_error_code) {
+ auto s = static_cast<Http3Session *>(session.get());
+ if (s->close_stream(stream_id, app_error_code) != 0) {
+ return -1;
+ }
+ return 0;
+}
+
+namespace {
+int stream_reset(ngtcp2_conn *conn, int64_t stream_id, uint64_t final_size,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data) {
+ auto c = static_cast<Client *>(user_data);
+ if (c->quic_stream_reset(stream_id, app_error_code) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+int Client::quic_stream_reset(int64_t stream_id, uint64_t app_error_code) {
+ auto s = static_cast<Http3Session *>(session.get());
+ if (s->shutdown_stream_read(stream_id) != 0) {
+ return -1;
+ }
+ return 0;
+}
+
+namespace {
+int stream_stop_sending(ngtcp2_conn *conn, int64_t stream_id,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data) {
+ auto c = static_cast<Client *>(user_data);
+ if (c->quic_stream_stop_sending(stream_id, app_error_code) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+int Client::quic_stream_stop_sending(int64_t stream_id,
+ uint64_t app_error_code) {
+ auto s = static_cast<Http3Session *>(session.get());
+ if (s->shutdown_stream_read(stream_id) != 0) {
+ return -1;
+ }
+ return 0;
+}
+
+namespace {
+int extend_max_local_streams_bidi(ngtcp2_conn *conn, uint64_t max_streams,
+ void *user_data) {
+ auto c = static_cast<Client *>(user_data);
+
+ if (c->quic_extend_max_local_streams() != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Client::quic_extend_max_local_streams() {
+ auto s = static_cast<Http3Session *>(session.get());
+ if (s->extend_max_local_streams() != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+
+namespace {
+int extend_max_stream_data(ngtcp2_conn *conn, int64_t stream_id,
+ uint64_t max_data, void *user_data,
+ void *stream_user_data) {
+ auto c = static_cast<Client *>(user_data);
+
+ if (c->quic_extend_max_stream_data(stream_id) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Client::quic_extend_max_stream_data(int64_t stream_id) {
+ auto s = static_cast<Http3Session *>(session.get());
+ if (s->unblock_stream(stream_id) != 0) {
+ return -1;
+ }
+ return 0;
+}
+
+namespace {
+int get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, uint8_t *token,
+ size_t cidlen, void *user_data) {
+ if (RAND_bytes(cid->data, cidlen) != 1) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ cid->datalen = cidlen;
+
+ if (RAND_bytes(token, NGTCP2_STATELESS_RESET_TOKENLEN) != 1) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+void debug_log_printf(void *user_data, const char *fmt, ...) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+
+ fprintf(stderr, "\n");
+}
+} // namespace
+
+namespace {
+int generate_cid(ngtcp2_cid &dest) {
+ dest.datalen = 8;
+
+ if (RAND_bytes(dest.data, dest.datalen) != 1) {
+ return -1;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+ngtcp2_tstamp quic_timestamp() {
+ return std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::steady_clock::now().time_since_epoch())
+ .count();
+}
+} // namespace
+
+// qlog write callback -- excerpted from ngtcp2/examples/client_base.cc
+namespace {
+void qlog_write_cb(void *user_data, uint32_t flags, const void *data,
+ size_t datalen) {
+ auto c = static_cast<Client *>(user_data);
+ c->quic_write_qlog(data, datalen);
+}
+} // namespace
+
+void Client::quic_write_qlog(const void *data, size_t datalen) {
+ assert(quic.qlog_file != nullptr);
+ fwrite(data, 1, datalen, quic.qlog_file);
+}
+
+namespace {
+void rand(uint8_t *dest, size_t destlen, const ngtcp2_rand_ctx *rand_ctx) {
+ util::random_bytes(dest, dest + destlen,
+ *static_cast<std::mt19937 *>(rand_ctx->native_handle));
+}
+} // namespace
+
+namespace {
+int recv_rx_key(ngtcp2_conn *conn, ngtcp2_encryption_level level,
+ void *user_data) {
+ if (level != NGTCP2_ENCRYPTION_LEVEL_1RTT) {
+ return 0;
+ }
+
+ auto c = static_cast<Client *>(user_data);
+
+ if (c->quic_make_http3_session() != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Client::quic_make_http3_session() {
+ auto s = std::make_unique<Http3Session>(this);
+ if (s->init_conn() == -1) {
+ return -1;
+ }
+ session = std::move(s);
+
+ return 0;
+}
+
+namespace {
+ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) {
+ auto c = static_cast<Client *>(conn_ref->user_data);
+ return c->quic.conn;
+}
+} // namespace
+
+int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen,
+ const sockaddr *remote_addr, socklen_t remote_addrlen) {
+ int rv;
+
+ if (!ssl) {
+ ssl = SSL_new(worker->ssl_ctx);
+
+ quic.conn_ref.get_conn = get_conn;
+ quic.conn_ref.user_data = this;
+
+ SSL_set_app_data(ssl, &quic.conn_ref);
+ SSL_set_connect_state(ssl);
+ SSL_set_quic_use_legacy_codepoint(ssl, 0);
+ }
+
+ auto callbacks = ngtcp2_callbacks{
+ ngtcp2_crypto_client_initial_cb,
+ nullptr, // recv_client_initial
+ ngtcp2_crypto_recv_crypto_data_cb,
+ h2load::handshake_completed,
+ nullptr, // recv_version_negotiation
+ ngtcp2_crypto_encrypt_cb,
+ ngtcp2_crypto_decrypt_cb,
+ ngtcp2_crypto_hp_mask_cb,
+ h2load::recv_stream_data,
+ h2load::acked_stream_data_offset,
+ nullptr, // stream_open
+ h2load::stream_close,
+ nullptr, // recv_stateless_reset
+ ngtcp2_crypto_recv_retry_cb,
+ h2load::extend_max_local_streams_bidi,
+ nullptr, // extend_max_local_streams_uni
+ h2load::rand,
+ get_new_connection_id,
+ nullptr, // remove_connection_id
+ ngtcp2_crypto_update_key_cb,
+ nullptr, // path_validation
+ nullptr, // select_preferred_addr
+ h2load::stream_reset,
+ nullptr, // extend_max_remote_streams_bidi
+ nullptr, // extend_max_remote_streams_uni
+ h2load::extend_max_stream_data,
+ nullptr, // dcid_status
+ nullptr, // handshake_confirmed
+ nullptr, // recv_new_token
+ ngtcp2_crypto_delete_crypto_aead_ctx_cb,
+ ngtcp2_crypto_delete_crypto_cipher_ctx_cb,
+ nullptr, // recv_datagram
+ nullptr, // ack_datagram
+ nullptr, // lost_datagram
+ ngtcp2_crypto_get_path_challenge_data_cb,
+ h2load::stream_stop_sending,
+ nullptr, // version_negotiation
+ h2load::recv_rx_key,
+ nullptr, // recv_tx_key
+ };
+
+ ngtcp2_cid scid, dcid;
+ if (generate_cid(scid) != 0) {
+ return -1;
+ }
+ if (generate_cid(dcid) != 0) {
+ return -1;
+ }
+
+ auto config = worker->config;
+
+ ngtcp2_settings settings;
+ ngtcp2_settings_default(&settings);
+ if (config->verbose) {
+ settings.log_printf = debug_log_printf;
+ }
+ settings.initial_ts = quic_timestamp();
+ settings.rand_ctx.native_handle = &worker->randgen;
+ if (!config->qlog_file_base.empty()) {
+ assert(quic.qlog_file == nullptr);
+ auto path = config->qlog_file_base;
+ path += '.';
+ path += util::utos(worker->id);
+ path += '.';
+ path += util::utos(id);
+ path += ".sqlog";
+ quic.qlog_file = fopen(path.c_str(), "w");
+ if (quic.qlog_file == nullptr) {
+ std::cerr << "Failed to open a qlog file: " << path << std::endl;
+ return -1;
+ }
+ settings.qlog_write = qlog_write_cb;
+ }
+ if (config->max_udp_payload_size) {
+ settings.max_tx_udp_payload_size = config->max_udp_payload_size;
+ settings.no_tx_udp_payload_size_shaping = 1;
+ }
+
+ ngtcp2_transport_params params;
+ ngtcp2_transport_params_default(&params);
+ auto max_stream_data =
+ std::min((1 << 26) - 1, (1 << config->window_bits) - 1);
+ params.initial_max_stream_data_bidi_local = max_stream_data;
+ params.initial_max_stream_data_uni = max_stream_data;
+ params.initial_max_data = (1 << config->connection_window_bits) - 1;
+ params.initial_max_streams_bidi = 0;
+ params.initial_max_streams_uni = 100;
+ params.max_idle_timeout = 30 * NGTCP2_SECONDS;
+
+ auto path = ngtcp2_path{
+ {
+ const_cast<sockaddr *>(local_addr),
+ local_addrlen,
+ },
+ {
+ const_cast<sockaddr *>(remote_addr),
+ remote_addrlen,
+ },
+ };
+
+ assert(config->alpn_list.size());
+
+ uint32_t quic_version;
+
+ if (config->alpn_list[0] == NGHTTP3_ALPN_H3) {
+ quic_version = NGTCP2_PROTO_VER_V1;
+ } else {
+ quic_version = NGTCP2_PROTO_VER_MIN;
+ }
+
+ rv = ngtcp2_conn_client_new(&quic.conn, &dcid, &scid, &path, quic_version,
+ &callbacks, &settings, &params, nullptr, this);
+ if (rv != 0) {
+ return -1;
+ }
+
+ ngtcp2_conn_set_tls_native_handle(quic.conn, ssl);
+
+ return 0;
+}
+
+void Client::quic_free() {
+ ngtcp2_conn_del(quic.conn);
+ if (quic.qlog_file != nullptr) {
+ fclose(quic.qlog_file);
+ quic.qlog_file = nullptr;
+ }
+}
+
+void Client::quic_close_connection() {
+ if (!quic.conn) {
+ return;
+ }
+
+ std::array<uint8_t, NGTCP2_MAX_UDP_PAYLOAD_SIZE> buf;
+ ngtcp2_path_storage ps;
+ ngtcp2_path_storage_zero(&ps);
+
+ auto nwrite = ngtcp2_conn_write_connection_close(
+ quic.conn, &ps.path, nullptr, buf.data(), buf.size(), &quic.last_error,
+ quic_timestamp());
+
+ if (nwrite <= 0) {
+ return;
+ }
+
+ write_udp(reinterpret_cast<sockaddr *>(ps.path.remote.addr),
+ ps.path.remote.addrlen, buf.data(), nwrite, 0);
+}
+
+int Client::quic_write_client_handshake(ngtcp2_encryption_level level,
+ const uint8_t *data, size_t datalen) {
+ int rv;
+
+ assert(level < 2);
+
+ rv = ngtcp2_conn_submit_crypto_data(quic.conn, level, data, datalen);
+ if (rv != 0) {
+ std::cerr << "ngtcp2_conn_submit_crypto_data: " << ngtcp2_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ return 0;
+}
+
+void quic_pkt_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto c = static_cast<Client *>(w->data);
+
+ if (c->quic_pkt_timeout() != 0) {
+ c->fail();
+ c->worker->free_client(c);
+ delete c;
+ return;
+ }
+}
+
+int Client::quic_pkt_timeout() {
+ int rv;
+ auto now = quic_timestamp();
+
+ rv = ngtcp2_conn_handle_expiry(quic.conn, now);
+ if (rv != 0) {
+ ngtcp2_ccerr_set_liberr(&quic.last_error, rv, nullptr, 0);
+ return -1;
+ }
+
+ return write_quic();
+}
+
+void Client::quic_restart_pkt_timer() {
+ auto expiry = ngtcp2_conn_get_expiry(quic.conn);
+ auto now = quic_timestamp();
+ auto t = expiry > now ? static_cast<ev_tstamp>(expiry - now) / NGTCP2_SECONDS
+ : 1e-9;
+ quic.pkt_timer.repeat = t;
+ ev_timer_again(worker->loop, &quic.pkt_timer);
+}
+
+int Client::read_quic() {
+ std::array<uint8_t, 65535> buf;
+ sockaddr_union su;
+ int rv;
+ size_t pktcnt = 0;
+ ngtcp2_pkt_info pi{};
+
+ iovec msg_iov;
+ msg_iov.iov_base = buf.data();
+ msg_iov.iov_len = buf.size();
+
+ msghdr msg{};
+ msg.msg_name = &su;
+ msg.msg_iov = &msg_iov;
+ msg.msg_iovlen = 1;
+
+ uint8_t msg_ctrl[CMSG_SPACE(sizeof(uint16_t))];
+ msg.msg_control = msg_ctrl;
+
+ auto ts = quic_timestamp();
+
+ for (;;) {
+ msg.msg_namelen = sizeof(su);
+ msg.msg_controllen = sizeof(msg_ctrl);
+
+ auto nread = recvmsg(fd, &msg, 0);
+ if (nread == -1) {
+ return 0;
+ }
+
+ auto gso_size = util::msghdr_get_udp_gro(&msg);
+ if (gso_size == 0) {
+ gso_size = static_cast<size_t>(nread);
+ }
+
+ assert(quic.conn);
+
+ ++worker->stats.udp_dgram_recv;
+
+ auto path = ngtcp2_path{
+ {
+ &local_addr.su.sa,
+ static_cast<socklen_t>(local_addr.len),
+ },
+ {
+ &su.sa,
+ msg.msg_namelen,
+ },
+ };
+
+ auto data = buf.data();
+
+ for (;;) {
+ auto datalen = std::min(static_cast<size_t>(nread), gso_size);
+
+ ++pktcnt;
+
+ rv = ngtcp2_conn_read_pkt(quic.conn, &path, &pi, data, datalen, ts);
+ if (rv != 0) {
+ if (!quic.last_error.error_code) {
+ if (rv == NGTCP2_ERR_CRYPTO) {
+ ngtcp2_ccerr_set_tls_alert(&quic.last_error,
+ ngtcp2_conn_get_tls_alert(quic.conn),
+ nullptr, 0);
+ } else {
+ ngtcp2_ccerr_set_liberr(&quic.last_error, rv, nullptr, 0);
+ }
+ }
+
+ return -1;
+ }
+
+ nread -= datalen;
+ if (nread == 0) {
+ break;
+ }
+
+ data += datalen;
+ }
+
+ if (pktcnt >= 100) {
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int Client::write_quic() {
+ int rv;
+
+ ev_io_stop(worker->loop, &wev);
+
+ if (quic.close_requested) {
+ return -1;
+ }
+
+ if (quic.tx.send_blocked) {
+ rv = send_blocked_packet();
+ if (rv != 0) {
+ return -1;
+ }
+
+ if (quic.tx.send_blocked) {
+ return 0;
+ }
+ }
+
+ std::array<nghttp3_vec, 16> vec;
+ size_t pktcnt = 0;
+ auto max_udp_payload_size =
+ ngtcp2_conn_get_max_tx_udp_payload_size(quic.conn);
+#ifdef UDP_SEGMENT
+ auto path_max_udp_payload_size =
+ ngtcp2_conn_get_path_max_tx_udp_payload_size(quic.conn);
+#endif // UDP_SEGMENT
+ auto max_pktcnt =
+ ngtcp2_conn_get_send_quantum(quic.conn) / max_udp_payload_size;
+ uint8_t *bufpos = quic.tx.data.get();
+ ngtcp2_path_storage ps;
+ size_t gso_size = 0;
+
+ ngtcp2_path_storage_zero(&ps);
+
+ auto s = static_cast<Http3Session *>(session.get());
+ auto ts = quic_timestamp();
+
+ for (;;) {
+ int64_t stream_id = -1;
+ int fin = 0;
+ ssize_t sveccnt = 0;
+
+ if (session && ngtcp2_conn_get_max_data_left(quic.conn)) {
+ sveccnt = s->write_stream(stream_id, fin, vec.data(), vec.size());
+ if (sveccnt == -1) {
+ return -1;
+ }
+ }
+
+ ngtcp2_ssize ndatalen;
+ auto v = vec.data();
+ auto vcnt = static_cast<size_t>(sveccnt);
+
+ uint32_t flags = NGTCP2_WRITE_STREAM_FLAG_MORE;
+ if (fin) {
+ flags |= NGTCP2_WRITE_STREAM_FLAG_FIN;
+ }
+
+ auto nwrite = ngtcp2_conn_writev_stream(
+ quic.conn, &ps.path, nullptr, bufpos, max_udp_payload_size, &ndatalen,
+ flags, stream_id, reinterpret_cast<const ngtcp2_vec *>(v), vcnt, ts);
+ if (nwrite < 0) {
+ switch (nwrite) {
+ case NGTCP2_ERR_STREAM_DATA_BLOCKED:
+ assert(ndatalen == -1);
+ s->block_stream(stream_id);
+ continue;
+ case NGTCP2_ERR_STREAM_SHUT_WR:
+ assert(ndatalen == -1);
+ s->shutdown_stream_write(stream_id);
+ continue;
+ case NGTCP2_ERR_WRITE_MORE:
+ assert(ndatalen >= 0);
+ if (s->add_write_offset(stream_id, ndatalen) != 0) {
+ return -1;
+ }
+ continue;
+ }
+
+ ngtcp2_ccerr_set_liberr(&quic.last_error, nwrite, nullptr, 0);
+ return -1;
+ } else if (ndatalen >= 0 && s->add_write_offset(stream_id, ndatalen) != 0) {
+ return -1;
+ }
+
+ quic_restart_pkt_timer();
+
+ if (nwrite == 0) {
+ if (bufpos - quic.tx.data.get()) {
+ auto data = quic.tx.data.get();
+ auto datalen = bufpos - quic.tx.data.get();
+ rv = write_udp(ps.path.remote.addr, ps.path.remote.addrlen, data,
+ datalen, gso_size);
+ if (rv == 1) {
+ on_send_blocked(ps.path.remote, data, datalen, gso_size);
+ signal_write();
+ return 0;
+ }
+ }
+ return 0;
+ }
+
+ bufpos += nwrite;
+
+#ifdef UDP_SEGMENT
+ if (worker->config->no_udp_gso) {
+#endif // UDP_SEGMENT
+ auto data = quic.tx.data.get();
+ auto datalen = bufpos - quic.tx.data.get();
+ rv = write_udp(ps.path.remote.addr, ps.path.remote.addrlen, data, datalen,
+ 0);
+ if (rv == 1) {
+ on_send_blocked(ps.path.remote, data, datalen, 0);
+ signal_write();
+ return 0;
+ }
+
+ if (++pktcnt == max_pktcnt) {
+ signal_write();
+ return 0;
+ }
+
+ bufpos = quic.tx.data.get();
+
+#ifdef UDP_SEGMENT
+ continue;
+ }
+#endif // UDP_SEGMENT
+
+#ifdef UDP_SEGMENT
+ if (pktcnt == 0) {
+ gso_size = nwrite;
+ } else if (static_cast<size_t>(nwrite) > gso_size ||
+ (gso_size > path_max_udp_payload_size &&
+ static_cast<size_t>(nwrite) != gso_size)) {
+ auto data = quic.tx.data.get();
+ auto datalen = bufpos - quic.tx.data.get() - nwrite;
+ rv = write_udp(ps.path.remote.addr, ps.path.remote.addrlen, data, datalen,
+ gso_size);
+ if (rv == 1) {
+ on_send_blocked(ps.path.remote, data, datalen, gso_size);
+ on_send_blocked(ps.path.remote, bufpos - nwrite, nwrite, 0);
+ } else {
+ auto data = bufpos - nwrite;
+ rv = write_udp(ps.path.remote.addr, ps.path.remote.addrlen, data,
+ nwrite, 0);
+ if (rv == 1) {
+ on_send_blocked(ps.path.remote, data, nwrite, 0);
+ }
+ }
+
+ signal_write();
+ return 0;
+ }
+
+ // Assume that the path does not change.
+ if (++pktcnt == max_pktcnt || static_cast<size_t>(nwrite) < gso_size) {
+ auto data = quic.tx.data.get();
+ auto datalen = bufpos - quic.tx.data.get();
+ rv = write_udp(ps.path.remote.addr, ps.path.remote.addrlen, data, datalen,
+ gso_size);
+ if (rv == 1) {
+ on_send_blocked(ps.path.remote, data, datalen, gso_size);
+ }
+ signal_write();
+ return 0;
+ }
+#endif // UDP_SEGMENT
+ }
+}
+
+void Client::on_send_blocked(const ngtcp2_addr &remote_addr,
+ const uint8_t *data, size_t datalen,
+ size_t gso_size) {
+ assert(quic.tx.num_blocked || !quic.tx.send_blocked);
+ assert(quic.tx.num_blocked < 2);
+
+ quic.tx.send_blocked = true;
+
+ auto &p = quic.tx.blocked[quic.tx.num_blocked++];
+
+ memcpy(&p.remote_addr.su, remote_addr.addr, remote_addr.addrlen);
+
+ p.remote_addr.len = remote_addr.addrlen;
+ p.data = data;
+ p.datalen = datalen;
+ p.gso_size = gso_size;
+}
+
+int Client::send_blocked_packet() {
+ int rv;
+
+ assert(quic.tx.send_blocked);
+
+ for (; quic.tx.num_blocked_sent < quic.tx.num_blocked;
+ ++quic.tx.num_blocked_sent) {
+ auto &p = quic.tx.blocked[quic.tx.num_blocked_sent];
+
+ rv = write_udp(&p.remote_addr.su.sa, p.remote_addr.len, p.data, p.datalen,
+ p.gso_size);
+ if (rv == 1) {
+ signal_write();
+
+ return 0;
+ }
+ }
+
+ quic.tx.send_blocked = false;
+ quic.tx.num_blocked = 0;
+ quic.tx.num_blocked_sent = 0;
+
+ return 0;
+}
+
+} // namespace h2load
diff --git a/src/h2load_quic.h b/src/h2load_quic.h
new file mode 100644
index 0000000..225f00f
--- /dev/null
+++ b/src/h2load_quic.h
@@ -0,0 +1,38 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2019 nghttp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef H2LOAD_QUIC_H
+#define H2LOAD_QUIC_H
+
+#include "nghttp2_config.h"
+
+#include <ev.h>
+
+#include "h2load.h"
+
+namespace h2load {
+void quic_pkt_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents);
+} // namespace h2load
+
+#endif // H2LOAD_QUIC_H
diff --git a/src/h2load_session.h b/src/h2load_session.h
new file mode 100644
index 0000000..ab3b8ec
--- /dev/null
+++ b/src/h2load_session.h
@@ -0,0 +1,59 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef H2LOAD_SESSION_H
+#define H2LOAD_SESSION_H
+
+#include "nghttp2_config.h"
+
+#include <sys/types.h>
+
+#include <cinttypes>
+
+#include "h2load.h"
+
+namespace h2load {
+
+class Session {
+public:
+ virtual ~Session() {}
+ // Called when the connection was made.
+ virtual void on_connect() = 0;
+ // Called when one request must be issued.
+ virtual int submit_request() = 0;
+ // Called when incoming bytes are available. The subclass has to
+ // return the number of bytes read.
+ virtual int on_read(const uint8_t *data, size_t len) = 0;
+ // Called when write is available. Returns 0 on success, otherwise
+ // return -1.
+ virtual int on_write() = 0;
+ // Called when the underlying session must be terminated.
+ virtual void terminate() = 0;
+ // Return the maximum concurrency per connection
+ virtual size_t max_concurrent_streams() = 0;
+};
+
+} // namespace h2load
+
+#endif // H2LOAD_SESSION_H
diff --git a/src/http-parser.patch b/src/http-parser.patch
new file mode 100644
index 0000000..ef80940
--- /dev/null
+++ b/src/http-parser.patch
@@ -0,0 +1,28 @@
+commit a143133d43420ef89e4ba0d84c73998863cf9f81
+Author: Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com>
+Date: Wed Jul 11 18:46:00 2012 +0900
+
+ Use http_parser for tunneling connection transparently
+
+diff --git a/examples/http-parser/http_parser.c b/examples/http-parser/http_parser.c
+index 0c11eb8..610da57 100644
+--- a/examples/http-parser/http_parser.c
++++ b/examples/http-parser/http_parser.c
+@@ -1627,9 +1627,14 @@ size_t http_parser_execute (http_parser *parser,
+
+ /* Exit, the rest of the connect is in a different protocol. */
+ if (parser->upgrade) {
+- parser->state = NEW_MESSAGE();
+- CALLBACK_NOTIFY(message_complete);
+- return (p - data) + 1;
++ /* We want to use http_parser for tunneling connection
++ transparently */
++ /* Read body until EOF */
++ parser->state = s_body_identity_eof;
++ break;
++ /* parser->state = NEW_MESSAGE(); */
++ /* CALLBACK_NOTIFY(message_complete); */
++ /* return (p - data) + 1; */
+ }
+
+ if (parser->flags & F_SKIPBODY) {
diff --git a/src/http2.cc b/src/http2.cc
new file mode 100644
index 0000000..661c4c9
--- /dev/null
+++ b/src/http2.cc
@@ -0,0 +1,2096 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "http2.h"
+
+#include "llhttp.h"
+
+#include "util.h"
+
+namespace nghttp2 {
+
+namespace http2 {
+
+StringRef get_reason_phrase(unsigned int status_code) {
+ switch (status_code) {
+ case 100:
+ return StringRef::from_lit("Continue");
+ case 101:
+ return StringRef::from_lit("Switching Protocols");
+ case 103:
+ return StringRef::from_lit("Early Hints");
+ case 200:
+ return StringRef::from_lit("OK");
+ case 201:
+ return StringRef::from_lit("Created");
+ case 202:
+ return StringRef::from_lit("Accepted");
+ case 203:
+ return StringRef::from_lit("Non-Authoritative Information");
+ case 204:
+ return StringRef::from_lit("No Content");
+ case 205:
+ return StringRef::from_lit("Reset Content");
+ case 206:
+ return StringRef::from_lit("Partial Content");
+ case 300:
+ return StringRef::from_lit("Multiple Choices");
+ case 301:
+ return StringRef::from_lit("Moved Permanently");
+ case 302:
+ return StringRef::from_lit("Found");
+ case 303:
+ return StringRef::from_lit("See Other");
+ case 304:
+ return StringRef::from_lit("Not Modified");
+ case 305:
+ return StringRef::from_lit("Use Proxy");
+ // case 306: return StringRef::from_lit("(Unused)");
+ case 307:
+ return StringRef::from_lit("Temporary Redirect");
+ case 308:
+ return StringRef::from_lit("Permanent Redirect");
+ case 400:
+ return StringRef::from_lit("Bad Request");
+ case 401:
+ return StringRef::from_lit("Unauthorized");
+ case 402:
+ return StringRef::from_lit("Payment Required");
+ case 403:
+ return StringRef::from_lit("Forbidden");
+ case 404:
+ return StringRef::from_lit("Not Found");
+ case 405:
+ return StringRef::from_lit("Method Not Allowed");
+ case 406:
+ return StringRef::from_lit("Not Acceptable");
+ case 407:
+ return StringRef::from_lit("Proxy Authentication Required");
+ case 408:
+ return StringRef::from_lit("Request Timeout");
+ case 409:
+ return StringRef::from_lit("Conflict");
+ case 410:
+ return StringRef::from_lit("Gone");
+ case 411:
+ return StringRef::from_lit("Length Required");
+ case 412:
+ return StringRef::from_lit("Precondition Failed");
+ case 413:
+ return StringRef::from_lit("Payload Too Large");
+ case 414:
+ return StringRef::from_lit("URI Too Long");
+ case 415:
+ return StringRef::from_lit("Unsupported Media Type");
+ case 416:
+ return StringRef::from_lit("Requested Range Not Satisfiable");
+ case 417:
+ return StringRef::from_lit("Expectation Failed");
+ case 421:
+ return StringRef::from_lit("Misdirected Request");
+ case 425:
+ // https://tools.ietf.org/html/rfc8470
+ return StringRef::from_lit("Too Early");
+ case 426:
+ return StringRef::from_lit("Upgrade Required");
+ case 428:
+ return StringRef::from_lit("Precondition Required");
+ case 429:
+ return StringRef::from_lit("Too Many Requests");
+ case 431:
+ return StringRef::from_lit("Request Header Fields Too Large");
+ case 451:
+ return StringRef::from_lit("Unavailable For Legal Reasons");
+ case 500:
+ return StringRef::from_lit("Internal Server Error");
+ case 501:
+ return StringRef::from_lit("Not Implemented");
+ case 502:
+ return StringRef::from_lit("Bad Gateway");
+ case 503:
+ return StringRef::from_lit("Service Unavailable");
+ case 504:
+ return StringRef::from_lit("Gateway Timeout");
+ case 505:
+ return StringRef::from_lit("HTTP Version Not Supported");
+ case 511:
+ return StringRef::from_lit("Network Authentication Required");
+ default:
+ return StringRef{};
+ }
+}
+
+StringRef stringify_status(BlockAllocator &balloc, unsigned int status_code) {
+ switch (status_code) {
+ case 100:
+ return StringRef::from_lit("100");
+ case 101:
+ return StringRef::from_lit("101");
+ case 103:
+ return StringRef::from_lit("103");
+ case 200:
+ return StringRef::from_lit("200");
+ case 201:
+ return StringRef::from_lit("201");
+ case 202:
+ return StringRef::from_lit("202");
+ case 203:
+ return StringRef::from_lit("203");
+ case 204:
+ return StringRef::from_lit("204");
+ case 205:
+ return StringRef::from_lit("205");
+ case 206:
+ return StringRef::from_lit("206");
+ case 300:
+ return StringRef::from_lit("300");
+ case 301:
+ return StringRef::from_lit("301");
+ case 302:
+ return StringRef::from_lit("302");
+ case 303:
+ return StringRef::from_lit("303");
+ case 304:
+ return StringRef::from_lit("304");
+ case 305:
+ return StringRef::from_lit("305");
+ // case 306: return StringRef::from_lit("306");
+ case 307:
+ return StringRef::from_lit("307");
+ case 308:
+ return StringRef::from_lit("308");
+ case 400:
+ return StringRef::from_lit("400");
+ case 401:
+ return StringRef::from_lit("401");
+ case 402:
+ return StringRef::from_lit("402");
+ case 403:
+ return StringRef::from_lit("403");
+ case 404:
+ return StringRef::from_lit("404");
+ case 405:
+ return StringRef::from_lit("405");
+ case 406:
+ return StringRef::from_lit("406");
+ case 407:
+ return StringRef::from_lit("407");
+ case 408:
+ return StringRef::from_lit("408");
+ case 409:
+ return StringRef::from_lit("409");
+ case 410:
+ return StringRef::from_lit("410");
+ case 411:
+ return StringRef::from_lit("411");
+ case 412:
+ return StringRef::from_lit("412");
+ case 413:
+ return StringRef::from_lit("413");
+ case 414:
+ return StringRef::from_lit("414");
+ case 415:
+ return StringRef::from_lit("415");
+ case 416:
+ return StringRef::from_lit("416");
+ case 417:
+ return StringRef::from_lit("417");
+ case 421:
+ return StringRef::from_lit("421");
+ case 426:
+ return StringRef::from_lit("426");
+ case 428:
+ return StringRef::from_lit("428");
+ case 429:
+ return StringRef::from_lit("429");
+ case 431:
+ return StringRef::from_lit("431");
+ case 451:
+ return StringRef::from_lit("451");
+ case 500:
+ return StringRef::from_lit("500");
+ case 501:
+ return StringRef::from_lit("501");
+ case 502:
+ return StringRef::from_lit("502");
+ case 503:
+ return StringRef::from_lit("503");
+ case 504:
+ return StringRef::from_lit("504");
+ case 505:
+ return StringRef::from_lit("505");
+ case 511:
+ return StringRef::from_lit("511");
+ default:
+ return util::make_string_ref_uint(balloc, status_code);
+ }
+}
+
+void capitalize(DefaultMemchunks *buf, const StringRef &s) {
+ buf->append(util::upcase(s[0]));
+ for (size_t i = 1; i < s.size(); ++i) {
+ if (s[i - 1] == '-') {
+ buf->append(util::upcase(s[i]));
+ } else {
+ buf->append(s[i]);
+ }
+ }
+}
+
+bool lws(const char *value) {
+ for (; *value; ++value) {
+ switch (*value) {
+ case '\t':
+ case ' ':
+ continue;
+ default:
+ return false;
+ }
+ }
+ return true;
+}
+
+void copy_url_component(std::string &dest, const http_parser_url *u, int field,
+ const char *url) {
+ if (u->field_set & (1 << field)) {
+ dest.assign(url + u->field_data[field].off, u->field_data[field].len);
+ }
+}
+
+Headers::value_type to_header(const uint8_t *name, size_t namelen,
+ const uint8_t *value, size_t valuelen,
+ bool no_index, int32_t token) {
+ return Header(std::string(reinterpret_cast<const char *>(name), namelen),
+ std::string(reinterpret_cast<const char *>(value), valuelen),
+ no_index, token);
+}
+
+void add_header(Headers &nva, const uint8_t *name, size_t namelen,
+ const uint8_t *value, size_t valuelen, bool no_index,
+ int32_t token) {
+ if (valuelen > 0) {
+ size_t i, j;
+ for (i = 0; i < valuelen && (value[i] == ' ' || value[i] == '\t'); ++i)
+ ;
+ for (j = valuelen - 1; j > i && (value[j] == ' ' || value[j] == '\t'); --j)
+ ;
+ value += i;
+ valuelen -= i + (valuelen - j - 1);
+ }
+ nva.push_back(to_header(name, namelen, value, valuelen, no_index, token));
+}
+
+const Headers::value_type *get_header(const Headers &nva, const char *name) {
+ const Headers::value_type *res = nullptr;
+ for (auto &nv : nva) {
+ if (nv.name == name) {
+ res = &nv;
+ }
+ }
+ return res;
+}
+
+bool non_empty_value(const HeaderRefs::value_type *nv) {
+ return nv && !nv->value.empty();
+}
+
+namespace {
+nghttp2_nv make_nv_internal(const std::string &name, const std::string &value,
+ bool no_index, uint8_t nv_flags) {
+ uint8_t flags;
+
+ flags =
+ nv_flags | (no_index ? NGHTTP2_NV_FLAG_NO_INDEX : NGHTTP2_NV_FLAG_NONE);
+
+ return {(uint8_t *)name.c_str(), (uint8_t *)value.c_str(), name.size(),
+ value.size(), flags};
+}
+} // namespace
+
+namespace {
+nghttp2_nv make_nv_internal(const StringRef &name, const StringRef &value,
+ bool no_index, uint8_t nv_flags) {
+ uint8_t flags;
+
+ flags =
+ nv_flags | (no_index ? NGHTTP2_NV_FLAG_NO_INDEX : NGHTTP2_NV_FLAG_NONE);
+
+ return {(uint8_t *)name.c_str(), (uint8_t *)value.c_str(), name.size(),
+ value.size(), flags};
+}
+} // namespace
+
+nghttp2_nv make_nv(const std::string &name, const std::string &value,
+ bool no_index) {
+ return make_nv_internal(name, value, no_index, NGHTTP2_NV_FLAG_NONE);
+}
+
+nghttp2_nv make_nv(const StringRef &name, const StringRef &value,
+ bool no_index) {
+ return make_nv_internal(name, value, no_index, NGHTTP2_NV_FLAG_NONE);
+}
+
+nghttp2_nv make_nv_nocopy(const std::string &name, const std::string &value,
+ bool no_index) {
+ return make_nv_internal(name, value, no_index,
+ NGHTTP2_NV_FLAG_NO_COPY_NAME |
+ NGHTTP2_NV_FLAG_NO_COPY_VALUE);
+}
+
+nghttp2_nv make_nv_nocopy(const StringRef &name, const StringRef &value,
+ bool no_index) {
+ return make_nv_internal(name, value, no_index,
+ NGHTTP2_NV_FLAG_NO_COPY_NAME |
+ NGHTTP2_NV_FLAG_NO_COPY_VALUE);
+}
+
+namespace {
+void copy_headers_to_nva_internal(std::vector<nghttp2_nv> &nva,
+ const HeaderRefs &headers, uint8_t nv_flags,
+ uint32_t flags) {
+ auto it_forwarded = std::end(headers);
+ auto it_xff = std::end(headers);
+ auto it_xfp = std::end(headers);
+ auto it_via = std::end(headers);
+
+ for (auto it = std::begin(headers); it != std::end(headers); ++it) {
+ auto kv = &(*it);
+ if (kv->name.empty() || kv->name[0] == ':') {
+ continue;
+ }
+ switch (kv->token) {
+ case HD_COOKIE:
+ case HD_CONNECTION:
+ case HD_HOST:
+ case HD_HTTP2_SETTINGS:
+ case HD_KEEP_ALIVE:
+ case HD_PROXY_CONNECTION:
+ case HD_SERVER:
+ case HD_TE:
+ case HD_TRANSFER_ENCODING:
+ case HD_UPGRADE:
+ continue;
+ case HD_EARLY_DATA:
+ if (flags & HDOP_STRIP_EARLY_DATA) {
+ continue;
+ }
+ break;
+ case HD_SEC_WEBSOCKET_ACCEPT:
+ if (flags & HDOP_STRIP_SEC_WEBSOCKET_ACCEPT) {
+ continue;
+ }
+ break;
+ case HD_SEC_WEBSOCKET_KEY:
+ if (flags & HDOP_STRIP_SEC_WEBSOCKET_KEY) {
+ continue;
+ }
+ break;
+ case HD_FORWARDED:
+ if (flags & HDOP_STRIP_FORWARDED) {
+ continue;
+ }
+
+ if (it_forwarded == std::end(headers)) {
+ it_forwarded = it;
+ continue;
+ }
+
+ kv = &(*it_forwarded);
+ it_forwarded = it;
+ break;
+ case HD_X_FORWARDED_FOR:
+ if (flags & HDOP_STRIP_X_FORWARDED_FOR) {
+ continue;
+ }
+
+ if (it_xff == std::end(headers)) {
+ it_xff = it;
+ continue;
+ }
+
+ kv = &(*it_xff);
+ it_xff = it;
+ break;
+ case HD_X_FORWARDED_PROTO:
+ if (flags & HDOP_STRIP_X_FORWARDED_PROTO) {
+ continue;
+ }
+
+ if (it_xfp == std::end(headers)) {
+ it_xfp = it;
+ continue;
+ }
+
+ kv = &(*it_xfp);
+ it_xfp = it;
+ break;
+ case HD_VIA:
+ if (flags & HDOP_STRIP_VIA) {
+ continue;
+ }
+
+ if (it_via == std::end(headers)) {
+ it_via = it;
+ continue;
+ }
+
+ kv = &(*it_via);
+ it_via = it;
+ break;
+ }
+ nva.push_back(
+ make_nv_internal(kv->name, kv->value, kv->no_index, nv_flags));
+ }
+}
+} // namespace
+
+void copy_headers_to_nva(std::vector<nghttp2_nv> &nva,
+ const HeaderRefs &headers, uint32_t flags) {
+ copy_headers_to_nva_internal(nva, headers, NGHTTP2_NV_FLAG_NONE, flags);
+}
+
+void copy_headers_to_nva_nocopy(std::vector<nghttp2_nv> &nva,
+ const HeaderRefs &headers, uint32_t flags) {
+ copy_headers_to_nva_internal(
+ nva, headers,
+ NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE, flags);
+}
+
+void build_http1_headers_from_headers(DefaultMemchunks *buf,
+ const HeaderRefs &headers,
+ uint32_t flags) {
+ auto it_forwarded = std::end(headers);
+ auto it_xff = std::end(headers);
+ auto it_xfp = std::end(headers);
+ auto it_via = std::end(headers);
+
+ for (auto it = std::begin(headers); it != std::end(headers); ++it) {
+ auto kv = &(*it);
+ if (kv->name.empty() || kv->name[0] == ':') {
+ continue;
+ }
+ switch (kv->token) {
+ case HD_CONNECTION:
+ case HD_COOKIE:
+ case HD_HOST:
+ case HD_HTTP2_SETTINGS:
+ case HD_KEEP_ALIVE:
+ case HD_PROXY_CONNECTION:
+ case HD_SERVER:
+ case HD_UPGRADE:
+ continue;
+ case HD_EARLY_DATA:
+ if (flags & HDOP_STRIP_EARLY_DATA) {
+ continue;
+ }
+ break;
+ case HD_TRANSFER_ENCODING:
+ if (flags & HDOP_STRIP_TRANSFER_ENCODING) {
+ continue;
+ }
+ break;
+ case HD_FORWARDED:
+ if (flags & HDOP_STRIP_FORWARDED) {
+ continue;
+ }
+
+ if (it_forwarded == std::end(headers)) {
+ it_forwarded = it;
+ continue;
+ }
+
+ kv = &(*it_forwarded);
+ it_forwarded = it;
+ break;
+ case HD_X_FORWARDED_FOR:
+ if (flags & HDOP_STRIP_X_FORWARDED_FOR) {
+ continue;
+ }
+
+ if (it_xff == std::end(headers)) {
+ it_xff = it;
+ continue;
+ }
+
+ kv = &(*it_xff);
+ it_xff = it;
+ break;
+ case HD_X_FORWARDED_PROTO:
+ if (flags & HDOP_STRIP_X_FORWARDED_PROTO) {
+ continue;
+ }
+
+ if (it_xfp == std::end(headers)) {
+ it_xfp = it;
+ continue;
+ }
+
+ kv = &(*it_xfp);
+ it_xfp = it;
+ break;
+ case HD_VIA:
+ if (flags & HDOP_STRIP_VIA) {
+ continue;
+ }
+
+ if (it_via == std::end(headers)) {
+ it_via = it;
+ continue;
+ }
+
+ kv = &(*it_via);
+ it_via = it;
+ break;
+ }
+ capitalize(buf, kv->name);
+ buf->append(": ");
+ buf->append(kv->value);
+ buf->append("\r\n");
+ }
+}
+
+int32_t determine_window_update_transmission(nghttp2_session *session,
+ int32_t stream_id) {
+ int32_t recv_length, window_size;
+ if (stream_id == 0) {
+ recv_length = nghttp2_session_get_effective_recv_data_length(session);
+ window_size = nghttp2_session_get_effective_local_window_size(session);
+ } else {
+ recv_length = nghttp2_session_get_stream_effective_recv_data_length(
+ session, stream_id);
+ window_size = nghttp2_session_get_stream_effective_local_window_size(
+ session, stream_id);
+ }
+ if (recv_length != -1 && window_size != -1) {
+ if (recv_length >= window_size / 2) {
+ return recv_length;
+ }
+ }
+ return -1;
+}
+
+void dump_nv(FILE *out, const char **nv) {
+ for (size_t i = 0; nv[i]; i += 2) {
+ fprintf(out, "%s: %s\n", nv[i], nv[i + 1]);
+ }
+ fputc('\n', out);
+ fflush(out);
+}
+
+void dump_nv(FILE *out, const nghttp2_nv *nva, size_t nvlen) {
+ auto end = nva + nvlen;
+ for (; nva != end; ++nva) {
+ fprintf(out, "%s: %s\n", nva->name, nva->value);
+ }
+ fputc('\n', out);
+ fflush(out);
+}
+
+void dump_nv(FILE *out, const Headers &nva) {
+ for (auto &nv : nva) {
+ fprintf(out, "%s: %s\n", nv.name.c_str(), nv.value.c_str());
+ }
+ fputc('\n', out);
+ fflush(out);
+}
+
+void dump_nv(FILE *out, const HeaderRefs &nva) {
+ for (auto &nv : nva) {
+ fprintf(out, "%s: %s\n", nv.name.c_str(), nv.value.c_str());
+ }
+ fputc('\n', out);
+ fflush(out);
+}
+
+void erase_header(HeaderRef *hd) {
+ hd->name = StringRef{};
+ hd->token = -1;
+}
+
+StringRef rewrite_location_uri(BlockAllocator &balloc, const StringRef &uri,
+ const http_parser_url &u,
+ const StringRef &match_host,
+ const StringRef &request_authority,
+ const StringRef &upstream_scheme) {
+ // We just rewrite scheme and authority.
+ if ((u.field_set & (1 << UF_HOST)) == 0) {
+ return StringRef{};
+ }
+ auto field = &u.field_data[UF_HOST];
+ if (!util::starts_with(std::begin(match_host), std::end(match_host),
+ &uri[field->off], &uri[field->off] + field->len) ||
+ (match_host.size() != field->len && match_host[field->len] != ':')) {
+ return StringRef{};
+ }
+
+ auto len = 0;
+ if (!request_authority.empty()) {
+ len += upstream_scheme.size() + str_size("://") + request_authority.size();
+ }
+
+ if (u.field_set & (1 << UF_PATH)) {
+ field = &u.field_data[UF_PATH];
+ len += field->len;
+ }
+
+ if (u.field_set & (1 << UF_QUERY)) {
+ field = &u.field_data[UF_QUERY];
+ len += 1 + field->len;
+ }
+
+ if (u.field_set & (1 << UF_FRAGMENT)) {
+ field = &u.field_data[UF_FRAGMENT];
+ len += 1 + field->len;
+ }
+
+ auto iov = make_byte_ref(balloc, len + 1);
+ auto p = iov.base;
+
+ if (!request_authority.empty()) {
+ p = std::copy(std::begin(upstream_scheme), std::end(upstream_scheme), p);
+ p = util::copy_lit(p, "://");
+ p = std::copy(std::begin(request_authority), std::end(request_authority),
+ p);
+ }
+ if (u.field_set & (1 << UF_PATH)) {
+ field = &u.field_data[UF_PATH];
+ p = std::copy_n(&uri[field->off], field->len, p);
+ }
+ if (u.field_set & (1 << UF_QUERY)) {
+ field = &u.field_data[UF_QUERY];
+ *p++ = '?';
+ p = std::copy_n(&uri[field->off], field->len, p);
+ }
+ if (u.field_set & (1 << UF_FRAGMENT)) {
+ field = &u.field_data[UF_FRAGMENT];
+ *p++ = '#';
+ p = std::copy_n(&uri[field->off], field->len, p);
+ }
+
+ *p = '\0';
+
+ return StringRef{iov.base, p};
+}
+
+int parse_http_status_code(const StringRef &src) {
+ if (src.size() != 3) {
+ return -1;
+ }
+
+ int status = 0;
+ for (auto c : src) {
+ if (!isdigit(c)) {
+ return -1;
+ }
+ status *= 10;
+ status += c - '0';
+ }
+
+ if (status < 100) {
+ return -1;
+ }
+
+ return status;
+}
+
+int lookup_token(const StringRef &name) {
+ return lookup_token(name.byte(), name.size());
+}
+
+// This function was generated by genheaderfunc.py. Inspired by h2o
+// header lookup. https://github.com/h2o/h2o
+int lookup_token(const uint8_t *name, size_t namelen) {
+ switch (namelen) {
+ case 2:
+ switch (name[1]) {
+ case 'e':
+ if (util::streq_l("t", name, 1)) {
+ return HD_TE;
+ }
+ break;
+ }
+ break;
+ case 3:
+ switch (name[2]) {
+ case 'a':
+ if (util::streq_l("vi", name, 2)) {
+ return HD_VIA;
+ }
+ break;
+ }
+ break;
+ case 4:
+ switch (name[3]) {
+ case 'e':
+ if (util::streq_l("dat", name, 3)) {
+ return HD_DATE;
+ }
+ break;
+ case 'k':
+ if (util::streq_l("lin", name, 3)) {
+ return HD_LINK;
+ }
+ break;
+ case 't':
+ if (util::streq_l("hos", name, 3)) {
+ return HD_HOST;
+ }
+ break;
+ }
+ break;
+ case 5:
+ switch (name[4]) {
+ case 'h':
+ if (util::streq_l(":pat", name, 4)) {
+ return HD__PATH;
+ }
+ break;
+ case 't':
+ if (util::streq_l(":hos", name, 4)) {
+ return HD__HOST;
+ }
+ break;
+ }
+ break;
+ case 6:
+ switch (name[5]) {
+ case 'e':
+ if (util::streq_l("cooki", name, 5)) {
+ return HD_COOKIE;
+ }
+ break;
+ case 'r':
+ if (util::streq_l("serve", name, 5)) {
+ return HD_SERVER;
+ }
+ break;
+ case 't':
+ if (util::streq_l("expec", name, 5)) {
+ return HD_EXPECT;
+ }
+ break;
+ }
+ break;
+ case 7:
+ switch (name[6]) {
+ case 'c':
+ if (util::streq_l("alt-sv", name, 6)) {
+ return HD_ALT_SVC;
+ }
+ break;
+ case 'd':
+ if (util::streq_l(":metho", name, 6)) {
+ return HD__METHOD;
+ }
+ break;
+ case 'e':
+ if (util::streq_l(":schem", name, 6)) {
+ return HD__SCHEME;
+ }
+ if (util::streq_l("upgrad", name, 6)) {
+ return HD_UPGRADE;
+ }
+ break;
+ case 'r':
+ if (util::streq_l("traile", name, 6)) {
+ return HD_TRAILER;
+ }
+ break;
+ case 's':
+ if (util::streq_l(":statu", name, 6)) {
+ return HD__STATUS;
+ }
+ break;
+ }
+ break;
+ case 8:
+ switch (name[7]) {
+ case 'n':
+ if (util::streq_l("locatio", name, 7)) {
+ return HD_LOCATION;
+ }
+ break;
+ case 'y':
+ if (util::streq_l("priorit", name, 7)) {
+ return HD_PRIORITY;
+ }
+ break;
+ }
+ break;
+ case 9:
+ switch (name[8]) {
+ case 'd':
+ if (util::streq_l("forwarde", name, 8)) {
+ return HD_FORWARDED;
+ }
+ break;
+ case 'l':
+ if (util::streq_l(":protoco", name, 8)) {
+ return HD__PROTOCOL;
+ }
+ break;
+ }
+ break;
+ case 10:
+ switch (name[9]) {
+ case 'a':
+ if (util::streq_l("early-dat", name, 9)) {
+ return HD_EARLY_DATA;
+ }
+ break;
+ case 'e':
+ if (util::streq_l("keep-aliv", name, 9)) {
+ return HD_KEEP_ALIVE;
+ }
+ break;
+ case 'n':
+ if (util::streq_l("connectio", name, 9)) {
+ return HD_CONNECTION;
+ }
+ break;
+ case 't':
+ if (util::streq_l("user-agen", name, 9)) {
+ return HD_USER_AGENT;
+ }
+ break;
+ case 'y':
+ if (util::streq_l(":authorit", name, 9)) {
+ return HD__AUTHORITY;
+ }
+ break;
+ }
+ break;
+ case 12:
+ switch (name[11]) {
+ case 'e':
+ if (util::streq_l("content-typ", name, 11)) {
+ return HD_CONTENT_TYPE;
+ }
+ break;
+ }
+ break;
+ case 13:
+ switch (name[12]) {
+ case 'l':
+ if (util::streq_l("cache-contro", name, 12)) {
+ return HD_CACHE_CONTROL;
+ }
+ break;
+ }
+ break;
+ case 14:
+ switch (name[13]) {
+ case 'h':
+ if (util::streq_l("content-lengt", name, 13)) {
+ return HD_CONTENT_LENGTH;
+ }
+ break;
+ case 's':
+ if (util::streq_l("http2-setting", name, 13)) {
+ return HD_HTTP2_SETTINGS;
+ }
+ break;
+ }
+ break;
+ case 15:
+ switch (name[14]) {
+ case 'e':
+ if (util::streq_l("accept-languag", name, 14)) {
+ return HD_ACCEPT_LANGUAGE;
+ }
+ break;
+ case 'g':
+ if (util::streq_l("accept-encodin", name, 14)) {
+ return HD_ACCEPT_ENCODING;
+ }
+ break;
+ case 'r':
+ if (util::streq_l("x-forwarded-fo", name, 14)) {
+ return HD_X_FORWARDED_FOR;
+ }
+ break;
+ }
+ break;
+ case 16:
+ switch (name[15]) {
+ case 'n':
+ if (util::streq_l("proxy-connectio", name, 15)) {
+ return HD_PROXY_CONNECTION;
+ }
+ break;
+ }
+ break;
+ case 17:
+ switch (name[16]) {
+ case 'e':
+ if (util::streq_l("if-modified-sinc", name, 16)) {
+ return HD_IF_MODIFIED_SINCE;
+ }
+ break;
+ case 'g':
+ if (util::streq_l("transfer-encodin", name, 16)) {
+ return HD_TRANSFER_ENCODING;
+ }
+ break;
+ case 'o':
+ if (util::streq_l("x-forwarded-prot", name, 16)) {
+ return HD_X_FORWARDED_PROTO;
+ }
+ break;
+ case 'y':
+ if (util::streq_l("sec-websocket-ke", name, 16)) {
+ return HD_SEC_WEBSOCKET_KEY;
+ }
+ break;
+ }
+ break;
+ case 20:
+ switch (name[19]) {
+ case 't':
+ if (util::streq_l("sec-websocket-accep", name, 19)) {
+ return HD_SEC_WEBSOCKET_ACCEPT;
+ }
+ break;
+ }
+ break;
+ }
+ return -1;
+}
+
+void init_hdidx(HeaderIndex &hdidx) {
+ std::fill(std::begin(hdidx), std::end(hdidx), -1);
+}
+
+void index_header(HeaderIndex &hdidx, int32_t token, size_t idx) {
+ if (token == -1) {
+ return;
+ }
+ assert(token < HD_MAXIDX);
+ hdidx[token] = idx;
+}
+
+const Headers::value_type *get_header(const HeaderIndex &hdidx, int32_t token,
+ const Headers &nva) {
+ auto i = hdidx[token];
+ if (i == -1) {
+ return nullptr;
+ }
+ return &nva[i];
+}
+
+Headers::value_type *get_header(const HeaderIndex &hdidx, int32_t token,
+ Headers &nva) {
+ auto i = hdidx[token];
+ if (i == -1) {
+ return nullptr;
+ }
+ return &nva[i];
+}
+
+namespace {
+template <typename InputIt> InputIt skip_lws(InputIt first, InputIt last) {
+ for (; first != last; ++first) {
+ switch (*first) {
+ case ' ':
+ case '\t':
+ continue;
+ default:
+ return first;
+ }
+ }
+ return first;
+}
+} // namespace
+
+namespace {
+template <typename InputIt>
+InputIt skip_to_next_field(InputIt first, InputIt last) {
+ for (; first != last; ++first) {
+ switch (*first) {
+ case ' ':
+ case '\t':
+ case ',':
+ continue;
+ default:
+ return first;
+ }
+ }
+ return first;
+}
+} // namespace
+
+namespace {
+// Skip to the right dquote ('"'), handling backslash escapes.
+// Returns |last| if input is not terminated with '"'.
+template <typename InputIt>
+InputIt skip_to_right_dquote(InputIt first, InputIt last) {
+ for (; first != last;) {
+ switch (*first) {
+ case '"':
+ return first;
+ // quoted-pair
+ case '\\':
+ ++first;
+ if (first == last) {
+ return first;
+ }
+
+ switch (*first) {
+ case '\t':
+ case ' ':
+ break;
+ default:
+ if ((0x21 <= *first && *first <= 0x7e) /* VCHAR */ ||
+ (0x80 <= *first && *first <= 0xff) /* obs-text */) {
+ break;
+ }
+
+ return last;
+ }
+
+ break;
+ // qdtext
+ case '\t':
+ case ' ':
+ case '!':
+ break;
+ default:
+ if ((0x23 <= *first && *first <= 0x5b) ||
+ (0x5d <= *first && *first <= 0x7e)) {
+ break;
+ }
+
+ return last;
+ }
+ ++first;
+ }
+ return first;
+}
+} // namespace
+
+namespace {
+// Returns true if link-param does not match pattern |pat| of length
+// |patlen| or it has empty value (""). |pat| should be parmname
+// followed by "=".
+bool check_link_param_empty(const char *first, const char *last,
+ const char *pat, size_t patlen) {
+ if (first + patlen <= last) {
+ if (std::equal(pat, pat + patlen, first, util::CaseCmp())) {
+ // we only accept URI if pat is followed by "" (e.g.,
+ // loadpolicy="") here.
+ if (first + patlen + 2 <= last) {
+ if (*(first + patlen) != '"' || *(first + patlen + 1) != '"') {
+ return false;
+ }
+ } else {
+ // here we got invalid production (anchor=") or anchor=?
+ return false;
+ }
+ }
+ }
+ return true;
+}
+} // namespace
+
+namespace {
+// Returns true if link-param consists of only parmname, and it
+// matches string [pat, pat + patlen).
+bool check_link_param_without_value(const char *first, const char *last,
+ const char *pat, size_t patlen) {
+ if (first + patlen > last) {
+ return false;
+ }
+
+ if (first + patlen == last) {
+ return std::equal(pat, pat + patlen, first, util::CaseCmp());
+ }
+
+ switch (*(first + patlen)) {
+ case ';':
+ case ',':
+ return std::equal(pat, pat + patlen, first, util::CaseCmp());
+ }
+
+ return false;
+}
+} // namespace
+
+namespace {
+std::pair<LinkHeader, const char *>
+parse_next_link_header_once(const char *first, const char *last) {
+ first = skip_to_next_field(first, last);
+ if (first == last || *first != '<') {
+ return {{StringRef{}}, last};
+ }
+ auto url_first = ++first;
+ first = std::find(first, last, '>');
+ if (first == last) {
+ return {{StringRef{}}, first};
+ }
+ auto url_last = first++;
+ if (first == last) {
+ return {{StringRef{}}, first};
+ }
+ // we expect ';' or ',' here
+ switch (*first) {
+ case ',':
+ return {{StringRef{}}, ++first};
+ case ';':
+ ++first;
+ break;
+ default:
+ return {{StringRef{}}, last};
+ }
+
+ auto ok = false;
+ auto ign = false;
+ for (;;) {
+ first = skip_lws(first, last);
+ if (first == last) {
+ return {{StringRef{}}, first};
+ }
+ // we expect link-param
+
+ if (!ign) {
+ if (!ok) {
+ // rel can take several relations using quoted form.
+ static constexpr char PLP[] = "rel=\"";
+ static constexpr size_t PLPLEN = str_size(PLP);
+
+ static constexpr char PLT[] = "preload";
+ static constexpr size_t PLTLEN = str_size(PLT);
+ if (first + PLPLEN < last && *(first + PLPLEN - 1) == '"' &&
+ std::equal(PLP, PLP + PLPLEN, first, util::CaseCmp())) {
+ // we have to search preload in whitespace separated list:
+ // rel="preload something http://example.org/foo"
+ first += PLPLEN;
+ auto start = first;
+ for (; first != last;) {
+ if (*first != ' ' && *first != '"') {
+ ++first;
+ continue;
+ }
+
+ if (start == first) {
+ return {{StringRef{}}, last};
+ }
+
+ if (!ok && start + PLTLEN == first &&
+ std::equal(PLT, PLT + PLTLEN, start, util::CaseCmp())) {
+ ok = true;
+ }
+
+ if (*first == '"') {
+ break;
+ }
+ first = skip_lws(first, last);
+ start = first;
+ }
+ if (first == last) {
+ return {{StringRef{}}, last};
+ }
+ assert(*first == '"');
+ ++first;
+ if (first == last || *first == ',') {
+ goto almost_done;
+ }
+ if (*first == ';') {
+ ++first;
+ // parse next link-param
+ continue;
+ }
+ return {{StringRef{}}, last};
+ }
+ }
+ // we are only interested in rel=preload parameter. Others are
+ // simply skipped.
+ static constexpr char PL[] = "rel=preload";
+ static constexpr size_t PLLEN = str_size(PL);
+ if (first + PLLEN == last) {
+ if (std::equal(PL, PL + PLLEN, first, util::CaseCmp())) {
+ // ok = true;
+ // this is the end of sequence
+ return {{{url_first, url_last}}, last};
+ }
+ } else if (first + PLLEN + 1 <= last) {
+ switch (*(first + PLLEN)) {
+ case ',':
+ if (!std::equal(PL, PL + PLLEN, first, util::CaseCmp())) {
+ break;
+ }
+ // ok = true;
+ // skip including ','
+ first += PLLEN + 1;
+ return {{{url_first, url_last}}, first};
+ case ';':
+ if (!std::equal(PL, PL + PLLEN, first, util::CaseCmp())) {
+ break;
+ }
+ ok = true;
+ // skip including ';'
+ first += PLLEN + 1;
+ // continue parse next link-param
+ continue;
+ }
+ }
+ // we have to reject URI if we have nonempty anchor parameter.
+ static constexpr char ANCHOR[] = "anchor=";
+ static constexpr size_t ANCHORLEN = str_size(ANCHOR);
+ if (!ign && !check_link_param_empty(first, last, ANCHOR, ANCHORLEN)) {
+ ign = true;
+ }
+
+ // reject URI if we have non-empty loadpolicy. This could be
+ // tightened up to just pick up "next" or "insert".
+ static constexpr char LOADPOLICY[] = "loadpolicy=";
+ static constexpr size_t LOADPOLICYLEN = str_size(LOADPOLICY);
+ if (!ign &&
+ !check_link_param_empty(first, last, LOADPOLICY, LOADPOLICYLEN)) {
+ ign = true;
+ }
+
+ // reject URI if we have nopush attribute.
+ static constexpr char NOPUSH[] = "nopush";
+ static constexpr size_t NOPUSHLEN = str_size(NOPUSH);
+ if (!ign &&
+ check_link_param_without_value(first, last, NOPUSH, NOPUSHLEN)) {
+ ign = true;
+ }
+ }
+
+ auto param_first = first;
+ for (; first != last;) {
+ if (util::in_attr_char(*first)) {
+ ++first;
+ continue;
+ }
+ // '*' is only allowed at the end of parameter name and must be
+ // followed by '='
+ if (last - first >= 2 && first != param_first) {
+ if (*first == '*' && *(first + 1) == '=') {
+ ++first;
+ break;
+ }
+ }
+ if (*first == '=' || *first == ';' || *first == ',') {
+ break;
+ }
+ return {{StringRef{}}, last};
+ }
+ if (param_first == first) {
+ // empty parmname
+ return {{StringRef{}}, last};
+ }
+ // link-param without value is acceptable (see link-extension) if
+ // it is not followed by '='
+ if (first == last || *first == ',') {
+ goto almost_done;
+ }
+ if (*first == ';') {
+ ++first;
+ // parse next link-param
+ continue;
+ }
+ // now parsing link-param value
+ assert(*first == '=');
+ ++first;
+ if (first == last) {
+ // empty value is not acceptable
+ return {{StringRef{}}, first};
+ }
+ if (*first == '"') {
+ // quoted-string
+ first = skip_to_right_dquote(first + 1, last);
+ if (first == last) {
+ return {{StringRef{}}, first};
+ }
+ ++first;
+ if (first == last || *first == ',') {
+ goto almost_done;
+ }
+ if (*first == ';') {
+ ++first;
+ // parse next link-param
+ continue;
+ }
+ return {{StringRef{}}, last};
+ }
+ // not quoted-string, skip to next ',' or ';'
+ if (*first == ',' || *first == ';') {
+ // empty value
+ return {{StringRef{}}, last};
+ }
+ for (; first != last; ++first) {
+ if (*first == ',' || *first == ';') {
+ break;
+ }
+ }
+ if (first == last || *first == ',') {
+ goto almost_done;
+ }
+ assert(*first == ';');
+ ++first;
+ // parse next link-param
+ }
+
+almost_done:
+ assert(first == last || *first == ',');
+
+ if (first != last) {
+ ++first;
+ }
+ if (ok && !ign) {
+ return {{{url_first, url_last}}, first};
+ }
+ return {{StringRef{}}, first};
+}
+} // namespace
+
+std::vector<LinkHeader> parse_link_header(const StringRef &src) {
+ std::vector<LinkHeader> res;
+ for (auto first = std::begin(src); first != std::end(src);) {
+ auto rv = parse_next_link_header_once(first, std::end(src));
+ first = rv.second;
+ auto &link = rv.first;
+ if (!link.uri.empty()) {
+ res.push_back(link);
+ }
+ }
+ return res;
+}
+
+std::string path_join(const StringRef &base_path, const StringRef &base_query,
+ const StringRef &rel_path, const StringRef &rel_query) {
+ BlockAllocator balloc(1024, 1024);
+
+ return path_join(balloc, base_path, base_query, rel_path, rel_query).str();
+}
+
+bool expect_response_body(int status_code) {
+ return status_code == 101 ||
+ (status_code / 100 != 1 && status_code != 304 && status_code != 204);
+}
+
+bool expect_response_body(const std::string &method, int status_code) {
+ return method != "HEAD" && expect_response_body(status_code);
+}
+
+bool expect_response_body(int method_token, int status_code) {
+ return method_token != HTTP_HEAD && expect_response_body(status_code);
+}
+
+int lookup_method_token(const StringRef &name) {
+ return lookup_method_token(name.byte(), name.size());
+}
+
+// This function was generated by genmethodfunc.py.
+int lookup_method_token(const uint8_t *name, size_t namelen) {
+ switch (namelen) {
+ case 3:
+ switch (name[2]) {
+ case 'L':
+ if (util::streq_l("AC", name, 2)) {
+ return HTTP_ACL;
+ }
+ break;
+ case 'T':
+ if (util::streq_l("GE", name, 2)) {
+ return HTTP_GET;
+ }
+ if (util::streq_l("PU", name, 2)) {
+ return HTTP_PUT;
+ }
+ break;
+ }
+ break;
+ case 4:
+ switch (name[3]) {
+ case 'D':
+ if (util::streq_l("BIN", name, 3)) {
+ return HTTP_BIND;
+ }
+ if (util::streq_l("HEA", name, 3)) {
+ return HTTP_HEAD;
+ }
+ break;
+ case 'E':
+ if (util::streq_l("MOV", name, 3)) {
+ return HTTP_MOVE;
+ }
+ break;
+ case 'K':
+ if (util::streq_l("LIN", name, 3)) {
+ return HTTP_LINK;
+ }
+ if (util::streq_l("LOC", name, 3)) {
+ return HTTP_LOCK;
+ }
+ break;
+ case 'T':
+ if (util::streq_l("POS", name, 3)) {
+ return HTTP_POST;
+ }
+ break;
+ case 'Y':
+ if (util::streq_l("COP", name, 3)) {
+ return HTTP_COPY;
+ }
+ break;
+ }
+ break;
+ case 5:
+ switch (name[4]) {
+ case 'E':
+ if (util::streq_l("MERG", name, 4)) {
+ return HTTP_MERGE;
+ }
+ if (util::streq_l("PURG", name, 4)) {
+ return HTTP_PURGE;
+ }
+ if (util::streq_l("TRAC", name, 4)) {
+ return HTTP_TRACE;
+ }
+ break;
+ case 'H':
+ if (util::streq_l("PATC", name, 4)) {
+ return HTTP_PATCH;
+ }
+ break;
+ case 'L':
+ if (util::streq_l("MKCO", name, 4)) {
+ return HTTP_MKCOL;
+ }
+ break;
+ }
+ break;
+ case 6:
+ switch (name[5]) {
+ case 'D':
+ if (util::streq_l("REBIN", name, 5)) {
+ return HTTP_REBIND;
+ }
+ if (util::streq_l("UNBIN", name, 5)) {
+ return HTTP_UNBIND;
+ }
+ break;
+ case 'E':
+ if (util::streq_l("DELET", name, 5)) {
+ return HTTP_DELETE;
+ }
+ if (util::streq_l("SOURC", name, 5)) {
+ return HTTP_SOURCE;
+ }
+ break;
+ case 'H':
+ if (util::streq_l("SEARC", name, 5)) {
+ return HTTP_SEARCH;
+ }
+ break;
+ case 'K':
+ if (util::streq_l("UNLIN", name, 5)) {
+ return HTTP_UNLINK;
+ }
+ if (util::streq_l("UNLOC", name, 5)) {
+ return HTTP_UNLOCK;
+ }
+ break;
+ case 'T':
+ if (util::streq_l("REPOR", name, 5)) {
+ return HTTP_REPORT;
+ }
+ break;
+ case 'Y':
+ if (util::streq_l("NOTIF", name, 5)) {
+ return HTTP_NOTIFY;
+ }
+ break;
+ }
+ break;
+ case 7:
+ switch (name[6]) {
+ case 'H':
+ if (util::streq_l("MSEARC", name, 6)) {
+ return HTTP_MSEARCH;
+ }
+ break;
+ case 'S':
+ if (util::streq_l("OPTION", name, 6)) {
+ return HTTP_OPTIONS;
+ }
+ break;
+ case 'T':
+ if (util::streq_l("CONNEC", name, 6)) {
+ return HTTP_CONNECT;
+ }
+ break;
+ }
+ break;
+ case 8:
+ switch (name[7]) {
+ case 'D':
+ if (util::streq_l("PROPFIN", name, 7)) {
+ return HTTP_PROPFIND;
+ }
+ break;
+ case 'T':
+ if (util::streq_l("CHECKOU", name, 7)) {
+ return HTTP_CHECKOUT;
+ }
+ break;
+ }
+ break;
+ case 9:
+ switch (name[8]) {
+ case 'E':
+ if (util::streq_l("SUBSCRIB", name, 8)) {
+ return HTTP_SUBSCRIBE;
+ }
+ break;
+ case 'H':
+ if (util::streq_l("PROPPATC", name, 8)) {
+ return HTTP_PROPPATCH;
+ }
+ break;
+ }
+ break;
+ case 10:
+ switch (name[9]) {
+ case 'R':
+ if (util::streq_l("MKCALENDA", name, 9)) {
+ return HTTP_MKCALENDAR;
+ }
+ break;
+ case 'Y':
+ if (util::streq_l("MKACTIVIT", name, 9)) {
+ return HTTP_MKACTIVITY;
+ }
+ break;
+ }
+ break;
+ case 11:
+ switch (name[10]) {
+ case 'E':
+ if (util::streq_l("UNSUBSCRIB", name, 10)) {
+ return HTTP_UNSUBSCRIBE;
+ }
+ break;
+ }
+ break;
+ }
+ return -1;
+}
+
+StringRef to_method_string(int method_token) {
+ // we happened to use same value for method with llhttp.
+ return StringRef{
+ llhttp_method_name(static_cast<llhttp_method>(method_token))};
+}
+
+StringRef get_pure_path_component(const StringRef &uri) {
+ int rv;
+
+ http_parser_url u{};
+ rv = http_parser_parse_url(uri.c_str(), uri.size(), 0, &u);
+ if (rv != 0) {
+ return StringRef{};
+ }
+
+ if (u.field_set & (1 << UF_PATH)) {
+ auto &f = u.field_data[UF_PATH];
+ return StringRef{uri.c_str() + f.off, f.len};
+ }
+
+ return StringRef::from_lit("/");
+}
+
+int construct_push_component(BlockAllocator &balloc, StringRef &scheme,
+ StringRef &authority, StringRef &path,
+ const StringRef &base, const StringRef &uri) {
+ int rv;
+ StringRef rel, relq;
+
+ if (uri.size() == 0) {
+ return -1;
+ }
+
+ http_parser_url u{};
+
+ rv = http_parser_parse_url(uri.c_str(), uri.size(), 0, &u);
+
+ if (rv != 0) {
+ if (uri[0] == '/') {
+ return -1;
+ }
+
+ // treat link_url as relative URI.
+ auto end = std::find(std::begin(uri), std::end(uri), '#');
+ auto q = std::find(std::begin(uri), end, '?');
+
+ rel = StringRef{std::begin(uri), q};
+ if (q != end) {
+ relq = StringRef{q + 1, std::end(uri)};
+ }
+ } else {
+ if (u.field_set & (1 << UF_SCHEMA)) {
+ scheme = util::get_uri_field(uri.c_str(), u, UF_SCHEMA);
+ }
+
+ if (u.field_set & (1 << UF_HOST)) {
+ auto auth = util::get_uri_field(uri.c_str(), u, UF_HOST);
+ auto len = auth.size();
+ auto port_exists = u.field_set & (1 << UF_PORT);
+ if (port_exists) {
+ len += 1 + str_size("65535");
+ }
+ auto iov = make_byte_ref(balloc, len + 1);
+ auto p = iov.base;
+ p = std::copy(std::begin(auth), std::end(auth), p);
+ if (port_exists) {
+ *p++ = ':';
+ p = util::utos(p, u.port);
+ }
+ *p = '\0';
+
+ authority = StringRef{iov.base, p};
+ }
+
+ if (u.field_set & (1 << UF_PATH)) {
+ auto &f = u.field_data[UF_PATH];
+ rel = StringRef{uri.c_str() + f.off, f.len};
+ } else {
+ rel = StringRef::from_lit("/");
+ }
+
+ if (u.field_set & (1 << UF_QUERY)) {
+ auto &f = u.field_data[UF_QUERY];
+ relq = StringRef{uri.c_str() + f.off, f.len};
+ }
+ }
+
+ path = http2::path_join(balloc, base, StringRef{}, rel, relq);
+
+ return 0;
+}
+
+namespace {
+template <typename InputIt> InputIt eat_file(InputIt first, InputIt last) {
+ if (first == last) {
+ *first++ = '/';
+ return first;
+ }
+
+ if (*(last - 1) == '/') {
+ return last;
+ }
+
+ auto p = last;
+ for (; p != first && *(p - 1) != '/'; --p)
+ ;
+ if (p == first) {
+ // this should not happened in normal case, where we expect path
+ // starts with '/'
+ *first++ = '/';
+ return first;
+ }
+
+ return p;
+}
+} // namespace
+
+namespace {
+template <typename InputIt> InputIt eat_dir(InputIt first, InputIt last) {
+ auto p = eat_file(first, last);
+
+ --p;
+
+ assert(*p == '/');
+
+ return eat_file(first, p);
+}
+} // namespace
+
+StringRef path_join(BlockAllocator &balloc, const StringRef &base_path,
+ const StringRef &base_query, const StringRef &rel_path,
+ const StringRef &rel_query) {
+ auto res = make_byte_ref(
+ balloc, std::max(static_cast<size_t>(1), base_path.size()) +
+ rel_path.size() + 1 +
+ std::max(base_query.size(), rel_query.size()) + 1);
+ auto p = res.base;
+
+ if (rel_path.empty()) {
+ if (base_path.empty()) {
+ *p++ = '/';
+ } else {
+ p = std::copy(std::begin(base_path), std::end(base_path), p);
+ }
+ if (rel_query.empty()) {
+ if (!base_query.empty()) {
+ *p++ = '?';
+ p = std::copy(std::begin(base_query), std::end(base_query), p);
+ }
+ *p = '\0';
+ return StringRef{res.base, p};
+ }
+ *p++ = '?';
+ p = std::copy(std::begin(rel_query), std::end(rel_query), p);
+ *p = '\0';
+ return StringRef{res.base, p};
+ }
+
+ auto first = std::begin(rel_path);
+ auto last = std::end(rel_path);
+
+ if (rel_path[0] == '/') {
+ *p++ = '/';
+ ++first;
+ for (; first != last && *first == '/'; ++first)
+ ;
+ } else if (base_path.empty()) {
+ *p++ = '/';
+ } else {
+ p = std::copy(std::begin(base_path), std::end(base_path), p);
+ }
+
+ for (; first != last;) {
+ if (*first == '.') {
+ if (first + 1 == last) {
+ if (*(p - 1) != '/') {
+ p = eat_file(res.base, p);
+ }
+ break;
+ }
+ if (*(first + 1) == '/') {
+ if (*(p - 1) != '/') {
+ p = eat_file(res.base, p);
+ }
+ first += 2;
+ continue;
+ }
+ if (*(first + 1) == '.') {
+ if (first + 2 == last) {
+ p = eat_dir(res.base, p);
+ break;
+ }
+ if (*(first + 2) == '/') {
+ p = eat_dir(res.base, p);
+ first += 3;
+ continue;
+ }
+ }
+ }
+ if (*(p - 1) != '/') {
+ p = eat_file(res.base, p);
+ }
+ auto slash = std::find(first, last, '/');
+ if (slash == last) {
+ p = std::copy(first, last, p);
+ break;
+ }
+ p = std::copy(first, slash + 1, p);
+ first = slash + 1;
+ for (; first != last && *first == '/'; ++first)
+ ;
+ }
+ if (!rel_query.empty()) {
+ *p++ = '?';
+ p = std::copy(std::begin(rel_query), std::end(rel_query), p);
+ }
+ *p = '\0';
+ return StringRef{res.base, p};
+}
+
+StringRef normalize_path(BlockAllocator &balloc, const StringRef &path,
+ const StringRef &query) {
+ // First, decode %XX for unreserved characters, then do
+ // http2::path_join
+
+ // We won't find %XX if length is less than 3.
+ if (path.size() < 3 ||
+ std::find(std::begin(path), std::end(path), '%') == std::end(path)) {
+ return path_join(balloc, StringRef{}, StringRef{}, path, query);
+ }
+
+ // includes last terminal NULL.
+ auto result = make_byte_ref(balloc, path.size() + 1);
+ auto p = result.base;
+
+ auto it = std::begin(path);
+ for (; it + 2 < std::end(path);) {
+ if (*it == '%') {
+ if (util::is_hex_digit(*(it + 1)) && util::is_hex_digit(*(it + 2))) {
+ auto c =
+ (util::hex_to_uint(*(it + 1)) << 4) + util::hex_to_uint(*(it + 2));
+ if (util::in_rfc3986_unreserved_chars(c)) {
+ *p++ = c;
+
+ it += 3;
+
+ continue;
+ }
+ *p++ = '%';
+ *p++ = util::upcase(*(it + 1));
+ *p++ = util::upcase(*(it + 2));
+
+ it += 3;
+
+ continue;
+ }
+ }
+ *p++ = *it++;
+ }
+
+ p = std::copy(it, std::end(path), p);
+ *p = '\0';
+
+ return path_join(balloc, StringRef{}, StringRef{}, StringRef{result.base, p},
+ query);
+}
+
+StringRef normalize_path_colon(BlockAllocator &balloc, const StringRef &path,
+ const StringRef &query) {
+ // First, decode %XX for unreserved characters and ':', then do
+ // http2::path_join
+
+ // We won't find %XX if length is less than 3.
+ if (path.size() < 3 ||
+ std::find(std::begin(path), std::end(path), '%') == std::end(path)) {
+ return path_join(balloc, StringRef{}, StringRef{}, path, query);
+ }
+
+ // includes last terminal NULL.
+ auto result = make_byte_ref(balloc, path.size() + 1);
+ auto p = result.base;
+
+ auto it = std::begin(path);
+ for (; it + 2 < std::end(path);) {
+ if (*it == '%') {
+ if (util::is_hex_digit(*(it + 1)) && util::is_hex_digit(*(it + 2))) {
+ auto c =
+ (util::hex_to_uint(*(it + 1)) << 4) + util::hex_to_uint(*(it + 2));
+ if (util::in_rfc3986_unreserved_chars(c) || c == ':') {
+ *p++ = c;
+
+ it += 3;
+
+ continue;
+ }
+ *p++ = '%';
+ *p++ = util::upcase(*(it + 1));
+ *p++ = util::upcase(*(it + 2));
+
+ it += 3;
+
+ continue;
+ }
+ }
+ *p++ = *it++;
+ }
+
+ p = std::copy(it, std::end(path), p);
+ *p = '\0';
+
+ return path_join(balloc, StringRef{}, StringRef{}, StringRef{result.base, p},
+ query);
+}
+
+std::string normalize_path(const StringRef &path, const StringRef &query) {
+ BlockAllocator balloc(1024, 1024);
+
+ return normalize_path(balloc, path, query).str();
+}
+
+StringRef rewrite_clean_path(BlockAllocator &balloc, const StringRef &src) {
+ if (src.empty() || src[0] != '/') {
+ return src;
+ }
+ // probably, not necessary most of the case, but just in case.
+ auto fragment = std::find(std::begin(src), std::end(src), '#');
+ auto raw_query = std::find(std::begin(src), fragment, '?');
+ auto query = raw_query;
+ if (query != fragment) {
+ ++query;
+ }
+ return normalize_path(balloc, StringRef{std::begin(src), raw_query},
+ StringRef{query, fragment});
+}
+
+StringRef copy_lower(BlockAllocator &balloc, const StringRef &src) {
+ auto iov = make_byte_ref(balloc, src.size() + 1);
+ auto p = iov.base;
+ p = std::copy(std::begin(src), std::end(src), p);
+ *p = '\0';
+ util::inp_strlower(iov.base, p);
+ return StringRef{iov.base, p};
+}
+
+bool contains_trailers(const StringRef &s) {
+ constexpr auto trailers = StringRef::from_lit("trailers");
+
+ for (auto p = std::begin(s), end = std::end(s);; ++p) {
+ p = std::find_if(p, end, [](char c) { return c != ' ' && c != '\t'; });
+ if (p == end || static_cast<size_t>(end - p) < trailers.size()) {
+ return false;
+ }
+ if (util::strieq(trailers, StringRef{p, p + trailers.size()})) {
+ // Make sure that there is no character other than white spaces
+ // before next "," or end of string.
+ p = std::find_if(p + trailers.size(), end,
+ [](char c) { return c != ' ' && c != '\t'; });
+ if (p == end || *p == ',') {
+ return true;
+ }
+ }
+ // Skip to next ",".
+ p = std::find_if(p, end, [](char c) { return c == ','; });
+ if (p == end) {
+ return false;
+ }
+ }
+}
+
+StringRef make_websocket_accept_token(uint8_t *dest, const StringRef &key) {
+ static constexpr uint8_t magic[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+ std::array<uint8_t, base64::encode_length(16) + str_size(magic)> s;
+ auto p = std::copy(std::begin(key), std::end(key), std::begin(s));
+ std::copy_n(magic, str_size(magic), p);
+
+ std::array<uint8_t, 20> h;
+ if (util::sha1(h.data(), StringRef{std::begin(s), std::end(s)}) != 0) {
+ return StringRef{};
+ }
+
+ auto end = base64::encode(std::begin(h), std::end(h), dest);
+ return StringRef{dest, end};
+}
+
+bool legacy_http1(int major, int minor) {
+ return major <= 0 || (major == 1 && minor == 0);
+}
+
+bool check_transfer_encoding(const StringRef &s) {
+ if (s.empty()) {
+ return false;
+ }
+
+ auto it = std::begin(s);
+
+ for (;;) {
+ // token
+ if (!util::in_token(*it)) {
+ return false;
+ }
+
+ ++it;
+
+ for (; it != std::end(s) && util::in_token(*it); ++it)
+ ;
+
+ if (it == std::end(s)) {
+ return true;
+ }
+
+ for (;;) {
+ // OWS
+ it = skip_lws(it, std::end(s));
+ if (it == std::end(s)) {
+ return false;
+ }
+
+ if (*it == ',') {
+ ++it;
+
+ it = skip_lws(it, std::end(s));
+ if (it == std::end(s)) {
+ return false;
+ }
+
+ break;
+ }
+
+ if (*it != ';') {
+ return false;
+ }
+
+ ++it;
+
+ // transfer-parameter follows
+
+ // OWS
+ it = skip_lws(it, std::end(s));
+ if (it == std::end(s)) {
+ return false;
+ }
+
+ // token
+ if (!util::in_token(*it)) {
+ return false;
+ }
+
+ ++it;
+
+ for (; it != std::end(s) && util::in_token(*it); ++it)
+ ;
+
+ if (it == std::end(s)) {
+ return false;
+ }
+
+ // No BWS allowed
+ if (*it != '=') {
+ return false;
+ }
+
+ ++it;
+
+ if (util::in_token(*it)) {
+ // token
+ ++it;
+
+ for (; it != std::end(s) && util::in_token(*it); ++it)
+ ;
+ } else if (*it == '"') {
+ // quoted-string
+ ++it;
+
+ it = skip_to_right_dquote(it, std::end(s));
+ if (it == std::end(s)) {
+ return false;
+ }
+
+ ++it;
+ } else {
+ return false;
+ }
+
+ if (it == std::end(s)) {
+ return true;
+ }
+ }
+ }
+}
+
+} // namespace http2
+
+} // namespace nghttp2
diff --git a/src/http2.h b/src/http2.h
new file mode 100644
index 0000000..7cfe461
--- /dev/null
+++ b/src/http2.h
@@ -0,0 +1,457 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef HTTP2_H
+#define HTTP2_H
+
+#include "nghttp2_config.h"
+
+#include <cstdio>
+#include <cstring>
+#include <string>
+#include <vector>
+#include <array>
+
+#include <nghttp2/nghttp2.h>
+
+#include "url-parser/url_parser.h"
+
+#include "util.h"
+#include "memchunk.h"
+#include "template.h"
+#include "allocator.h"
+#include "base64.h"
+
+namespace nghttp2 {
+
+struct Header {
+ Header(std::string name, std::string value, bool no_index = false,
+ int32_t token = -1)
+ : name(std::move(name)),
+ value(std::move(value)),
+ token(token),
+ no_index(no_index) {}
+
+ Header() : token(-1), no_index(false) {}
+
+ bool operator==(const Header &other) const {
+ return name == other.name && value == other.value;
+ }
+
+ bool operator<(const Header &rhs) const {
+ return name < rhs.name || (name == rhs.name && value < rhs.value);
+ }
+
+ std::string name;
+ std::string value;
+ int32_t token;
+ bool no_index;
+};
+
+struct HeaderRef {
+ HeaderRef(const StringRef &name, const StringRef &value,
+ bool no_index = false, int32_t token = -1)
+ : name(name), value(value), token(token), no_index(no_index) {}
+
+ HeaderRef() : token(-1), no_index(false) {}
+
+ bool operator==(const HeaderRef &other) const {
+ return name == other.name && value == other.value;
+ }
+
+ bool operator<(const HeaderRef &rhs) const {
+ return name < rhs.name || (name == rhs.name && value < rhs.value);
+ }
+
+ StringRef name;
+ StringRef value;
+ int32_t token;
+ bool no_index;
+};
+
+using Headers = std::vector<Header>;
+using HeaderRefs = std::vector<HeaderRef>;
+
+namespace http2 {
+
+// Returns reason-phrase for given |status code|. If there is no
+// known reason-phrase for the given code, returns empty string.
+StringRef get_reason_phrase(unsigned int status_code);
+
+// Returns string version of |status_code|. (e.g., "404")
+StringRef stringify_status(BlockAllocator &balloc, unsigned int status_code);
+
+void capitalize(DefaultMemchunks *buf, const StringRef &s);
+
+// Returns true if |value| is LWS
+bool lws(const char *value);
+
+// Copies the |field| component value from |u| and |url| to the
+// |dest|. If |u| does not have |field|, then this function does
+// nothing.
+void copy_url_component(std::string &dest, const http_parser_url *u, int field,
+ const char *url);
+
+Headers::value_type to_header(const uint8_t *name, size_t namelen,
+ const uint8_t *value, size_t valuelen,
+ bool no_index, int32_t token);
+
+// Add name/value pairs to |nva|. If |no_index| is true, this
+// name/value pair won't be indexed when it is forwarded to the next
+// hop. This function strips white spaces around |value|.
+void add_header(Headers &nva, const uint8_t *name, size_t namelen,
+ const uint8_t *value, size_t valuelen, bool no_index,
+ int32_t token);
+
+// Returns pointer to the entry in |nva| which has name |name|. If
+// more than one entries which have the name |name|, last occurrence
+// in |nva| is returned. If no such entry exist, returns nullptr.
+const Headers::value_type *get_header(const Headers &nva, const char *name);
+
+// Returns true if the value of |nv| is not empty.
+bool non_empty_value(const HeaderRefs::value_type *nv);
+
+// Creates nghttp2_nv using |name| and |value| and returns it. The
+// returned value only references the data pointer to name.c_str() and
+// value.c_str(). If |no_index| is true, nghttp2_nv flags member has
+// NGHTTP2_NV_FLAG_NO_INDEX flag set.
+nghttp2_nv make_nv(const std::string &name, const std::string &value,
+ bool no_index = false);
+
+nghttp2_nv make_nv(const StringRef &name, const StringRef &value,
+ bool no_index = false);
+
+nghttp2_nv make_nv_nocopy(const std::string &name, const std::string &value,
+ bool no_index = false);
+
+nghttp2_nv make_nv_nocopy(const StringRef &name, const StringRef &value,
+ bool no_index = false);
+
+// Create nghttp2_nv from string literal |name| and |value|.
+template <size_t N, size_t M>
+constexpr nghttp2_nv make_nv_ll(const char (&name)[N], const char (&value)[M]) {
+ return {(uint8_t *)name, (uint8_t *)value, N - 1, M - 1,
+ NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE};
+}
+
+// Create nghttp2_nv from string literal |name| and c-string |value|.
+template <size_t N>
+nghttp2_nv make_nv_lc(const char (&name)[N], const char *value) {
+ return {(uint8_t *)name, (uint8_t *)value, N - 1, strlen(value),
+ NGHTTP2_NV_FLAG_NO_COPY_NAME};
+}
+
+template <size_t N>
+nghttp2_nv make_nv_lc_nocopy(const char (&name)[N], const char *value) {
+ return {(uint8_t *)name, (uint8_t *)value, N - 1, strlen(value),
+ NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE};
+}
+
+// Create nghttp2_nv from string literal |name| and std::string
+// |value|.
+template <size_t N>
+nghttp2_nv make_nv_ls(const char (&name)[N], const std::string &value) {
+ return {(uint8_t *)name, (uint8_t *)value.c_str(), N - 1, value.size(),
+ NGHTTP2_NV_FLAG_NO_COPY_NAME};
+}
+
+template <size_t N>
+nghttp2_nv make_nv_ls_nocopy(const char (&name)[N], const std::string &value) {
+ return {(uint8_t *)name, (uint8_t *)value.c_str(), N - 1, value.size(),
+ NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE};
+}
+
+template <size_t N>
+nghttp2_nv make_nv_ls_nocopy(const char (&name)[N], const StringRef &value) {
+ return {(uint8_t *)name, (uint8_t *)value.c_str(), N - 1, value.size(),
+ NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE};
+}
+
+enum HeaderBuildOp {
+ HDOP_NONE,
+ // Forwarded header fields must be stripped. If this flag is not
+ // set, all Forwarded header fields other than last one are added.
+ HDOP_STRIP_FORWARDED = 1,
+ // X-Forwarded-For header fields must be stripped. If this flag is
+ // not set, all X-Forwarded-For header fields other than last one
+ // are added.
+ HDOP_STRIP_X_FORWARDED_FOR = 1 << 1,
+ // X-Forwarded-Proto header fields must be stripped. If this flag
+ // is not set, all X-Forwarded-Proto header fields other than last
+ // one are added.
+ HDOP_STRIP_X_FORWARDED_PROTO = 1 << 2,
+ // Via header fields must be stripped. If this flag is not set, all
+ // Via header fields other than last one are added.
+ HDOP_STRIP_VIA = 1 << 3,
+ // Early-Data header fields must be stripped. If this flag is not
+ // set, all Early-Data header fields are added.
+ HDOP_STRIP_EARLY_DATA = 1 << 4,
+ // Strip above all header fields.
+ HDOP_STRIP_ALL = HDOP_STRIP_FORWARDED | HDOP_STRIP_X_FORWARDED_FOR |
+ HDOP_STRIP_X_FORWARDED_PROTO | HDOP_STRIP_VIA |
+ HDOP_STRIP_EARLY_DATA,
+ // Sec-WebSocket-Accept header field must be stripped. If this flag
+ // is not set, all Sec-WebSocket-Accept header fields are added.
+ HDOP_STRIP_SEC_WEBSOCKET_ACCEPT = 1 << 5,
+ // Sec-WebSocket-Key header field must be stripped. If this flag is
+ // not set, all Sec-WebSocket-Key header fields are added.
+ HDOP_STRIP_SEC_WEBSOCKET_KEY = 1 << 6,
+ // Transfer-Encoding header field must be stripped. If this flag is
+ // not set, all Transfer-Encoding header fields are added.
+ HDOP_STRIP_TRANSFER_ENCODING = 1 << 7,
+};
+
+// Appends headers in |headers| to |nv|. |headers| must be indexed
+// before this call (its element's token field is assigned). Certain
+// headers, including disallowed headers in HTTP/2 spec and headers
+// which require special handling (i.e. via), are not copied. |flags|
+// is one or more of HeaderBuildOp flags. They tell function that
+// certain header fields should not be added.
+void copy_headers_to_nva(std::vector<nghttp2_nv> &nva,
+ const HeaderRefs &headers, uint32_t flags);
+
+// Just like copy_headers_to_nva(), but this adds
+// NGHTTP2_NV_FLAG_NO_COPY_NAME and NGHTTP2_NV_FLAG_NO_COPY_VALUE.
+void copy_headers_to_nva_nocopy(std::vector<nghttp2_nv> &nva,
+ const HeaderRefs &headers, uint32_t flags);
+
+// Appends HTTP/1.1 style header lines to |buf| from headers in
+// |headers|. |headers| must be indexed before this call (its
+// element's token field is assigned). Certain headers, which
+// requires special handling (i.e. via and cookie), are not appended.
+// |flags| is one or more of HeaderBuildOp flags. They tell function
+// that certain header fields should not be added.
+void build_http1_headers_from_headers(DefaultMemchunks *buf,
+ const HeaderRefs &headers,
+ uint32_t flags);
+
+// Return positive window_size_increment if WINDOW_UPDATE should be
+// sent for the stream |stream_id|. If |stream_id| == 0, this function
+// determines the necessity of the WINDOW_UPDATE for a connection.
+//
+// If the function determines WINDOW_UPDATE is not necessary at the
+// moment, it returns -1.
+int32_t determine_window_update_transmission(nghttp2_session *session,
+ int32_t stream_id);
+
+// Dumps name/value pairs in |nv| to |out|. The |nv| must be
+// terminated by nullptr.
+void dump_nv(FILE *out, const char **nv);
+
+// Dumps name/value pairs in |nva| to |out|.
+void dump_nv(FILE *out, const nghttp2_nv *nva, size_t nvlen);
+
+// Dumps name/value pairs in |nva| to |out|.
+void dump_nv(FILE *out, const Headers &nva);
+
+void dump_nv(FILE *out, const HeaderRefs &nva);
+
+// Ereases header in |hd|.
+void erase_header(HeaderRef *hd);
+
+// Rewrites redirection URI which usually appears in location header
+// field. The |uri| is the URI in the location header field. The |u|
+// stores the result of parsed |uri|. The |request_authority| is the
+// host or :authority header field value in the request. The
+// |upstream_scheme| is either "https" or "http" in the upstream
+// interface. Rewrite is done only if location header field value
+// contains |match_host| as host excluding port. The |match_host| and
+// |request_authority| could be different. If |request_authority| is
+// empty, strip authority.
+//
+// This function returns the new rewritten URI on success. If the
+// location URI is not subject to the rewrite, this function returns
+// empty string.
+StringRef rewrite_location_uri(BlockAllocator &balloc, const StringRef &uri,
+ const http_parser_url &u,
+ const StringRef &match_host,
+ const StringRef &request_authority,
+ const StringRef &upstream_scheme);
+
+// Returns parsed HTTP status code. Returns -1 on failure.
+int parse_http_status_code(const StringRef &src);
+
+// Header fields to be indexed, except HD_MAXIDX which is convenient
+// member to get maximum value.
+//
+// generated by genheaderfunc.py
+enum {
+ HD__AUTHORITY,
+ HD__HOST,
+ HD__METHOD,
+ HD__PATH,
+ HD__PROTOCOL,
+ HD__SCHEME,
+ HD__STATUS,
+ HD_ACCEPT_ENCODING,
+ HD_ACCEPT_LANGUAGE,
+ HD_ALT_SVC,
+ HD_CACHE_CONTROL,
+ HD_CONNECTION,
+ HD_CONTENT_LENGTH,
+ HD_CONTENT_TYPE,
+ HD_COOKIE,
+ HD_DATE,
+ HD_EARLY_DATA,
+ HD_EXPECT,
+ HD_FORWARDED,
+ HD_HOST,
+ HD_HTTP2_SETTINGS,
+ HD_IF_MODIFIED_SINCE,
+ HD_KEEP_ALIVE,
+ HD_LINK,
+ HD_LOCATION,
+ HD_PRIORITY,
+ HD_PROXY_CONNECTION,
+ HD_SEC_WEBSOCKET_ACCEPT,
+ HD_SEC_WEBSOCKET_KEY,
+ HD_SERVER,
+ HD_TE,
+ HD_TRAILER,
+ HD_TRANSFER_ENCODING,
+ HD_UPGRADE,
+ HD_USER_AGENT,
+ HD_VIA,
+ HD_X_FORWARDED_FOR,
+ HD_X_FORWARDED_PROTO,
+ HD_MAXIDX,
+};
+
+using HeaderIndex = std::array<int16_t, HD_MAXIDX>;
+
+// Looks up header token for header name |name| of length |namelen|.
+// Only headers we are interested in are tokenized. If header name
+// cannot be tokenized, returns -1.
+int lookup_token(const uint8_t *name, size_t namelen);
+int lookup_token(const StringRef &name);
+
+// Initializes |hdidx|, header index. The |hdidx| must point to the
+// array containing at least HD_MAXIDX elements.
+void init_hdidx(HeaderIndex &hdidx);
+// Indexes header |token| using index |idx|.
+void index_header(HeaderIndex &hdidx, int32_t token, size_t idx);
+
+// Returns header denoted by |token| using index |hdidx|.
+const Headers::value_type *get_header(const HeaderIndex &hdidx, int32_t token,
+ const Headers &nva);
+
+Headers::value_type *get_header(const HeaderIndex &hdidx, int32_t token,
+ Headers &nva);
+
+struct LinkHeader {
+ // The region of URI. This might not be NULL-terminated.
+ StringRef uri;
+};
+
+// Returns next URI-reference in Link header field value |src|. If no
+// URI-reference found after searching all input, returned uri field
+// is empty. This imply that empty URI-reference is ignored during
+// parsing.
+std::vector<LinkHeader> parse_link_header(const StringRef &src);
+
+// Constructs path by combining base path |base_path| with another
+// path |rel_path|. The base path and another path can have optional
+// query component. This function assumes |base_path| is normalized.
+// In other words, it does not contain ".." or "." path components
+// and starts with "/" if it is not empty.
+std::string path_join(const StringRef &base, const StringRef &base_query,
+ const StringRef &rel_path, const StringRef &rel_query);
+
+StringRef path_join(BlockAllocator &balloc, const StringRef &base_path,
+ const StringRef &base_query, const StringRef &rel_path,
+ const StringRef &rel_query);
+
+// true if response has body, taking into account the request method
+// and status code.
+bool expect_response_body(const std::string &method, int status_code);
+bool expect_response_body(int method_token, int status_code);
+
+// true if response has body, taking into account status code only.
+bool expect_response_body(int status_code);
+
+// Looks up method token for method name |name| of length |namelen|.
+// Only methods defined in llhttp.h (llhttp_method) are tokenized. If
+// method name cannot be tokenized, returns -1.
+int lookup_method_token(const uint8_t *name, size_t namelen);
+int lookup_method_token(const StringRef &name);
+
+// Returns string representation of |method_token|. This is wrapper
+// around llhttp_method_name from llhttp. If |method_token| is
+// unknown, program aborts. The returned StringRef is guaranteed to
+// be NULL-terminated.
+StringRef to_method_string(int method_token);
+
+StringRef normalize_path(BlockAllocator &balloc, const StringRef &path,
+ const StringRef &query);
+
+// normalize_path_colon is like normalize_path, but it additionally
+// does percent-decoding %3A in order to workaround the issue that ':'
+// cannot be included in backend pattern.
+StringRef normalize_path_colon(BlockAllocator &balloc, const StringRef &path,
+ const StringRef &query);
+
+std::string normalize_path(const StringRef &path, const StringRef &query);
+
+StringRef rewrite_clean_path(BlockAllocator &balloc, const StringRef &src);
+
+// Returns path component of |uri|. The returned path does not
+// include query component. This function returns empty string if it
+// fails.
+StringRef get_pure_path_component(const StringRef &uri);
+
+// Deduces scheme, authority and path from given |uri|, and stores
+// them in |scheme|, |authority|, and |path| respectively. If |uri|
+// is relative path, path resolution takes place using path given in
+// |base| of length |baselen|. This function returns 0 if it
+// succeeds, or -1.
+int construct_push_component(BlockAllocator &balloc, StringRef &scheme,
+ StringRef &authority, StringRef &path,
+ const StringRef &base, const StringRef &uri);
+
+// Copies |src| and return its lower-cased version.
+StringRef copy_lower(BlockAllocator &balloc, const StringRef &src);
+
+// Returns true if te header field value |s| contains "trailers".
+bool contains_trailers(const StringRef &s);
+
+// Creates Sec-WebSocket-Accept value for |key|. The capacity of
+// buffer pointed by |dest| must have at least 24 bytes (base64
+// encoded length of 16 bytes data). It returns empty string in case
+// of error.
+StringRef make_websocket_accept_token(uint8_t *dest, const StringRef &key);
+
+// Returns true if HTTP version represents pre-HTTP/1.1 (e.g.,
+// HTTP/0.9 or HTTP/1.0).
+bool legacy_http1(int major, int minor);
+
+// Returns true if transfer-encoding field value |s| conforms RFC
+// strictly. This function does not allow empty value, BWS, and empty
+// list elements.
+bool check_transfer_encoding(const StringRef &s);
+
+} // namespace http2
+
+} // namespace nghttp2
+
+#endif // HTTP2_H
diff --git a/src/http2_test.cc b/src/http2_test.cc
new file mode 100644
index 0000000..f8be9f4
--- /dev/null
+++ b/src/http2_test.cc
@@ -0,0 +1,1249 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "http2_test.h"
+
+#include <cassert>
+#include <cstring>
+#include <iostream>
+
+#include <CUnit/CUnit.h>
+
+#include "url-parser/url_parser.h"
+
+#include "http2.h"
+#include "util.h"
+
+using namespace nghttp2;
+
+#define MAKE_NV(K, V) \
+ { \
+ (uint8_t *)K, (uint8_t *)V, sizeof(K) - 1, sizeof(V) - 1, \
+ NGHTTP2_NV_FLAG_NONE \
+ }
+
+namespace shrpx {
+
+namespace {
+void check_nv(const HeaderRef &a, const nghttp2_nv *b) {
+ CU_ASSERT(a.name.size() == b->namelen);
+ CU_ASSERT(a.value.size() == b->valuelen);
+ CU_ASSERT(memcmp(a.name.c_str(), b->name, b->namelen) == 0);
+ CU_ASSERT(memcmp(a.value.c_str(), b->value, b->valuelen) == 0);
+}
+} // namespace
+
+void test_http2_add_header(void) {
+ auto nva = Headers();
+
+ http2::add_header(nva, (const uint8_t *)"alpha", 5, (const uint8_t *)"123", 3,
+ false, -1);
+ CU_ASSERT(Headers::value_type("alpha", "123") == nva[0]);
+ CU_ASSERT(!nva[0].no_index);
+
+ nva.clear();
+
+ http2::add_header(nva, (const uint8_t *)"alpha", 5, (const uint8_t *)"", 0,
+ true, -1);
+ CU_ASSERT(Headers::value_type("alpha", "") == nva[0]);
+ CU_ASSERT(nva[0].no_index);
+
+ nva.clear();
+
+ http2::add_header(nva, (const uint8_t *)"a", 1, (const uint8_t *)" b", 2,
+ false, -1);
+ CU_ASSERT(Headers::value_type("a", "b") == nva[0]);
+
+ nva.clear();
+
+ http2::add_header(nva, (const uint8_t *)"a", 1, (const uint8_t *)"b ", 2,
+ false, -1);
+ CU_ASSERT(Headers::value_type("a", "b") == nva[0]);
+
+ nva.clear();
+
+ http2::add_header(nva, (const uint8_t *)"a", 1, (const uint8_t *)" b ", 5,
+ false, -1);
+ CU_ASSERT(Headers::value_type("a", "b") == nva[0]);
+
+ nva.clear();
+
+ http2::add_header(nva, (const uint8_t *)"a", 1, (const uint8_t *)" bravo ",
+ 9, false, -1);
+ CU_ASSERT(Headers::value_type("a", "bravo") == nva[0]);
+
+ nva.clear();
+
+ http2::add_header(nva, (const uint8_t *)"a", 1, (const uint8_t *)" ", 4,
+ false, -1);
+ CU_ASSERT(Headers::value_type("a", "") == nva[0]);
+
+ nva.clear();
+
+ http2::add_header(nva, (const uint8_t *)"te", 2, (const uint8_t *)"trailers",
+ 8, false, http2::HD_TE);
+ CU_ASSERT(http2::HD_TE == nva[0].token);
+}
+
+void test_http2_get_header(void) {
+ auto nva = Headers{{"alpha", "1"}, {"bravo", "2"}, {"bravo", "3"},
+ {"charlie", "4"}, {"delta", "5"}, {"echo", "6"},
+ {"content-length", "7"}};
+ const Headers::value_type *rv;
+ rv = http2::get_header(nva, "delta");
+ CU_ASSERT(rv != nullptr);
+ CU_ASSERT("delta" == rv->name);
+
+ rv = http2::get_header(nva, "bravo");
+ CU_ASSERT(rv != nullptr);
+ CU_ASSERT("bravo" == rv->name);
+
+ rv = http2::get_header(nva, "foxtrot");
+ CU_ASSERT(rv == nullptr);
+
+ http2::HeaderIndex hdidx;
+ http2::init_hdidx(hdidx);
+ hdidx[http2::HD_CONTENT_LENGTH] = 6;
+ rv = http2::get_header(hdidx, http2::HD_CONTENT_LENGTH, nva);
+ CU_ASSERT("content-length" == rv->name);
+}
+
+namespace {
+auto headers = HeaderRefs{
+ {StringRef::from_lit("alpha"), StringRef::from_lit("0"), true},
+ {StringRef::from_lit("bravo"), StringRef::from_lit("1")},
+ {StringRef::from_lit("connection"), StringRef::from_lit("2"), false,
+ http2::HD_CONNECTION},
+ {StringRef::from_lit("connection"), StringRef::from_lit("3"), false,
+ http2::HD_CONNECTION},
+ {StringRef::from_lit("delta"), StringRef::from_lit("4")},
+ {StringRef::from_lit("expect"), StringRef::from_lit("5")},
+ {StringRef::from_lit("foxtrot"), StringRef::from_lit("6")},
+ {StringRef::from_lit("tango"), StringRef::from_lit("7")},
+ {StringRef::from_lit("te"), StringRef::from_lit("8"), false, http2::HD_TE},
+ {StringRef::from_lit("te"), StringRef::from_lit("9"), false, http2::HD_TE},
+ {StringRef::from_lit("x-forwarded-proto"), StringRef::from_lit("10"), false,
+ http2::HD_X_FORWARDED_FOR},
+ {StringRef::from_lit("x-forwarded-proto"), StringRef::from_lit("11"), false,
+ http2::HD_X_FORWARDED_FOR},
+ {StringRef::from_lit("zulu"), StringRef::from_lit("12")}};
+} // namespace
+
+namespace {
+auto headers2 = HeaderRefs{
+ {StringRef::from_lit("x-forwarded-for"), StringRef::from_lit("xff1"), false,
+ http2::HD_X_FORWARDED_FOR},
+ {StringRef::from_lit("x-forwarded-for"), StringRef::from_lit("xff2"), false,
+ http2::HD_X_FORWARDED_FOR},
+ {StringRef::from_lit("x-forwarded-proto"), StringRef::from_lit("xfp1"),
+ false, http2::HD_X_FORWARDED_PROTO},
+ {StringRef::from_lit("x-forwarded-proto"), StringRef::from_lit("xfp2"),
+ false, http2::HD_X_FORWARDED_PROTO},
+ {StringRef::from_lit("forwarded"), StringRef::from_lit("fwd1"), false,
+ http2::HD_FORWARDED},
+ {StringRef::from_lit("forwarded"), StringRef::from_lit("fwd2"), false,
+ http2::HD_FORWARDED},
+ {StringRef::from_lit("via"), StringRef::from_lit("via1"), false,
+ http2::HD_VIA},
+ {StringRef::from_lit("via"), StringRef::from_lit("via2"), false,
+ http2::HD_VIA},
+};
+} // namespace
+
+void test_http2_copy_headers_to_nva(void) {
+ auto ans = std::vector<int>{0, 1, 4, 5, 6, 7, 12};
+ std::vector<nghttp2_nv> nva;
+
+ http2::copy_headers_to_nva_nocopy(nva, headers,
+ http2::HDOP_STRIP_X_FORWARDED_FOR);
+ CU_ASSERT(7 == nva.size());
+ for (size_t i = 0; i < ans.size(); ++i) {
+ check_nv(headers[ans[i]], &nva[i]);
+
+ if (ans[i] == 0) {
+ CU_ASSERT((NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE |
+ NGHTTP2_NV_FLAG_NO_INDEX) == nva[i].flags);
+ } else {
+ CU_ASSERT((NGHTTP2_NV_FLAG_NO_COPY_NAME |
+ NGHTTP2_NV_FLAG_NO_COPY_VALUE) == nva[i].flags);
+ }
+ }
+
+ nva.clear();
+ http2::copy_headers_to_nva(nva, headers, http2::HDOP_STRIP_X_FORWARDED_FOR);
+ CU_ASSERT(7 == nva.size());
+ for (size_t i = 0; i < ans.size(); ++i) {
+ check_nv(headers[ans[i]], &nva[i]);
+
+ if (ans[i] == 0) {
+ CU_ASSERT(nva[i].flags & NGHTTP2_NV_FLAG_NO_INDEX);
+ } else {
+ CU_ASSERT(NGHTTP2_NV_FLAG_NONE == nva[i].flags);
+ }
+ }
+
+ nva.clear();
+
+ auto ans2 = std::vector<int>{0, 2, 4, 6};
+ http2::copy_headers_to_nva(nva, headers2, http2::HDOP_NONE);
+ CU_ASSERT(ans2.size() == nva.size());
+ for (size_t i = 0; i < ans2.size(); ++i) {
+ check_nv(headers2[ans2[i]], &nva[i]);
+ }
+
+ nva.clear();
+
+ http2::copy_headers_to_nva(nva, headers2, http2::HDOP_STRIP_ALL);
+ CU_ASSERT(nva.empty());
+}
+
+void test_http2_build_http1_headers_from_headers(void) {
+ MemchunkPool pool;
+ DefaultMemchunks buf(&pool);
+ http2::build_http1_headers_from_headers(&buf, headers,
+ http2::HDOP_STRIP_X_FORWARDED_FOR);
+ auto hdrs = std::string(buf.head->pos, buf.head->last);
+ CU_ASSERT("Alpha: 0\r\n"
+ "Bravo: 1\r\n"
+ "Delta: 4\r\n"
+ "Expect: 5\r\n"
+ "Foxtrot: 6\r\n"
+ "Tango: 7\r\n"
+ "Te: 8\r\n"
+ "Te: 9\r\n"
+ "Zulu: 12\r\n" == hdrs);
+
+ buf.reset();
+
+ http2::build_http1_headers_from_headers(&buf, headers2, http2::HDOP_NONE);
+ hdrs = std::string(buf.head->pos, buf.head->last);
+ CU_ASSERT("X-Forwarded-For: xff1\r\n"
+ "X-Forwarded-Proto: xfp1\r\n"
+ "Forwarded: fwd1\r\n"
+ "Via: via1\r\n" == hdrs);
+
+ buf.reset();
+
+ http2::build_http1_headers_from_headers(&buf, headers2,
+ http2::HDOP_STRIP_ALL);
+ CU_ASSERT(0 == buf.rleft());
+}
+
+void test_http2_lws(void) {
+ CU_ASSERT(!http2::lws("alpha"));
+ CU_ASSERT(http2::lws(" "));
+ CU_ASSERT(http2::lws(""));
+}
+
+namespace {
+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) {
+ BlockAllocator balloc(4096, 4096);
+ http_parser_url u{};
+ CU_ASSERT(0 == http_parser_parse_url(uri.c_str(), uri.size(), 0, &u));
+ auto got = http2::rewrite_location_uri(
+ balloc, StringRef{uri}, u, StringRef{match_host},
+ StringRef{req_authority}, StringRef{upstream_scheme});
+ CU_ASSERT(want == got);
+}
+} // namespace
+
+void test_http2_rewrite_location_uri(void) {
+ check_rewrite_location_uri("https://localhost:3000/alpha?bravo#charlie",
+ "http://localhost:3001/alpha?bravo#charlie",
+ "localhost:3001", "localhost:3000", "https");
+ check_rewrite_location_uri("https://localhost/", "http://localhost:3001/",
+ "localhost", "localhost", "https");
+ check_rewrite_location_uri("http://localhost/", "http://localhost:3001/",
+ "localhost", "localhost", "http");
+ check_rewrite_location_uri("http://localhost:443/", "http://localhost:3001/",
+ "localhost", "localhost:443", "http");
+ check_rewrite_location_uri("https://localhost:80/", "http://localhost:3001/",
+ "localhost", "localhost:80", "https");
+ check_rewrite_location_uri("", "http://localhost:3001/", "127.0.0.1",
+ "127.0.0.1", "https");
+ check_rewrite_location_uri("https://localhost:3000/",
+ "http://localhost:3001/", "localhost",
+ "localhost:3000", "https");
+ check_rewrite_location_uri("https://localhost:3000/", "http://localhost/",
+ "localhost", "localhost:3000", "https");
+
+ // match_host != req_authority
+ check_rewrite_location_uri("https://example.org", "http://127.0.0.1:8080",
+ "127.0.0.1", "example.org", "https");
+ check_rewrite_location_uri("", "http://example.org", "127.0.0.1",
+ "example.org", "https");
+}
+
+void test_http2_parse_http_status_code(void) {
+ CU_ASSERT(200 == http2::parse_http_status_code(StringRef::from_lit("200")));
+ CU_ASSERT(102 == http2::parse_http_status_code(StringRef::from_lit("102")));
+ CU_ASSERT(-1 == http2::parse_http_status_code(StringRef::from_lit("099")));
+ CU_ASSERT(-1 == http2::parse_http_status_code(StringRef::from_lit("99")));
+ CU_ASSERT(-1 == http2::parse_http_status_code(StringRef::from_lit("-1")));
+ CU_ASSERT(-1 == http2::parse_http_status_code(StringRef::from_lit("20a")));
+ CU_ASSERT(-1 == http2::parse_http_status_code(StringRef{}));
+}
+
+void test_http2_index_header(void) {
+ http2::HeaderIndex hdidx;
+ http2::init_hdidx(hdidx);
+
+ http2::index_header(hdidx, http2::HD__AUTHORITY, 0);
+ http2::index_header(hdidx, -1, 1);
+
+ CU_ASSERT(0 == hdidx[http2::HD__AUTHORITY]);
+}
+
+void test_http2_lookup_token(void) {
+ CU_ASSERT(http2::HD__AUTHORITY ==
+ http2::lookup_token(StringRef::from_lit(":authority")));
+ CU_ASSERT(-1 == http2::lookup_token(StringRef::from_lit(":authorit")));
+ CU_ASSERT(-1 == http2::lookup_token(StringRef::from_lit(":Authority")));
+ CU_ASSERT(http2::HD_EXPECT ==
+ http2::lookup_token(StringRef::from_lit("expect")));
+}
+
+void test_http2_parse_link_header(void) {
+ {
+ // only URI appears; we don't extract URI unless it bears rel=preload
+ auto res = http2::parse_link_header(StringRef::from_lit("<url>"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // URI url should be extracted
+ auto res =
+ http2::parse_link_header(StringRef::from_lit("<url>; rel=preload"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // With extra link-param. URI url should be extracted
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>; rel=preload; as=file"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // With extra link-param. URI url should be extracted
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>; as=file; rel=preload"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // With extra link-param and quote-string. URI url should be
+ // extracted
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel=preload; title="foo,bar")"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // With extra link-param and quote-string. URI url should be
+ // extracted
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; title="foo,bar"; rel=preload)"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // ',' after quote-string
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; title="foo,bar", <url2>; rel=preload)"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url2" == res[0].uri);
+ }
+ {
+ // Only first URI should be extracted.
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>; rel=preload, <url2>"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // Both have rel=preload, so both urls should be extracted
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>; rel=preload, <url2>; rel=preload"));
+ CU_ASSERT(2 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ CU_ASSERT("url2" == res[1].uri);
+ }
+ {
+ // Second URI uri should be extracted.
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>, <url2>;rel=preload"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url2" == res[0].uri);
+ }
+ {
+ // Error if input ends with ';'
+ auto res =
+ http2::parse_link_header(StringRef::from_lit("<url>;rel=preload;"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // Error if link header ends with ';'
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>;rel=preload;, <url>"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // OK if input ends with ','
+ auto res =
+ http2::parse_link_header(StringRef::from_lit("<url>;rel=preload,"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // Multiple repeated ','s between fields is OK
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>,,,<url2>;rel=preload"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url2" == res[0].uri);
+ }
+ {
+ // Error if url is not enclosed by <>
+ auto res =
+ http2::parse_link_header(StringRef::from_lit("url>;rel=preload"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // Error if url is not enclosed by <>
+ auto res =
+ http2::parse_link_header(StringRef::from_lit("<url;rel=preload"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // Empty parameter value is not allowed
+ auto res =
+ http2::parse_link_header(StringRef::from_lit("<url>;rel=preload; as="));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // Empty parameter value is not allowed
+ auto res =
+ http2::parse_link_header(StringRef::from_lit("<url>;as=;rel=preload"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // Empty parameter value is not allowed
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>;as=, <url>;rel=preload"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // Empty parameter name is not allowed
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>; =file; rel=preload"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // Without whitespaces
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>;as=file;rel=preload,<url2>;rel=preload"));
+ CU_ASSERT(2 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ CU_ASSERT("url2" == res[1].uri);
+ }
+ {
+ // link-extension may have no value
+ auto res =
+ http2::parse_link_header(StringRef::from_lit("<url>; as; rel=preload"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // ext-name-star
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>; foo*=bar; rel=preload"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // '*' is not allowed expect for trailing one
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>; *=bar; rel=preload"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // '*' is not allowed expect for trailing one
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>; foo*bar=buzz; rel=preload"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // ext-name-star must be followed by '='
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>; foo*; rel=preload"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // '>' is not followed by ';'
+ auto res =
+ http2::parse_link_header(StringRef::from_lit("<url> rel=preload"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // Starting with whitespace is no problem.
+ auto res =
+ http2::parse_link_header(StringRef::from_lit(" <url>; rel=preload"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // preload is a prefix of bogus rel parameter value
+ auto res =
+ http2::parse_link_header(StringRef::from_lit("<url>; rel=preloadx"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // preload in relation-types list
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel="preload")"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // preload in relation-types list followed by another parameter
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel="preload foo")"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // preload in relation-types list following another parameter
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel="foo preload")"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // preload in relation-types list between other parameters
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel="foo preload bar")"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // preload in relation-types list between other parameters
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel="foo preload bar")"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // no preload in relation-types list
+ auto res =
+ http2::parse_link_header(StringRef::from_lit(R"(<url>; rel="foo")"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // no preload in relation-types list, multiple unrelated elements.
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel="foo bar")"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // preload in relation-types list, followed by another link-value.
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel="preload", <url2>)"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // preload in relation-types list, following another link-value.
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>, <url2>; rel="preload")"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url2" == res[0].uri);
+ }
+ {
+ // preload in relation-types list, followed by another link-param.
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel="preload"; as="font")"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // preload in relation-types list, followed by character other
+ // than ';' or ','
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel="preload".)"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // preload in relation-types list, followed by ';' but it
+ // terminates input
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel="preload";)"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // preload in relation-types list, followed by ',' but it
+ // terminates input
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel="preload",)"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // preload in relation-types list but there is preceding white
+ // space.
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel=" preload")"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // preload in relation-types list but there is trailing white
+ // space.
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel="preload ")"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // backslash escaped characters in quoted-string
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel=preload; title="foo\"baz\"bar")"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // anchor="" is acceptable
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel=preload; anchor="")"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // With anchor="#foo", url should be ignored
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel=preload; anchor="#foo")"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // With anchor=f, url should be ignored
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>; rel=preload; anchor=f"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // First url is ignored With anchor="#foo", but url should be
+ // accepted.
+ auto res = http2::parse_link_header(StringRef::from_lit(
+ R"(<url>; rel=preload; anchor="#foo", <url2>; rel=preload)"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url2" == res[0].uri);
+ }
+ {
+ // With loadpolicy="next", url should be ignored
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel=preload; loadpolicy="next")"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // url should be picked up if empty loadpolicy is specified
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel=preload; loadpolicy="")"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // case-insensitive match
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel=preload; ANCHOR="#foo", <url2>; )"
+ R"(REL=PRELOAD, <url3>; REL="foo PRELOAD bar")"));
+ CU_ASSERT(2 == res.size());
+ CU_ASSERT("url2" == res[0].uri);
+ CU_ASSERT("url3" == res[1].uri);
+ }
+ {
+ // nopush at the end of input
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>; rel=preload; nopush"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // nopush followed by ';'
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>; rel=preload; nopush; foo"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // nopush followed by ','
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>; nopush; rel=preload"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // string whose prefix is nopush
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>; nopushyes; rel=preload"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // rel=preload twice
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>; rel=preload; rel=preload"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+}
+
+void test_http2_path_join(void) {
+ {
+ auto base = StringRef::from_lit("/");
+ auto rel = StringRef::from_lit("/");
+ CU_ASSERT("/" == http2::path_join(base, StringRef{}, rel, StringRef{}));
+ }
+ {
+ auto base = StringRef::from_lit("/");
+ auto rel = StringRef::from_lit("/alpha");
+ CU_ASSERT("/alpha" ==
+ http2::path_join(base, StringRef{}, rel, StringRef{}));
+ }
+ {
+ // rel ends with trailing '/'
+ auto base = StringRef::from_lit("/");
+ auto rel = StringRef::from_lit("/alpha/");
+ CU_ASSERT("/alpha/" ==
+ http2::path_join(base, StringRef{}, rel, StringRef{}));
+ }
+ {
+ // rel contains multiple components
+ auto base = StringRef::from_lit("/");
+ auto rel = StringRef::from_lit("/alpha/bravo");
+ CU_ASSERT("/alpha/bravo" ==
+ http2::path_join(base, StringRef{}, rel, StringRef{}));
+ }
+ {
+ // rel is relative
+ auto base = StringRef::from_lit("/");
+ auto rel = StringRef::from_lit("alpha/bravo");
+ CU_ASSERT("/alpha/bravo" ==
+ http2::path_join(base, StringRef{}, rel, StringRef{}));
+ }
+ {
+ // rel is relative and base ends without /, which means it refers
+ // to file.
+ auto base = StringRef::from_lit("/alpha");
+ auto rel = StringRef::from_lit("bravo/charlie");
+ CU_ASSERT("/bravo/charlie" ==
+ http2::path_join(base, StringRef{}, rel, StringRef{}));
+ }
+ {
+ // rel contains repeated '/'s
+ auto base = StringRef::from_lit("/");
+ auto rel = StringRef::from_lit("/alpha/////bravo/////");
+ CU_ASSERT("/alpha/bravo/" ==
+ http2::path_join(base, StringRef{}, rel, StringRef{}));
+ }
+ {
+ // base ends with '/', so '..' eats 'bravo'
+ auto base = StringRef::from_lit("/alpha/bravo/");
+ auto rel = StringRef::from_lit("../charlie/delta");
+ CU_ASSERT("/alpha/charlie/delta" ==
+ http2::path_join(base, StringRef{}, rel, StringRef{}));
+ }
+ {
+ // base does not end with '/', so '..' eats 'alpha/bravo'
+ auto base = StringRef::from_lit("/alpha/bravo");
+ auto rel = StringRef::from_lit("../charlie");
+ CU_ASSERT("/charlie" ==
+ http2::path_join(base, StringRef{}, rel, StringRef{}));
+ }
+ {
+ // 'charlie' is eaten by following '..'
+ auto base = StringRef::from_lit("/alpha/bravo/");
+ auto rel = StringRef::from_lit("../charlie/../delta");
+ CU_ASSERT("/alpha/delta" ==
+ http2::path_join(base, StringRef{}, rel, StringRef{}));
+ }
+ {
+ // excessive '..' results in '/'
+ auto base = StringRef::from_lit("/alpha/bravo/");
+ auto rel = StringRef::from_lit("../../../");
+ CU_ASSERT("/" == http2::path_join(base, StringRef{}, rel, StringRef{}));
+ }
+ {
+ // excessive '..' and path component
+ auto base = StringRef::from_lit("/alpha/bravo/");
+ auto rel = StringRef::from_lit("../../../charlie");
+ CU_ASSERT("/charlie" ==
+ http2::path_join(base, StringRef{}, rel, StringRef{}));
+ }
+ {
+ // rel ends with '..'
+ auto base = StringRef::from_lit("/alpha/bravo/");
+ auto rel = StringRef::from_lit("charlie/..");
+ CU_ASSERT("/alpha/bravo/" ==
+ http2::path_join(base, StringRef{}, rel, StringRef{}));
+ }
+ {
+ // base empty and rel contains '..'
+ auto base = StringRef{};
+ auto rel = StringRef::from_lit("charlie/..");
+ CU_ASSERT("/" == http2::path_join(base, StringRef{}, rel, StringRef{}));
+ }
+ {
+ // '.' is ignored
+ auto base = StringRef::from_lit("/");
+ auto rel = StringRef::from_lit("charlie/././././delta");
+ CU_ASSERT("/charlie/delta" ==
+ http2::path_join(base, StringRef{}, rel, StringRef{}));
+ }
+ {
+ // trailing '.' is ignored
+ auto base = StringRef::from_lit("/");
+ auto rel = StringRef::from_lit("charlie/.");
+ CU_ASSERT("/charlie/" ==
+ http2::path_join(base, StringRef{}, rel, StringRef{}));
+ }
+ {
+ // query
+ auto base = StringRef::from_lit("/");
+ auto rel = StringRef::from_lit("/");
+ auto relq = StringRef::from_lit("q");
+ CU_ASSERT("/?q" == http2::path_join(base, StringRef{}, rel, relq));
+ }
+ {
+ // empty rel and query
+ auto base = StringRef::from_lit("/alpha");
+ auto rel = StringRef{};
+ auto relq = StringRef::from_lit("q");
+ CU_ASSERT("/alpha?q" == http2::path_join(base, StringRef{}, rel, relq));
+ }
+ {
+ // both rel and query are empty
+ auto base = StringRef::from_lit("/alpha");
+ auto baseq = StringRef::from_lit("r");
+ auto rel = StringRef{};
+ auto relq = StringRef{};
+ CU_ASSERT("/alpha?r" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ // empty base
+ auto base = StringRef{};
+ auto rel = StringRef::from_lit("/alpha");
+ CU_ASSERT("/alpha" ==
+ http2::path_join(base, StringRef{}, rel, StringRef{}));
+ }
+ {
+ // everything is empty
+ CU_ASSERT("/" == http2::path_join(StringRef{}, StringRef{}, StringRef{},
+ StringRef{}));
+ }
+ {
+ // only baseq is not empty
+ auto base = StringRef{};
+ auto baseq = StringRef::from_lit("r");
+ auto rel = StringRef{};
+ CU_ASSERT("/?r" == http2::path_join(base, baseq, rel, StringRef{}));
+ }
+ {
+ // path starts with multiple '/'s.
+ auto base = StringRef{};
+ auto baseq = StringRef{};
+ auto rel = StringRef::from_lit("//alpha//bravo");
+ auto relq = StringRef::from_lit("charlie");
+ CU_ASSERT("/alpha/bravo?charlie" ==
+ http2::path_join(base, baseq, rel, relq));
+ }
+ // Test cases from RFC 3986, section 5.4.
+ constexpr auto base = StringRef::from_lit("/b/c/d;p");
+ constexpr auto baseq = StringRef::from_lit("q");
+ {
+ auto rel = StringRef::from_lit("g");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/c/g" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("./g");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/c/g" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("g/");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/c/g/" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("/g");
+ auto relq = StringRef{};
+ CU_ASSERT("/g" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef{};
+ auto relq = StringRef::from_lit("y");
+ CU_ASSERT("/b/c/d;p?y" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("g");
+ auto relq = StringRef::from_lit("y");
+ CU_ASSERT("/b/c/g?y" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit(";x");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/c/;x" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("g;x");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/c/g;x" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("g;x");
+ auto relq = StringRef::from_lit("y");
+ CU_ASSERT("/b/c/g;x?y" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef{};
+ auto relq = StringRef{};
+ CU_ASSERT("/b/c/d;p?q" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit(".");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/c/" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("./");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/c/" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("..");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("../");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("../g");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/g" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("../..");
+ auto relq = StringRef{};
+ CU_ASSERT("/" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("../../");
+ auto relq = StringRef{};
+ CU_ASSERT("/" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("../../g");
+ auto relq = StringRef{};
+ CU_ASSERT("/g" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("../../../g");
+ auto relq = StringRef{};
+ CU_ASSERT("/g" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("../../../../g");
+ auto relq = StringRef{};
+ CU_ASSERT("/g" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("/./g");
+ auto relq = StringRef{};
+ CU_ASSERT("/g" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("/../g");
+ auto relq = StringRef{};
+ CU_ASSERT("/g" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("g.");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/c/g." == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit(".g");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/c/.g" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("g..");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/c/g.." == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("..g");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/c/..g" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("./../g");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/g" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("./g/.");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/c/g/" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("g/./h");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/c/g/h" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("g/../h");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/c/h" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("g;x=1/./y");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/c/g;x=1/y" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("g;x=1/../y");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/c/y" == http2::path_join(base, baseq, rel, relq));
+ }
+}
+
+void test_http2_normalize_path(void) {
+ CU_ASSERT("/alpha/charlie" ==
+ http2::normalize_path(
+ StringRef::from_lit("/alpha/bravo/../charlie"), StringRef{}));
+
+ CU_ASSERT("/alpha" ==
+ http2::normalize_path(StringRef::from_lit("/a%6c%70%68%61"),
+ StringRef{}));
+
+ CU_ASSERT(
+ "/alpha%2F%3A" ==
+ http2::normalize_path(StringRef::from_lit("/alpha%2f%3a"), StringRef{}));
+
+ CU_ASSERT("/%2F" ==
+ http2::normalize_path(StringRef::from_lit("%2f"), StringRef{}));
+
+ CU_ASSERT("/%f" ==
+ http2::normalize_path(StringRef::from_lit("%f"), StringRef{}));
+
+ CU_ASSERT("/%" ==
+ http2::normalize_path(StringRef::from_lit("%"), StringRef{}));
+
+ CU_ASSERT("/" == http2::normalize_path(StringRef{}, StringRef{}));
+
+ CU_ASSERT("/alpha?bravo" ==
+ http2::normalize_path(StringRef::from_lit("/alpha"),
+ StringRef::from_lit("bravo")));
+}
+
+void test_http2_rewrite_clean_path(void) {
+ BlockAllocator balloc(4096, 4096);
+
+ // unreserved characters
+ CU_ASSERT("/alpha/bravo/" ==
+ http2::rewrite_clean_path(balloc,
+ StringRef::from_lit("/alpha/%62ravo/")));
+
+ // percent-encoding is converted to upper case.
+ CU_ASSERT("/delta%3A" == http2::rewrite_clean_path(
+ balloc, StringRef::from_lit("/delta%3a")));
+
+ // path component is normalized before matching
+ CU_ASSERT(
+ "/alpha/bravo/" ==
+ http2::rewrite_clean_path(
+ balloc, StringRef::from_lit("/alpha/charlie/%2e././bravo/delta/..")));
+
+ CU_ASSERT("alpha%3a" ==
+ http2::rewrite_clean_path(balloc, StringRef::from_lit("alpha%3a")));
+
+ CU_ASSERT("" == http2::rewrite_clean_path(balloc, StringRef{}));
+
+ CU_ASSERT(
+ "/alpha?bravo" ==
+ http2::rewrite_clean_path(balloc, StringRef::from_lit("//alpha?bravo")));
+}
+
+void test_http2_get_pure_path_component(void) {
+ CU_ASSERT("/" == http2::get_pure_path_component(StringRef::from_lit("/")));
+
+ CU_ASSERT("/foo" ==
+ http2::get_pure_path_component(StringRef::from_lit("/foo")));
+
+ CU_ASSERT("/bar" == http2::get_pure_path_component(
+ StringRef::from_lit("https://example.org/bar")));
+
+ CU_ASSERT("/alpha" == http2::get_pure_path_component(StringRef::from_lit(
+ "https://example.org/alpha?q=a")));
+
+ CU_ASSERT("/bravo" == http2::get_pure_path_component(StringRef::from_lit(
+ "https://example.org/bravo?q=a#fragment")));
+
+ CU_ASSERT("" ==
+ http2::get_pure_path_component(StringRef::from_lit("\x01\x02")));
+}
+
+void test_http2_construct_push_component(void) {
+ BlockAllocator balloc(4096, 4096);
+ StringRef base, uri;
+ StringRef scheme, authority, path;
+
+ base = StringRef::from_lit("/b/");
+ uri = StringRef::from_lit("https://example.org/foo");
+
+ CU_ASSERT(0 == http2::construct_push_component(balloc, scheme, authority,
+ path, base, uri));
+ CU_ASSERT("https" == scheme);
+ CU_ASSERT("example.org" == authority);
+ CU_ASSERT("/foo" == path);
+
+ scheme = StringRef{};
+ authority = StringRef{};
+ path = StringRef{};
+
+ uri = StringRef::from_lit("/foo/bar?q=a");
+
+ CU_ASSERT(0 == http2::construct_push_component(balloc, scheme, authority,
+ path, base, uri));
+ CU_ASSERT("" == scheme);
+ CU_ASSERT("" == authority);
+ CU_ASSERT("/foo/bar?q=a" == path);
+
+ scheme = StringRef{};
+ authority = StringRef{};
+ path = StringRef{};
+
+ uri = StringRef::from_lit("foo/../bar?q=a");
+
+ CU_ASSERT(0 == http2::construct_push_component(balloc, scheme, authority,
+ path, base, uri));
+ CU_ASSERT("" == scheme);
+ CU_ASSERT("" == authority);
+ CU_ASSERT("/b/bar?q=a" == path);
+
+ scheme = StringRef{};
+ authority = StringRef{};
+ path = StringRef{};
+
+ uri = StringRef{};
+
+ CU_ASSERT(-1 == http2::construct_push_component(balloc, scheme, authority,
+ path, base, uri));
+ scheme = StringRef{};
+ authority = StringRef{};
+ path = StringRef{};
+
+ uri = StringRef::from_lit("?q=a");
+
+ CU_ASSERT(0 == http2::construct_push_component(balloc, scheme, authority,
+ path, base, uri));
+ CU_ASSERT("" == scheme);
+ CU_ASSERT("" == authority);
+ CU_ASSERT("/b/?q=a" == path);
+}
+
+void test_http2_contains_trailers(void) {
+ CU_ASSERT(!http2::contains_trailers(StringRef::from_lit("")));
+ CU_ASSERT(http2::contains_trailers(StringRef::from_lit("trailers")));
+ // Match must be case-insensitive.
+ CU_ASSERT(http2::contains_trailers(StringRef::from_lit("TRAILERS")));
+ CU_ASSERT(!http2::contains_trailers(StringRef::from_lit("trailer")));
+ CU_ASSERT(!http2::contains_trailers(StringRef::from_lit("trailers 3")));
+ CU_ASSERT(http2::contains_trailers(StringRef::from_lit("trailers,")));
+ CU_ASSERT(http2::contains_trailers(StringRef::from_lit("trailers,foo")));
+ CU_ASSERT(http2::contains_trailers(StringRef::from_lit("foo,trailers")));
+ CU_ASSERT(http2::contains_trailers(StringRef::from_lit("foo,trailers,bar")));
+ CU_ASSERT(
+ http2::contains_trailers(StringRef::from_lit("foo, trailers ,bar")));
+ CU_ASSERT(http2::contains_trailers(StringRef::from_lit(",trailers")));
+}
+
+void test_http2_check_transfer_encoding(void) {
+ CU_ASSERT(http2::check_transfer_encoding(StringRef::from_lit("chunked")));
+ CU_ASSERT(http2::check_transfer_encoding(StringRef::from_lit("foo,chunked")));
+ CU_ASSERT(
+ http2::check_transfer_encoding(StringRef::from_lit("foo, chunked")));
+ CU_ASSERT(
+ http2::check_transfer_encoding(StringRef::from_lit("foo , chunked")));
+ CU_ASSERT(
+ http2::check_transfer_encoding(StringRef::from_lit("chunked;foo=bar")));
+ CU_ASSERT(
+ http2::check_transfer_encoding(StringRef::from_lit("chunked ; foo=bar")));
+ CU_ASSERT(http2::check_transfer_encoding(
+ StringRef::from_lit(R"(chunked;foo="bar")")));
+ CU_ASSERT(http2::check_transfer_encoding(
+ StringRef::from_lit(R"(chunked;foo="\bar\"";FOO=BAR)")));
+ CU_ASSERT(
+ http2::check_transfer_encoding(StringRef::from_lit(R"(chunked;foo="")")));
+ CU_ASSERT(http2::check_transfer_encoding(
+ StringRef::from_lit(R"(chunked;foo="bar" , gzip)")));
+
+ CU_ASSERT(!http2::check_transfer_encoding(StringRef{}));
+ CU_ASSERT(!http2::check_transfer_encoding(StringRef::from_lit(",chunked")));
+ CU_ASSERT(!http2::check_transfer_encoding(StringRef::from_lit("chunked,")));
+ CU_ASSERT(!http2::check_transfer_encoding(StringRef::from_lit("chunked, ")));
+ CU_ASSERT(
+ !http2::check_transfer_encoding(StringRef::from_lit("foo,,chunked")));
+ CU_ASSERT(
+ !http2::check_transfer_encoding(StringRef::from_lit("chunked;foo")));
+ CU_ASSERT(!http2::check_transfer_encoding(StringRef::from_lit("chunked;")));
+ CU_ASSERT(
+ !http2::check_transfer_encoding(StringRef::from_lit("chunked;foo=bar;")));
+ CU_ASSERT(
+ !http2::check_transfer_encoding(StringRef::from_lit("chunked;?=bar")));
+ CU_ASSERT(
+ !http2::check_transfer_encoding(StringRef::from_lit("chunked;=bar")));
+ CU_ASSERT(!http2::check_transfer_encoding(StringRef::from_lit("chunked;;")));
+ CU_ASSERT(!http2::check_transfer_encoding(StringRef::from_lit("chunked?")));
+ CU_ASSERT(!http2::check_transfer_encoding(StringRef::from_lit(",")));
+ CU_ASSERT(!http2::check_transfer_encoding(StringRef::from_lit(" ")));
+ CU_ASSERT(!http2::check_transfer_encoding(StringRef::from_lit(";")));
+ CU_ASSERT(!http2::check_transfer_encoding(StringRef::from_lit("\"")));
+ CU_ASSERT(!http2::check_transfer_encoding(
+ StringRef::from_lit(R"(chunked;foo="bar)")));
+ CU_ASSERT(!http2::check_transfer_encoding(
+ StringRef::from_lit(R"(chunked;foo="bar\)")));
+ CU_ASSERT(
+ !http2::check_transfer_encoding(StringRef::from_lit(R"(chunked;foo="bar\)"
+ "\x0a"
+ R"(")")));
+ CU_ASSERT(
+ !http2::check_transfer_encoding(StringRef::from_lit(R"(chunked;foo=")"
+ "\x0a"
+ R"(")")));
+ CU_ASSERT(!http2::check_transfer_encoding(
+ StringRef::from_lit(R"(chunked;foo="bar",,gzip)")));
+}
+
+} // namespace shrpx
diff --git a/src/http2_test.h b/src/http2_test.h
new file mode 100644
index 0000000..382470d
--- /dev/null
+++ b/src/http2_test.h
@@ -0,0 +1,54 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_HTTP2_TEST_H
+#define SHRPX_HTTP2_TEST_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+namespace shrpx {
+
+void test_http2_add_header(void);
+void test_http2_get_header(void);
+void test_http2_copy_headers_to_nva(void);
+void test_http2_build_http1_headers_from_headers(void);
+void test_http2_lws(void);
+void test_http2_rewrite_location_uri(void);
+void test_http2_parse_http_status_code(void);
+void test_http2_index_header(void);
+void test_http2_lookup_token(void);
+void test_http2_parse_link_header(void);
+void test_http2_path_join(void);
+void test_http2_normalize_path(void);
+void test_http2_rewrite_clean_path(void);
+void test_http2_get_pure_path_component(void);
+void test_http2_construct_push_component(void);
+void test_http2_contains_trailers(void);
+void test_http2_check_transfer_encoding(void);
+
+} // namespace shrpx
+
+#endif // SHRPX_HTTP2_TEST_H
diff --git a/src/http3.cc b/src/http3.cc
new file mode 100644
index 0000000..61134ad
--- /dev/null
+++ b/src/http3.cc
@@ -0,0 +1,206 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2021 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "http3.h"
+
+namespace nghttp2 {
+
+namespace http3 {
+
+namespace {
+nghttp3_nv make_nv_internal(const std::string &name, const std::string &value,
+ bool never_index, uint8_t nv_flags) {
+ uint8_t flags;
+
+ flags = nv_flags |
+ (never_index ? NGHTTP3_NV_FLAG_NEVER_INDEX : NGHTTP3_NV_FLAG_NONE);
+
+ return {(uint8_t *)name.c_str(), (uint8_t *)value.c_str(), name.size(),
+ value.size(), flags};
+}
+} // namespace
+
+namespace {
+nghttp3_nv make_nv_internal(const StringRef &name, const StringRef &value,
+ bool never_index, uint8_t nv_flags) {
+ uint8_t flags;
+
+ flags = nv_flags |
+ (never_index ? NGHTTP3_NV_FLAG_NEVER_INDEX : NGHTTP3_NV_FLAG_NONE);
+
+ return {(uint8_t *)name.c_str(), (uint8_t *)value.c_str(), name.size(),
+ value.size(), flags};
+}
+} // namespace
+
+nghttp3_nv make_nv(const std::string &name, const std::string &value,
+ bool never_index) {
+ return make_nv_internal(name, value, never_index, NGHTTP3_NV_FLAG_NONE);
+}
+
+nghttp3_nv make_nv(const StringRef &name, const StringRef &value,
+ bool never_index) {
+ return make_nv_internal(name, value, never_index, NGHTTP3_NV_FLAG_NONE);
+}
+
+nghttp3_nv make_nv_nocopy(const std::string &name, const std::string &value,
+ bool never_index) {
+ return make_nv_internal(name, value, never_index,
+ NGHTTP3_NV_FLAG_NO_COPY_NAME |
+ NGHTTP3_NV_FLAG_NO_COPY_VALUE);
+}
+
+nghttp3_nv make_nv_nocopy(const StringRef &name, const StringRef &value,
+ bool never_index) {
+ return make_nv_internal(name, value, never_index,
+ NGHTTP3_NV_FLAG_NO_COPY_NAME |
+ NGHTTP3_NV_FLAG_NO_COPY_VALUE);
+}
+
+namespace {
+void copy_headers_to_nva_internal(std::vector<nghttp3_nv> &nva,
+ const HeaderRefs &headers, uint8_t nv_flags,
+ uint32_t flags) {
+ auto it_forwarded = std::end(headers);
+ auto it_xff = std::end(headers);
+ auto it_xfp = std::end(headers);
+ auto it_via = std::end(headers);
+
+ for (auto it = std::begin(headers); it != std::end(headers); ++it) {
+ auto kv = &(*it);
+ if (kv->name.empty() || kv->name[0] == ':') {
+ continue;
+ }
+ switch (kv->token) {
+ case http2::HD_COOKIE:
+ case http2::HD_CONNECTION:
+ case http2::HD_HOST:
+ case http2::HD_HTTP2_SETTINGS:
+ case http2::HD_KEEP_ALIVE:
+ case http2::HD_PROXY_CONNECTION:
+ case http2::HD_SERVER:
+ case http2::HD_TE:
+ case http2::HD_TRANSFER_ENCODING:
+ case http2::HD_UPGRADE:
+ continue;
+ case http2::HD_EARLY_DATA:
+ if (flags & http2::HDOP_STRIP_EARLY_DATA) {
+ continue;
+ }
+ break;
+ case http2::HD_SEC_WEBSOCKET_ACCEPT:
+ if (flags & http2::HDOP_STRIP_SEC_WEBSOCKET_ACCEPT) {
+ continue;
+ }
+ break;
+ case http2::HD_SEC_WEBSOCKET_KEY:
+ if (flags & http2::HDOP_STRIP_SEC_WEBSOCKET_KEY) {
+ continue;
+ }
+ break;
+ case http2::HD_FORWARDED:
+ if (flags & http2::HDOP_STRIP_FORWARDED) {
+ continue;
+ }
+
+ if (it_forwarded == std::end(headers)) {
+ it_forwarded = it;
+ continue;
+ }
+
+ kv = &(*it_forwarded);
+ it_forwarded = it;
+ break;
+ case http2::HD_X_FORWARDED_FOR:
+ if (flags & http2::HDOP_STRIP_X_FORWARDED_FOR) {
+ continue;
+ }
+
+ if (it_xff == std::end(headers)) {
+ it_xff = it;
+ continue;
+ }
+
+ kv = &(*it_xff);
+ it_xff = it;
+ break;
+ case http2::HD_X_FORWARDED_PROTO:
+ if (flags & http2::HDOP_STRIP_X_FORWARDED_PROTO) {
+ continue;
+ }
+
+ if (it_xfp == std::end(headers)) {
+ it_xfp = it;
+ continue;
+ }
+
+ kv = &(*it_xfp);
+ it_xfp = it;
+ break;
+ case http2::HD_VIA:
+ if (flags & http2::HDOP_STRIP_VIA) {
+ continue;
+ }
+
+ if (it_via == std::end(headers)) {
+ it_via = it;
+ continue;
+ }
+
+ kv = &(*it_via);
+ it_via = it;
+ break;
+ }
+ nva.push_back(
+ make_nv_internal(kv->name, kv->value, kv->no_index, nv_flags));
+ }
+}
+} // namespace
+
+void copy_headers_to_nva(std::vector<nghttp3_nv> &nva,
+ const HeaderRefs &headers, uint32_t flags) {
+ copy_headers_to_nva_internal(nva, headers, NGHTTP3_NV_FLAG_NONE, flags);
+}
+
+void copy_headers_to_nva_nocopy(std::vector<nghttp3_nv> &nva,
+ const HeaderRefs &headers, uint32_t flags) {
+ copy_headers_to_nva_internal(
+ nva, headers,
+ NGHTTP3_NV_FLAG_NO_COPY_NAME | NGHTTP3_NV_FLAG_NO_COPY_VALUE, flags);
+}
+
+int check_nv(const uint8_t *name, size_t namelen, const uint8_t *value,
+ size_t valuelen) {
+ if (!nghttp3_check_header_name(name, namelen)) {
+ return 0;
+ }
+ if (!nghttp3_check_header_value(value, valuelen)) {
+ return 0;
+ }
+ return 1;
+}
+
+} // namespace http3
+
+} // namespace nghttp2
diff --git a/src/http3.h b/src/http3.h
new file mode 100644
index 0000000..81ee0d7
--- /dev/null
+++ b/src/http3.h
@@ -0,0 +1,123 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2021 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef HTTP3_H
+#define HTTP3_H
+
+#include "nghttp2_config.h"
+
+#include <cstring>
+#include <string>
+#include <vector>
+
+#include <nghttp3/nghttp3.h>
+
+#include "http2.h"
+#include "template.h"
+
+namespace nghttp2 {
+
+namespace http3 {
+
+// Creates nghttp3_nv using |name| and |value| and returns it. The
+// returned value only references the data pointer to name.c_str() and
+// value.c_str(). If |no_index| is true, nghttp3_nv flags member has
+// NGHTTP3_NV_FLAG_NEVER_INDEX flag set.
+nghttp3_nv make_nv(const std::string &name, const std::string &value,
+ bool never_index = false);
+
+nghttp3_nv make_nv(const StringRef &name, const StringRef &value,
+ bool never_index = false);
+
+nghttp3_nv make_nv_nocopy(const std::string &name, const std::string &value,
+ bool never_index = false);
+
+nghttp3_nv make_nv_nocopy(const StringRef &name, const StringRef &value,
+ bool never_index = false);
+
+// Create nghttp3_nv from string literal |name| and |value|.
+template <size_t N, size_t M>
+constexpr nghttp3_nv make_nv_ll(const char (&name)[N], const char (&value)[M]) {
+ return {(uint8_t *)name, (uint8_t *)value, N - 1, M - 1,
+ NGHTTP3_NV_FLAG_NO_COPY_NAME | NGHTTP3_NV_FLAG_NO_COPY_VALUE};
+}
+
+// Create nghttp3_nv from string literal |name| and c-string |value|.
+template <size_t N>
+nghttp3_nv make_nv_lc(const char (&name)[N], const char *value) {
+ return {(uint8_t *)name, (uint8_t *)value, N - 1, strlen(value),
+ NGHTTP3_NV_FLAG_NO_COPY_NAME};
+}
+
+template <size_t N>
+nghttp3_nv make_nv_lc_nocopy(const char (&name)[N], const char *value) {
+ return {(uint8_t *)name, (uint8_t *)value, N - 1, strlen(value),
+ NGHTTP3_NV_FLAG_NO_COPY_NAME | NGHTTP3_NV_FLAG_NO_COPY_VALUE};
+}
+
+// Create nghttp3_nv from string literal |name| and std::string
+// |value|.
+template <size_t N>
+nghttp3_nv make_nv_ls(const char (&name)[N], const std::string &value) {
+ return {(uint8_t *)name, (uint8_t *)value.c_str(), N - 1, value.size(),
+ NGHTTP3_NV_FLAG_NO_COPY_NAME};
+}
+
+template <size_t N>
+nghttp3_nv make_nv_ls_nocopy(const char (&name)[N], const std::string &value) {
+ return {(uint8_t *)name, (uint8_t *)value.c_str(), N - 1, value.size(),
+ NGHTTP3_NV_FLAG_NO_COPY_NAME | NGHTTP3_NV_FLAG_NO_COPY_VALUE};
+}
+
+template <size_t N>
+nghttp3_nv make_nv_ls_nocopy(const char (&name)[N], const StringRef &value) {
+ return {(uint8_t *)name, (uint8_t *)value.c_str(), N - 1, value.size(),
+ NGHTTP3_NV_FLAG_NO_COPY_NAME | NGHTTP3_NV_FLAG_NO_COPY_VALUE};
+}
+
+// Appends headers in |headers| to |nv|. |headers| must be indexed
+// before this call (its element's token field is assigned). Certain
+// headers, including disallowed headers in HTTP/3 spec and headers
+// which require special handling (i.e. via), are not copied. |flags|
+// is one or more of HeaderBuildOp flags. They tell function that
+// certain header fields should not be added.
+void copy_headers_to_nva(std::vector<nghttp3_nv> &nva,
+ const HeaderRefs &headers, uint32_t flags);
+
+// Just like copy_headers_to_nva(), but this adds
+// NGHTTP3_NV_FLAG_NO_COPY_NAME and NGHTTP3_NV_FLAG_NO_COPY_VALUE.
+void copy_headers_to_nva_nocopy(std::vector<nghttp3_nv> &nva,
+ const HeaderRefs &headers, uint32_t flags);
+
+// Checks the header name/value pair using nghttp3_check_header_name()
+// and nghttp3_check_header_value(). If both function returns nonzero,
+// this function returns nonzero.
+int check_nv(const uint8_t *name, size_t namelen, const uint8_t *value,
+ size_t valuelen);
+
+} // namespace http3
+
+} // namespace nghttp2
+
+#endif // HTTP3_H
diff --git a/src/inflatehd.cc b/src/inflatehd.cc
new file mode 100644
index 0000000..f484042
--- /dev/null
+++ b/src/inflatehd.cc
@@ -0,0 +1,289 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+#include <getopt.h>
+
+#include <cstdio>
+#include <cstring>
+#include <assert.h>
+#include <cerrno>
+#include <cstdlib>
+#include <vector>
+#include <iostream>
+
+#include <jansson.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "template.h"
+#include "comp_helper.h"
+
+namespace nghttp2 {
+
+typedef struct {
+ int dump_header_table;
+} inflate_config;
+
+static inflate_config config;
+
+static uint8_t to_ud(char c) {
+ if (c >= 'A' && c <= 'Z') {
+ return c - 'A' + 10;
+ } else if (c >= 'a' && c <= 'z') {
+ return c - 'a' + 10;
+ } else {
+ return c - '0';
+ }
+}
+
+static void decode_hex(uint8_t *dest, const char *src, size_t len) {
+ size_t i;
+ for (i = 0; i < len; i += 2) {
+ *dest++ = to_ud(src[i]) << 4 | to_ud(src[i + 1]);
+ }
+}
+
+static void to_json(nghttp2_hd_inflater *inflater, json_t *headers,
+ json_t *wire, int seq, size_t old_settings_table_size) {
+ auto obj = json_object();
+ json_object_set_new(obj, "seq", json_integer(seq));
+ json_object_set(obj, "wire", wire);
+ json_object_set(obj, "headers", headers);
+ auto max_dyn_table_size =
+ nghttp2_hd_inflate_get_max_dynamic_table_size(inflater);
+ if (old_settings_table_size != max_dyn_table_size) {
+ json_object_set_new(obj, "header_table_size",
+ json_integer(max_dyn_table_size));
+ }
+ if (config.dump_header_table) {
+ json_object_set_new(obj, "header_table",
+ dump_inflate_header_table(inflater));
+ }
+ json_dumpf(obj, stdout, JSON_INDENT(2) | JSON_PRESERVE_ORDER);
+ json_decref(obj);
+ printf("\n");
+}
+
+static int inflate_hd(json_t *obj, nghttp2_hd_inflater *inflater, int seq) {
+ ssize_t rv;
+ nghttp2_nv nv;
+ int inflate_flags;
+ size_t old_settings_table_size =
+ nghttp2_hd_inflate_get_max_dynamic_table_size(inflater);
+
+ auto wire = json_object_get(obj, "wire");
+
+ if (wire == nullptr) {
+ fprintf(stderr, "'wire' key is missing at %d\n", seq);
+ return -1;
+ }
+
+ if (!json_is_string(wire)) {
+ fprintf(stderr, "'wire' value is not string at %d\n", seq);
+ return -1;
+ }
+
+ auto table_size = json_object_get(obj, "header_table_size");
+
+ if (table_size) {
+ if (!json_is_integer(table_size)) {
+ fprintf(stderr,
+ "The value of 'header_table_size key' is not integer at %d\n",
+ seq);
+ return -1;
+ }
+ rv = nghttp2_hd_inflate_change_table_size(inflater,
+ json_integer_value(table_size));
+ if (rv != 0) {
+ fprintf(stderr,
+ "nghttp2_hd_change_table_size() failed with error %s at %d\n",
+ nghttp2_strerror(rv), seq);
+ return -1;
+ }
+ }
+
+ auto inputlen = strlen(json_string_value(wire));
+
+ if (inputlen & 1) {
+ fprintf(stderr, "Badly formatted output value at %d\n", seq);
+ exit(EXIT_FAILURE);
+ }
+
+ auto buflen = inputlen / 2;
+ auto buf = std::vector<uint8_t>(buflen);
+
+ decode_hex(buf.data(), json_string_value(wire), inputlen);
+
+ auto headers = json_array();
+
+ auto p = buf.data();
+ for (;;) {
+ inflate_flags = 0;
+ rv = nghttp2_hd_inflate_hd(inflater, &nv, &inflate_flags, p, buflen, 1);
+ if (rv < 0) {
+ fprintf(stderr, "inflate failed with error code %zd at %d\n", rv, seq);
+ exit(EXIT_FAILURE);
+ }
+ p += rv;
+ buflen -= rv;
+ if (inflate_flags & NGHTTP2_HD_INFLATE_EMIT) {
+ json_array_append_new(
+ headers, dump_header(nv.name, nv.namelen, nv.value, nv.valuelen));
+ }
+ if (inflate_flags & NGHTTP2_HD_INFLATE_FINAL) {
+ break;
+ }
+ }
+ assert(buflen == 0);
+ nghttp2_hd_inflate_end_headers(inflater);
+ to_json(inflater, headers, wire, seq, old_settings_table_size);
+ json_decref(headers);
+
+ return 0;
+}
+
+static int perform(void) {
+ nghttp2_hd_inflater *inflater = nullptr;
+ json_error_t error;
+
+ auto json = json_loadf(stdin, 0, &error);
+
+ if (json == nullptr) {
+ fprintf(stderr, "JSON loading failed\n");
+ exit(EXIT_FAILURE);
+ }
+
+ auto cases = json_object_get(json, "cases");
+
+ if (cases == nullptr) {
+ fprintf(stderr, "Missing 'cases' key in root object\n");
+ exit(EXIT_FAILURE);
+ }
+
+ if (!json_is_array(cases)) {
+ fprintf(stderr, "'cases' must be JSON array\n");
+ exit(EXIT_FAILURE);
+ }
+
+ nghttp2_hd_inflate_new(&inflater);
+ output_json_header();
+ auto len = json_array_size(cases);
+
+ for (size_t i = 0; i < len; ++i) {
+ auto obj = json_array_get(cases, i);
+ if (!json_is_object(obj)) {
+ fprintf(stderr, "Unexpected JSON type at %zu. It should be object.\n", i);
+ continue;
+ }
+ if (inflate_hd(obj, inflater, i) != 0) {
+ continue;
+ }
+ if (i + 1 < len) {
+ printf(",\n");
+ }
+ }
+ output_json_footer();
+ nghttp2_hd_inflate_del(inflater);
+ json_decref(json);
+
+ return 0;
+}
+
+static void print_help(void) {
+ std::cout << R"(HPACK HTTP/2 header decoder
+Usage: inflatehd [OPTIONS] < INPUT
+
+Reads JSON data from stdin and outputs inflated name/value pairs in
+JSON.
+
+The root JSON object must contain "context" key, which indicates which
+compression context is used. If it is "request", request compression
+context is used. Otherwise, response compression context is used.
+The value of "cases" key contains the sequence of compressed header
+block. They share the same compression context and are processed in
+the order they appear. Each item in the sequence is a JSON object and
+it must have at least "wire" key. Its value is a string containing
+compressed header block in hex string.
+
+Example:
+
+{
+ "context": "request",
+ "cases":
+ [
+ { "wire": "0284f77778ff" },
+ { "wire": "0185fafd3c3c7f81" }
+ ]
+}
+
+The output of this program can be used as input for deflatehd.
+
+OPTIONS:
+ -d, --dump-header-table
+ Output dynamic header table.)"
+ << std::endl;
+ ;
+}
+
+constexpr static struct option long_options[] = {
+ {"dump-header-table", no_argument, nullptr, 'd'}, {nullptr, 0, nullptr, 0}};
+
+int main(int argc, char **argv) {
+ config.dump_header_table = 0;
+ while (1) {
+ int option_index = 0;
+ int c = getopt_long(argc, argv, "dh", long_options, &option_index);
+ if (c == -1) {
+ break;
+ }
+ switch (c) {
+ case 'h':
+ print_help();
+ exit(EXIT_SUCCESS);
+ case 'd':
+ // --dump-header-table
+ config.dump_header_table = 1;
+ break;
+ case '?':
+ exit(EXIT_FAILURE);
+ default:
+ break;
+ }
+ }
+ perform();
+ return 0;
+}
+
+} // namespace nghttp2
+
+int main(int argc, char **argv) {
+ return nghttp2::run_app(nghttp2::main, argc, argv);
+}
diff --git a/src/libevent_util.cc b/src/libevent_util.cc
new file mode 100644
index 0000000..3b60b6d
--- /dev/null
+++ b/src/libevent_util.cc
@@ -0,0 +1,162 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "libevent_util.h"
+
+#include <cstring>
+#include <algorithm>
+
+namespace nghttp2 {
+
+namespace util {
+
+EvbufferBuffer::EvbufferBuffer()
+ : evbuffer_(nullptr),
+ bucket_(nullptr),
+ buf_(nullptr),
+ bufmax_(0),
+ buflen_(0),
+ limit_(0),
+ writelen_(0) {}
+
+EvbufferBuffer::EvbufferBuffer(evbuffer *evbuffer, uint8_t *buf, size_t bufmax,
+ ssize_t limit)
+ : evbuffer_(evbuffer),
+ bucket_(limit == -1 ? nullptr : evbuffer_new()),
+ buf_(buf),
+ bufmax_(bufmax),
+ buflen_(0),
+ limit_(limit),
+ writelen_(0) {}
+
+void EvbufferBuffer::reset(evbuffer *evbuffer, uint8_t *buf, size_t bufmax,
+ ssize_t limit) {
+ evbuffer_ = evbuffer;
+ buf_ = buf;
+ if (limit != -1 && !bucket_) {
+ bucket_ = evbuffer_new();
+ }
+ bufmax_ = bufmax;
+ buflen_ = 0;
+ limit_ = limit;
+ writelen_ = 0;
+}
+
+EvbufferBuffer::~EvbufferBuffer() {
+ if (bucket_) {
+ evbuffer_free(bucket_);
+ }
+}
+
+int EvbufferBuffer::write_buffer() {
+ for (auto pos = buf_, end = buf_ + buflen_; pos < end;) {
+ // To avoid merging chunks in evbuffer, we first add to temporal
+ // buffer bucket_ and then move its chain to evbuffer_.
+ auto nwrite = std::min(end - pos, limit_);
+ auto rv = evbuffer_add(bucket_, pos, nwrite);
+ if (rv == -1) {
+ return -1;
+ }
+ rv = evbuffer_add_buffer(evbuffer_, bucket_);
+ if (rv == -1) {
+ return -1;
+ }
+ pos += nwrite;
+ }
+ return 0;
+}
+
+int EvbufferBuffer::flush() {
+ int rv;
+ if (buflen_ > 0) {
+ if (limit_ == -1) {
+ rv = evbuffer_add(evbuffer_, buf_, buflen_);
+ } else {
+ rv = write_buffer();
+ }
+ if (rv == -1) {
+ return -1;
+ }
+ writelen_ += buflen_;
+ buflen_ = 0;
+ }
+ return 0;
+}
+
+int EvbufferBuffer::add(const uint8_t *data, size_t datalen) {
+ int rv;
+ if (buflen_ + datalen > bufmax_) {
+ if (buflen_ > 0) {
+ if (limit_ == -1) {
+ rv = evbuffer_add(evbuffer_, buf_, buflen_);
+ } else {
+ rv = write_buffer();
+ }
+ if (rv == -1) {
+ return -1;
+ }
+ writelen_ += buflen_;
+ buflen_ = 0;
+ }
+ if (datalen > bufmax_) {
+ if (limit_ == -1) {
+ rv = evbuffer_add(evbuffer_, data, datalen);
+ } else {
+ rv = write_buffer();
+ }
+ if (rv == -1) {
+ return -1;
+ }
+ writelen_ += buflen_;
+ return 0;
+ }
+ }
+ memcpy(buf_ + buflen_, data, datalen);
+ buflen_ += datalen;
+ return 0;
+}
+
+size_t EvbufferBuffer::get_buflen() const { return buflen_; }
+
+size_t EvbufferBuffer::get_writelen() const { return writelen_; }
+
+void bev_enable_unless(bufferevent *bev, int events) {
+ if ((bufferevent_get_enabled(bev) & events) == events) {
+ return;
+ }
+
+ bufferevent_enable(bev, events);
+}
+
+void bev_disable_unless(bufferevent *bev, int events) {
+ if ((bufferevent_get_enabled(bev) & events) == 0) {
+ return;
+ }
+
+ bufferevent_disable(bev, events);
+}
+
+} // namespace util
+
+} // namespace nghttp2
diff --git a/src/libevent_util.h b/src/libevent_util.h
new file mode 100644
index 0000000..1d1ee91
--- /dev/null
+++ b/src/libevent_util.h
@@ -0,0 +1,75 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef LIBEVENT_UTIL_H
+#define LIBEVENT_UTIL_H
+
+#include "nghttp2_config.h"
+
+#include <event2/buffer.h>
+#include <event2/bufferevent.h>
+
+namespace nghttp2 {
+
+namespace util {
+
+class EvbufferBuffer {
+public:
+ EvbufferBuffer();
+ // If |limit| is not -1, at most min(limit, bufmax) size bytes are
+ // added to evbuffer_.
+ EvbufferBuffer(evbuffer *evbuffer, uint8_t *buf, size_t bufmax,
+ ssize_t limit = -1);
+ ~EvbufferBuffer();
+ void reset(evbuffer *evbuffer, uint8_t *buf, size_t bufmax,
+ ssize_t limit = -1);
+ int flush();
+ int add(const uint8_t *data, size_t datalen);
+ size_t get_buflen() const;
+ int write_buffer();
+ // Returns the number of written bytes to evbuffer_ so far. reset()
+ // resets this value to 0.
+ size_t get_writelen() const;
+
+private:
+ evbuffer *evbuffer_;
+ evbuffer *bucket_;
+ uint8_t *buf_;
+ size_t bufmax_;
+ size_t buflen_;
+ ssize_t limit_;
+ size_t writelen_;
+};
+
+// These functions are provided to reduce epoll_ctl syscall. Avoid
+// calling bufferevent_enable/disable() unless it is required by
+// sniffing current enabled events.
+void bev_enable_unless(bufferevent *bev, int events);
+void bev_disable_unless(bufferevent *bev, int events);
+
+} // namespace util
+
+} // namespace nghttp2
+
+#endif // LIBEVENT_UTIL_H
diff --git a/src/memchunk.h b/src/memchunk.h
new file mode 100644
index 0000000..7a7f2e9
--- /dev/null
+++ b/src/memchunk.h
@@ -0,0 +1,664 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef MEMCHUNK_H
+#define MEMCHUNK_H
+
+#include "nghttp2_config.h"
+
+#include <limits.h>
+#ifdef _WIN32
+/* Structure for scatter/gather I/O. */
+struct iovec {
+ void *iov_base; /* Pointer to data. */
+ size_t iov_len; /* Length of data. */
+};
+#else // !_WIN32
+# include <sys/uio.h>
+#endif // !_WIN32
+
+#include <cassert>
+#include <cstring>
+#include <memory>
+#include <array>
+#include <algorithm>
+#include <string>
+#include <utility>
+
+#include "template.h"
+
+namespace nghttp2 {
+
+#define DEFAULT_WR_IOVCNT 16
+
+#if defined(IOV_MAX) && IOV_MAX < DEFAULT_WR_IOVCNT
+# define MAX_WR_IOVCNT IOV_MAX
+#else // !defined(IOV_MAX) || IOV_MAX >= DEFAULT_WR_IOVCNT
+# define MAX_WR_IOVCNT DEFAULT_WR_IOVCNT
+#endif // !defined(IOV_MAX) || IOV_MAX >= DEFAULT_WR_IOVCNT
+
+template <size_t N> struct Memchunk {
+ Memchunk(Memchunk *next_chunk)
+ : pos(std::begin(buf)), last(pos), knext(next_chunk), next(nullptr) {}
+ size_t len() const { return last - pos; }
+ size_t left() const { return std::end(buf) - last; }
+ void reset() { pos = last = std::begin(buf); }
+ std::array<uint8_t, N> buf;
+ uint8_t *pos, *last;
+ Memchunk *knext;
+ Memchunk *next;
+ static const size_t size = N;
+};
+
+template <typename T> struct Pool {
+ Pool() : pool(nullptr), freelist(nullptr), poolsize(0), freelistsize(0) {}
+ ~Pool() { clear(); }
+ T *get() {
+ if (freelist) {
+ auto m = freelist;
+ freelist = freelist->next;
+ m->next = nullptr;
+ m->reset();
+ freelistsize -= T::size;
+ return m;
+ }
+
+ pool = new T{pool};
+ poolsize += T::size;
+ return pool;
+ }
+ void recycle(T *m) {
+ m->next = freelist;
+ freelist = m;
+ freelistsize += T::size;
+ }
+ void clear() {
+ freelist = nullptr;
+ freelistsize = 0;
+ for (auto p = pool; p;) {
+ auto knext = p->knext;
+ delete p;
+ p = knext;
+ }
+ pool = nullptr;
+ poolsize = 0;
+ }
+ using value_type = T;
+ T *pool;
+ T *freelist;
+ size_t poolsize;
+ size_t freelistsize;
+};
+
+template <typename Memchunk> struct Memchunks {
+ Memchunks(Pool<Memchunk> *pool)
+ : pool(pool),
+ head(nullptr),
+ tail(nullptr),
+ len(0),
+ mark(nullptr),
+ mark_pos(nullptr),
+ mark_offset(0) {}
+ Memchunks(const Memchunks &) = delete;
+ Memchunks(Memchunks &&other) noexcept
+ : pool{other.pool}, // keep other.pool
+ head{std::exchange(other.head, nullptr)},
+ tail{std::exchange(other.tail, nullptr)},
+ len{std::exchange(other.len, 0)},
+ mark{std::exchange(other.mark, nullptr)},
+ mark_pos{std::exchange(other.mark_pos, nullptr)},
+ mark_offset{std::exchange(other.mark_offset, 0)} {}
+ Memchunks &operator=(const Memchunks &) = delete;
+ Memchunks &operator=(Memchunks &&other) noexcept {
+ if (this == &other) {
+ return *this;
+ }
+
+ reset();
+
+ pool = other.pool;
+ head = std::exchange(other.head, nullptr);
+ tail = std::exchange(other.tail, nullptr);
+ len = std::exchange(other.len, 0);
+ mark = std::exchange(other.mark, nullptr);
+ mark_pos = std::exchange(other.mark_pos, nullptr);
+ mark_offset = std::exchange(other.mark_offset, 0);
+
+ return *this;
+ }
+ ~Memchunks() {
+ if (!pool) {
+ return;
+ }
+ for (auto m = head; m;) {
+ auto next = m->next;
+ pool->recycle(m);
+ m = next;
+ }
+ }
+ size_t append(char c) {
+ if (!tail) {
+ head = tail = pool->get();
+ } else if (tail->left() == 0) {
+ tail->next = pool->get();
+ tail = tail->next;
+ }
+ *tail->last++ = c;
+ ++len;
+ return 1;
+ }
+ size_t append(const void *src, size_t count) {
+ if (count == 0) {
+ return 0;
+ }
+
+ auto first = static_cast<const uint8_t *>(src);
+ auto last = first + count;
+
+ if (!tail) {
+ head = tail = pool->get();
+ }
+
+ for (;;) {
+ auto n = std::min(static_cast<size_t>(last - first), tail->left());
+ tail->last = std::copy_n(first, n, tail->last);
+ first += n;
+ len += n;
+ if (first == last) {
+ break;
+ }
+
+ tail->next = pool->get();
+ tail = tail->next;
+ }
+
+ return count;
+ }
+ template <size_t N> size_t append(const char (&s)[N]) {
+ return append(s, N - 1);
+ }
+ size_t append(const std::string &s) { return append(s.c_str(), s.size()); }
+ size_t append(const StringRef &s) { return append(s.c_str(), s.size()); }
+ size_t append(const ImmutableString &s) {
+ return append(s.c_str(), s.size());
+ }
+ size_t copy(Memchunks &dest) {
+ auto m = head;
+ while (m) {
+ dest.append(m->pos, m->len());
+ m = m->next;
+ }
+ return len;
+ }
+ size_t remove(void *dest, size_t count) {
+ assert(mark == nullptr);
+
+ if (!tail || count == 0) {
+ return 0;
+ }
+
+ auto first = static_cast<uint8_t *>(dest);
+ auto last = first + count;
+
+ auto m = head;
+
+ while (m) {
+ auto next = m->next;
+ auto n = std::min(static_cast<size_t>(last - first), m->len());
+
+ assert(m->len());
+ first = std::copy_n(m->pos, n, first);
+ m->pos += n;
+ len -= n;
+ if (m->len() > 0) {
+ break;
+ }
+ pool->recycle(m);
+ m = next;
+ }
+ head = m;
+ if (head == nullptr) {
+ tail = nullptr;
+ }
+
+ return first - static_cast<uint8_t *>(dest);
+ }
+ size_t remove(Memchunks &dest, size_t count) {
+ assert(mark == nullptr);
+
+ if (!tail || count == 0) {
+ return 0;
+ }
+
+ auto left = count;
+ auto m = head;
+
+ while (m) {
+ auto next = m->next;
+ auto n = std::min(left, m->len());
+
+ assert(m->len());
+ dest.append(m->pos, n);
+ m->pos += n;
+ len -= n;
+ left -= n;
+ if (m->len() > 0) {
+ break;
+ }
+ pool->recycle(m);
+ m = next;
+ }
+ head = m;
+ if (head == nullptr) {
+ tail = nullptr;
+ }
+
+ return count - left;
+ }
+ size_t remove(Memchunks &dest) {
+ assert(pool == dest.pool);
+ assert(mark == nullptr);
+
+ if (head == nullptr) {
+ return 0;
+ }
+
+ auto n = len;
+
+ if (dest.tail == nullptr) {
+ dest.head = head;
+ } else {
+ dest.tail->next = head;
+ }
+
+ dest.tail = tail;
+ dest.len += len;
+
+ head = tail = nullptr;
+ len = 0;
+
+ return n;
+ }
+ size_t drain(size_t count) {
+ assert(mark == nullptr);
+
+ auto ndata = count;
+ auto m = head;
+ while (m) {
+ auto next = m->next;
+ auto n = std::min(count, m->len());
+ m->pos += n;
+ count -= n;
+ len -= n;
+ if (m->len() > 0) {
+ break;
+ }
+
+ pool->recycle(m);
+ m = next;
+ }
+ head = m;
+ if (head == nullptr) {
+ tail = nullptr;
+ }
+ return ndata - count;
+ }
+ size_t drain_mark(size_t count) {
+ auto ndata = count;
+ auto m = head;
+ while (m) {
+ auto next = m->next;
+ auto n = std::min(count, m->len());
+ m->pos += n;
+ count -= n;
+ len -= n;
+ mark_offset -= n;
+
+ if (m->len() > 0) {
+ assert(mark != m || m->pos <= mark_pos);
+ break;
+ }
+ if (mark == m) {
+ assert(m->pos <= mark_pos);
+
+ mark = nullptr;
+ mark_pos = nullptr;
+ mark_offset = 0;
+ }
+
+ pool->recycle(m);
+ m = next;
+ }
+ head = m;
+ if (head == nullptr) {
+ tail = nullptr;
+ }
+ return ndata - count;
+ }
+ int riovec(struct iovec *iov, int iovcnt) const {
+ if (!head) {
+ return 0;
+ }
+ auto m = head;
+ int i;
+ for (i = 0; i < iovcnt && m; ++i, m = m->next) {
+ iov[i].iov_base = m->pos;
+ iov[i].iov_len = m->len();
+ }
+ return i;
+ }
+ int riovec_mark(struct iovec *iov, int iovcnt) {
+ if (!head || iovcnt == 0) {
+ return 0;
+ }
+
+ int i = 0;
+ Memchunk *m;
+ if (mark) {
+ if (mark_pos != mark->last) {
+ iov[0].iov_base = mark_pos;
+ iov[0].iov_len = mark->len() - (mark_pos - mark->pos);
+
+ mark_pos = mark->last;
+ mark_offset += iov[0].iov_len;
+ i = 1;
+ }
+ m = mark->next;
+ } else {
+ i = 0;
+ m = head;
+ }
+
+ for (; i < iovcnt && m; ++i, m = m->next) {
+ iov[i].iov_base = m->pos;
+ iov[i].iov_len = m->len();
+
+ mark = m;
+ mark_pos = m->last;
+ mark_offset += m->len();
+ }
+
+ return i;
+ }
+ size_t rleft() const { return len; }
+ size_t rleft_mark() const { return len - mark_offset; }
+ void reset() {
+ for (auto m = head; m;) {
+ auto next = m->next;
+ pool->recycle(m);
+ m = next;
+ }
+ len = 0;
+ head = tail = mark = nullptr;
+ mark_pos = nullptr;
+ mark_offset = 0;
+ }
+
+ Pool<Memchunk> *pool;
+ Memchunk *head, *tail;
+ size_t len;
+ Memchunk *mark;
+ uint8_t *mark_pos;
+ size_t mark_offset;
+};
+
+// Wrapper around Memchunks to offer "peeking" functionality.
+template <typename Memchunk> struct PeekMemchunks {
+ PeekMemchunks(Pool<Memchunk> *pool)
+ : memchunks(pool),
+ cur(nullptr),
+ cur_pos(nullptr),
+ cur_last(nullptr),
+ len(0),
+ peeking(true) {}
+ PeekMemchunks(const PeekMemchunks &) = delete;
+ PeekMemchunks(PeekMemchunks &&other) noexcept
+ : memchunks{std::move(other.memchunks)},
+ cur{std::exchange(other.cur, nullptr)},
+ cur_pos{std::exchange(other.cur_pos, nullptr)},
+ cur_last{std::exchange(other.cur_last, nullptr)},
+ len{std::exchange(other.len, 0)},
+ peeking{std::exchange(other.peeking, true)} {}
+ PeekMemchunks &operator=(const PeekMemchunks &) = delete;
+ PeekMemchunks &operator=(PeekMemchunks &&other) noexcept {
+ if (this == &other) {
+ return *this;
+ }
+
+ memchunks = std::move(other.memchunks);
+ cur = std::exchange(other.cur, nullptr);
+ cur_pos = std::exchange(other.cur_pos, nullptr);
+ cur_last = std::exchange(other.cur_last, nullptr);
+ len = std::exchange(other.len, 0);
+ peeking = std::exchange(other.peeking, true);
+
+ return *this;
+ }
+ size_t append(const void *src, size_t count) {
+ count = memchunks.append(src, count);
+ len += count;
+ return count;
+ }
+ size_t remove(void *dest, size_t count) {
+ if (!peeking) {
+ count = memchunks.remove(dest, count);
+ len -= count;
+ return count;
+ }
+
+ if (count == 0 || len == 0) {
+ return 0;
+ }
+
+ if (!cur) {
+ cur = memchunks.head;
+ cur_pos = cur->pos;
+ }
+
+ // cur_last could be updated in append
+ cur_last = cur->last;
+
+ if (cur_pos == cur_last) {
+ assert(cur->next);
+ cur = cur->next;
+ }
+
+ auto first = static_cast<uint8_t *>(dest);
+ auto last = first + count;
+
+ for (;;) {
+ auto n = std::min(last - first, cur_last - cur_pos);
+
+ first = std::copy_n(cur_pos, n, first);
+ cur_pos += n;
+ len -= n;
+
+ if (first == last) {
+ break;
+ }
+ assert(cur_pos == cur_last);
+ if (!cur->next) {
+ break;
+ }
+ cur = cur->next;
+ cur_pos = cur->pos;
+ cur_last = cur->last;
+ }
+ return first - static_cast<uint8_t *>(dest);
+ }
+ size_t rleft() const { return len; }
+ size_t rleft_buffered() const { return memchunks.rleft(); }
+ void disable_peek(bool drain) {
+ if (!peeking) {
+ return;
+ }
+ if (drain) {
+ auto n = rleft_buffered() - rleft();
+ memchunks.drain(n);
+ assert(len == memchunks.rleft());
+ } else {
+ len = memchunks.rleft();
+ }
+ cur = nullptr;
+ cur_pos = cur_last = nullptr;
+ peeking = false;
+ }
+ void reset() {
+ memchunks.reset();
+ cur = nullptr;
+ cur_pos = cur_last = nullptr;
+ len = 0;
+ peeking = true;
+ }
+ Memchunks<Memchunk> memchunks;
+ // Pointer to the Memchunk currently we are reading/writing.
+ Memchunk *cur;
+ // Region inside cur, we have processed to cur_pos.
+ uint8_t *cur_pos, *cur_last;
+ // This is the length we have left unprocessed. len <=
+ // memchunk.rleft() must hold.
+ size_t len;
+ // true if peeking is enabled. Initially it is true.
+ bool peeking;
+};
+
+using Memchunk16K = Memchunk<16_k>;
+using MemchunkPool = Pool<Memchunk16K>;
+using DefaultMemchunks = Memchunks<Memchunk16K>;
+using DefaultPeekMemchunks = PeekMemchunks<Memchunk16K>;
+
+inline int limit_iovec(struct iovec *iov, int iovcnt, size_t max) {
+ if (max == 0) {
+ return 0;
+ }
+ for (int i = 0; i < iovcnt; ++i) {
+ auto d = std::min(max, iov[i].iov_len);
+ iov[i].iov_len = d;
+ max -= d;
+ if (max == 0) {
+ return i + 1;
+ }
+ }
+ return iovcnt;
+}
+
+// MemchunkBuffer is similar to Buffer, but it uses pooled Memchunk
+// for its underlying buffer.
+template <typename Memchunk> struct MemchunkBuffer {
+ MemchunkBuffer(Pool<Memchunk> *pool) : pool(pool), chunk(nullptr) {}
+ MemchunkBuffer(const MemchunkBuffer &) = delete;
+ MemchunkBuffer(MemchunkBuffer &&other) noexcept
+ : pool(other.pool), chunk(other.chunk) {
+ other.chunk = nullptr;
+ }
+ MemchunkBuffer &operator=(const MemchunkBuffer &) = delete;
+ MemchunkBuffer &operator=(MemchunkBuffer &&other) noexcept {
+ if (this == &other) {
+ return *this;
+ }
+
+ pool = other.pool;
+ chunk = other.chunk;
+
+ other.chunk = nullptr;
+
+ return *this;
+ }
+
+ ~MemchunkBuffer() {
+ if (!pool || !chunk) {
+ return;
+ }
+ pool->recycle(chunk);
+ }
+
+ // Ensures that the underlying buffer is allocated.
+ void ensure_chunk() {
+ if (chunk) {
+ return;
+ }
+ chunk = pool->get();
+ }
+
+ // Releases the underlying buffer.
+ void release_chunk() {
+ if (!chunk) {
+ return;
+ }
+ pool->recycle(chunk);
+ chunk = nullptr;
+ }
+
+ // Returns true if the underlying buffer is allocated.
+ bool chunk_avail() const { return chunk != nullptr; }
+
+ // The functions below must be called after the underlying buffer is
+ // allocated (use ensure_chunk).
+
+ // MemchunkBuffer provides the same interface functions with Buffer.
+ // Since we has chunk as a member variable, pos and last are
+ // implemented as wrapper functions.
+
+ uint8_t *pos() const { return chunk->pos; }
+ uint8_t *last() const { return chunk->last; }
+
+ size_t rleft() const { return chunk->len(); }
+ size_t wleft() const { return chunk->left(); }
+ size_t write(const void *src, size_t count) {
+ count = std::min(count, wleft());
+ auto p = static_cast<const uint8_t *>(src);
+ chunk->last = std::copy_n(p, count, chunk->last);
+ return count;
+ }
+ size_t write(size_t count) {
+ count = std::min(count, wleft());
+ chunk->last += count;
+ return count;
+ }
+ size_t drain(size_t count) {
+ count = std::min(count, rleft());
+ chunk->pos += count;
+ return count;
+ }
+ size_t drain_reset(size_t count) {
+ count = std::min(count, rleft());
+ std::copy(chunk->pos + count, chunk->last, std::begin(chunk->buf));
+ chunk->last = std::begin(chunk->buf) + (chunk->last - (chunk->pos + count));
+ chunk->pos = std::begin(chunk->buf);
+ return count;
+ }
+ void reset() { chunk->reset(); }
+ uint8_t *begin() { return std::begin(chunk->buf); }
+ uint8_t &operator[](size_t n) { return chunk->buf[n]; }
+ const uint8_t &operator[](size_t n) const { return chunk->buf[n]; }
+
+ Pool<Memchunk> *pool;
+ Memchunk *chunk;
+};
+
+using DefaultMemchunkBuffer = MemchunkBuffer<Memchunk16K>;
+
+} // namespace nghttp2
+
+#endif // MEMCHUNK_H
diff --git a/src/memchunk_test.cc b/src/memchunk_test.cc
new file mode 100644
index 0000000..236d9ea
--- /dev/null
+++ b/src/memchunk_test.cc
@@ -0,0 +1,340 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "memchunk_test.h"
+
+#include <CUnit/CUnit.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "memchunk.h"
+#include "util.h"
+
+namespace nghttp2 {
+
+void test_pool_recycle(void) {
+ MemchunkPool pool;
+
+ CU_ASSERT(!pool.pool);
+ CU_ASSERT(0 == pool.poolsize);
+ CU_ASSERT(nullptr == pool.freelist);
+
+ auto m1 = pool.get();
+
+ CU_ASSERT(m1 == pool.pool);
+ CU_ASSERT(MemchunkPool::value_type::size == pool.poolsize);
+ CU_ASSERT(nullptr == pool.freelist);
+
+ auto m2 = pool.get();
+
+ CU_ASSERT(m2 == pool.pool);
+ CU_ASSERT(2 * MemchunkPool::value_type::size == pool.poolsize);
+ CU_ASSERT(nullptr == pool.freelist);
+ CU_ASSERT(m1 == m2->knext);
+ CU_ASSERT(nullptr == m1->knext);
+
+ auto m3 = pool.get();
+
+ CU_ASSERT(m3 == pool.pool);
+ CU_ASSERT(3 * MemchunkPool::value_type::size == pool.poolsize);
+ CU_ASSERT(nullptr == pool.freelist);
+
+ pool.recycle(m3);
+
+ CU_ASSERT(m3 == pool.pool);
+ CU_ASSERT(3 * MemchunkPool::value_type::size == pool.poolsize);
+ CU_ASSERT(m3 == pool.freelist);
+
+ auto m4 = pool.get();
+
+ CU_ASSERT(m3 == m4);
+ CU_ASSERT(m4 == pool.pool);
+ CU_ASSERT(3 * MemchunkPool::value_type::size == pool.poolsize);
+ CU_ASSERT(nullptr == pool.freelist);
+
+ pool.recycle(m2);
+ pool.recycle(m1);
+
+ CU_ASSERT(m1 == pool.freelist);
+ CU_ASSERT(m2 == m1->next);
+ CU_ASSERT(nullptr == m2->next);
+}
+
+using Memchunk16 = Memchunk<16>;
+using MemchunkPool16 = Pool<Memchunk16>;
+using Memchunks16 = Memchunks<Memchunk16>;
+using PeekMemchunks16 = PeekMemchunks<Memchunk16>;
+
+void test_memchunks_append(void) {
+ MemchunkPool16 pool;
+ Memchunks16 chunks(&pool);
+
+ chunks.append("012");
+
+ auto m = chunks.tail;
+
+ CU_ASSERT(3 == m->len());
+ CU_ASSERT(13 == m->left());
+
+ chunks.append("3456789abcdef@");
+
+ CU_ASSERT(16 == m->len());
+ CU_ASSERT(0 == m->left());
+
+ m = chunks.tail;
+
+ CU_ASSERT(1 == m->len());
+ CU_ASSERT(15 == m->left());
+ CU_ASSERT(17 == chunks.rleft());
+
+ char buf[16];
+ size_t nread;
+
+ nread = chunks.remove(buf, 8);
+
+ CU_ASSERT(8 == nread);
+ CU_ASSERT(0 == memcmp("01234567", buf, nread));
+ CU_ASSERT(9 == chunks.rleft());
+
+ nread = chunks.remove(buf, sizeof(buf));
+
+ CU_ASSERT(9 == nread);
+ CU_ASSERT(0 == memcmp("89abcdef@", buf, nread));
+ CU_ASSERT(0 == chunks.rleft());
+ CU_ASSERT(nullptr == chunks.head);
+ CU_ASSERT(nullptr == chunks.tail);
+ CU_ASSERT(32 == pool.poolsize);
+}
+
+void test_memchunks_drain(void) {
+ MemchunkPool16 pool;
+ Memchunks16 chunks(&pool);
+
+ chunks.append("0123456789");
+
+ size_t nread;
+
+ nread = chunks.drain(3);
+
+ CU_ASSERT(3 == nread);
+
+ char buf[16];
+
+ nread = chunks.remove(buf, sizeof(buf));
+
+ CU_ASSERT(7 == nread);
+ CU_ASSERT(0 == memcmp("3456789", buf, nread));
+}
+
+void test_memchunks_riovec(void) {
+ MemchunkPool16 pool;
+ Memchunks16 chunks(&pool);
+
+ std::array<char, 3 * 16> buf{};
+
+ chunks.append(buf.data(), buf.size());
+
+ std::array<struct iovec, 2> iov;
+ auto iovcnt = chunks.riovec(iov.data(), iov.size());
+
+ auto m = chunks.head;
+
+ CU_ASSERT(2 == iovcnt);
+ CU_ASSERT(m->buf.data() == iov[0].iov_base);
+ CU_ASSERT(m->len() == iov[0].iov_len);
+
+ m = m->next;
+
+ CU_ASSERT(m->buf.data() == iov[1].iov_base);
+ CU_ASSERT(m->len() == iov[1].iov_len);
+
+ chunks.drain(2 * 16);
+
+ iovcnt = chunks.riovec(iov.data(), iov.size());
+
+ CU_ASSERT(1 == iovcnt);
+
+ m = chunks.head;
+ CU_ASSERT(m->buf.data() == iov[0].iov_base);
+ CU_ASSERT(m->len() == iov[0].iov_len);
+}
+
+void test_memchunks_recycle(void) {
+ MemchunkPool16 pool;
+ {
+ Memchunks16 chunks(&pool);
+ std::array<char, 32> buf{};
+ chunks.append(buf.data(), buf.size());
+ }
+ CU_ASSERT(32 == pool.poolsize);
+ CU_ASSERT(nullptr != pool.freelist);
+
+ auto m = pool.freelist;
+ m = m->next;
+
+ CU_ASSERT(nullptr != m);
+ CU_ASSERT(nullptr == m->next);
+}
+
+void test_memchunks_reset(void) {
+ MemchunkPool16 pool;
+ Memchunks16 chunks(&pool);
+
+ std::array<uint8_t, 32> b{};
+
+ chunks.append(b.data(), b.size());
+
+ CU_ASSERT(32 == chunks.rleft());
+
+ chunks.reset();
+
+ CU_ASSERT(0 == chunks.rleft());
+ CU_ASSERT(nullptr == chunks.head);
+ CU_ASSERT(nullptr == chunks.tail);
+
+ auto m = pool.freelist;
+
+ CU_ASSERT(nullptr != m);
+ CU_ASSERT(nullptr != m->next);
+ CU_ASSERT(nullptr == m->next->next);
+}
+
+void test_peek_memchunks_append(void) {
+ MemchunkPool16 pool;
+ PeekMemchunks16 pchunks(&pool);
+
+ std::array<uint8_t, 32> b{
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
+ '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1',
+ },
+ d;
+
+ pchunks.append(b.data(), b.size());
+
+ CU_ASSERT(32 == pchunks.rleft());
+ CU_ASSERT(32 == pchunks.rleft_buffered());
+
+ CU_ASSERT(0 == pchunks.remove(nullptr, 0));
+
+ CU_ASSERT(32 == pchunks.rleft());
+ CU_ASSERT(32 == pchunks.rleft_buffered());
+
+ CU_ASSERT(12 == pchunks.remove(d.data(), 12));
+
+ CU_ASSERT(std::equal(std::begin(b), std::begin(b) + 12, std::begin(d)));
+
+ CU_ASSERT(20 == pchunks.rleft());
+ CU_ASSERT(32 == pchunks.rleft_buffered());
+
+ CU_ASSERT(20 == pchunks.remove(d.data(), d.size()));
+
+ CU_ASSERT(std::equal(std::begin(b) + 12, std::end(b), std::begin(d)));
+
+ CU_ASSERT(0 == pchunks.rleft());
+ CU_ASSERT(32 == pchunks.rleft_buffered());
+}
+
+void test_peek_memchunks_disable_peek_drain(void) {
+ MemchunkPool16 pool;
+ PeekMemchunks16 pchunks(&pool);
+
+ std::array<uint8_t, 32> b{
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
+ '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1',
+ },
+ d;
+
+ pchunks.append(b.data(), b.size());
+
+ CU_ASSERT(12 == pchunks.remove(d.data(), 12));
+
+ pchunks.disable_peek(true);
+
+ CU_ASSERT(!pchunks.peeking);
+ CU_ASSERT(20 == pchunks.rleft());
+ CU_ASSERT(20 == pchunks.rleft_buffered());
+
+ CU_ASSERT(20 == pchunks.remove(d.data(), d.size()));
+
+ CU_ASSERT(std::equal(std::begin(b) + 12, std::end(b), std::begin(d)));
+
+ CU_ASSERT(0 == pchunks.rleft());
+ CU_ASSERT(0 == pchunks.rleft_buffered());
+}
+
+void test_peek_memchunks_disable_peek_no_drain(void) {
+ MemchunkPool16 pool;
+ PeekMemchunks16 pchunks(&pool);
+
+ std::array<uint8_t, 32> b{
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
+ '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1',
+ },
+ d;
+
+ pchunks.append(b.data(), b.size());
+
+ CU_ASSERT(12 == pchunks.remove(d.data(), 12));
+
+ pchunks.disable_peek(false);
+
+ CU_ASSERT(!pchunks.peeking);
+ CU_ASSERT(32 == pchunks.rleft());
+ CU_ASSERT(32 == pchunks.rleft_buffered());
+
+ CU_ASSERT(32 == pchunks.remove(d.data(), d.size()));
+
+ CU_ASSERT(std::equal(std::begin(b), std::end(b), std::begin(d)));
+
+ CU_ASSERT(0 == pchunks.rleft());
+ CU_ASSERT(0 == pchunks.rleft_buffered());
+}
+
+void test_peek_memchunks_reset(void) {
+ MemchunkPool16 pool;
+ PeekMemchunks16 pchunks(&pool);
+
+ std::array<uint8_t, 32> b{
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
+ '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1',
+ },
+ d;
+
+ pchunks.append(b.data(), b.size());
+
+ CU_ASSERT(12 == pchunks.remove(d.data(), 12));
+
+ pchunks.disable_peek(true);
+ pchunks.reset();
+
+ CU_ASSERT(0 == pchunks.rleft());
+ CU_ASSERT(0 == pchunks.rleft_buffered());
+
+ CU_ASSERT(nullptr == pchunks.cur);
+ CU_ASSERT(nullptr == pchunks.cur_pos);
+ CU_ASSERT(nullptr == pchunks.cur_last);
+ CU_ASSERT(pchunks.peeking);
+}
+
+} // namespace nghttp2
diff --git a/src/memchunk_test.h b/src/memchunk_test.h
new file mode 100644
index 0000000..7d677e7
--- /dev/null
+++ b/src/memchunk_test.h
@@ -0,0 +1,47 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef MEMCHUNK_TEST_H
+#define MEMCHUNK_TEST_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+namespace nghttp2 {
+
+void test_pool_recycle(void);
+void test_memchunks_append(void);
+void test_memchunks_drain(void);
+void test_memchunks_riovec(void);
+void test_memchunks_recycle(void);
+void test_memchunks_reset(void);
+void test_peek_memchunks_append(void);
+void test_peek_memchunks_disable_peek_drain(void);
+void test_peek_memchunks_disable_peek_no_drain(void);
+void test_peek_memchunks_reset(void);
+
+} // namespace nghttp2
+
+#endif // MEMCHUNK_TEST_H
diff --git a/src/network.h b/src/network.h
new file mode 100644
index 0000000..45311d8
--- /dev/null
+++ b/src/network.h
@@ -0,0 +1,67 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NETWORK_H
+#define NETWORK_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include <sys/types.h>
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif // HAVE_SYS_SOCKET_H
+#ifdef _WIN32
+# include <ws2tcpip.h>
+#else // !_WIN32
+# include <sys/un.h>
+#endif // !_WIN32
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif // HAVE_NETINET_IN_H
+#ifdef HAVE_ARPA_INET_H
+# include <arpa/inet.h>
+#endif // HAVE_ARPA_INET_H
+
+namespace nghttp2 {
+
+union sockaddr_union {
+ sockaddr_storage storage;
+ sockaddr sa;
+ sockaddr_in6 in6;
+ sockaddr_in in;
+#ifndef _WIN32
+ sockaddr_un un;
+#endif // !_WIN32
+};
+
+struct Address {
+ size_t len;
+ union sockaddr_union su;
+};
+
+} // namespace nghttp2
+
+#endif // NETWORK_H
diff --git a/src/nghttp.cc b/src/nghttp.cc
new file mode 100644
index 0000000..41a88c6
--- /dev/null
+++ b/src/nghttp.cc
@@ -0,0 +1,3115 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp.h"
+
+#include <sys/stat.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+#ifdef HAVE_FCNTL_H
+# include <fcntl.h>
+#endif // HAVE_FCNTL_H
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif // HAVE_NETINET_IN_H
+#include <netinet/tcp.h>
+#include <getopt.h>
+
+#include <cassert>
+#include <cstdio>
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <iostream>
+#include <iomanip>
+#include <sstream>
+#include <tuple>
+
+#include <openssl/err.h>
+
+#ifdef HAVE_JANSSON
+# include <jansson.h>
+#endif // HAVE_JANSSON
+
+#include "app_helper.h"
+#include "HtmlParser.h"
+#include "util.h"
+#include "base64.h"
+#include "tls.h"
+#include "template.h"
+#include "ssl_compat.h"
+
+#ifndef O_BINARY
+# define O_BINARY (0)
+#endif // O_BINARY
+
+namespace nghttp2 {
+
+// The anchor stream nodes when --no-dep is not used. The stream ID =
+// 1 is excluded since it is used as first stream in upgrade case. We
+// follows the same dependency anchor nodes as Firefox does.
+struct Anchor {
+ int32_t stream_id;
+ // stream ID this anchor depends on
+ int32_t dep_stream_id;
+ // .. with this weight.
+ int32_t weight;
+};
+
+// This is index into anchors. Firefox uses ANCHOR_FOLLOWERS for html
+// file.
+enum {
+ ANCHOR_LEADERS,
+ ANCHOR_UNBLOCKED,
+ ANCHOR_BACKGROUND,
+ ANCHOR_SPECULATIVE,
+ ANCHOR_FOLLOWERS,
+};
+
+namespace {
+constexpr auto anchors = std::array<Anchor, 5>{{
+ {3, 0, 201},
+ {5, 0, 101},
+ {7, 0, 1},
+ {9, 7, 1},
+ {11, 3, 1},
+}};
+} // namespace
+
+Config::Config()
+ : header_table_size(-1),
+ min_header_table_size(std::numeric_limits<uint32_t>::max()),
+ encoder_header_table_size(-1),
+ padding(0),
+ max_concurrent_streams(100),
+ peer_max_concurrent_streams(100),
+ multiply(1),
+ timeout(0.),
+ window_bits(-1),
+ connection_window_bits(-1),
+ verbose(0),
+ port_override(0),
+ null_out(false),
+ remote_name(false),
+ get_assets(false),
+ stat(false),
+ upgrade(false),
+ continuation(false),
+ no_content_length(false),
+ no_dep(false),
+ hexdump(false),
+ no_push(false),
+ expect_continue(false),
+ verify_peer(true),
+ ktls(false),
+ no_rfc7540_pri(false) {
+ nghttp2_option_new(&http2_option);
+ nghttp2_option_set_peer_max_concurrent_streams(http2_option,
+ peer_max_concurrent_streams);
+ nghttp2_option_set_builtin_recv_extension_type(http2_option, NGHTTP2_ALTSVC);
+ nghttp2_option_set_builtin_recv_extension_type(http2_option, NGHTTP2_ORIGIN);
+}
+
+Config::~Config() { nghttp2_option_del(http2_option); }
+
+namespace {
+Config config;
+} // namespace
+
+namespace {
+void print_protocol_nego_error() {
+ std::cerr << "[ERROR] HTTP/2 protocol was not selected."
+ << " (nghttp2 expects " << NGHTTP2_PROTO_VERSION_ID << ")"
+ << std::endl;
+}
+} // namespace
+
+namespace {
+std::string strip_fragment(const char *raw_uri) {
+ const char *end;
+ for (end = raw_uri; *end && *end != '#'; ++end)
+ ;
+ size_t len = end - raw_uri;
+ return std::string(raw_uri, len);
+}
+} // namespace
+
+Request::Request(const std::string &uri, const http_parser_url &u,
+ const nghttp2_data_provider *data_prd, int64_t data_length,
+ const nghttp2_priority_spec &pri_spec, int level)
+ : uri(uri),
+ u(u),
+ pri_spec(pri_spec),
+ data_length(data_length),
+ data_offset(0),
+ response_len(0),
+ inflater(nullptr),
+ data_prd(data_prd),
+ header_buffer_size(0),
+ stream_id(-1),
+ status(0),
+ level(level),
+ expect_final_response(false) {
+ http2::init_hdidx(res_hdidx);
+ http2::init_hdidx(req_hdidx);
+}
+
+Request::~Request() { nghttp2_gzip_inflate_del(inflater); }
+
+void Request::init_inflater() {
+ int rv;
+ // This is required with --disable-assert.
+ (void)rv;
+ rv = nghttp2_gzip_inflate_new(&inflater);
+ assert(rv == 0);
+}
+
+StringRef Request::get_real_scheme() const {
+ return config.scheme_override.empty()
+ ? util::get_uri_field(uri.c_str(), u, UF_SCHEMA)
+ : StringRef{config.scheme_override};
+}
+
+StringRef Request::get_real_host() const {
+ return config.host_override.empty()
+ ? util::get_uri_field(uri.c_str(), u, UF_HOST)
+ : StringRef{config.host_override};
+}
+
+uint16_t Request::get_real_port() const {
+ auto scheme = get_real_scheme();
+ return config.host_override.empty() ? util::has_uri_field(u, UF_PORT) ? u.port
+ : scheme == "https" ? 443
+ : 80
+ : config.port_override == 0 ? scheme == "https" ? 443 : 80
+ : config.port_override;
+}
+
+void Request::init_html_parser() {
+ // We crawl HTML using overridden scheme, host, and port.
+ auto scheme = get_real_scheme();
+ auto host = get_real_host();
+ auto port = get_real_port();
+ auto ipv6_lit =
+ std::find(std::begin(host), std::end(host), ':') != std::end(host);
+
+ auto base_uri = scheme.str();
+ base_uri += "://";
+ if (ipv6_lit) {
+ base_uri += '[';
+ }
+ base_uri += host;
+ if (ipv6_lit) {
+ base_uri += ']';
+ }
+ if (!((scheme == "https" && port == 443) ||
+ (scheme == "http" && port == 80))) {
+ base_uri += ':';
+ base_uri += util::utos(port);
+ }
+ base_uri += util::get_uri_field(uri.c_str(), u, UF_PATH);
+ if (util::has_uri_field(u, UF_QUERY)) {
+ base_uri += '?';
+ base_uri += util::get_uri_field(uri.c_str(), u, UF_QUERY);
+ }
+
+ html_parser = std::make_unique<HtmlParser>(base_uri);
+}
+
+int Request::update_html_parser(const uint8_t *data, size_t len, int fin) {
+ if (!html_parser) {
+ return 0;
+ }
+ return html_parser->parse_chunk(reinterpret_cast<const char *>(data), len,
+ fin);
+}
+
+std::string Request::make_reqpath() const {
+ std::string path = util::has_uri_field(u, UF_PATH)
+ ? util::get_uri_field(uri.c_str(), u, UF_PATH).str()
+ : "/";
+ if (util::has_uri_field(u, UF_QUERY)) {
+ path += '?';
+ path.append(uri.c_str() + u.field_data[UF_QUERY].off,
+ u.field_data[UF_QUERY].len);
+ }
+ return path;
+}
+
+namespace {
+// Perform special handling |host| if it is IPv6 literal and includes
+// zone ID per RFC 6874.
+std::string decode_host(const StringRef &host) {
+ auto zone_start = std::find(std::begin(host), std::end(host), '%');
+ if (zone_start == std::end(host) ||
+ !util::ipv6_numeric_addr(
+ std::string(std::begin(host), zone_start).c_str())) {
+ return host.str();
+ }
+ // case: ::1%
+ if (zone_start + 1 == std::end(host)) {
+ return StringRef{host.c_str(), host.size() - 1}.str();
+ }
+ // case: ::1%12 or ::1%1
+ if (zone_start + 3 >= std::end(host)) {
+ return host.str();
+ }
+ // If we see "%25", followed by more characters, then decode %25 as
+ // '%'.
+ auto zone_id_src = (*(zone_start + 1) == '2' && *(zone_start + 2) == '5')
+ ? zone_start + 3
+ : zone_start + 1;
+ auto zone_id = util::percent_decode(zone_id_src, std::end(host));
+ auto res = std::string(std::begin(host), zone_start + 1);
+ res += zone_id;
+ return res;
+}
+} // namespace
+
+namespace {
+nghttp2_priority_spec resolve_dep(int res_type) {
+ nghttp2_priority_spec pri_spec;
+
+ if (config.no_dep) {
+ nghttp2_priority_spec_default_init(&pri_spec);
+
+ return pri_spec;
+ }
+
+ int32_t anchor_id;
+ int32_t weight;
+ switch (res_type) {
+ case REQ_CSS:
+ case REQ_JS:
+ anchor_id = anchors[ANCHOR_LEADERS].stream_id;
+ weight = 32;
+ break;
+ case REQ_UNBLOCK_JS:
+ anchor_id = anchors[ANCHOR_UNBLOCKED].stream_id;
+ weight = 32;
+ break;
+ case REQ_IMG:
+ anchor_id = anchors[ANCHOR_FOLLOWERS].stream_id;
+ weight = 12;
+ break;
+ default:
+ anchor_id = anchors[ANCHOR_FOLLOWERS].stream_id;
+ weight = 32;
+ }
+
+ nghttp2_priority_spec_init(&pri_spec, anchor_id, weight, 0);
+ return pri_spec;
+}
+} // namespace
+
+bool Request::is_ipv6_literal_addr() const {
+ if (util::has_uri_field(u, UF_HOST)) {
+ return memchr(uri.c_str() + u.field_data[UF_HOST].off, ':',
+ u.field_data[UF_HOST].len);
+ } else {
+ return false;
+ }
+}
+
+Headers::value_type *Request::get_res_header(int32_t token) {
+ auto idx = res_hdidx[token];
+ if (idx == -1) {
+ return nullptr;
+ }
+ return &res_nva[idx];
+}
+
+Headers::value_type *Request::get_req_header(int32_t token) {
+ auto idx = req_hdidx[token];
+ if (idx == -1) {
+ return nullptr;
+ }
+ return &req_nva[idx];
+}
+
+void Request::record_request_start_time() {
+ timing.state = RequestState::ON_REQUEST;
+ timing.request_start_time = get_time();
+}
+
+void Request::record_response_start_time() {
+ timing.state = RequestState::ON_RESPONSE;
+ timing.response_start_time = get_time();
+}
+
+void Request::record_response_end_time() {
+ timing.state = RequestState::ON_COMPLETE;
+ timing.response_end_time = get_time();
+}
+
+namespace {
+void continue_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto client = static_cast<HttpClient *>(ev_userdata(loop));
+ auto req = static_cast<Request *>(w->data);
+ int error;
+
+ error = nghttp2_submit_data(client->session, NGHTTP2_FLAG_END_STREAM,
+ req->stream_id, req->data_prd);
+
+ if (error) {
+ std::cerr << "[ERROR] nghttp2_submit_data() returned error: "
+ << nghttp2_strerror(error) << std::endl;
+ nghttp2_submit_rst_stream(client->session, NGHTTP2_FLAG_NONE,
+ req->stream_id, NGHTTP2_INTERNAL_ERROR);
+ }
+
+ client->signal_write();
+}
+} // namespace
+
+ContinueTimer::ContinueTimer(struct ev_loop *loop, Request *req) : loop(loop) {
+ ev_timer_init(&timer, continue_timeout_cb, 1., 0.);
+ timer.data = req;
+}
+
+ContinueTimer::~ContinueTimer() { stop(); }
+
+void ContinueTimer::start() { ev_timer_start(loop, &timer); }
+
+void ContinueTimer::stop() { ev_timer_stop(loop, &timer); }
+
+void ContinueTimer::dispatch_continue() {
+ // Only dispatch the timeout callback if it hasn't already been called.
+ if (ev_is_active(&timer)) {
+ ev_feed_event(loop, &timer, 0);
+ }
+}
+
+namespace {
+int htp_msg_begincb(llhttp_t *htp) {
+ if (config.verbose) {
+ print_timer();
+ std::cout << " HTTP Upgrade response" << std::endl;
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+int htp_msg_completecb(llhttp_t *htp) {
+ auto client = static_cast<HttpClient *>(htp->data);
+ client->upgrade_response_status_code = htp->status_code;
+ client->upgrade_response_complete = true;
+ return 0;
+}
+} // namespace
+
+namespace {
+constexpr llhttp_settings_t htp_hooks = {
+ htp_msg_begincb, // llhttp_cb on_message_begin;
+ nullptr, // llhttp_data_cb on_url;
+ nullptr, // llhttp_data_cb on_status;
+ nullptr, // llhttp_data_cb on_method;
+ nullptr, // llhttp_data_cb on_version;
+ nullptr, // llhttp_data_cb on_header_field;
+ nullptr, // llhttp_data_cb on_header_value;
+ nullptr, // llhttp_data_cb on_chunk_extension_name;
+ nullptr, // llhttp_data_cb on_chunk_extension_value;
+ nullptr, // llhttp_cb on_headers_complete;
+ nullptr, // llhttp_data_cb on_body;
+ htp_msg_completecb, // llhttp_cb on_message_complete;
+ nullptr, // llhttp_cb on_url_complete;
+ nullptr, // llhttp_cb on_status_complete;
+ nullptr, // llhttp_cb on_method_complete;
+ nullptr, // llhttp_cb on_version_complete;
+ nullptr, // llhttp_cb on_header_field_complete;
+ nullptr, // llhttp_cb on_header_value_complete;
+ nullptr, // llhttp_cb on_chunk_extension_name_complete;
+ nullptr, // llhttp_cb on_chunk_extension_value_complete;
+ nullptr, // llhttp_cb on_chunk_header;
+ nullptr, // llhttp_cb on_chunk_complete;
+ nullptr, // llhttp_cb on_reset;
+};
+} // namespace
+
+namespace {
+int submit_request(HttpClient *client, const Headers &headers, Request *req) {
+ auto scheme = util::get_uri_field(req->uri.c_str(), req->u, UF_SCHEMA);
+ auto build_headers = Headers{{":method", req->data_prd ? "POST" : "GET"},
+ {":path", req->make_reqpath()},
+ {":scheme", scheme.str()},
+ {":authority", client->hostport},
+ {"accept", "*/*"},
+ {"accept-encoding", "gzip, deflate"},
+ {"user-agent", "nghttp2/" NGHTTP2_VERSION}};
+ bool expect_continue = false;
+
+ if (config.continuation) {
+ for (size_t i = 0; i < 6; ++i) {
+ build_headers.emplace_back("continuation-test-" + util::utos(i + 1),
+ std::string(4_k, '-'));
+ }
+ }
+
+ auto num_initial_headers = build_headers.size();
+
+ if (req->data_prd) {
+ if (!config.no_content_length) {
+ build_headers.emplace_back("content-length",
+ util::utos(req->data_length));
+ }
+ if (config.expect_continue) {
+ expect_continue = true;
+ build_headers.emplace_back("expect", "100-continue");
+ }
+ }
+
+ for (auto &kv : headers) {
+ size_t i;
+ for (i = 0; i < num_initial_headers; ++i) {
+ if (kv.name == build_headers[i].name) {
+ build_headers[i].value = kv.value;
+ break;
+ }
+ }
+ if (i < num_initial_headers) {
+ continue;
+ }
+
+ build_headers.emplace_back(kv.name, kv.value, kv.no_index);
+ }
+
+ auto nva = std::vector<nghttp2_nv>();
+ nva.reserve(build_headers.size());
+
+ for (auto &kv : build_headers) {
+ nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index));
+ }
+
+ auto method = http2::get_header(build_headers, ":method");
+ assert(method);
+
+ req->method = method->value;
+
+ std::string trailer_names;
+ if (!config.trailer.empty()) {
+ trailer_names = config.trailer[0].name;
+ for (size_t i = 1; i < config.trailer.size(); ++i) {
+ trailer_names += ", ";
+ trailer_names += config.trailer[i].name;
+ }
+ nva.push_back(http2::make_nv_ls("trailer", trailer_names));
+ }
+
+ int32_t stream_id;
+
+ if (expect_continue) {
+ stream_id = nghttp2_submit_headers(client->session, 0, -1, &req->pri_spec,
+ nva.data(), nva.size(), req);
+ } else {
+ stream_id =
+ nghttp2_submit_request(client->session, &req->pri_spec, nva.data(),
+ nva.size(), req->data_prd, req);
+ }
+
+ if (stream_id < 0) {
+ std::cerr << "[ERROR] nghttp2_submit_"
+ << (expect_continue ? "headers" : "request")
+ << "() returned error: " << nghttp2_strerror(stream_id)
+ << std::endl;
+ return -1;
+ }
+
+ req->stream_id = stream_id;
+ client->request_done(req);
+
+ req->req_nva = std::move(build_headers);
+
+ if (expect_continue) {
+ auto timer = std::make_unique<ContinueTimer>(client->loop, req);
+ req->continue_timer = std::move(timer);
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+void readcb(struct ev_loop *loop, ev_io *w, int revents) {
+ auto client = static_cast<HttpClient *>(w->data);
+ if (client->do_read() != 0) {
+ client->disconnect();
+ }
+}
+} // namespace
+
+namespace {
+void writecb(struct ev_loop *loop, ev_io *w, int revents) {
+ auto client = static_cast<HttpClient *>(w->data);
+ auto rv = client->do_write();
+ if (rv == HttpClient::ERR_CONNECT_FAIL) {
+ client->connect_fail();
+ return;
+ }
+ if (rv != 0) {
+ client->disconnect();
+ }
+}
+} // namespace
+
+namespace {
+void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto client = static_cast<HttpClient *>(w->data);
+ std::cerr << "[ERROR] Timeout" << std::endl;
+ client->disconnect();
+}
+} // namespace
+
+namespace {
+void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto client = static_cast<HttpClient *>(w->data);
+ ev_timer_stop(loop, w);
+
+ nghttp2_session_terminate_session(client->session, NGHTTP2_SETTINGS_TIMEOUT);
+
+ client->signal_write();
+}
+} // namespace
+
+HttpClient::HttpClient(const nghttp2_session_callbacks *callbacks,
+ struct ev_loop *loop, SSL_CTX *ssl_ctx)
+ : wb(&mcpool),
+ session(nullptr),
+ callbacks(callbacks),
+ loop(loop),
+ ssl_ctx(ssl_ctx),
+ ssl(nullptr),
+ addrs(nullptr),
+ next_addr(nullptr),
+ cur_addr(nullptr),
+ complete(0),
+ success(0),
+ settings_payloadlen(0),
+ state(ClientState::IDLE),
+ upgrade_response_status_code(0),
+ fd(-1),
+ upgrade_response_complete(false) {
+ ev_io_init(&wev, writecb, 0, EV_WRITE);
+ ev_io_init(&rev, readcb, 0, EV_READ);
+
+ wev.data = this;
+ rev.data = this;
+
+ ev_timer_init(&wt, timeoutcb, 0., config.timeout);
+ ev_timer_init(&rt, timeoutcb, 0., config.timeout);
+
+ wt.data = this;
+ rt.data = this;
+
+ ev_timer_init(&settings_timer, settings_timeout_cb, 0., 10.);
+
+ settings_timer.data = this;
+}
+
+HttpClient::~HttpClient() {
+ disconnect();
+
+ if (addrs) {
+ freeaddrinfo(addrs);
+ addrs = nullptr;
+ next_addr = nullptr;
+ }
+}
+
+bool HttpClient::need_upgrade() const {
+ return config.upgrade && scheme == "http";
+}
+
+int HttpClient::resolve_host(const std::string &host, uint16_t port) {
+ int rv;
+ this->host = host;
+ addrinfo hints{};
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = 0;
+ hints.ai_flags = AI_ADDRCONFIG;
+ rv = getaddrinfo(host.c_str(), util::utos(port).c_str(), &hints, &addrs);
+ if (rv != 0) {
+ std::cerr << "[ERROR] getaddrinfo() failed: " << gai_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+ if (addrs == nullptr) {
+ std::cerr << "[ERROR] No address returned" << std::endl;
+ return -1;
+ }
+ next_addr = addrs;
+ return 0;
+}
+
+namespace {
+// Just returns 1 to continue handshake.
+int verify_cb(int preverify_ok, X509_STORE_CTX *ctx) { return 1; }
+} // namespace
+
+int HttpClient::initiate_connection() {
+ int rv;
+
+ cur_addr = nullptr;
+ while (next_addr) {
+ cur_addr = next_addr;
+ next_addr = next_addr->ai_next;
+ fd = util::create_nonblock_socket(cur_addr->ai_family);
+ if (fd == -1) {
+ continue;
+ }
+
+ if (ssl_ctx) {
+ // We are establishing TLS connection.
+ ssl = SSL_new(ssl_ctx);
+ if (!ssl) {
+ std::cerr << "[ERROR] SSL_new() failed: "
+ << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+ return -1;
+ }
+
+ SSL_set_connect_state(ssl);
+
+ // If the user overrode the :authority or host header, use that
+ // value for the SNI extension
+ const auto &host_string =
+ config.host_override.empty() ? host : config.host_override;
+
+ auto param = SSL_get0_param(ssl);
+ X509_VERIFY_PARAM_set_hostflags(param, 0);
+ X509_VERIFY_PARAM_set1_host(param, host_string.c_str(),
+ host_string.size());
+ SSL_set_verify(ssl, SSL_VERIFY_PEER, verify_cb);
+
+ if (!util::numeric_host(host_string.c_str())) {
+ SSL_set_tlsext_host_name(ssl, host_string.c_str());
+ }
+ }
+
+ rv = connect(fd, cur_addr->ai_addr, cur_addr->ai_addrlen);
+
+ if (rv != 0 && errno != EINPROGRESS) {
+ if (ssl) {
+ SSL_free(ssl);
+ ssl = nullptr;
+ }
+ close(fd);
+ fd = -1;
+ continue;
+ }
+ break;
+ }
+
+ if (fd == -1) {
+ return -1;
+ }
+
+ writefn = &HttpClient::connected;
+
+ if (need_upgrade()) {
+ on_readfn = &HttpClient::on_upgrade_read;
+ on_writefn = &HttpClient::on_upgrade_connect;
+ } else {
+ on_readfn = &HttpClient::on_read;
+ on_writefn = &HttpClient::on_write;
+ }
+
+ ev_io_set(&rev, fd, EV_READ);
+ ev_io_set(&wev, fd, EV_WRITE);
+
+ ev_io_start(loop, &wev);
+
+ ev_timer_again(loop, &wt);
+
+ return 0;
+}
+
+void HttpClient::disconnect() {
+ state = ClientState::IDLE;
+
+ for (auto req = std::begin(reqvec); req != std::end(reqvec); ++req) {
+ if ((*req)->continue_timer) {
+ (*req)->continue_timer->stop();
+ }
+ }
+
+ ev_timer_stop(loop, &settings_timer);
+
+ ev_timer_stop(loop, &rt);
+ ev_timer_stop(loop, &wt);
+
+ ev_io_stop(loop, &rev);
+ ev_io_stop(loop, &wev);
+
+ nghttp2_session_del(session);
+ session = nullptr;
+
+ if (ssl) {
+ SSL_set_shutdown(ssl, SSL_get_shutdown(ssl) | SSL_RECEIVED_SHUTDOWN);
+ ERR_clear_error();
+ SSL_shutdown(ssl);
+ SSL_free(ssl);
+ ssl = nullptr;
+ }
+
+ if (fd != -1) {
+ shutdown(fd, SHUT_WR);
+ close(fd);
+ fd = -1;
+ }
+}
+
+int HttpClient::read_clear() {
+ ev_timer_again(loop, &rt);
+
+ std::array<uint8_t, 8_k> buf;
+
+ for (;;) {
+ ssize_t nread;
+ while ((nread = read(fd, buf.data(), buf.size())) == -1 && errno == EINTR)
+ ;
+ if (nread == -1) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ return 0;
+ }
+ return -1;
+ }
+
+ if (nread == 0) {
+ return -1;
+ }
+
+ if (on_readfn(*this, buf.data(), nread) != 0) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int HttpClient::write_clear() {
+ ev_timer_again(loop, &rt);
+
+ std::array<struct iovec, 2> iov;
+
+ for (;;) {
+ if (on_writefn(*this) != 0) {
+ return -1;
+ }
+
+ auto iovcnt = wb.riovec(iov.data(), iov.size());
+
+ if (iovcnt == 0) {
+ break;
+ }
+
+ ssize_t nwrite;
+ while ((nwrite = writev(fd, iov.data(), iovcnt)) == -1 && errno == EINTR)
+ ;
+ if (nwrite == -1) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ ev_io_start(loop, &wev);
+ ev_timer_again(loop, &wt);
+ return 0;
+ }
+ return -1;
+ }
+
+ wb.drain(nwrite);
+ }
+
+ ev_io_stop(loop, &wev);
+ ev_timer_stop(loop, &wt);
+
+ return 0;
+}
+
+int HttpClient::noop() { return 0; }
+
+void HttpClient::connect_fail() {
+ if (state == ClientState::IDLE) {
+ std::cerr << "[ERROR] Could not connect to the address "
+ << util::numeric_name(cur_addr->ai_addr, cur_addr->ai_addrlen)
+ << std::endl;
+ }
+ auto cur_state = state;
+ disconnect();
+ if (cur_state == ClientState::IDLE) {
+ if (initiate_connection() == 0) {
+ std::cerr << "Trying next address "
+ << util::numeric_name(cur_addr->ai_addr, cur_addr->ai_addrlen)
+ << std::endl;
+ }
+ }
+}
+
+int HttpClient::connected() {
+ if (!util::check_socket_connected(fd)) {
+ return ERR_CONNECT_FAIL;
+ }
+
+ if (config.verbose) {
+ print_timer();
+ std::cout << " Connected" << std::endl;
+ }
+
+ state = ClientState::CONNECTED;
+
+ ev_io_start(loop, &rev);
+ ev_io_stop(loop, &wev);
+
+ ev_timer_again(loop, &rt);
+ ev_timer_stop(loop, &wt);
+
+ if (ssl) {
+ SSL_set_fd(ssl, fd);
+
+ readfn = &HttpClient::tls_handshake;
+ writefn = &HttpClient::tls_handshake;
+
+ return do_write();
+ }
+
+ readfn = &HttpClient::read_clear;
+ writefn = &HttpClient::write_clear;
+
+ if (need_upgrade()) {
+ htp = std::make_unique<llhttp_t>();
+ llhttp_init(htp.get(), HTTP_RESPONSE, &htp_hooks);
+ htp->data = this;
+
+ return do_write();
+ }
+
+ if (connection_made() != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+namespace {
+size_t populate_settings(nghttp2_settings_entry *iv) {
+ size_t niv = 2;
+
+ iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
+ iv[0].value = config.max_concurrent_streams;
+
+ iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+ if (config.window_bits != -1) {
+ iv[1].value = (1 << config.window_bits) - 1;
+ } else {
+ iv[1].value = NGHTTP2_INITIAL_WINDOW_SIZE;
+ }
+
+ if (config.header_table_size >= 0) {
+ if (config.min_header_table_size < config.header_table_size) {
+ iv[niv].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
+ iv[niv].value = config.min_header_table_size;
+ ++niv;
+ }
+
+ iv[niv].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
+ iv[niv].value = config.header_table_size;
+ ++niv;
+ }
+
+ if (config.no_push) {
+ iv[niv].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
+ iv[niv].value = 0;
+ ++niv;
+ }
+
+ if (config.no_rfc7540_pri) {
+ iv[niv].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES;
+ iv[niv].value = 1;
+ ++niv;
+ }
+
+ return niv;
+}
+} // namespace
+
+int HttpClient::on_upgrade_connect() {
+ ssize_t rv;
+ record_connect_end_time();
+ assert(!reqvec.empty());
+ std::array<nghttp2_settings_entry, 16> iv;
+ size_t niv = populate_settings(iv.data());
+ assert(settings_payload.size() >= 8 * niv);
+ rv = nghttp2_pack_settings_payload(settings_payload.data(),
+ settings_payload.size(), iv.data(), niv);
+ if (rv < 0) {
+ return -1;
+ }
+ settings_payloadlen = rv;
+ auto token68 =
+ base64::encode(std::begin(settings_payload),
+ std::begin(settings_payload) + settings_payloadlen);
+ util::to_token68(token68);
+
+ std::string req;
+ if (reqvec[0]->data_prd) {
+ // If the request contains upload data, use OPTIONS * to upgrade
+ req = "OPTIONS *";
+ } else {
+ auto meth = std::find_if(
+ std::begin(config.headers), std::end(config.headers),
+ [](const Header &kv) { return util::streq_l(":method", kv.name); });
+
+ if (meth == std::end(config.headers)) {
+ req = "GET ";
+ reqvec[0]->method = "GET";
+ } else {
+ req = (*meth).value;
+ req += ' ';
+ reqvec[0]->method = (*meth).value;
+ }
+ req += reqvec[0]->make_reqpath();
+ }
+
+ auto headers = Headers{{"host", hostport},
+ {"connection", "Upgrade, HTTP2-Settings"},
+ {"upgrade", NGHTTP2_CLEARTEXT_PROTO_VERSION_ID},
+ {"http2-settings", std::move(token68)},
+ {"accept", "*/*"},
+ {"user-agent", "nghttp2/" NGHTTP2_VERSION}};
+ auto initial_headerslen = headers.size();
+
+ for (auto &kv : config.headers) {
+ size_t i;
+ if (kv.name.empty() || kv.name[0] == ':') {
+ continue;
+ }
+ for (i = 0; i < initial_headerslen; ++i) {
+ if (kv.name == headers[i].name) {
+ headers[i].value = kv.value;
+ break;
+ }
+ }
+ if (i < initial_headerslen) {
+ continue;
+ }
+ headers.emplace_back(kv.name, kv.value, kv.no_index);
+ }
+
+ req += " HTTP/1.1\r\n";
+
+ for (auto &kv : headers) {
+ req += kv.name;
+ req += ": ";
+ req += kv.value;
+ req += "\r\n";
+ }
+ req += "\r\n";
+
+ wb.append(req);
+
+ if (config.verbose) {
+ print_timer();
+ std::cout << " HTTP Upgrade request\n" << req << std::endl;
+ }
+
+ if (!reqvec[0]->data_prd) {
+ // record request time if this is a part of real request.
+ reqvec[0]->record_request_start_time();
+ reqvec[0]->req_nva = std::move(headers);
+ }
+
+ on_writefn = &HttpClient::noop;
+
+ signal_write();
+
+ return 0;
+}
+
+int HttpClient::on_upgrade_read(const uint8_t *data, size_t len) {
+ int rv;
+
+ auto htperr =
+ llhttp_execute(htp.get(), reinterpret_cast<const char *>(data), len);
+ auto nread = htperr == HPE_OK
+ ? len
+ : static_cast<size_t>(reinterpret_cast<const uint8_t *>(
+ llhttp_get_error_pos(htp.get())) -
+ data);
+
+ if (config.verbose) {
+ std::cout.write(reinterpret_cast<const char *>(data), nread);
+ }
+
+ if (htperr != HPE_OK && htperr != HPE_PAUSED_UPGRADE) {
+ std::cerr << "[ERROR] Failed to parse HTTP Upgrade response header: "
+ << "(" << llhttp_errno_name(htperr) << ") "
+ << llhttp_get_error_reason(htp.get()) << std::endl;
+ return -1;
+ }
+
+ if (!upgrade_response_complete) {
+ return 0;
+ }
+
+ if (config.verbose) {
+ std::cout << std::endl;
+ }
+
+ if (upgrade_response_status_code != 101) {
+ std::cerr << "[ERROR] HTTP Upgrade failed" << std::endl;
+
+ return -1;
+ }
+
+ if (config.verbose) {
+ print_timer();
+ std::cout << " HTTP Upgrade success" << std::endl;
+ }
+
+ on_readfn = &HttpClient::on_read;
+ on_writefn = &HttpClient::on_write;
+
+ rv = connection_made();
+ if (rv != 0) {
+ return rv;
+ }
+
+ // Read remaining data in the buffer because it is not notified
+ // callback anymore.
+ rv = on_readfn(*this, data + nread, len - nread);
+ if (rv != 0) {
+ return rv;
+ }
+
+ return 0;
+}
+
+int HttpClient::do_read() { return readfn(*this); }
+int HttpClient::do_write() { return writefn(*this); }
+
+int HttpClient::connection_made() {
+ int rv;
+
+ if (!need_upgrade()) {
+ record_connect_end_time();
+ }
+
+ if (ssl) {
+ // Check ALPN result
+ const unsigned char *next_proto = nullptr;
+ unsigned int next_proto_len;
+
+ SSL_get0_alpn_selected(ssl, &next_proto, &next_proto_len);
+ if (next_proto) {
+ auto proto = StringRef{next_proto, next_proto_len};
+ if (config.verbose) {
+ std::cout << "The negotiated protocol: " << proto << std::endl;
+ }
+ if (!util::check_h2_is_selected(proto)) {
+ next_proto = nullptr;
+ }
+ }
+ if (!next_proto) {
+ print_protocol_nego_error();
+ return -1;
+ }
+ }
+
+ rv = nghttp2_session_client_new2(&session, callbacks, this,
+ config.http2_option);
+
+ if (rv != 0) {
+ return -1;
+ }
+ if (need_upgrade()) {
+ // Adjust stream user-data depending on the existence of upload
+ // data
+ Request *stream_user_data = nullptr;
+ if (!reqvec[0]->data_prd) {
+ stream_user_data = reqvec[0].get();
+ }
+ // If HEAD is used, that is only when user specified it with -H
+ // option.
+ auto head_request = stream_user_data && stream_user_data->method == "HEAD";
+ rv = nghttp2_session_upgrade2(session, settings_payload.data(),
+ settings_payloadlen, head_request,
+ stream_user_data);
+ if (rv != 0) {
+ std::cerr << "[ERROR] nghttp2_session_upgrade() returned error: "
+ << nghttp2_strerror(rv) << std::endl;
+ return -1;
+ }
+ if (stream_user_data) {
+ stream_user_data->stream_id = 1;
+ request_done(stream_user_data);
+ }
+ }
+ // If upgrade succeeds, the SETTINGS value sent with
+ // HTTP2-Settings header field has already been submitted to
+ // session object.
+ if (!need_upgrade()) {
+ std::array<nghttp2_settings_entry, 16> iv;
+ auto niv = populate_settings(iv.data());
+ rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv.data(), niv);
+ if (rv != 0) {
+ return -1;
+ }
+ }
+ if (!config.no_dep) {
+ // Create anchor stream nodes
+ nghttp2_priority_spec pri_spec;
+
+ for (auto &anchor : anchors) {
+ nghttp2_priority_spec_init(&pri_spec, anchor.dep_stream_id, anchor.weight,
+ 0);
+ rv = nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, anchor.stream_id,
+ &pri_spec);
+ if (rv != 0) {
+ return -1;
+ }
+ }
+
+ rv = nghttp2_session_set_next_stream_id(
+ session, anchors[ANCHOR_FOLLOWERS].stream_id + 2);
+ if (rv != 0) {
+ return -1;
+ }
+
+ if (need_upgrade() && !reqvec[0]->data_prd) {
+ // Amend the priority because we cannot send priority in
+ // HTTP/1.1 Upgrade.
+ auto &anchor = anchors[ANCHOR_FOLLOWERS];
+ nghttp2_priority_spec_init(&pri_spec, anchor.stream_id,
+ reqvec[0]->pri_spec.weight, 0);
+
+ rv = nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec);
+ if (rv != 0) {
+ return -1;
+ }
+ }
+ } else if (need_upgrade() && !reqvec[0]->data_prd &&
+ reqvec[0]->pri_spec.weight != NGHTTP2_DEFAULT_WEIGHT) {
+ // Amend the priority because we cannot send priority in HTTP/1.1
+ // Upgrade.
+ nghttp2_priority_spec pri_spec;
+
+ nghttp2_priority_spec_init(&pri_spec, 0, reqvec[0]->pri_spec.weight, 0);
+
+ rv = nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec);
+ if (rv != 0) {
+ return -1;
+ }
+ }
+
+ ev_timer_again(loop, &settings_timer);
+
+ if (config.connection_window_bits != -1) {
+ int32_t window_size = (1 << config.connection_window_bits) - 1;
+ rv = nghttp2_session_set_local_window_size(session, NGHTTP2_FLAG_NONE, 0,
+ window_size);
+ if (rv != 0) {
+ return -1;
+ }
+ }
+ // Adjust first request depending on the existence of the upload
+ // data
+ for (auto i = std::begin(reqvec) + (need_upgrade() && !reqvec[0]->data_prd);
+ i != std::end(reqvec); ++i) {
+ if (submit_request(this, config.headers, (*i).get()) != 0) {
+ return -1;
+ }
+ }
+
+ signal_write();
+
+ return 0;
+}
+
+int HttpClient::on_read(const uint8_t *data, size_t len) {
+ if (config.hexdump) {
+ util::hexdump(stdout, data, len);
+ }
+
+ auto rv = nghttp2_session_mem_recv(session, data, len);
+ if (rv < 0) {
+ std::cerr << "[ERROR] nghttp2_session_mem_recv() returned error: "
+ << nghttp2_strerror(rv) << std::endl;
+ return -1;
+ }
+
+ assert(static_cast<size_t>(rv) == len);
+
+ if (nghttp2_session_want_read(session) == 0 &&
+ nghttp2_session_want_write(session) == 0 && wb.rleft() == 0) {
+ return -1;
+ }
+
+ signal_write();
+
+ return 0;
+}
+
+int HttpClient::on_write() {
+ for (;;) {
+ if (wb.rleft() >= 16384) {
+ return 0;
+ }
+
+ const uint8_t *data;
+ auto len = nghttp2_session_mem_send(session, &data);
+ if (len < 0) {
+ std::cerr << "[ERROR] nghttp2_session_send() returned error: "
+ << nghttp2_strerror(len) << std::endl;
+ return -1;
+ }
+
+ if (len == 0) {
+ break;
+ }
+
+ wb.append(data, len);
+ }
+
+ if (nghttp2_session_want_read(session) == 0 &&
+ nghttp2_session_want_write(session) == 0 && wb.rleft() == 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int HttpClient::tls_handshake() {
+ ev_timer_again(loop, &rt);
+
+ ERR_clear_error();
+
+ auto rv = SSL_do_handshake(ssl);
+
+ if (rv <= 0) {
+ auto err = SSL_get_error(ssl, rv);
+ switch (err) {
+ case SSL_ERROR_WANT_READ:
+ ev_io_stop(loop, &wev);
+ ev_timer_stop(loop, &wt);
+ return 0;
+ case SSL_ERROR_WANT_WRITE:
+ ev_io_start(loop, &wev);
+ ev_timer_again(loop, &wt);
+ return 0;
+ default:
+ return -1;
+ }
+ }
+
+ ev_io_stop(loop, &wev);
+ ev_timer_stop(loop, &wt);
+
+ readfn = &HttpClient::read_tls;
+ writefn = &HttpClient::write_tls;
+
+ if (config.verify_peer) {
+ auto verify_res = SSL_get_verify_result(ssl);
+ if (verify_res != X509_V_OK) {
+ std::cerr << "[WARNING] Certificate verification failed: "
+ << X509_verify_cert_error_string(verify_res) << std::endl;
+ }
+ }
+
+ if (connection_made() != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int HttpClient::read_tls() {
+ ev_timer_again(loop, &rt);
+
+ ERR_clear_error();
+
+ std::array<uint8_t, 8_k> buf;
+ for (;;) {
+ auto rv = SSL_read(ssl, buf.data(), buf.size());
+
+ if (rv <= 0) {
+ auto err = SSL_get_error(ssl, rv);
+ switch (err) {
+ case SSL_ERROR_WANT_READ:
+ return 0;
+ case SSL_ERROR_WANT_WRITE:
+ // renegotiation started
+ return -1;
+ default:
+ return -1;
+ }
+ }
+
+ if (on_readfn(*this, buf.data(), rv) != 0) {
+ return -1;
+ }
+ }
+}
+
+int HttpClient::write_tls() {
+ ev_timer_again(loop, &rt);
+
+ ERR_clear_error();
+
+ struct iovec iov;
+
+ for (;;) {
+ if (on_writefn(*this) != 0) {
+ return -1;
+ }
+
+ auto iovcnt = wb.riovec(&iov, 1);
+
+ if (iovcnt == 0) {
+ break;
+ }
+
+ auto rv = SSL_write(ssl, iov.iov_base, iov.iov_len);
+
+ if (rv <= 0) {
+ auto err = SSL_get_error(ssl, rv);
+ switch (err) {
+ case SSL_ERROR_WANT_READ:
+ // renegotiation started
+ return -1;
+ case SSL_ERROR_WANT_WRITE:
+ ev_io_start(loop, &wev);
+ ev_timer_again(loop, &wt);
+ return 0;
+ default:
+ return -1;
+ }
+ }
+
+ wb.drain(rv);
+ }
+
+ ev_io_stop(loop, &wev);
+ ev_timer_stop(loop, &wt);
+
+ return 0;
+}
+
+void HttpClient::signal_write() { ev_io_start(loop, &wev); }
+
+bool HttpClient::all_requests_processed() const {
+ return complete == reqvec.size();
+}
+
+void HttpClient::update_hostport() {
+ if (reqvec.empty()) {
+ return;
+ }
+ scheme = util::get_uri_field(reqvec[0]->uri.c_str(), reqvec[0]->u, UF_SCHEMA)
+ .str();
+ std::stringstream ss;
+ if (reqvec[0]->is_ipv6_literal_addr()) {
+ // we may have zone ID, which must start with "%25", or "%". RFC
+ // 6874 defines "%25" only, and just "%" is allowed for just
+ // convenience to end-user input.
+ auto host =
+ util::get_uri_field(reqvec[0]->uri.c_str(), reqvec[0]->u, UF_HOST);
+ auto end = std::find(std::begin(host), std::end(host), '%');
+ ss << "[";
+ ss.write(host.c_str(), end - std::begin(host));
+ ss << "]";
+ } else {
+ util::write_uri_field(ss, reqvec[0]->uri.c_str(), reqvec[0]->u, UF_HOST);
+ }
+ if (util::has_uri_field(reqvec[0]->u, UF_PORT) &&
+ reqvec[0]->u.port !=
+ util::get_default_port(reqvec[0]->uri.c_str(), reqvec[0]->u)) {
+ ss << ":" << reqvec[0]->u.port;
+ }
+ hostport = ss.str();
+}
+
+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{};
+ if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
+ return false;
+ }
+ if (path_cache.count(uri)) {
+ return false;
+ }
+
+ if (config.multiply == 1) {
+ path_cache.insert(uri);
+ }
+
+ reqvec.push_back(std::make_unique<Request>(uri, u, data_prd, data_length,
+ pri_spec, level));
+ return true;
+}
+
+void HttpClient::record_start_time() {
+ timing.system_start_time = std::chrono::system_clock::now();
+ timing.start_time = get_time();
+}
+
+void HttpClient::record_domain_lookup_end_time() {
+ timing.domain_lookup_end_time = get_time();
+}
+
+void HttpClient::record_connect_end_time() {
+ timing.connect_end_time = get_time();
+}
+
+void HttpClient::request_done(Request *req) {
+ if (req->stream_id % 2 == 0) {
+ return;
+ }
+}
+
+#ifdef HAVE_JANSSON
+void HttpClient::output_har(FILE *outfile) {
+ static auto PAGE_ID = "page_0";
+
+ auto root = json_object();
+ auto log = json_object();
+ json_object_set_new(root, "log", log);
+ json_object_set_new(log, "version", json_string("1.2"));
+
+ auto creator = json_object();
+ json_object_set_new(log, "creator", creator);
+
+ json_object_set_new(creator, "name", json_string("nghttp"));
+ json_object_set_new(creator, "version", json_string(NGHTTP2_VERSION));
+
+ auto pages = json_array();
+ json_object_set_new(log, "pages", pages);
+
+ auto page = json_object();
+ json_array_append_new(pages, page);
+
+ json_object_set_new(
+ page, "startedDateTime",
+ json_string(util::format_iso8601(timing.system_start_time).c_str()));
+ json_object_set_new(page, "id", json_string(PAGE_ID));
+ json_object_set_new(page, "title", json_string(""));
+
+ json_object_set_new(page, "pageTimings", json_object());
+
+ auto entries = json_array();
+ json_object_set_new(log, "entries", entries);
+
+ auto dns_delta = std::chrono::duration_cast<std::chrono::microseconds>(
+ timing.domain_lookup_end_time - timing.start_time)
+ .count() /
+ 1000.0;
+ auto connect_delta =
+ std::chrono::duration_cast<std::chrono::microseconds>(
+ timing.connect_end_time - timing.domain_lookup_end_time)
+ .count() /
+ 1000.0;
+
+ for (size_t i = 0; i < reqvec.size(); ++i) {
+ auto &req = reqvec[i];
+
+ if (req->timing.state != RequestState::ON_COMPLETE) {
+ continue;
+ }
+
+ auto entry = json_object();
+ json_array_append_new(entries, entry);
+
+ auto &req_timing = req->timing;
+ auto request_time =
+ (i == 0) ? timing.system_start_time
+ : timing.system_start_time +
+ std::chrono::duration_cast<
+ std::chrono::system_clock::duration>(
+ req_timing.request_start_time - timing.start_time);
+
+ auto wait_delta =
+ std::chrono::duration_cast<std::chrono::microseconds>(
+ req_timing.response_start_time - req_timing.request_start_time)
+ .count() /
+ 1000.0;
+ auto receive_delta =
+ std::chrono::duration_cast<std::chrono::microseconds>(
+ req_timing.response_end_time - req_timing.response_start_time)
+ .count() /
+ 1000.0;
+
+ auto time_sum =
+ std::chrono::duration_cast<std::chrono::microseconds>(
+ (i == 0) ? (req_timing.response_end_time - timing.start_time)
+ : (req_timing.response_end_time -
+ req_timing.request_start_time))
+ .count() /
+ 1000.0;
+
+ json_object_set_new(
+ entry, "startedDateTime",
+ json_string(util::format_iso8601(request_time).c_str()));
+ json_object_set_new(entry, "time", json_real(time_sum));
+
+ auto pushed = req->stream_id % 2 == 0;
+
+ json_object_set_new(entry, "comment",
+ json_string(pushed ? "Pushed Object" : ""));
+
+ auto request = json_object();
+ json_object_set_new(entry, "request", request);
+
+ auto req_headers = json_array();
+ json_object_set_new(request, "headers", req_headers);
+
+ for (auto &nv : req->req_nva) {
+ auto hd = json_object();
+ json_array_append_new(req_headers, hd);
+
+ json_object_set_new(hd, "name", json_string(nv.name.c_str()));
+ json_object_set_new(hd, "value", json_string(nv.value.c_str()));
+ }
+
+ json_object_set_new(request, "method", json_string(req->method.c_str()));
+ json_object_set_new(request, "url", json_string(req->uri.c_str()));
+ json_object_set_new(request, "httpVersion", json_string("HTTP/2.0"));
+ json_object_set_new(request, "cookies", json_array());
+ json_object_set_new(request, "queryString", json_array());
+ json_object_set_new(request, "headersSize", json_integer(-1));
+ json_object_set_new(request, "bodySize", json_integer(-1));
+
+ auto response = json_object();
+ json_object_set_new(entry, "response", response);
+
+ auto res_headers = json_array();
+ json_object_set_new(response, "headers", res_headers);
+
+ for (auto &nv : req->res_nva) {
+ auto hd = json_object();
+ json_array_append_new(res_headers, hd);
+
+ json_object_set_new(hd, "name", json_string(nv.name.c_str()));
+ json_object_set_new(hd, "value", json_string(nv.value.c_str()));
+ }
+
+ json_object_set_new(response, "status", json_integer(req->status));
+ json_object_set_new(response, "statusText", json_string(""));
+ json_object_set_new(response, "httpVersion", json_string("HTTP/2.0"));
+ json_object_set_new(response, "cookies", json_array());
+
+ auto content = json_object();
+ json_object_set_new(response, "content", content);
+
+ json_object_set_new(content, "size", json_integer(req->response_len));
+
+ auto content_type_ptr = http2::get_header(req->res_nva, "content-type");
+
+ const char *content_type = "";
+ if (content_type_ptr) {
+ content_type = content_type_ptr->value.c_str();
+ }
+
+ json_object_set_new(content, "mimeType", json_string(content_type));
+
+ json_object_set_new(response, "redirectURL", json_string(""));
+ json_object_set_new(response, "headersSize", json_integer(-1));
+ json_object_set_new(response, "bodySize", json_integer(-1));
+ json_object_set_new(entry, "cache", json_object());
+
+ auto timings = json_object();
+ json_object_set_new(entry, "timings", timings);
+
+ auto dns_timing = (i == 0) ? dns_delta : 0;
+ auto connect_timing = (i == 0) ? connect_delta : 0;
+
+ json_object_set_new(timings, "dns", json_real(dns_timing));
+ json_object_set_new(timings, "connect", json_real(connect_timing));
+
+ json_object_set_new(timings, "blocked", json_real(0.0));
+ json_object_set_new(timings, "send", json_real(0.0));
+ json_object_set_new(timings, "wait", json_real(wait_delta));
+ json_object_set_new(timings, "receive", json_real(receive_delta));
+
+ json_object_set_new(entry, "pageref", json_string(PAGE_ID));
+ json_object_set_new(entry, "connection",
+ json_string(util::utos(req->stream_id).c_str()));
+ }
+
+ json_dumpf(root, outfile, JSON_PRESERVE_ORDER | JSON_INDENT(2));
+ json_decref(root);
+}
+#endif // HAVE_JANSSON
+
+namespace {
+void update_html_parser(HttpClient *client, Request *req, const uint8_t *data,
+ size_t len, int fin) {
+ if (!req->html_parser) {
+ return;
+ }
+ req->update_html_parser(data, len, fin);
+
+ auto scheme = req->get_real_scheme();
+ auto host = req->get_real_host();
+ auto port = req->get_real_port();
+
+ for (auto &p : req->html_parser->get_links()) {
+ auto uri = strip_fragment(p.first.c_str());
+ auto res_type = p.second;
+
+ http_parser_url u{};
+ if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
+ continue;
+ }
+
+ if (!util::fieldeq(uri.c_str(), u, UF_SCHEMA, scheme) ||
+ !util::fieldeq(uri.c_str(), u, UF_HOST, host)) {
+ continue;
+ }
+
+ auto link_port = util::has_uri_field(u, UF_PORT) ? u.port
+ : scheme == "https" ? 443
+ : 80;
+
+ if (port != link_port) {
+ continue;
+ }
+
+ // No POST data for assets
+ auto pri_spec = resolve_dep(res_type);
+
+ if (client->add_request(uri, nullptr, 0, pri_spec, req->level + 1)) {
+ submit_request(client, config.headers, client->reqvec.back().get());
+ }
+ }
+ req->html_parser->clear_links();
+}
+} // namespace
+
+namespace {
+HttpClient *get_client(void *user_data) {
+ return static_cast<HttpClient *>(user_data);
+}
+} // namespace
+
+namespace {
+int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
+ int32_t stream_id, const uint8_t *data,
+ size_t len, void *user_data) {
+ auto client = get_client(user_data);
+ auto req = static_cast<Request *>(
+ nghttp2_session_get_stream_user_data(session, stream_id));
+
+ if (!req) {
+ return 0;
+ }
+
+ if (config.verbose >= 2) {
+ verbose_on_data_chunk_recv_callback(session, flags, stream_id, data, len,
+ user_data);
+ }
+
+ req->response_len += len;
+
+ if (req->inflater) {
+ while (len > 0) {
+ const size_t MAX_OUTLEN = 4_k;
+ std::array<uint8_t, MAX_OUTLEN> out;
+ size_t outlen = MAX_OUTLEN;
+ size_t tlen = len;
+ int rv =
+ nghttp2_gzip_inflate(req->inflater, out.data(), &outlen, data, &tlen);
+ if (rv != 0) {
+ nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id,
+ NGHTTP2_INTERNAL_ERROR);
+ break;
+ }
+
+ if (!config.null_out) {
+ std::cout.write(reinterpret_cast<const char *>(out.data()), outlen);
+ }
+
+ update_html_parser(client, req, out.data(), outlen, 0);
+ data += tlen;
+ len -= tlen;
+ }
+
+ return 0;
+ }
+
+ if (!config.null_out) {
+ std::cout.write(reinterpret_cast<const char *>(data), len);
+ }
+
+ update_html_parser(client, req, data, len, 0);
+
+ return 0;
+}
+} // namespace
+
+namespace {
+ssize_t select_padding_callback(nghttp2_session *session,
+ const nghttp2_frame *frame, size_t max_payload,
+ void *user_data) {
+ return std::min(max_payload, frame->hd.length + config.padding);
+}
+} // namespace
+
+namespace {
+void check_response_header(nghttp2_session *session, Request *req) {
+ bool gzip = false;
+
+ req->expect_final_response = false;
+
+ auto status_hd = req->get_res_header(http2::HD__STATUS);
+
+ if (!status_hd) {
+ nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id,
+ NGHTTP2_PROTOCOL_ERROR);
+ return;
+ }
+
+ auto status = http2::parse_http_status_code(StringRef{status_hd->value});
+ if (status == -1) {
+ nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id,
+ NGHTTP2_PROTOCOL_ERROR);
+ return;
+ }
+
+ req->status = status;
+
+ for (auto &nv : req->res_nva) {
+ if ("content-encoding" == nv.name) {
+ gzip = util::strieq_l("gzip", nv.value) ||
+ util::strieq_l("deflate", nv.value);
+ continue;
+ }
+ }
+
+ if (req->status / 100 == 1) {
+ if (req->continue_timer && (req->status == 100)) {
+ // If the request is waiting for a 100 Continue, complete the handshake.
+ req->continue_timer->dispatch_continue();
+ }
+
+ req->expect_final_response = true;
+ req->status = 0;
+ req->res_nva.clear();
+ http2::init_hdidx(req->res_hdidx);
+ return;
+ } else if (req->continue_timer) {
+ // A final response stops any pending Expect/Continue handshake.
+ req->continue_timer->stop();
+ }
+
+ if (gzip) {
+ if (!req->inflater) {
+ req->init_inflater();
+ }
+ }
+ if (config.get_assets && req->level == 0) {
+ if (!req->html_parser) {
+ req->init_html_parser();
+ }
+ }
+}
+} // namespace
+
+namespace {
+int on_begin_headers_callback(nghttp2_session *session,
+ const nghttp2_frame *frame, void *user_data) {
+ auto client = get_client(user_data);
+ switch (frame->hd.type) {
+ case NGHTTP2_HEADERS: {
+ auto req = static_cast<Request *>(
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+ if (!req) {
+ break;
+ }
+
+ switch (frame->headers.cat) {
+ case NGHTTP2_HCAT_RESPONSE:
+ case NGHTTP2_HCAT_PUSH_RESPONSE:
+ req->record_response_start_time();
+ break;
+ default:
+ break;
+ }
+
+ break;
+ }
+ case NGHTTP2_PUSH_PROMISE: {
+ auto stream_id = frame->push_promise.promised_stream_id;
+ http_parser_url u{};
+ // TODO Set pri and level
+ nghttp2_priority_spec pri_spec;
+
+ nghttp2_priority_spec_default_init(&pri_spec);
+
+ auto req = std::make_unique<Request>("", u, nullptr, 0, pri_spec);
+ req->stream_id = stream_id;
+
+ nghttp2_session_set_stream_user_data(session, stream_id, req.get());
+
+ client->request_done(req.get());
+ req->record_request_start_time();
+ client->reqvec.push_back(std::move(req));
+
+ break;
+ }
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
+ const uint8_t *name, size_t namelen,
+ const uint8_t *value, size_t valuelen, uint8_t flags,
+ void *user_data) {
+ if (config.verbose) {
+ verbose_on_header_callback(session, frame, name, namelen, value, valuelen,
+ flags, user_data);
+ }
+
+ switch (frame->hd.type) {
+ case NGHTTP2_HEADERS: {
+ auto req = static_cast<Request *>(
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+
+ if (!req) {
+ break;
+ }
+
+ /* ignore trailer header */
+ if (frame->headers.cat == NGHTTP2_HCAT_HEADERS &&
+ !req->expect_final_response) {
+ break;
+ }
+
+ if (req->header_buffer_size + namelen + valuelen > 64_k) {
+ nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id,
+ NGHTTP2_INTERNAL_ERROR);
+ return 0;
+ }
+
+ req->header_buffer_size += namelen + valuelen;
+
+ auto token = http2::lookup_token(name, namelen);
+
+ http2::index_header(req->res_hdidx, token, req->res_nva.size());
+ http2::add_header(req->res_nva, name, namelen, value, valuelen,
+ flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
+ break;
+ }
+ case NGHTTP2_PUSH_PROMISE: {
+ auto req = static_cast<Request *>(nghttp2_session_get_stream_user_data(
+ session, frame->push_promise.promised_stream_id));
+
+ if (!req) {
+ break;
+ }
+
+ if (req->header_buffer_size + namelen + valuelen > 64_k) {
+ nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
+ frame->push_promise.promised_stream_id,
+ NGHTTP2_INTERNAL_ERROR);
+ return 0;
+ }
+
+ req->header_buffer_size += namelen + valuelen;
+
+ auto token = http2::lookup_token(name, namelen);
+
+ http2::index_header(req->req_hdidx, token, req->req_nva.size());
+ http2::add_header(req->req_nva, name, namelen, value, valuelen,
+ flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
+ break;
+ }
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+int on_frame_recv_callback2(nghttp2_session *session,
+ const nghttp2_frame *frame, void *user_data) {
+ int rv = 0;
+
+ if (config.verbose) {
+ verbose_on_frame_recv_callback(session, frame, user_data);
+ }
+
+ auto client = get_client(user_data);
+ switch (frame->hd.type) {
+ case NGHTTP2_DATA: {
+ auto req = static_cast<Request *>(
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+ if (!req) {
+ return 0;
+ ;
+ }
+
+ if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+ req->record_response_end_time();
+ ++client->success;
+ }
+
+ break;
+ }
+ case NGHTTP2_HEADERS: {
+ auto req = static_cast<Request *>(
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+ // If this is the HTTP Upgrade with OPTIONS method to avoid POST,
+ // req is nullptr.
+ if (!req) {
+ return 0;
+ ;
+ }
+
+ switch (frame->headers.cat) {
+ case NGHTTP2_HCAT_RESPONSE:
+ case NGHTTP2_HCAT_PUSH_RESPONSE:
+ check_response_header(session, req);
+ break;
+ case NGHTTP2_HCAT_HEADERS:
+ if (req->expect_final_response) {
+ check_response_header(session, req);
+ break;
+ }
+ if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) {
+ nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
+ frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR);
+ return 0;
+ }
+ break;
+ default:
+ assert(0);
+ }
+
+ if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+ req->record_response_end_time();
+ ++client->success;
+ }
+
+ break;
+ }
+ case NGHTTP2_SETTINGS:
+ if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) {
+ break;
+ }
+ ev_timer_stop(client->loop, &client->settings_timer);
+ break;
+ case NGHTTP2_PUSH_PROMISE: {
+ auto req = static_cast<Request *>(nghttp2_session_get_stream_user_data(
+ session, frame->push_promise.promised_stream_id));
+ if (!req) {
+ break;
+ }
+
+ // Reset for response header field reception
+ req->header_buffer_size = 0;
+
+ auto scheme = req->get_req_header(http2::HD__SCHEME);
+ auto authority = req->get_req_header(http2::HD__AUTHORITY);
+ auto path = req->get_req_header(http2::HD__PATH);
+
+ if (!authority) {
+ authority = req->get_req_header(http2::HD_HOST);
+ }
+
+ // libnghttp2 guarantees :scheme, :method, :path and (:authority |
+ // host) exist and non-empty.
+ if (path->value[0] != '/') {
+ nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
+ frame->push_promise.promised_stream_id,
+ NGHTTP2_PROTOCOL_ERROR);
+ break;
+ }
+ std::string uri = scheme->value;
+ uri += "://";
+ uri += authority->value;
+ uri += path->value;
+ 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,
+ NGHTTP2_PROTOCOL_ERROR);
+ break;
+ }
+ req->uri = uri;
+ req->u = u;
+
+ if (client->path_cache.count(uri)) {
+ nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
+ frame->push_promise.promised_stream_id,
+ NGHTTP2_CANCEL);
+ break;
+ }
+
+ if (config.multiply == 1) {
+ client->path_cache.insert(uri);
+ }
+
+ break;
+ }
+ }
+ return rv;
+}
+} // namespace
+
+namespace {
+int before_frame_send_callback(nghttp2_session *session,
+ const nghttp2_frame *frame, void *user_data) {
+ if (frame->hd.type != NGHTTP2_HEADERS ||
+ frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
+ return 0;
+ }
+ auto req = static_cast<Request *>(
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+ assert(req);
+ req->record_request_start_time();
+ return 0;
+}
+
+} // namespace
+
+namespace {
+int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame,
+ void *user_data) {
+ if (config.verbose) {
+ verbose_on_frame_send_callback(session, frame, user_data);
+ }
+
+ if (frame->hd.type != NGHTTP2_HEADERS ||
+ frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
+ return 0;
+ }
+
+ auto req = static_cast<Request *>(
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+ if (!req) {
+ return 0;
+ }
+
+ // If this request is using Expect/Continue, start its ContinueTimer.
+ if (req->continue_timer) {
+ req->continue_timer->start();
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int on_frame_not_send_callback(nghttp2_session *session,
+ const nghttp2_frame *frame, int lib_error_code,
+ void *user_data) {
+ if (frame->hd.type != NGHTTP2_HEADERS ||
+ frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
+ return 0;
+ }
+
+ auto req = static_cast<Request *>(
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+ if (!req) {
+ return 0;
+ }
+
+ std::cerr << "[ERROR] request " << req->uri
+ << " failed: " << nghttp2_strerror(lib_error_code) << std::endl;
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
+ uint32_t error_code, void *user_data) {
+ auto client = get_client(user_data);
+ auto req = static_cast<Request *>(
+ nghttp2_session_get_stream_user_data(session, stream_id));
+
+ if (!req) {
+ return 0;
+ }
+
+ // If this request is using Expect/Continue, stop its ContinueTimer.
+ if (req->continue_timer) {
+ req->continue_timer->stop();
+ }
+
+ update_html_parser(client, req, nullptr, 0, 1);
+ ++client->complete;
+
+ if (client->all_requests_processed()) {
+ nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR);
+ }
+
+ return 0;
+}
+} // namespace
+
+struct RequestResult {
+ std::chrono::microseconds time;
+};
+
+namespace {
+void print_stats(const HttpClient &client) {
+ std::cout << "***** Statistics *****" << std::endl;
+
+ std::vector<Request *> reqs;
+ reqs.reserve(client.reqvec.size());
+ for (const auto &req : client.reqvec) {
+ if (req->timing.state == RequestState::ON_COMPLETE) {
+ reqs.push_back(req.get());
+ }
+ }
+
+ std::sort(std::begin(reqs), std::end(reqs),
+ [](const Request *lhs, const Request *rhs) {
+ const auto &ltiming = lhs->timing;
+ const auto &rtiming = rhs->timing;
+ return ltiming.response_end_time < rtiming.response_end_time ||
+ (ltiming.response_end_time == rtiming.response_end_time &&
+ ltiming.request_start_time < rtiming.request_start_time);
+ });
+
+ std::cout << R"(
+Request timing:
+ responseEnd: the time when last byte of response was received
+ relative to connectEnd
+ requestStart: the time just before first byte of request was sent
+ relative to connectEnd. If '*' is shown, this was
+ pushed by server.
+ process: responseEnd - requestStart
+ code: HTTP status code
+ size: number of bytes received as response body without
+ inflation.
+ URI: request URI
+
+see http://www.w3.org/TR/resource-timing/#processing-model
+
+sorted by 'complete'
+
+id responseEnd requestStart process code size request path)"
+ << std::endl;
+
+ const auto &base = client.timing.connect_end_time;
+ for (const auto &req : reqs) {
+ auto response_end = std::chrono::duration_cast<std::chrono::microseconds>(
+ req->timing.response_end_time - base);
+ auto request_start = std::chrono::duration_cast<std::chrono::microseconds>(
+ req->timing.request_start_time - base);
+ auto total = std::chrono::duration_cast<std::chrono::microseconds>(
+ req->timing.response_end_time - req->timing.request_start_time);
+ auto pushed = req->stream_id % 2 == 0;
+
+ std::cout << std::setw(3) << req->stream_id << " " << std::setw(11)
+ << ("+" + util::format_duration(response_end)) << " "
+ << (pushed ? "*" : " ") << std::setw(11)
+ << ("+" + util::format_duration(request_start)) << " "
+ << std::setw(8) << util::format_duration(total) << " "
+ << std::setw(4) << req->status << " " << std::setw(4)
+ << util::utos_unit(req->response_len) << " "
+ << req->make_reqpath() << std::endl;
+ }
+}
+} // namespace
+
+namespace {
+int communicate(
+ const std::string &scheme, const std::string &host, uint16_t port,
+ std::vector<
+ std::tuple<std::string, nghttp2_data_provider *, int64_t, int32_t>>
+ requests,
+ const nghttp2_session_callbacks *callbacks) {
+ int result = 0;
+ auto loop = EV_DEFAULT;
+ SSL_CTX *ssl_ctx = nullptr;
+ if (scheme == "https") {
+ ssl_ctx = SSL_CTX_new(TLS_client_method());
+ if (!ssl_ctx) {
+ std::cerr << "[ERROR] Failed to create SSL_CTX: "
+ << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+ result = -1;
+ goto fin;
+ }
+
+ auto ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) |
+ SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION |
+ SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION;
+
+#ifdef SSL_OP_ENABLE_KTLS
+ if (config.ktls) {
+ ssl_opts |= SSL_OP_ENABLE_KTLS;
+ }
+#endif // SSL_OP_ENABLE_KTLS
+
+ SSL_CTX_set_options(ssl_ctx, ssl_opts);
+ SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
+ SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
+
+ if (SSL_CTX_set_default_verify_paths(ssl_ctx) != 1) {
+ std::cerr << "[WARNING] Could not load system trusted CA certificates: "
+ << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+ }
+
+ if (nghttp2::tls::ssl_ctx_set_proto_versions(
+ ssl_ctx, nghttp2::tls::NGHTTP2_TLS_MIN_VERSION,
+ nghttp2::tls::NGHTTP2_TLS_MAX_VERSION) != 0) {
+ std::cerr << "[ERROR] Could not set TLS versions" << std::endl;
+ result = -1;
+ goto fin;
+ }
+
+ if (SSL_CTX_set_cipher_list(ssl_ctx, tls::DEFAULT_CIPHER_LIST) == 0) {
+ std::cerr << "[ERROR] " << ERR_error_string(ERR_get_error(), nullptr)
+ << std::endl;
+ result = -1;
+ goto fin;
+ }
+ if (!config.keyfile.empty()) {
+ if (SSL_CTX_use_PrivateKey_file(ssl_ctx, config.keyfile.c_str(),
+ SSL_FILETYPE_PEM) != 1) {
+ std::cerr << "[ERROR] " << ERR_error_string(ERR_get_error(), nullptr)
+ << std::endl;
+ result = -1;
+ goto fin;
+ }
+ }
+ if (!config.certfile.empty()) {
+ if (SSL_CTX_use_certificate_chain_file(ssl_ctx,
+ config.certfile.c_str()) != 1) {
+ std::cerr << "[ERROR] " << ERR_error_string(ERR_get_error(), nullptr)
+ << std::endl;
+ result = -1;
+ goto fin;
+ }
+ }
+
+ auto proto_list = util::get_default_alpn();
+
+ SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size());
+ }
+ {
+ HttpClient client{callbacks, loop, ssl_ctx};
+
+ int32_t dep_stream_id = 0;
+
+ if (!config.no_dep) {
+ dep_stream_id = anchors[ANCHOR_FOLLOWERS].stream_id;
+ }
+
+ for (auto &req : requests) {
+ nghttp2_priority_spec pri_spec;
+
+ nghttp2_priority_spec_init(&pri_spec, dep_stream_id, std::get<3>(req), 0);
+
+ for (int i = 0; i < config.multiply; ++i) {
+ client.add_request(std::get<0>(req), std::get<1>(req), std::get<2>(req),
+ pri_spec);
+ }
+ }
+ client.update_hostport();
+
+ client.record_start_time();
+
+ if (client.resolve_host(host, port) != 0) {
+ goto fin;
+ }
+
+ client.record_domain_lookup_end_time();
+
+ if (client.initiate_connection() != 0) {
+ std::cerr << "[ERROR] Could not connect to " << host << ", port " << port
+ << std::endl;
+ goto fin;
+ }
+
+ ev_set_userdata(loop, &client);
+ ev_run(loop, 0);
+ ev_set_userdata(loop, nullptr);
+
+#ifdef HAVE_JANSSON
+ if (!config.harfile.empty()) {
+ FILE *outfile;
+ if (config.harfile == "-") {
+ outfile = stdout;
+ } else {
+ outfile = fopen(config.harfile.c_str(), "wb");
+ }
+
+ if (outfile) {
+ client.output_har(outfile);
+
+ if (outfile != stdout) {
+ fclose(outfile);
+ }
+ } else {
+ std::cerr << "Cannot open file " << config.harfile << ". "
+ << "har file could not be created." << std::endl;
+ }
+ }
+#endif // HAVE_JANSSON
+
+ if (client.success != client.reqvec.size()) {
+ std::cerr << "Some requests were not processed. total="
+ << client.reqvec.size() << ", processed=" << client.success
+ << std::endl;
+ }
+ if (config.stat) {
+ print_stats(client);
+ }
+ }
+fin:
+ if (ssl_ctx) {
+ SSL_CTX_free(ssl_ctx);
+ }
+ return result;
+}
+} // namespace
+
+namespace {
+ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id,
+ uint8_t *buf, size_t length, uint32_t *data_flags,
+ nghttp2_data_source *source, void *user_data) {
+ int rv;
+ auto req = static_cast<Request *>(
+ nghttp2_session_get_stream_user_data(session, stream_id));
+ assert(req);
+ int fd = source->fd;
+ ssize_t nread;
+
+ while ((nread = pread(fd, buf, length, req->data_offset)) == -1 &&
+ errno == EINTR)
+ ;
+
+ if (nread == -1) {
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+
+ req->data_offset += nread;
+
+ if (req->data_offset == req->data_length) {
+ *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+ if (!config.trailer.empty()) {
+ std::vector<nghttp2_nv> nva;
+ nva.reserve(config.trailer.size());
+ for (auto &kv : config.trailer) {
+ nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index));
+ }
+ rv = nghttp2_submit_trailer(session, stream_id, nva.data(), nva.size());
+ if (rv != 0) {
+ if (nghttp2_is_fatal(rv)) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ } else {
+ *data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
+ }
+ }
+
+ return nread;
+ }
+
+ if (req->data_offset > req->data_length || nread == 0) {
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+
+ return nread;
+}
+} // namespace
+
+namespace {
+int run(char **uris, int n) {
+ nghttp2_session_callbacks *callbacks;
+
+ nghttp2_session_callbacks_new(&callbacks);
+ auto cbsdel = defer(nghttp2_session_callbacks_del, callbacks);
+
+ nghttp2_session_callbacks_set_on_stream_close_callback(
+ callbacks, on_stream_close_callback);
+
+ nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
+ on_frame_recv_callback2);
+
+ if (config.verbose) {
+ nghttp2_session_callbacks_set_on_invalid_frame_recv_callback(
+ callbacks, verbose_on_invalid_frame_recv_callback);
+
+ nghttp2_session_callbacks_set_error_callback2(callbacks,
+ verbose_error_callback);
+ }
+
+ nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
+ callbacks, on_data_chunk_recv_callback);
+
+ nghttp2_session_callbacks_set_on_begin_headers_callback(
+ callbacks, on_begin_headers_callback);
+
+ nghttp2_session_callbacks_set_on_header_callback(callbacks,
+ on_header_callback);
+
+ nghttp2_session_callbacks_set_before_frame_send_callback(
+ callbacks, before_frame_send_callback);
+
+ nghttp2_session_callbacks_set_on_frame_send_callback(callbacks,
+ on_frame_send_callback);
+
+ nghttp2_session_callbacks_set_on_frame_not_send_callback(
+ callbacks, on_frame_not_send_callback);
+
+ if (config.padding) {
+ nghttp2_session_callbacks_set_select_padding_callback(
+ callbacks, select_padding_callback);
+ }
+
+ std::string prev_scheme;
+ std::string prev_host;
+ uint16_t prev_port = 0;
+ int failures = 0;
+ int data_fd = -1;
+ nghttp2_data_provider data_prd;
+ struct stat data_stat;
+
+ if (!config.datafile.empty()) {
+ if (config.datafile == "-") {
+ if (fstat(0, &data_stat) == 0 &&
+ (data_stat.st_mode & S_IFMT) == S_IFREG) {
+ // use STDIN if it is a regular file
+ data_fd = 0;
+ } else {
+ // copy the contents of STDIN to a temporary file
+ char tempfn[] = "/tmp/nghttp.temp.XXXXXX";
+ data_fd = mkstemp(tempfn);
+ if (data_fd == -1) {
+ std::cerr << "[ERROR] Could not create a temporary file in /tmp"
+ << std::endl;
+ return 1;
+ }
+ if (unlink(tempfn) != 0) {
+ std::cerr << "[WARNING] failed to unlink temporary file:" << tempfn
+ << std::endl;
+ }
+ while (1) {
+ std::array<char, 1_k> buf;
+ ssize_t rret, wret;
+ while ((rret = read(0, buf.data(), buf.size())) == -1 &&
+ errno == EINTR)
+ ;
+ if (rret == 0)
+ break;
+ if (rret == -1) {
+ std::cerr << "[ERROR] I/O error while reading from STDIN"
+ << std::endl;
+ return 1;
+ }
+ while ((wret = write(data_fd, buf.data(), rret)) == -1 &&
+ errno == EINTR)
+ ;
+ if (wret != rret) {
+ std::cerr << "[ERROR] I/O error while writing to temporary file"
+ << std::endl;
+ return 1;
+ }
+ }
+ if (fstat(data_fd, &data_stat) == -1) {
+ close(data_fd);
+ std::cerr << "[ERROR] Could not stat temporary file" << std::endl;
+ return 1;
+ }
+ }
+ } else {
+ data_fd = open(config.datafile.c_str(), O_RDONLY | O_BINARY);
+ if (data_fd == -1) {
+ std::cerr << "[ERROR] Could not open file " << config.datafile
+ << std::endl;
+ return 1;
+ }
+ if (fstat(data_fd, &data_stat) == -1) {
+ close(data_fd);
+ std::cerr << "[ERROR] Could not stat file " << config.datafile
+ << std::endl;
+ return 1;
+ }
+ }
+ data_prd.source.fd = data_fd;
+ data_prd.read_callback = file_read_callback;
+ }
+ std::vector<
+ std::tuple<std::string, nghttp2_data_provider *, int64_t, int32_t>>
+ requests;
+
+ size_t next_weight_idx = 0;
+
+ for (int i = 0; i < n; ++i) {
+ http_parser_url u{};
+ auto uri = strip_fragment(uris[i]);
+ if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
+ ++next_weight_idx;
+ std::cerr << "[ERROR] Could not parse URI " << uri << std::endl;
+ continue;
+ }
+ if (!util::has_uri_field(u, UF_SCHEMA)) {
+ ++next_weight_idx;
+ std::cerr << "[ERROR] URI " << uri << " does not have scheme part"
+ << std::endl;
+ continue;
+ }
+ auto port = util::has_uri_field(u, UF_PORT)
+ ? u.port
+ : util::get_default_port(uri.c_str(), u);
+ auto host = decode_host(util::get_uri_field(uri.c_str(), u, UF_HOST));
+ if (!util::fieldeq(uri.c_str(), u, UF_SCHEMA, prev_scheme.c_str()) ||
+ host != prev_host || port != prev_port) {
+ if (!requests.empty()) {
+ if (communicate(prev_scheme, prev_host, prev_port, std::move(requests),
+ callbacks) != 0) {
+ ++failures;
+ }
+ requests.clear();
+ }
+ prev_scheme = util::get_uri_field(uri.c_str(), u, UF_SCHEMA).str();
+ prev_host = std::move(host);
+ prev_port = port;
+ }
+ requests.emplace_back(uri, data_fd == -1 ? nullptr : &data_prd,
+ data_stat.st_size, config.weight[next_weight_idx++]);
+ }
+ if (!requests.empty()) {
+ if (communicate(prev_scheme, prev_host, prev_port, std::move(requests),
+ callbacks) != 0) {
+ ++failures;
+ }
+ }
+ return failures;
+}
+} // namespace
+
+namespace {
+void print_version(std::ostream &out) {
+ out << "nghttp nghttp2/" NGHTTP2_VERSION << std::endl;
+}
+} // namespace
+
+namespace {
+void print_usage(std::ostream &out) {
+ out << R"(Usage: nghttp [OPTIONS]... <URI>...
+HTTP/2 client)"
+ << std::endl;
+}
+} // namespace
+
+namespace {
+void print_help(std::ostream &out) {
+ print_usage(out);
+ out << R"(
+ <URI> Specify URI to access.
+Options:
+ -v, --verbose
+ Print debug information such as reception and
+ transmission of frames and name/value pairs. Specifying
+ this option multiple times increases verbosity.
+ -n, --null-out
+ Discard downloaded data.
+ -O, --remote-name
+ Save download data in the current directory. The
+ filename is derived from URI. If URI ends with '/',
+ 'index.html' is used as a filename. Not implemented
+ yet.
+ -t, --timeout=<DURATION>
+ Timeout each request after <DURATION>. Set 0 to disable
+ timeout.
+ -w, --window-bits=<N>
+ Sets the stream level initial window size to 2**<N>-1.
+ -W, --connection-window-bits=<N>
+ Sets the connection level initial window size to
+ 2**<N>-1.
+ -a, --get-assets
+ Download assets such as stylesheets, images and script
+ files linked from the downloaded resource. Only links
+ whose origins are the same with the linking resource
+ will be downloaded. nghttp prioritizes resources using
+ HTTP/2 dependency based priority. The priority order,
+ from highest to lowest, is html itself, css, javascript
+ and images.
+ -s, --stat Print statistics.
+ -H, --header=<HEADER>
+ Add a header to the requests. Example: -H':method: PUT'
+ --trailer=<HEADER>
+ Add a trailer header to the requests. <HEADER> must not
+ include pseudo header field (header field name starting
+ with ':'). To send trailer, one must use -d option to
+ send request body. Example: --trailer 'foo: bar'.
+ --cert=<CERT>
+ Use the specified client certificate file. The file
+ must be in PEM format.
+ --key=<KEY> Use the client private key file. The file must be in
+ PEM format.
+ -d, --data=<PATH>
+ Post FILE to server. If '-' is given, data will be read
+ from stdin.
+ -m, --multiply=<N>
+ Request each URI <N> times. By default, same URI is not
+ requested twice. This option disables it too.
+ -u, --upgrade
+ Perform HTTP Upgrade for HTTP/2. This option is ignored
+ if the request URI has https scheme. If -d is used, the
+ HTTP upgrade request is performed with OPTIONS method.
+ -p, --weight=<WEIGHT>
+ Sets weight of given URI. This option can be used
+ multiple times, and N-th -p option sets weight of N-th
+ URI in the command line. If the number of -p option is
+ less than the number of URI, the last -p option value is
+ repeated. If there is no -p option, default weight, 16,
+ is assumed. The valid value range is
+ [)"
+ << NGHTTP2_MIN_WEIGHT << ", " << NGHTTP2_MAX_WEIGHT << R"(], inclusive.
+ -M, --peer-max-concurrent-streams=<N>
+ Use <N> as SETTINGS_MAX_CONCURRENT_STREAMS value of
+ remote endpoint as if it is received in SETTINGS frame.
+ Default: 100
+ -c, --header-table-size=<SIZE>
+ Specify decoder header table size. If this option is
+ used multiple times, and the minimum value among the
+ given values except for last one is strictly less than
+ the last value, that minimum value is set in SETTINGS
+ frame payload before the last value, to simulate
+ multiple header table size change.
+ --encoder-header-table-size=<SIZE>
+ Specify encoder header table size. The decoder (server)
+ specifies the maximum dynamic table size it accepts.
+ Then the negotiated dynamic table size is the minimum of
+ this option value and the value which server specified.
+ -b, --padding=<N>
+ Add at most <N> bytes to a frame payload as padding.
+ Specify 0 to disable padding.
+ -r, --har=<PATH>
+ Output HTTP transactions <PATH> in HAR format. If '-'
+ is given, data is written to stdout.
+ --color Force colored log output.
+ --continuation
+ Send large header to test CONTINUATION.
+ --no-content-length
+ Don't send content-length header field.
+ --no-dep Don't send dependency based priority hint to server.
+ --hexdump Display the incoming traffic in hexadecimal (Canonical
+ hex+ASCII display). If SSL/TLS is used, decrypted data
+ are used.
+ --no-push Disable server push.
+ --max-concurrent-streams=<N>
+ The number of concurrent pushed streams this client
+ accepts.
+ --expect-continue
+ Perform an Expect/Continue handshake: wait to send DATA
+ (up to a short timeout) until the server sends a 100
+ Continue interim response. This option is ignored unless
+ combined with the -d option.
+ -y, --no-verify-peer
+ Suppress warning on server certificate verification
+ failure.
+ --ktls Enable ktls.
+ --no-rfc7540-pri
+ Disable RFC7540 priorities.
+ --version Display version information and exit.
+ -h, --help Display this help and exit.
+
+--
+
+ The <SIZE> argument is an integer and an optional unit (e.g., 10K is
+ 10 * 1024). Units are K, M and G (powers of 1024).
+
+ The <DURATION> argument is an integer and an optional unit (e.g., 1s
+ is 1 second and 500ms is 500 milliseconds). Units are h, m, s or ms
+ (hours, minutes, seconds and milliseconds, respectively). If a unit
+ is omitted, a second is used as unit.)"
+ << std::endl;
+}
+} // namespace
+
+int main(int argc, char **argv) {
+ bool color = false;
+ while (1) {
+ static int flag = 0;
+ constexpr static option long_options[] = {
+ {"verbose", no_argument, nullptr, 'v'},
+ {"null-out", no_argument, nullptr, 'n'},
+ {"remote-name", no_argument, nullptr, 'O'},
+ {"timeout", required_argument, nullptr, 't'},
+ {"window-bits", required_argument, nullptr, 'w'},
+ {"connection-window-bits", required_argument, nullptr, 'W'},
+ {"get-assets", no_argument, nullptr, 'a'},
+ {"stat", no_argument, nullptr, 's'},
+ {"help", no_argument, nullptr, 'h'},
+ {"header", required_argument, nullptr, 'H'},
+ {"data", required_argument, nullptr, 'd'},
+ {"multiply", required_argument, nullptr, 'm'},
+ {"upgrade", no_argument, nullptr, 'u'},
+ {"weight", required_argument, nullptr, 'p'},
+ {"peer-max-concurrent-streams", required_argument, nullptr, 'M'},
+ {"header-table-size", required_argument, nullptr, 'c'},
+ {"padding", required_argument, nullptr, 'b'},
+ {"har", required_argument, nullptr, 'r'},
+ {"no-verify-peer", no_argument, nullptr, 'y'},
+ {"cert", required_argument, &flag, 1},
+ {"key", required_argument, &flag, 2},
+ {"color", no_argument, &flag, 3},
+ {"continuation", no_argument, &flag, 4},
+ {"version", no_argument, &flag, 5},
+ {"no-content-length", no_argument, &flag, 6},
+ {"no-dep", no_argument, &flag, 7},
+ {"trailer", required_argument, &flag, 9},
+ {"hexdump", no_argument, &flag, 10},
+ {"no-push", no_argument, &flag, 11},
+ {"max-concurrent-streams", required_argument, &flag, 12},
+ {"expect-continue", no_argument, &flag, 13},
+ {"encoder-header-table-size", required_argument, &flag, 14},
+ {"ktls", no_argument, &flag, 15},
+ {"no-rfc7540-pri", no_argument, &flag, 16},
+ {nullptr, 0, nullptr, 0}};
+ int option_index = 0;
+ int c =
+ getopt_long(argc, argv, "M:Oab:c:d:m:np:r:hH:vst:uw:yW:", long_options,
+ &option_index);
+ if (c == -1) {
+ break;
+ }
+ switch (c) {
+ case 'M': {
+ // peer-max-concurrent-streams option
+ auto n = util::parse_uint(optarg);
+ if (n == -1) {
+ std::cerr << "-M: Bad option value: " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.peer_max_concurrent_streams = n;
+ break;
+ }
+ case 'O':
+ config.remote_name = true;
+ break;
+ case 'h':
+ print_help(std::cout);
+ exit(EXIT_SUCCESS);
+ case 'b': {
+ auto n = util::parse_uint(optarg);
+ if (n == -1) {
+ std::cerr << "-b: Bad option value: " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.padding = n;
+ break;
+ }
+ case 'n':
+ config.null_out = true;
+ break;
+ case 'p': {
+ auto n = util::parse_uint(optarg);
+ if (n == -1 || NGHTTP2_MIN_WEIGHT > n || n > NGHTTP2_MAX_WEIGHT) {
+ std::cerr << "-p: specify the integer in the range ["
+ << NGHTTP2_MIN_WEIGHT << ", " << NGHTTP2_MAX_WEIGHT
+ << "], inclusive" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.weight.push_back(n);
+ break;
+ }
+ case 'r':
+#ifdef HAVE_JANSSON
+ config.harfile = optarg;
+#else // !HAVE_JANSSON
+ std::cerr << "[WARNING]: -r, --har option is ignored because\n"
+ << "the binary was not compiled with libjansson." << std::endl;
+#endif // !HAVE_JANSSON
+ break;
+ case 'v':
+ ++config.verbose;
+ break;
+ case 't':
+ config.timeout = util::parse_duration_with_unit(optarg);
+ if (config.timeout == std::numeric_limits<double>::infinity()) {
+ std::cerr << "-t: bad timeout value: " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 'u':
+ config.upgrade = true;
+ break;
+ case 'w':
+ case 'W': {
+ auto n = util::parse_uint(optarg);
+ if (n == -1 || n > 30) {
+ std::cerr << "-" << static_cast<char>(c)
+ << ": specify the integer in the range [0, 30], inclusive"
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ if (c == 'w') {
+ config.window_bits = n;
+ } else {
+ config.connection_window_bits = n;
+ }
+ break;
+ }
+ case 'H': {
+ char *header = optarg;
+ // Skip first possible ':' in the header name
+ char *value = strchr(optarg + 1, ':');
+ if (!value || (header[0] == ':' && header + 1 == value)) {
+ std::cerr << "-H: invalid header: " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ *value = 0;
+ value++;
+ while (isspace(*value)) {
+ value++;
+ }
+ if (*value == 0) {
+ // This could also be a valid case for suppressing a header
+ // similar to curl
+ std::cerr << "-H: invalid header - value missing: " << optarg
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.headers.emplace_back(header, value, false);
+ util::inp_strlower(config.headers.back().name);
+ break;
+ }
+ case 'a':
+#ifdef HAVE_LIBXML2
+ config.get_assets = true;
+#else // !HAVE_LIBXML2
+ std::cerr << "[WARNING]: -a, --get-assets option is ignored because\n"
+ << "the binary was not compiled with libxml2." << std::endl;
+#endif // !HAVE_LIBXML2
+ break;
+ case 's':
+ config.stat = true;
+ break;
+ case 'd':
+ config.datafile = optarg;
+ break;
+ case 'm': {
+ auto n = util::parse_uint(optarg);
+ if (n == -1) {
+ std::cerr << "-m: Bad option value: " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.multiply = n;
+ break;
+ }
+ case 'c': {
+ auto n = util::parse_uint_with_unit(optarg);
+ if (n == -1) {
+ std::cerr << "-c: Bad option value: " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ if (n > std::numeric_limits<uint32_t>::max()) {
+ std::cerr << "-c: Value too large. It should be less than or equal to "
+ << std::numeric_limits<uint32_t>::max() << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.header_table_size = n;
+ config.min_header_table_size = std::min(config.min_header_table_size, n);
+ break;
+ }
+ case 'y':
+ config.verify_peer = false;
+ break;
+ case '?':
+ util::show_candidates(argv[optind - 1], long_options);
+ exit(EXIT_FAILURE);
+ case 0:
+ switch (flag) {
+ case 1:
+ // cert option
+ config.certfile = optarg;
+ break;
+ case 2:
+ // key option
+ config.keyfile = optarg;
+ break;
+ case 3:
+ // color option
+ color = true;
+ break;
+ case 4:
+ // continuation option
+ config.continuation = true;
+ break;
+ case 5:
+ // version option
+ print_version(std::cout);
+ exit(EXIT_SUCCESS);
+ case 6:
+ // no-content-length option
+ config.no_content_length = true;
+ break;
+ case 7:
+ // no-dep option
+ config.no_dep = true;
+ break;
+ case 9: {
+ // trailer option
+ auto header = optarg;
+ auto value = strchr(optarg, ':');
+ if (!value) {
+ std::cerr << "--trailer: invalid header: " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ *value = 0;
+ value++;
+ while (isspace(*value)) {
+ value++;
+ }
+ if (*value == 0) {
+ // This could also be a valid case for suppressing a header
+ // similar to curl
+ std::cerr << "--trailer: invalid header - value missing: " << optarg
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.trailer.emplace_back(header, value, false);
+ util::inp_strlower(config.trailer.back().name);
+ break;
+ }
+ case 10:
+ // hexdump option
+ config.hexdump = true;
+ break;
+ case 11:
+ // no-push option
+ config.no_push = true;
+ break;
+ case 12: {
+ // max-concurrent-streams option
+ auto n = util::parse_uint(optarg);
+ if (n == -1) {
+ std::cerr << "--max-concurrent-streams: Bad option value: " << optarg
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.max_concurrent_streams = n;
+ break;
+ }
+ case 13:
+ // expect-continue option
+ config.expect_continue = true;
+ break;
+ case 14: {
+ // encoder-header-table-size option
+ auto n = util::parse_uint_with_unit(optarg);
+ if (n == -1) {
+ std::cerr << "--encoder-header-table-size: Bad option value: "
+ << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ if (n > std::numeric_limits<uint32_t>::max()) {
+ std::cerr << "--encoder-header-table-size: Value too large. It "
+ "should be less than or equal to "
+ << std::numeric_limits<uint32_t>::max() << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.encoder_header_table_size = n;
+ break;
+ }
+ case 15:
+ // ktls option
+ config.ktls = true;
+ break;
+ case 16:
+ // no-rfc7540-pri option
+ config.no_rfc7540_pri = true;
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ int32_t weight_to_fill;
+ if (config.weight.empty()) {
+ weight_to_fill = NGHTTP2_DEFAULT_WEIGHT;
+ } else {
+ weight_to_fill = config.weight.back();
+ }
+ config.weight.insert(std::end(config.weight), argc - optind, weight_to_fill);
+
+ // Find scheme overridden by extra header fields.
+ auto scheme_it =
+ std::find_if(std::begin(config.headers), std::end(config.headers),
+ [](const Header &nv) { return nv.name == ":scheme"; });
+ if (scheme_it != std::end(config.headers)) {
+ config.scheme_override = (*scheme_it).value;
+ }
+
+ // Find host and port overridden by extra header fields.
+ auto authority_it =
+ std::find_if(std::begin(config.headers), std::end(config.headers),
+ [](const Header &nv) { return nv.name == ":authority"; });
+ if (authority_it == std::end(config.headers)) {
+ authority_it =
+ std::find_if(std::begin(config.headers), std::end(config.headers),
+ [](const Header &nv) { return nv.name == "host"; });
+ }
+
+ if (authority_it != std::end(config.headers)) {
+ // authority_it may looks like "host:port".
+ auto uri = "https://" + (*authority_it).value;
+ http_parser_url u{};
+ if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
+ std::cerr << "[ERROR] Could not parse authority in "
+ << (*authority_it).name << ": " << (*authority_it).value
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ config.host_override = util::get_uri_field(uri.c_str(), u, UF_HOST).str();
+ if (util::has_uri_field(u, UF_PORT)) {
+ config.port_override = u.port;
+ }
+ }
+
+ set_color_output(color || isatty(fileno(stdout)));
+
+ nghttp2_option_set_peer_max_concurrent_streams(
+ config.http2_option, config.peer_max_concurrent_streams);
+
+ if (config.encoder_header_table_size != -1) {
+ nghttp2_option_set_max_deflate_dynamic_table_size(
+ config.http2_option, config.encoder_header_table_size);
+ }
+
+ struct sigaction act {};
+ act.sa_handler = SIG_IGN;
+ sigaction(SIGPIPE, &act, nullptr);
+ reset_timer();
+ return run(argv + optind, argc - optind);
+}
+
+} // namespace nghttp2
+
+int main(int argc, char **argv) {
+ return nghttp2::run_app(nghttp2::main, argc, argv);
+}
diff --git a/src/nghttp.h b/src/nghttp.h
new file mode 100644
index 0000000..a880414
--- /dev/null
+++ b/src/nghttp.h
@@ -0,0 +1,310 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP_H
+#define NGHTTP_H
+
+#include "nghttp2_config.h"
+
+#include <sys/types.h>
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif // HAVE_SYS_SOCKET_H
+#ifdef HAVE_NETDB_H
+# include <netdb.h>
+#endif // HAVE_NETDB_H
+
+#include <string>
+#include <vector>
+#include <set>
+#include <chrono>
+#include <memory>
+
+#include <openssl/ssl.h>
+
+#include <ev.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "llhttp.h"
+
+#include "memchunk.h"
+#include "http2.h"
+#include "nghttp2_gzip.h"
+#include "template.h"
+
+namespace nghttp2 {
+
+class HtmlParser;
+
+struct Config {
+ Config();
+ ~Config();
+
+ Headers headers;
+ Headers trailer;
+ std::vector<int32_t> weight;
+ std::string certfile;
+ std::string keyfile;
+ std::string datafile;
+ std::string harfile;
+ std::string scheme_override;
+ std::string host_override;
+ nghttp2_option *http2_option;
+ int64_t header_table_size;
+ int64_t min_header_table_size;
+ int64_t encoder_header_table_size;
+ size_t padding;
+ size_t max_concurrent_streams;
+ ssize_t peer_max_concurrent_streams;
+ int multiply;
+ // milliseconds
+ ev_tstamp timeout;
+ int window_bits;
+ int connection_window_bits;
+ int verbose;
+ uint16_t port_override;
+ bool null_out;
+ bool remote_name;
+ bool get_assets;
+ bool stat;
+ bool upgrade;
+ bool continuation;
+ bool no_content_length;
+ bool no_dep;
+ bool hexdump;
+ bool no_push;
+ bool expect_continue;
+ bool verify_peer;
+ bool ktls;
+ bool no_rfc7540_pri;
+};
+
+enum class RequestState { INITIAL, ON_REQUEST, ON_RESPONSE, ON_COMPLETE };
+
+struct RequestTiming {
+ // The point in time when request is started to be sent.
+ // Corresponds to requestStart in Resource Timing TR.
+ std::chrono::steady_clock::time_point request_start_time;
+ // The point in time when first byte of response is received.
+ // Corresponds to responseStart in Resource Timing TR.
+ std::chrono::steady_clock::time_point response_start_time;
+ // The point in time when last byte of response is received.
+ // Corresponds to responseEnd in Resource Timing TR.
+ std::chrono::steady_clock::time_point response_end_time;
+ RequestState state;
+ RequestTiming() : state(RequestState::INITIAL) {}
+};
+
+struct Request; // forward declaration for ContinueTimer
+
+struct ContinueTimer {
+ ContinueTimer(struct ev_loop *loop, Request *req);
+ ~ContinueTimer();
+
+ void start();
+ void stop();
+
+ // Schedules an immediate run of the continue callback on the loop, if the
+ // callback has not already been run
+ void dispatch_continue();
+
+ struct ev_loop *loop;
+ ev_timer timer;
+};
+
+struct Request {
+ // For pushed request, |uri| is empty and |u| is zero-cleared.
+ Request(const std::string &uri, const http_parser_url &u,
+ const nghttp2_data_provider *data_prd, int64_t data_length,
+ const nghttp2_priority_spec &pri_spec, int level = 0);
+ ~Request();
+
+ void init_inflater();
+
+ void init_html_parser();
+ int update_html_parser(const uint8_t *data, size_t len, int fin);
+
+ std::string make_reqpath() const;
+
+ bool is_ipv6_literal_addr() const;
+
+ Headers::value_type *get_res_header(int32_t token);
+ Headers::value_type *get_req_header(int32_t token);
+
+ void record_request_start_time();
+ void record_response_start_time();
+ void record_response_end_time();
+
+ // Returns scheme taking into account overridden scheme.
+ StringRef get_real_scheme() const;
+ // Returns request host, without port, taking into account
+ // overridden host.
+ StringRef get_real_host() const;
+ // Returns request port, taking into account overridden host, port,
+ // and scheme.
+ uint16_t get_real_port() const;
+
+ Headers res_nva;
+ Headers req_nva;
+ std::string method;
+ // URI without fragment
+ std::string uri;
+ http_parser_url u;
+ nghttp2_priority_spec pri_spec;
+ RequestTiming timing;
+ int64_t data_length;
+ int64_t data_offset;
+ // Number of bytes received from server
+ int64_t response_len;
+ nghttp2_gzip *inflater;
+ std::unique_ptr<HtmlParser> html_parser;
+ const nghttp2_data_provider *data_prd;
+ size_t header_buffer_size;
+ int32_t stream_id;
+ int status;
+ // Recursion level: 0: first entity, 1: entity linked from first entity
+ int level;
+ http2::HeaderIndex res_hdidx;
+ // used for incoming PUSH_PROMISE
+ http2::HeaderIndex req_hdidx;
+ bool expect_final_response;
+ // only assigned if this request is using Expect/Continue
+ std::unique_ptr<ContinueTimer> continue_timer;
+};
+
+struct SessionTiming {
+ // The point in time when operation was started. Corresponds to
+ // startTime in Resource Timing TR, but recorded in system clock time.
+ std::chrono::system_clock::time_point system_start_time;
+ // Same as above, but recorded in steady clock time.
+ std::chrono::steady_clock::time_point start_time;
+ // The point in time when DNS resolution was completed. Corresponds
+ // to domainLookupEnd in Resource Timing TR.
+ std::chrono::steady_clock::time_point domain_lookup_end_time;
+ // The point in time when connection was established or SSL/TLS
+ // handshake was completed. Corresponds to connectEnd in Resource
+ // Timing TR.
+ std::chrono::steady_clock::time_point connect_end_time;
+};
+
+enum class ClientState { IDLE, CONNECTED };
+
+struct HttpClient {
+ HttpClient(const nghttp2_session_callbacks *callbacks, struct ev_loop *loop,
+ SSL_CTX *ssl_ctx);
+ ~HttpClient();
+
+ bool need_upgrade() const;
+ int resolve_host(const std::string &host, uint16_t port);
+ int initiate_connection();
+ void disconnect();
+
+ int noop();
+ int read_clear();
+ int write_clear();
+ int connected();
+ int tls_handshake();
+ int read_tls();
+ int write_tls();
+
+ int do_read();
+ int do_write();
+
+ int on_upgrade_connect();
+ int on_upgrade_read(const uint8_t *data, size_t len);
+ int on_read(const uint8_t *data, size_t len);
+ int on_write();
+
+ int connection_made();
+ void connect_fail();
+ void request_done(Request *req);
+
+ void signal_write();
+
+ bool all_requests_processed() const;
+ void update_hostport();
+ bool add_request(const std::string &uri,
+ const nghttp2_data_provider *data_prd, int64_t data_length,
+ const nghttp2_priority_spec &pri_spec, int level = 0);
+
+ void record_start_time();
+ void record_domain_lookup_end_time();
+ void record_connect_end_time();
+
+#ifdef HAVE_JANSSON
+ void output_har(FILE *outfile);
+#endif // HAVE_JANSSON
+
+ MemchunkPool mcpool;
+ DefaultMemchunks wb;
+ std::vector<std::unique_ptr<Request>> reqvec;
+ // Insert path already added in reqvec to prevent multiple request
+ // for 1 resource.
+ std::set<std::string> path_cache;
+ std::string scheme;
+ std::string host;
+ std::string hostport;
+ // Used for parse the HTTP upgrade response from server
+ std::unique_ptr<llhttp_t> htp;
+ SessionTiming timing;
+ ev_io wev;
+ ev_io rev;
+ ev_timer wt;
+ ev_timer rt;
+ ev_timer settings_timer;
+ std::function<int(HttpClient &)> readfn, writefn;
+ std::function<int(HttpClient &, const uint8_t *, size_t)> on_readfn;
+ std::function<int(HttpClient &)> on_writefn;
+ nghttp2_session *session;
+ const nghttp2_session_callbacks *callbacks;
+ struct ev_loop *loop;
+ SSL_CTX *ssl_ctx;
+ SSL *ssl;
+ addrinfo *addrs;
+ addrinfo *next_addr;
+ addrinfo *cur_addr;
+ // The number of completed requests, including failed ones.
+ size_t complete;
+ // The number of requests that local endpoint received END_STREAM
+ // from peer.
+ size_t success;
+ // The length of settings_payload
+ size_t settings_payloadlen;
+ ClientState state;
+ // The HTTP status code of the response message of HTTP Upgrade.
+ unsigned int upgrade_response_status_code;
+ int fd;
+ // true if the response message of HTTP Upgrade request is fully
+ // received. It is not relevant the upgrade succeeds, or not.
+ bool upgrade_response_complete;
+ // SETTINGS payload sent as token68 in HTTP Upgrade
+ std::array<uint8_t, 128> settings_payload;
+
+ enum { ERR_CONNECT_FAIL = -100 };
+};
+
+} // namespace nghttp2
+
+#endif // NGHTTP_H
diff --git a/src/nghttp2_config.h b/src/nghttp2_config.h
new file mode 100644
index 0000000..8e6cd05
--- /dev/null
+++ b/src/nghttp2_config.h
@@ -0,0 +1,32 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_CONFIG_H
+#define NGHTTP2_CONFIG_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#endif // NGHTTP2_CONFIG_H
diff --git a/src/nghttp2_gzip.c b/src/nghttp2_gzip.c
new file mode 100644
index 0000000..5f17ab2
--- /dev/null
+++ b/src/nghttp2_gzip.c
@@ -0,0 +1,87 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_gzip.h"
+
+#include <assert.h>
+
+int nghttp2_gzip_inflate_new(nghttp2_gzip **inflater_ptr) {
+ int rv;
+ *inflater_ptr = calloc(1, sizeof(nghttp2_gzip));
+ if (*inflater_ptr == NULL) {
+ return -1;
+ }
+ rv = inflateInit2(&(*inflater_ptr)->zst, 47);
+ if (rv != Z_OK) {
+ free(*inflater_ptr);
+ return -1;
+ }
+ return 0;
+}
+
+void nghttp2_gzip_inflate_del(nghttp2_gzip *inflater) {
+ if (inflater != NULL) {
+ inflateEnd(&inflater->zst);
+ free(inflater);
+ }
+}
+
+int nghttp2_gzip_inflate(nghttp2_gzip *inflater, uint8_t *out,
+ size_t *outlen_ptr, const uint8_t *in,
+ size_t *inlen_ptr) {
+ int rv;
+ if (inflater->finished) {
+ return -1;
+ }
+ inflater->zst.avail_in = (unsigned int)*inlen_ptr;
+ inflater->zst.next_in = (unsigned char *)in;
+ inflater->zst.avail_out = (unsigned int)*outlen_ptr;
+ inflater->zst.next_out = out;
+
+ rv = inflate(&inflater->zst, Z_NO_FLUSH);
+
+ *inlen_ptr -= inflater->zst.avail_in;
+ *outlen_ptr -= inflater->zst.avail_out;
+ switch (rv) {
+ case Z_STREAM_END:
+ inflater->finished = 1;
+ /* FALL THROUGH */
+ case Z_OK:
+ case Z_BUF_ERROR:
+ return 0;
+ case Z_DATA_ERROR:
+ case Z_STREAM_ERROR:
+ case Z_NEED_DICT:
+ case Z_MEM_ERROR:
+ return -1;
+ default:
+ assert(0);
+ /* We need this for some compilers */
+ return 0;
+ }
+}
+
+int nghttp2_gzip_inflate_finished(nghttp2_gzip *inflater) {
+ return inflater->finished;
+}
diff --git a/src/nghttp2_gzip.h b/src/nghttp2_gzip.h
new file mode 100644
index 0000000..a40352c
--- /dev/null
+++ b/src/nghttp2_gzip.h
@@ -0,0 +1,122 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_GZIP_H
+
+# ifdef HAVE_CONFIG_H
+# include <config.h>
+# endif /* HAVE_CONFIG_H */
+# include <zlib.h>
+
+# include <nghttp2/nghttp2.h>
+
+# ifdef __cplusplus
+extern "C" {
+# endif
+
+/**
+ * @struct
+ *
+ * The gzip stream to inflate data.
+ */
+typedef struct {
+ z_stream zst;
+ int8_t finished;
+} nghttp2_gzip;
+
+/**
+ * @function
+ *
+ * A helper function to set up a per request gzip stream to inflate
+ * data.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ */
+int nghttp2_gzip_inflate_new(nghttp2_gzip **inflater_ptr);
+
+/**
+ * @function
+ *
+ * Frees the inflate stream. The |inflater| may be ``NULL``.
+ */
+void nghttp2_gzip_inflate_del(nghttp2_gzip *inflater);
+
+/**
+ * @function
+ *
+ * Inflates data in |in| with the length |*inlen_ptr| and stores the
+ * inflated data to |out| which has allocated size at least
+ * |*outlen_ptr|. On return, |*outlen_ptr| is updated to represent
+ * the number of data written in |out|. Similarly, |*inlen_ptr| is
+ * updated to represent the number of input bytes processed.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ *
+ * The example follows::
+ *
+ * void on_data_chunk_recv_callback(nghttp2_session *session,
+ * uint8_t flags,
+ * int32_t stream_id,
+ * const uint8_t *data, size_t len,
+ * void *user_data)
+ * {
+ * ...
+ * req = nghttp2_session_get_stream_user_data(session, stream_id);
+ * nghttp2_gzip *inflater = req->inflater;
+ * while(len > 0) {
+ * uint8_t out[MAX_OUTLEN];
+ * size_t outlen = MAX_OUTLEN;
+ * size_t tlen = len;
+ * int rv;
+ * rv = nghttp2_gzip_inflate(inflater, out, &outlen, data, &tlen);
+ * if(rv != 0) {
+ * nghttp2_submit_rst_stream(session, stream_id,
+ * NGHTTP2_INTERNAL_ERROR);
+ * break;
+ * }
+ * ... Do stuff ...
+ * data += tlen;
+ * len -= tlen;
+ * }
+ * ....
+ * }
+ */
+int nghttp2_gzip_inflate(nghttp2_gzip *inflater, uint8_t *out,
+ size_t *outlen_ptr, const uint8_t *in,
+ size_t *inlen_ptr);
+
+/**
+ * @function
+ *
+ * Returns nonzero if |inflater| sees the end of deflate stream.
+ * After this function returns nonzero, `nghttp2_gzip_inflate()` with
+ * |inflater| gets to return error.
+ */
+int nghttp2_gzip_inflate_finished(nghttp2_gzip *inflater);
+
+# ifdef __cplusplus
+}
+# endif
+
+#endif /* NGHTTP2_GZIP_H */
diff --git a/src/nghttp2_gzip_test.c b/src/nghttp2_gzip_test.c
new file mode 100644
index 0000000..de19d5d
--- /dev/null
+++ b/src/nghttp2_gzip_test.c
@@ -0,0 +1,111 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_gzip_test.h"
+
+#include <stdio.h>
+#include <assert.h>
+
+#include <CUnit/CUnit.h>
+
+#include <zlib.h>
+
+#include "nghttp2_gzip.h"
+
+static size_t deflate_data(uint8_t *out, size_t outlen, const uint8_t *in,
+ size_t inlen) {
+ int rv;
+ z_stream zst = {0};
+
+ rv = deflateInit(&zst, Z_DEFAULT_COMPRESSION);
+ CU_ASSERT(rv == Z_OK);
+
+ zst.avail_in = (unsigned int)inlen;
+ zst.next_in = (uint8_t *)in;
+ zst.avail_out = (unsigned int)outlen;
+ zst.next_out = out;
+ rv = deflate(&zst, Z_SYNC_FLUSH);
+ CU_ASSERT(rv == Z_OK);
+
+ deflateEnd(&zst);
+
+ return outlen - zst.avail_out;
+}
+
+static const char input[] =
+ "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND "
+ "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF "
+ "MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND "
+ "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE "
+ "LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION "
+ "OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION "
+ "WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.";
+
+void test_nghttp2_gzip_inflate(void) {
+ nghttp2_gzip *inflater;
+ uint8_t in[4096], out[4096], *inptr;
+ size_t inlen = sizeof(in);
+ size_t inproclen, outproclen;
+ const char *inputptr = input;
+
+ inlen = deflate_data(in, inlen, (const uint8_t *)input, sizeof(input) - 1);
+
+ CU_ASSERT(0 == nghttp2_gzip_inflate_new(&inflater));
+ /* First 16 bytes */
+ inptr = in;
+ inproclen = inlen;
+ outproclen = 16;
+ CU_ASSERT(
+ 0 == nghttp2_gzip_inflate(inflater, out, &outproclen, inptr, &inproclen));
+ CU_ASSERT(16 == outproclen);
+ CU_ASSERT(inproclen > 0);
+ CU_ASSERT(0 == memcmp(inputptr, out, outproclen));
+ /* Next 32 bytes */
+ inptr += inproclen;
+ inlen -= inproclen;
+ inproclen = inlen;
+ inputptr += outproclen;
+ outproclen = 32;
+ CU_ASSERT(
+ 0 == nghttp2_gzip_inflate(inflater, out, &outproclen, inptr, &inproclen));
+ CU_ASSERT(32 == outproclen);
+ CU_ASSERT(inproclen > 0);
+ CU_ASSERT(0 == memcmp(inputptr, out, outproclen));
+ /* Rest */
+ inptr += inproclen;
+ inlen -= inproclen;
+ inproclen = inlen;
+ inputptr += outproclen;
+ outproclen = sizeof(out);
+ CU_ASSERT(
+ 0 == nghttp2_gzip_inflate(inflater, out, &outproclen, inptr, &inproclen));
+ CU_ASSERT(sizeof(input) - 49 == outproclen);
+ CU_ASSERT(inproclen > 0);
+ CU_ASSERT(0 == memcmp(inputptr, out, outproclen));
+
+ inlen -= inproclen;
+ CU_ASSERT(0 == inlen);
+
+ nghttp2_gzip_inflate_del(inflater);
+}
diff --git a/src/nghttp2_gzip_test.h b/src/nghttp2_gzip_test.h
new file mode 100644
index 0000000..8d554f7
--- /dev/null
+++ b/src/nghttp2_gzip_test.h
@@ -0,0 +1,42 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_GZIP_TEST_H
+#define NGHTTP2_GZIP_TEST_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void test_nghttp2_gzip_inflate(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* NGHTTP2_GZIP_TEST_H */
diff --git a/src/nghttpd.cc b/src/nghttpd.cc
new file mode 100644
index 0000000..9cad874
--- /dev/null
+++ b/src/nghttpd.cc
@@ -0,0 +1,502 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_config.h"
+
+#ifdef __sgi
+# define daemon _daemonize
+#endif
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+#include <signal.h>
+#include <getopt.h>
+
+#include <cstdlib>
+#include <cstring>
+#include <cassert>
+#include <string>
+#include <iostream>
+#include <string>
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <nghttp2/nghttp2.h>
+
+#include "app_helper.h"
+#include "HttpServer.h"
+#include "util.h"
+#include "tls.h"
+
+namespace nghttp2 {
+
+namespace {
+int parse_push_config(Config &config, const char *optarg) {
+ const char *eq = strchr(optarg, '=');
+ if (eq == nullptr) {
+ return -1;
+ }
+ auto &paths = config.push[std::string(optarg, eq)];
+ auto optarg_end = optarg + strlen(optarg);
+ auto i = eq + 1;
+ for (;;) {
+ const char *j = strchr(i, ',');
+ if (j == nullptr) {
+ j = optarg_end;
+ }
+ paths.emplace_back(i, j);
+ if (j == optarg_end) {
+ break;
+ }
+ i = j;
+ ++i;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+void print_version(std::ostream &out) {
+ out << "nghttpd nghttp2/" NGHTTP2_VERSION << std::endl;
+}
+} // namespace
+
+namespace {
+void print_usage(std::ostream &out) {
+ out << "Usage: nghttpd [OPTION]... <PORT> [<PRIVATE_KEY> <CERT>]\n"
+ << "HTTP/2 server" << std::endl;
+}
+} // namespace
+
+namespace {
+void print_help(std::ostream &out) {
+ Config config;
+ print_usage(out);
+ out << R"(
+ <PORT> Specify listening port number.
+ <PRIVATE_KEY>
+ Set path to server's private key. Required unless
+ --no-tls is specified.
+ <CERT> Set path to server's certificate. Required unless
+ --no-tls is specified.
+Options:
+ -a, --address=<ADDR>
+ The address to bind to. If not specified the default IP
+ address determined by getaddrinfo is used.
+ -D, --daemon
+ Run in a background. If -D is used, the current working
+ directory is changed to '/'. Therefore if this option
+ is used, -d option must be specified.
+ -V, --verify-client
+ The server sends a client certificate request. If the
+ client did not return a certificate, the handshake is
+ terminated. Currently, this option just requests a
+ client certificate and does not verify it.
+ -d, --htdocs=<PATH>
+ Specify document root. If this option is not specified,
+ the document root is the current working directory.
+ -v, --verbose
+ Print debug information such as reception/ transmission
+ of frames and name/value pairs.
+ --no-tls Disable SSL/TLS.
+ -c, --header-table-size=<SIZE>
+ Specify decoder header table size.
+ --encoder-header-table-size=<SIZE>
+ Specify encoder header table size. The decoder (client)
+ specifies the maximum dynamic table size it accepts.
+ Then the negotiated dynamic table size is the minimum of
+ this option value and the value which client specified.
+ --color Force colored log output.
+ -p, --push=<PATH>=<PUSH_PATH,...>
+ Push resources <PUSH_PATH>s when <PATH> is requested.
+ This option can be used repeatedly to specify multiple
+ push configurations. <PATH> and <PUSH_PATH>s are
+ relative to document root. See --htdocs option.
+ Example: -p/=/foo.png -p/doc=/bar.css
+ -b, --padding=<N>
+ Add at most <N> bytes to a frame payload as padding.
+ Specify 0 to disable padding.
+ -m, --max-concurrent-streams=<N>
+ Set the maximum number of the concurrent streams in one
+ HTTP/2 session.
+ Default: )"
+ << config.max_concurrent_streams << R"(
+ -n, --workers=<N>
+ Set the number of worker threads.
+ Default: 1
+ -e, --error-gzip
+ Make error response gzipped.
+ -w, --window-bits=<N>
+ Sets the stream level initial window size to 2**<N>-1.
+ -W, --connection-window-bits=<N>
+ Sets the connection level initial window size to
+ 2**<N>-1.
+ --dh-param-file=<PATH>
+ Path to file that contains DH parameters in PEM format.
+ Without this option, DHE cipher suites are not
+ available.
+ --early-response
+ Start sending response when request HEADERS is received,
+ rather than complete request is received.
+ --trailer=<HEADER>
+ Add a trailer header to a response. <HEADER> must not
+ include pseudo header field (header field name starting
+ with ':'). The trailer is sent only if a response has
+ body part. Example: --trailer 'foo: bar'.
+ --hexdump Display the incoming traffic in hexadecimal (Canonical
+ hex+ASCII display). If SSL/TLS is used, decrypted data
+ are used.
+ --echo-upload
+ Send back uploaded content if method is POST or PUT.
+ --mime-types-file=<PATH>
+ Path to file that contains MIME media types and the
+ extensions that represent them.
+ Default: )"
+ << config.mime_types_file << R"(
+ --no-content-length
+ Don't send content-length header field.
+ --ktls Enable ktls.
+ --no-rfc7540-pri
+ Disable RFC7540 priorities.
+ --version Display version information and exit.
+ -h, --help Display this help and exit.
+
+--
+
+ The <SIZE> argument is an integer and an optional unit (e.g., 10K is
+ 10 * 1024). Units are K, M and G (powers of 1024).)"
+ << std::endl;
+}
+} // namespace
+
+int main(int argc, char **argv) {
+ Config config;
+ bool color = false;
+ auto mime_types_file_set_manually = false;
+
+ while (1) {
+ static int flag = 0;
+ constexpr static option long_options[] = {
+ {"address", required_argument, nullptr, 'a'},
+ {"daemon", no_argument, nullptr, 'D'},
+ {"htdocs", required_argument, nullptr, 'd'},
+ {"help", no_argument, nullptr, 'h'},
+ {"verbose", no_argument, nullptr, 'v'},
+ {"verify-client", no_argument, nullptr, 'V'},
+ {"header-table-size", required_argument, nullptr, 'c'},
+ {"push", required_argument, nullptr, 'p'},
+ {"padding", required_argument, nullptr, 'b'},
+ {"max-concurrent-streams", required_argument, nullptr, 'm'},
+ {"workers", required_argument, nullptr, 'n'},
+ {"error-gzip", no_argument, nullptr, 'e'},
+ {"window-bits", required_argument, nullptr, 'w'},
+ {"connection-window-bits", required_argument, nullptr, 'W'},
+ {"no-tls", no_argument, &flag, 1},
+ {"color", no_argument, &flag, 2},
+ {"version", no_argument, &flag, 3},
+ {"dh-param-file", required_argument, &flag, 4},
+ {"early-response", no_argument, &flag, 5},
+ {"trailer", required_argument, &flag, 6},
+ {"hexdump", no_argument, &flag, 7},
+ {"echo-upload", no_argument, &flag, 8},
+ {"mime-types-file", required_argument, &flag, 9},
+ {"no-content-length", no_argument, &flag, 10},
+ {"encoder-header-table-size", required_argument, &flag, 11},
+ {"ktls", no_argument, &flag, 12},
+ {"no-rfc7540-pri", no_argument, &flag, 13},
+ {nullptr, 0, nullptr, 0}};
+ int option_index = 0;
+ int c = getopt_long(argc, argv, "DVb:c:d:ehm:n:p:va:w:W:", long_options,
+ &option_index);
+ if (c == -1) {
+ break;
+ }
+ switch (c) {
+ case 'a':
+ config.address = optarg;
+ break;
+ case 'D':
+ config.daemon = true;
+ break;
+ case 'V':
+ config.verify_client = true;
+ break;
+ case 'b': {
+ auto n = util::parse_uint(optarg);
+ if (n == -1) {
+ std::cerr << "-b: Bad option value: " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.padding = n;
+ break;
+ }
+ case 'd':
+ config.htdocs = optarg;
+ break;
+ case 'e':
+ config.error_gzip = true;
+ break;
+ case 'm': {
+ // max-concurrent-streams option
+ auto n = util::parse_uint(optarg);
+ if (n == -1) {
+ std::cerr << "-m: invalid argument: " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.max_concurrent_streams = n;
+ break;
+ }
+ case 'n': {
+#ifdef NOTHREADS
+ std::cerr << "-n: WARNING: Threading disabled at build time, "
+ << "no threads created." << std::endl;
+#else
+ auto n = util::parse_uint(optarg);
+ if (n == -1) {
+ std::cerr << "-n: Bad option value: " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.num_worker = n;
+#endif // NOTHREADS
+ break;
+ }
+ case 'h':
+ print_help(std::cout);
+ exit(EXIT_SUCCESS);
+ case 'v':
+ config.verbose = true;
+ break;
+ case 'c': {
+ auto n = util::parse_uint_with_unit(optarg);
+ if (n == -1) {
+ std::cerr << "-c: Bad option value: " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ if (n > std::numeric_limits<uint32_t>::max()) {
+ std::cerr << "-c: Value too large. It should be less than or equal to "
+ << std::numeric_limits<uint32_t>::max() << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.header_table_size = n;
+ break;
+ }
+ case 'p':
+ if (parse_push_config(config, optarg) != 0) {
+ std::cerr << "-p: Bad option value: " << optarg << std::endl;
+ }
+ break;
+ case 'w':
+ case 'W': {
+ auto n = util::parse_uint(optarg);
+ if (n == -1 || n > 30) {
+ std::cerr << "-" << static_cast<char>(c)
+ << ": specify the integer in the range [0, 30], inclusive"
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ if (c == 'w') {
+ config.window_bits = n;
+ } else {
+ config.connection_window_bits = n;
+ }
+
+ break;
+ }
+ case '?':
+ util::show_candidates(argv[optind - 1], long_options);
+ exit(EXIT_FAILURE);
+ case 0:
+ switch (flag) {
+ case 1:
+ // no-tls option
+ config.no_tls = true;
+ break;
+ case 2:
+ // color option
+ color = true;
+ break;
+ case 3:
+ // version
+ print_version(std::cout);
+ exit(EXIT_SUCCESS);
+ case 4:
+ // dh-param-file
+ config.dh_param_file = optarg;
+ break;
+ case 5:
+ // early-response
+ config.early_response = true;
+ break;
+ case 6: {
+ // trailer option
+ auto header = optarg;
+ auto value = strchr(optarg, ':');
+ if (!value) {
+ std::cerr << "--trailer: invalid header: " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ *value = 0;
+ value++;
+ while (isspace(*value)) {
+ value++;
+ }
+ if (*value == 0) {
+ // This could also be a valid case for suppressing a header
+ // similar to curl
+ std::cerr << "--trailer: invalid header - value missing: " << optarg
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.trailer.emplace_back(header, value, false);
+ util::inp_strlower(config.trailer.back().name);
+ break;
+ }
+ case 7:
+ // hexdump option
+ config.hexdump = true;
+ break;
+ case 8:
+ // echo-upload option
+ config.echo_upload = true;
+ break;
+ case 9:
+ // mime-types-file option
+ mime_types_file_set_manually = true;
+ config.mime_types_file = optarg;
+ break;
+ case 10:
+ // no-content-length option
+ config.no_content_length = true;
+ break;
+ case 11: {
+ // encoder-header-table-size option
+ auto n = util::parse_uint_with_unit(optarg);
+ if (n == -1) {
+ std::cerr << "--encoder-header-table-size: Bad option value: "
+ << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ if (n > std::numeric_limits<uint32_t>::max()) {
+ std::cerr << "--encoder-header-table-size: Value too large. It "
+ "should be less than or equal to "
+ << std::numeric_limits<uint32_t>::max() << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.encoder_header_table_size = n;
+ break;
+ }
+ case 12:
+ // tls option
+ config.ktls = true;
+ break;
+ case 13:
+ // no-rfc7540-pri option
+ config.no_rfc7540_pri = true;
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ if (argc - optind < (config.no_tls ? 1 : 3)) {
+ print_usage(std::cerr);
+ std::cerr << "Too few arguments" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ {
+ auto portStr = argv[optind++];
+ auto n = util::parse_uint(portStr);
+ if (n == -1 || n > std::numeric_limits<uint16_t>::max()) {
+ std::cerr << "<PORT>: Bad value: " << portStr << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.port = n;
+ }
+
+ if (!config.no_tls) {
+ config.private_key_file = argv[optind++];
+ config.cert_file = argv[optind++];
+ }
+
+ if (config.daemon) {
+ if (config.htdocs.empty()) {
+ print_usage(std::cerr);
+ std::cerr << "-d option must be specified when -D is used." << std::endl;
+ exit(EXIT_FAILURE);
+ }
+#ifdef __sgi
+ if (daemon(0, 0, 0, 0) == -1) {
+#else
+ if (util::daemonize(0, 0) == -1) {
+#endif
+ perror("daemon");
+ exit(EXIT_FAILURE);
+ }
+ }
+ if (config.htdocs.empty()) {
+ config.htdocs = "./";
+ }
+
+ if (util::read_mime_types(config.mime_types,
+ config.mime_types_file.c_str()) != 0) {
+ if (mime_types_file_set_manually) {
+ std::cerr << "--mime-types-file: Could not open mime types file: "
+ << config.mime_types_file << std::endl;
+ }
+ }
+
+ auto &trailer_names = config.trailer_names;
+ for (auto &h : config.trailer) {
+ trailer_names += h.name;
+ trailer_names += ", ";
+ }
+ if (trailer_names.size() >= 2) {
+ trailer_names.resize(trailer_names.size() - 2);
+ }
+
+ set_color_output(color || isatty(fileno(stdout)));
+
+ struct sigaction act {};
+ act.sa_handler = SIG_IGN;
+ sigaction(SIGPIPE, &act, nullptr);
+
+ reset_timer();
+
+ HttpServer server(&config);
+ if (server.run() != 0) {
+ exit(EXIT_FAILURE);
+ }
+ return 0;
+}
+
+} // namespace nghttp2
+
+int main(int argc, char **argv) {
+ return nghttp2::run_app(nghttp2::main, argc, argv);
+}
diff --git a/src/quic.cc b/src/quic.cc
new file mode 100644
index 0000000..1c68a5b
--- /dev/null
+++ b/src/quic.cc
@@ -0,0 +1,60 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2019 nghttp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "quic.h"
+
+#include <cassert>
+
+#include <ngtcp2/ngtcp2.h>
+#include <nghttp3/nghttp3.h>
+
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace quic {
+
+Error err_transport(int liberr) {
+ if (liberr == NGTCP2_ERR_RECV_VERSION_NEGOTIATION) {
+ return {ErrorType::TransportVersionNegotiation, 0};
+ }
+ return {ErrorType::Transport,
+ ngtcp2_err_infer_quic_transport_error_code(liberr)};
+}
+
+Error err_transport_idle_timeout() {
+ return {ErrorType::TransportIdleTimeout, 0};
+}
+
+Error err_transport_tls(int alert) {
+ return {ErrorType::Transport, ngtcp2_err_infer_quic_transport_error_code(
+ NGTCP2_CRYPTO_ERROR | alert)};
+}
+
+Error err_application(int liberr) {
+ return {ErrorType::Application,
+ nghttp3_err_infer_quic_app_error_code(liberr)};
+}
+
+} // namespace quic
diff --git a/src/quic.h b/src/quic.h
new file mode 100644
index 0000000..d72ae1f
--- /dev/null
+++ b/src/quic.h
@@ -0,0 +1,56 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2019 nghttp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef QUIC_H
+#define QUIC_H
+
+#include "nghttp2_config.h"
+
+#include "stdint.h"
+
+namespace quic {
+
+enum class ErrorType {
+ Transport,
+ TransportVersionNegotiation,
+ TransportIdleTimeout,
+ Application,
+};
+
+struct Error {
+ Error(ErrorType type, uint64_t code) : type(type), code(code) {}
+ Error() : type(ErrorType::Transport), code(0) {}
+
+ ErrorType type;
+ uint64_t code;
+};
+
+Error err_transport(int liberr);
+Error err_transport_idle_timeout();
+Error err_transport_tls(int alert);
+Error err_application(int liberr);
+
+} // namespace quic
+
+#endif // QUIC_H
diff --git a/src/shrpx-unittest.cc b/src/shrpx-unittest.cc
new file mode 100644
index 0000000..f089adf
--- /dev/null
+++ b/src/shrpx-unittest.cc
@@ -0,0 +1,246 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include <stdio.h>
+#include <string.h>
+#include <CUnit/Basic.h>
+// include test cases' include files here
+#include "shrpx_tls_test.h"
+#include "shrpx_downstream_test.h"
+#include "shrpx_config_test.h"
+#include "shrpx_worker_test.h"
+#include "http2_test.h"
+#include "util_test.h"
+#include "nghttp2_gzip_test.h"
+#include "buffer_test.h"
+#include "memchunk_test.h"
+#include "template_test.h"
+#include "shrpx_http_test.h"
+#include "base64_test.h"
+#include "shrpx_config.h"
+#include "tls.h"
+#include "shrpx_router_test.h"
+#include "shrpx_log.h"
+
+static int init_suite1(void) { return 0; }
+
+static int clean_suite1(void) { return 0; }
+
+int main(int argc, char *argv[]) {
+ CU_pSuite pSuite = nullptr;
+ unsigned int num_tests_failed;
+
+ shrpx::create_config();
+
+ // initialize the CUnit test registry
+ if (CUE_SUCCESS != CU_initialize_registry())
+ return CU_get_error();
+
+ // add a suite to the registry
+ pSuite = CU_add_suite("shrpx_TestSuite", init_suite1, clean_suite1);
+ if (nullptr == pSuite) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ // add the tests to the suite
+ if (!CU_add_test(pSuite, "tls_create_lookup_tree",
+ shrpx::test_shrpx_tls_create_lookup_tree) ||
+ !CU_add_test(pSuite, "tls_cert_lookup_tree_add_ssl_ctx",
+ shrpx::test_shrpx_tls_cert_lookup_tree_add_ssl_ctx) ||
+ !CU_add_test(pSuite, "tls_tls_hostname_match",
+ shrpx::test_shrpx_tls_tls_hostname_match) ||
+ !CU_add_test(pSuite, "tls_tls_verify_numeric_hostname",
+ shrpx::test_shrpx_tls_verify_numeric_hostname) ||
+ !CU_add_test(pSuite, "tls_tls_verify_dns_hostname",
+ shrpx::test_shrpx_tls_verify_dns_hostname) ||
+ !CU_add_test(pSuite, "http2_add_header", shrpx::test_http2_add_header) ||
+ !CU_add_test(pSuite, "http2_get_header", shrpx::test_http2_get_header) ||
+ !CU_add_test(pSuite, "http2_copy_headers_to_nva",
+ shrpx::test_http2_copy_headers_to_nva) ||
+ !CU_add_test(pSuite, "http2_build_http1_headers_from_headers",
+ shrpx::test_http2_build_http1_headers_from_headers) ||
+ !CU_add_test(pSuite, "http2_lws", shrpx::test_http2_lws) ||
+ !CU_add_test(pSuite, "http2_rewrite_location_uri",
+ shrpx::test_http2_rewrite_location_uri) ||
+ !CU_add_test(pSuite, "http2_parse_http_status_code",
+ shrpx::test_http2_parse_http_status_code) ||
+ !CU_add_test(pSuite, "http2_index_header",
+ shrpx::test_http2_index_header) ||
+ !CU_add_test(pSuite, "http2_lookup_token",
+ shrpx::test_http2_lookup_token) ||
+ !CU_add_test(pSuite, "http2_parse_link_header",
+ shrpx::test_http2_parse_link_header) ||
+ !CU_add_test(pSuite, "http2_path_join", shrpx::test_http2_path_join) ||
+ !CU_add_test(pSuite, "http2_normalize_path",
+ shrpx::test_http2_normalize_path) ||
+ !CU_add_test(pSuite, "http2_rewrite_clean_path",
+ shrpx::test_http2_rewrite_clean_path) ||
+ !CU_add_test(pSuite, "http2_get_pure_path_component",
+ shrpx::test_http2_get_pure_path_component) ||
+ !CU_add_test(pSuite, "http2_construct_push_component",
+ shrpx::test_http2_construct_push_component) ||
+ !CU_add_test(pSuite, "http2_contains_trailers",
+ shrpx::test_http2_contains_trailers) ||
+ !CU_add_test(pSuite, "http2_check_transfer_encoding",
+ shrpx::test_http2_check_transfer_encoding) ||
+ !CU_add_test(pSuite, "downstream_field_store_append_last_header",
+ shrpx::test_downstream_field_store_append_last_header) ||
+ !CU_add_test(pSuite, "downstream_field_store_header",
+ shrpx::test_downstream_field_store_header) ||
+ !CU_add_test(pSuite, "downstream_crumble_request_cookie",
+ shrpx::test_downstream_crumble_request_cookie) ||
+ !CU_add_test(pSuite, "downstream_assemble_request_cookie",
+ shrpx::test_downstream_assemble_request_cookie) ||
+ !CU_add_test(pSuite, "downstream_rewrite_location_response_header",
+ shrpx::test_downstream_rewrite_location_response_header) ||
+ !CU_add_test(pSuite, "downstream_supports_non_final_response",
+ shrpx::test_downstream_supports_non_final_response) ||
+ !CU_add_test(pSuite, "downstream_find_affinity_cookie",
+ shrpx::test_downstream_find_affinity_cookie) ||
+ !CU_add_test(pSuite, "config_parse_header",
+ shrpx::test_shrpx_config_parse_header) ||
+ !CU_add_test(pSuite, "config_parse_log_format",
+ 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, "worker_match_downstream_addr_group",
+ shrpx::test_shrpx_worker_match_downstream_addr_group) ||
+ !CU_add_test(pSuite, "http_create_forwarded",
+ shrpx::test_shrpx_http_create_forwarded) ||
+ !CU_add_test(pSuite, "http_create_via_header_value",
+ shrpx::test_shrpx_http_create_via_header_value) ||
+ !CU_add_test(pSuite, "http_create_affinity_cookie",
+ shrpx::test_shrpx_http_create_affinity_cookie) ||
+ !CU_add_test(pSuite, "http_create_atlsvc_header_field_value",
+ shrpx::test_shrpx_http_create_altsvc_header_value) ||
+ !CU_add_test(pSuite, "http_check_http_scheme",
+ shrpx::test_shrpx_http_check_http_scheme) ||
+ !CU_add_test(pSuite, "router_match", shrpx::test_shrpx_router_match) ||
+ !CU_add_test(pSuite, "router_match_wildcard",
+ shrpx::test_shrpx_router_match_wildcard) ||
+ !CU_add_test(pSuite, "router_match_prefix",
+ shrpx::test_shrpx_router_match_prefix) ||
+ !CU_add_test(pSuite, "util_streq", shrpx::test_util_streq) ||
+ !CU_add_test(pSuite, "util_strieq", shrpx::test_util_strieq) ||
+ !CU_add_test(pSuite, "util_inp_strlower",
+ shrpx::test_util_inp_strlower) ||
+ !CU_add_test(pSuite, "util_to_base64", shrpx::test_util_to_base64) ||
+ !CU_add_test(pSuite, "util_to_token68", shrpx::test_util_to_token68) ||
+ !CU_add_test(pSuite, "util_percent_encode_token",
+ shrpx::test_util_percent_encode_token) ||
+ !CU_add_test(pSuite, "util_percent_decode",
+ shrpx::test_util_percent_decode) ||
+ !CU_add_test(pSuite, "util_quote_string",
+ shrpx::test_util_quote_string) ||
+ !CU_add_test(pSuite, "util_utox", shrpx::test_util_utox) ||
+ !CU_add_test(pSuite, "util_http_date", shrpx::test_util_http_date) ||
+ !CU_add_test(pSuite, "util_select_h2", shrpx::test_util_select_h2) ||
+ !CU_add_test(pSuite, "util_ipv6_numeric_addr",
+ shrpx::test_util_ipv6_numeric_addr) ||
+ !CU_add_test(pSuite, "util_utos", shrpx::test_util_utos) ||
+ !CU_add_test(pSuite, "util_make_string_ref_uint",
+ shrpx::test_util_make_string_ref_uint) ||
+ !CU_add_test(pSuite, "util_utos_unit", shrpx::test_util_utos_unit) ||
+ !CU_add_test(pSuite, "util_utos_funit", shrpx::test_util_utos_funit) ||
+ !CU_add_test(pSuite, "util_parse_uint_with_unit",
+ shrpx::test_util_parse_uint_with_unit) ||
+ !CU_add_test(pSuite, "util_parse_uint", shrpx::test_util_parse_uint) ||
+ !CU_add_test(pSuite, "util_parse_duration_with_unit",
+ shrpx::test_util_parse_duration_with_unit) ||
+ !CU_add_test(pSuite, "util_duration_str",
+ shrpx::test_util_duration_str) ||
+ !CU_add_test(pSuite, "util_format_duration",
+ shrpx::test_util_format_duration) ||
+ !CU_add_test(pSuite, "util_starts_with", shrpx::test_util_starts_with) ||
+ !CU_add_test(pSuite, "util_ends_with", shrpx::test_util_ends_with) ||
+ !CU_add_test(pSuite, "util_parse_http_date",
+ shrpx::test_util_parse_http_date) ||
+ !CU_add_test(pSuite, "util_localtime_date",
+ shrpx::test_util_localtime_date) ||
+ !CU_add_test(pSuite, "util_get_uint64", shrpx::test_util_get_uint64) ||
+ !CU_add_test(pSuite, "util_parse_config_str_list",
+ shrpx::test_util_parse_config_str_list) ||
+ !CU_add_test(pSuite, "util_make_http_hostport",
+ shrpx::test_util_make_http_hostport) ||
+ !CU_add_test(pSuite, "util_make_hostport",
+ shrpx::test_util_make_hostport) ||
+ !CU_add_test(pSuite, "util_strifind", shrpx::test_util_strifind) ||
+ !CU_add_test(pSuite, "util_random_alpha_digit",
+ shrpx::test_util_random_alpha_digit) ||
+ !CU_add_test(pSuite, "util_format_hex", shrpx::test_util_format_hex) ||
+ !CU_add_test(pSuite, "util_is_hex_string",
+ shrpx::test_util_is_hex_string) ||
+ !CU_add_test(pSuite, "util_decode_hex", shrpx::test_util_decode_hex) ||
+ !CU_add_test(pSuite, "util_extract_host",
+ shrpx::test_util_extract_host) ||
+ !CU_add_test(pSuite, "util_split_hostport",
+ shrpx::test_util_split_hostport) ||
+ !CU_add_test(pSuite, "util_split_str", shrpx::test_util_split_str) ||
+ !CU_add_test(pSuite, "util_rstrip", shrpx::test_util_rstrip) ||
+ !CU_add_test(pSuite, "gzip_inflate", test_nghttp2_gzip_inflate) ||
+ !CU_add_test(pSuite, "buffer_write", nghttp2::test_buffer_write) ||
+ !CU_add_test(pSuite, "pool_recycle", nghttp2::test_pool_recycle) ||
+ !CU_add_test(pSuite, "memchunk_append", nghttp2::test_memchunks_append) ||
+ !CU_add_test(pSuite, "memchunk_drain", nghttp2::test_memchunks_drain) ||
+ !CU_add_test(pSuite, "memchunk_riovec", nghttp2::test_memchunks_riovec) ||
+ !CU_add_test(pSuite, "memchunk_recycle",
+ nghttp2::test_memchunks_recycle) ||
+ !CU_add_test(pSuite, "memchunk_reset", nghttp2::test_memchunks_reset) ||
+ !CU_add_test(pSuite, "peek_memchunk_append",
+ nghttp2::test_peek_memchunks_append) ||
+ !CU_add_test(pSuite, "peek_memchunk_disable_peek_drain",
+ nghttp2::test_peek_memchunks_disable_peek_drain) ||
+ !CU_add_test(pSuite, "peek_memchunk_disable_peek_no_drain",
+ nghttp2::test_peek_memchunks_disable_peek_no_drain) ||
+ !CU_add_test(pSuite, "peek_memchunk_reset",
+ nghttp2::test_peek_memchunks_reset) ||
+ !CU_add_test(pSuite, "template_immutable_string",
+ nghttp2::test_template_immutable_string) ||
+ !CU_add_test(pSuite, "template_string_ref",
+ nghttp2::test_template_string_ref) ||
+ !CU_add_test(pSuite, "base64_encode", nghttp2::test_base64_encode) ||
+ !CU_add_test(pSuite, "base64_decode", nghttp2::test_base64_decode)) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ // Run all tests using the CUnit Basic interface
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_tests_failed = CU_get_number_of_tests_failed();
+ CU_cleanup_registry();
+ if (CU_get_error() == CUE_SUCCESS) {
+ return num_tests_failed;
+ } else {
+ printf("CUnit Error: %s\n", CU_get_error_msg());
+ return CU_get_error();
+ }
+}
diff --git a/src/shrpx.cc b/src/shrpx.cc
new file mode 100644
index 0000000..b42054c
--- /dev/null
+++ b/src/shrpx.cc
@@ -0,0 +1,5329 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx.h"
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif // HAVE_SYS_SOCKET_H
+#include <sys/un.h>
+#ifdef HAVE_NETDB_H
+# include <netdb.h>
+#endif // HAVE_NETDB_H
+#include <signal.h>
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif // HAVE_NETINET_IN_H
+#include <netinet/tcp.h>
+#ifdef HAVE_ARPA_INET_H
+# include <arpa/inet.h>
+#endif // HAVE_ARPA_INET_H
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+#include <getopt.h>
+#ifdef HAVE_SYSLOG_H
+# include <syslog.h>
+#endif // HAVE_SYSLOG_H
+#ifdef HAVE_LIMITS_H
+# include <limits.h>
+#endif // HAVE_LIMITS_H
+#ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+#endif // HAVE_SYS_TIME_H
+#include <sys/resource.h>
+#ifdef HAVE_LIBSYSTEMD
+# include <systemd/sd-daemon.h>
+#endif // HAVE_LIBSYSTEMD
+#ifdef HAVE_LIBBPF
+# include <bpf/libbpf.h>
+#endif // HAVE_LIBBPF
+
+#include <cinttypes>
+#include <limits>
+#include <cstdlib>
+#include <iostream>
+#include <fstream>
+#include <vector>
+#include <initializer_list>
+#include <random>
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/rand.h>
+#include <ev.h>
+
+#include <nghttp2/nghttp2.h>
+
+#ifdef ENABLE_HTTP3
+# include <ngtcp2/ngtcp2.h>
+# include <nghttp3/nghttp3.h>
+#endif // ENABLE_HTTP3
+
+#include "shrpx_config.h"
+#include "shrpx_tls.h"
+#include "shrpx_log_config.h"
+#include "shrpx_worker.h"
+#include "shrpx_http2_upstream.h"
+#include "shrpx_http2_session.h"
+#include "shrpx_worker_process.h"
+#include "shrpx_process.h"
+#include "shrpx_signal.h"
+#include "shrpx_connection.h"
+#include "shrpx_log.h"
+#include "shrpx_http.h"
+#include "util.h"
+#include "app_helper.h"
+#include "tls.h"
+#include "template.h"
+#include "allocator.h"
+#include "ssl_compat.h"
+#include "xsi_strerror.h"
+
+extern char **environ;
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+// Deprecated: Environment variables to tell new binary the listening
+// socket's file descriptors. They are not close-on-exec.
+constexpr auto ENV_LISTENER4_FD = StringRef::from_lit("NGHTTPX_LISTENER4_FD");
+constexpr auto ENV_LISTENER6_FD = StringRef::from_lit("NGHTTPX_LISTENER6_FD");
+
+// Deprecated: Environment variable to tell new binary the port number
+// the current binary is listening to.
+constexpr auto ENV_PORT = StringRef::from_lit("NGHTTPX_PORT");
+
+// Deprecated: Environment variable to tell new binary the listening
+// socket's file descriptor if frontend listens UNIX domain socket.
+constexpr auto ENV_UNIX_FD = StringRef::from_lit("NGHTTP2_UNIX_FD");
+// Deprecated: Environment variable to tell new binary the UNIX domain
+// socket path.
+constexpr auto ENV_UNIX_PATH = StringRef::from_lit("NGHTTP2_UNIX_PATH");
+
+// Prefix of environment variables to tell new binary the listening
+// socket's file descriptor. They are not close-on-exec. For TCP
+// socket, the value must be comma separated 2 parameters: tcp,<FD>.
+// <FD> is file descriptor. For UNIX domain socket, the value must be
+// comma separated 3 parameters: unix,<FD>,<PATH>. <FD> is file
+// descriptor. <PATH> is a path to UNIX domain socket.
+constexpr auto ENV_ACCEPT_PREFIX = StringRef::from_lit("NGHTTPX_ACCEPT_");
+
+// This environment variable contains PID of the original main
+// process, assuming that it created this main process as a result of
+// SIGUSR2. The new main process is expected to send QUIT signal to
+// the original main process to shut it down gracefully.
+constexpr auto ENV_ORIG_PID = StringRef::from_lit("NGHTTPX_ORIG_PID");
+
+// Prefix of environment variables to tell new binary the QUIC IPC
+// file descriptor and CID prefix of the lingering worker process.
+// The value must be comma separated parameters:
+// <FD>,<CID_PREFIX_0>,<CID_PREFIX_1>,... <FD> is the file
+// descriptor. <CID_PREFIX_I> is the I-th CID prefix in hex encoded
+// string.
+constexpr auto ENV_QUIC_WORKER_PROCESS_PREFIX =
+ StringRef::from_lit("NGHTTPX_QUIC_WORKER_PROCESS_");
+
+#ifndef _KERNEL_FASTOPEN
+# define _KERNEL_FASTOPEN
+// conditional define for TCP_FASTOPEN mostly on ubuntu
+# ifndef TCP_FASTOPEN
+# define TCP_FASTOPEN 23
+# endif
+
+// conditional define for SOL_TCP mostly on ubuntu
+# ifndef SOL_TCP
+# define SOL_TCP 6
+# endif
+#endif
+
+// This configuration is fixed at the first startup of the main
+// process, and does not change after subsequent reloadings.
+struct StartupConfig {
+ // This contains all options given in command-line.
+ std::vector<std::pair<StringRef, StringRef>> cmdcfgs;
+ // The current working directory where this process started.
+ char *cwd;
+ // The pointer to original argv (not sure why we have this?)
+ char **original_argv;
+ // The pointer to argv, this is a deep copy of original argv.
+ char **argv;
+ // The number of elements in argv.
+ int argc;
+};
+
+namespace {
+StartupConfig suconfig;
+} // namespace
+
+struct InheritedAddr {
+ // IP address if TCP socket. Otherwise, UNIX domain socket path.
+ StringRef host;
+ uint16_t port;
+ // true if UNIX domain socket path
+ bool host_unix;
+ int fd;
+ bool used;
+};
+
+namespace {
+void signal_cb(struct ev_loop *loop, ev_signal *w, int revents);
+} // namespace
+
+namespace {
+void worker_process_child_cb(struct ev_loop *loop, ev_child *w, int revents);
+} // namespace
+
+struct WorkerProcess {
+ WorkerProcess(struct ev_loop *loop, pid_t worker_pid, int ipc_fd
+#ifdef ENABLE_HTTP3
+ ,
+ int quic_ipc_fd,
+ const std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>>
+ &cid_prefixes
+#endif // ENABLE_HTTP3
+ )
+ : loop(loop),
+ worker_pid(worker_pid),
+ ipc_fd(ipc_fd)
+#ifdef ENABLE_HTTP3
+ ,
+ quic_ipc_fd(quic_ipc_fd),
+ cid_prefixes(cid_prefixes)
+#endif // ENABLE_HTTP3
+ {
+ ev_child_init(&worker_process_childev, worker_process_child_cb, worker_pid,
+ 0);
+ worker_process_childev.data = this;
+ ev_child_start(loop, &worker_process_childev);
+ }
+
+ ~WorkerProcess() {
+ ev_child_stop(loop, &worker_process_childev);
+
+#ifdef ENABLE_HTTP3
+ if (quic_ipc_fd != -1) {
+ close(quic_ipc_fd);
+ }
+#endif // ENABLE_HTTP3
+
+ if (ipc_fd != -1) {
+ shutdown(ipc_fd, SHUT_WR);
+ close(ipc_fd);
+ }
+ }
+
+ ev_child worker_process_childev;
+ struct ev_loop *loop;
+ pid_t worker_pid;
+ int ipc_fd;
+ std::chrono::steady_clock::time_point termination_deadline;
+#ifdef ENABLE_HTTP3
+ int quic_ipc_fd;
+ std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>> cid_prefixes;
+#endif // ENABLE_HTTP3
+};
+
+namespace {
+void reload_config();
+} // namespace
+
+namespace {
+std::deque<std::unique_ptr<WorkerProcess>> worker_processes;
+} // namespace
+
+namespace {
+ev_timer worker_process_grace_period_timer;
+} // namespace
+
+namespace {
+void worker_process_grace_period_timercb(struct ev_loop *loop, ev_timer *w,
+ int revents) {
+ auto now = std::chrono::steady_clock::now();
+ auto next_repeat = std::chrono::steady_clock::duration::zero();
+
+ for (auto it = std::begin(worker_processes);
+ it != std::end(worker_processes);) {
+ auto &wp = *it;
+ if (wp->termination_deadline.time_since_epoch().count() == 0) {
+ ++it;
+
+ continue;
+ }
+
+ auto d = wp->termination_deadline - now;
+ if (d.count() > 0) {
+ if (next_repeat == std::chrono::steady_clock::duration::zero() ||
+ d < next_repeat) {
+ next_repeat = d;
+ }
+
+ ++it;
+
+ continue;
+ }
+
+ LOG(NOTICE) << "Deleting worker process pid=" << wp->worker_pid
+ << " because its grace shutdown period is over";
+
+ it = worker_processes.erase(it);
+ }
+
+ if (next_repeat.count() > 0) {
+ w->repeat = util::ev_tstamp_from(next_repeat);
+ ev_timer_again(loop, w);
+
+ return;
+ }
+
+ ev_timer_stop(loop, w);
+}
+} // namespace
+
+namespace {
+void worker_process_set_termination_deadline(WorkerProcess *wp,
+ struct ev_loop *loop) {
+ auto config = get_config();
+
+ if (!(config->worker_process_grace_shutdown_period > 0.)) {
+ return;
+ }
+
+ wp->termination_deadline =
+ std::chrono::steady_clock::now() +
+ util::duration_from(config->worker_process_grace_shutdown_period);
+
+ if (!ev_is_active(&worker_process_grace_period_timer)) {
+ worker_process_grace_period_timer.repeat =
+ config->worker_process_grace_shutdown_period;
+
+ ev_timer_again(loop, &worker_process_grace_period_timer);
+ }
+}
+} // namespace
+
+namespace {
+void worker_process_add(std::unique_ptr<WorkerProcess> wp) {
+ worker_processes.push_back(std::move(wp));
+}
+} // namespace
+
+namespace {
+void worker_process_remove(const WorkerProcess *wp, struct ev_loop *loop) {
+ for (auto it = std::begin(worker_processes); it != std::end(worker_processes);
+ ++it) {
+ auto &s = *it;
+
+ if (s.get() != wp) {
+ continue;
+ }
+
+ worker_processes.erase(it);
+
+ if (worker_processes.empty()) {
+ ev_timer_stop(loop, &worker_process_grace_period_timer);
+ }
+
+ break;
+ }
+}
+} // namespace
+
+namespace {
+void worker_process_adjust_limit() {
+ auto config = get_config();
+
+ if (config->max_worker_processes &&
+ worker_processes.size() > config->max_worker_processes) {
+ worker_processes.pop_front();
+ }
+}
+} // namespace
+
+namespace {
+void worker_process_remove_all(struct ev_loop *loop) {
+ std::deque<std::unique_ptr<WorkerProcess>>().swap(worker_processes);
+
+ ev_timer_stop(loop, &worker_process_grace_period_timer);
+}
+} // namespace
+
+namespace {
+// Send signal |signum| to all worker processes, and clears
+// worker_processes.
+void worker_process_kill(int signum, struct ev_loop *loop) {
+ for (auto &s : worker_processes) {
+ if (s->worker_pid == -1) {
+ continue;
+ }
+ kill(s->worker_pid, signum);
+ }
+ worker_process_remove_all(loop);
+}
+} // namespace
+
+namespace {
+int save_pid() {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ auto config = get_config();
+
+ constexpr auto SUFFIX = StringRef::from_lit(".XXXXXX");
+ auto &pid_file = config->pid_file;
+
+ auto len = config->pid_file.size() + SUFFIX.size();
+ auto buf = std::make_unique<char[]>(len + 1);
+ auto p = buf.get();
+
+ p = std::copy(std::begin(pid_file), std::end(pid_file), p);
+ p = std::copy(std::begin(SUFFIX), std::end(SUFFIX), p);
+ *p = '\0';
+
+ auto temp_path = buf.get();
+
+ auto fd = mkstemp(temp_path);
+ if (fd == -1) {
+ auto error = errno;
+ LOG(ERROR) << "Could not save PID to file " << pid_file << ": "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return -1;
+ }
+
+ auto content = util::utos(config->pid) + '\n';
+
+ if (write(fd, content.c_str(), content.size()) == -1) {
+ auto error = errno;
+ LOG(ERROR) << "Could not save PID to file " << pid_file << ": "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return -1;
+ }
+
+ if (fsync(fd) == -1) {
+ auto error = errno;
+ LOG(ERROR) << "Could not save PID to file " << pid_file << ": "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return -1;
+ }
+
+ close(fd);
+
+ if (rename(temp_path, pid_file.c_str()) == -1) {
+ auto error = errno;
+ LOG(ERROR) << "Could not save PID to file " << pid_file << ": "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+
+ unlink(temp_path);
+
+ return -1;
+ }
+
+ if (config->uid != 0) {
+ if (chown(pid_file.c_str(), config->uid, config->gid) == -1) {
+ auto error = errno;
+ LOG(WARN) << "Changing owner of pid file " << pid_file << " failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ }
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+void shrpx_sd_notifyf(int unset_environment, const char *format, ...) {
+#ifdef HAVE_LIBSYSTEMD
+ va_list args;
+
+ va_start(args, format);
+ sd_notifyf(unset_environment, format, va_arg(args, char *));
+ va_end(args);
+#endif // HAVE_LIBSYSTEMD
+}
+} // namespace
+
+namespace {
+void exec_binary() {
+ int rv;
+ sigset_t oldset;
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+
+ LOG(NOTICE) << "Executing new binary";
+
+ shrpx_sd_notifyf(0, "RELOADING=1");
+
+ rv = shrpx_signal_block_all(&oldset);
+ if (rv != 0) {
+ auto error = errno;
+ LOG(ERROR) << "Blocking all signals failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+
+ return;
+ }
+
+ auto pid = fork();
+
+ if (pid != 0) {
+ if (pid == -1) {
+ auto error = errno;
+ LOG(ERROR) << "fork() failed errno=" << error;
+ } else {
+ // update PID tracking information in systemd
+ shrpx_sd_notifyf(0, "MAINPID=%d\n", pid);
+ }
+
+ rv = shrpx_signal_set(&oldset);
+
+ if (rv != 0) {
+ auto error = errno;
+ LOG(FATAL) << "Restoring signal mask failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+
+ exit(EXIT_FAILURE);
+ }
+
+ return;
+ }
+
+ // child process
+
+ shrpx_signal_unset_main_proc_ign_handler();
+
+ rv = shrpx_signal_unblock_all();
+ if (rv != 0) {
+ auto error = errno;
+ LOG(ERROR) << "Unblocking all signals failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+
+ nghttp2_Exit(EXIT_FAILURE);
+ }
+
+ auto exec_path =
+ util::get_exec_path(suconfig.argc, suconfig.argv, suconfig.cwd);
+
+ if (!exec_path) {
+ LOG(ERROR) << "Could not resolve the executable path";
+ nghttp2_Exit(EXIT_FAILURE);
+ }
+
+ auto argv = std::make_unique<char *[]>(suconfig.argc + 1);
+
+ argv[0] = exec_path;
+ for (int i = 1; i < suconfig.argc; ++i) {
+ argv[i] = suconfig.argv[i];
+ }
+ argv[suconfig.argc] = nullptr;
+
+ size_t envlen = 0;
+ for (char **p = environ; *p; ++p, ++envlen)
+ ;
+
+ auto config = get_config();
+ auto &listenerconf = config->conn.listener;
+
+ // 2 for ENV_ORIG_PID and terminal nullptr.
+ auto envp = std::make_unique<char *[]>(envlen + listenerconf.addrs.size() +
+ worker_processes.size() + 2);
+ size_t envidx = 0;
+
+ std::vector<ImmutableString> fd_envs;
+ for (size_t i = 0; i < listenerconf.addrs.size(); ++i) {
+ auto &addr = listenerconf.addrs[i];
+ auto s = ENV_ACCEPT_PREFIX.str();
+ s += util::utos(i + 1);
+ s += '=';
+ if (addr.host_unix) {
+ s += "unix,";
+ s += util::utos(addr.fd);
+ s += ',';
+ s += addr.host;
+ } else {
+ s += "tcp,";
+ s += util::utos(addr.fd);
+ }
+
+ fd_envs.emplace_back(s);
+ envp[envidx++] = const_cast<char *>(fd_envs.back().c_str());
+ }
+
+ auto ipc_fd_str = ENV_ORIG_PID.str();
+ ipc_fd_str += '=';
+ ipc_fd_str += util::utos(config->pid);
+ envp[envidx++] = const_cast<char *>(ipc_fd_str.c_str());
+
+#ifdef ENABLE_HTTP3
+ std::vector<ImmutableString> quic_lwps;
+ for (size_t i = 0; i < worker_processes.size(); ++i) {
+ auto &wp = worker_processes[i];
+ auto s = ENV_QUIC_WORKER_PROCESS_PREFIX.str();
+ s += util::utos(i + 1);
+ s += '=';
+ s += util::utos(wp->quic_ipc_fd);
+ for (auto &cid_prefix : wp->cid_prefixes) {
+ s += ',';
+ s += util::format_hex(cid_prefix);
+ }
+
+ quic_lwps.emplace_back(s);
+ envp[envidx++] = const_cast<char *>(quic_lwps.back().c_str());
+ }
+#endif // ENABLE_HTTP3
+
+ for (size_t i = 0; i < envlen; ++i) {
+ auto env = StringRef{environ[i]};
+ if (util::starts_with(env, ENV_ACCEPT_PREFIX) ||
+ util::starts_with(env, ENV_LISTENER4_FD) ||
+ util::starts_with(env, ENV_LISTENER6_FD) ||
+ util::starts_with(env, ENV_PORT) ||
+ util::starts_with(env, ENV_UNIX_FD) ||
+ util::starts_with(env, ENV_UNIX_PATH) ||
+ util::starts_with(env, ENV_ORIG_PID) ||
+ util::starts_with(env, ENV_QUIC_WORKER_PROCESS_PREFIX)) {
+ continue;
+ }
+
+ envp[envidx++] = environ[i];
+ }
+
+ envp[envidx++] = nullptr;
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "cmdline";
+ for (int i = 0; argv[i]; ++i) {
+ LOG(INFO) << i << ": " << argv[i];
+ }
+ LOG(INFO) << "environ";
+ for (int i = 0; envp[i]; ++i) {
+ LOG(INFO) << i << ": " << envp[i];
+ }
+ }
+
+ // restores original stderr
+ restore_original_fds();
+
+ // reloading finished
+ shrpx_sd_notifyf(0, "READY=1");
+
+ if (execve(argv[0], argv.get(), envp.get()) == -1) {
+ auto error = errno;
+ LOG(ERROR) << "execve failed: errno=" << error;
+ nghttp2_Exit(EXIT_FAILURE);
+ }
+}
+} // namespace
+
+namespace {
+void ipc_send(WorkerProcess *wp, uint8_t ipc_event) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ ssize_t nwrite;
+ while ((nwrite = write(wp->ipc_fd, &ipc_event, 1)) == -1 && errno == EINTR)
+ ;
+
+ if (nwrite < 0) {
+ auto error = errno;
+ LOG(ERROR) << "Could not send IPC event to worker process: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return;
+ }
+
+ if (nwrite == 0) {
+ LOG(ERROR) << "Could not send IPC event due to pipe overflow";
+ return;
+ }
+}
+} // namespace
+
+namespace {
+void reopen_log(WorkerProcess *wp) {
+ LOG(NOTICE) << "Reopening log files: main process";
+
+ auto config = get_config();
+ auto &loggingconf = config->logging;
+
+ (void)reopen_log_files(loggingconf);
+ redirect_stderr_to_errorlog(loggingconf);
+ ipc_send(wp, SHRPX_IPC_REOPEN_LOG);
+}
+} // namespace
+
+namespace {
+void signal_cb(struct ev_loop *loop, ev_signal *w, int revents) {
+ switch (w->signum) {
+ case REOPEN_LOG_SIGNAL:
+ for (auto &wp : worker_processes) {
+ reopen_log(wp.get());
+ }
+
+ return;
+ case EXEC_BINARY_SIGNAL:
+ exec_binary();
+ return;
+ case GRACEFUL_SHUTDOWN_SIGNAL: {
+ auto &listenerconf = get_config()->conn.listener;
+ for (auto &addr : listenerconf.addrs) {
+ close(addr.fd);
+ }
+
+ for (auto &wp : worker_processes) {
+ ipc_send(wp.get(), SHRPX_IPC_GRACEFUL_SHUTDOWN);
+ worker_process_set_termination_deadline(wp.get(), loop);
+ }
+
+ return;
+ }
+ case RELOAD_SIGNAL:
+ reload_config();
+
+ return;
+ default:
+ worker_process_kill(w->signum, loop);
+ ev_break(loop);
+ return;
+ }
+}
+} // namespace
+
+namespace {
+void worker_process_child_cb(struct ev_loop *loop, ev_child *w, int revents) {
+ auto wp = static_cast<WorkerProcess *>(w->data);
+
+ log_chld(w->rpid, w->rstatus, "Worker process");
+
+ worker_process_remove(wp, loop);
+
+ if (worker_processes.empty()) {
+ ev_break(loop);
+ }
+}
+} // namespace
+
+namespace {
+int create_unix_domain_server_socket(UpstreamAddr &faddr,
+ std::vector<InheritedAddr> &iaddrs) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ auto found = std::find_if(
+ std::begin(iaddrs), std::end(iaddrs), [&faddr](const InheritedAddr &ia) {
+ return !ia.used && ia.host_unix && ia.host == faddr.host;
+ });
+
+ if (found != std::end(iaddrs)) {
+ LOG(NOTICE) << "Listening on UNIX domain socket " << faddr.host
+ << (faddr.tls ? ", tls" : "");
+ (*found).used = true;
+ faddr.fd = (*found).fd;
+ faddr.hostport = StringRef::from_lit("localhost");
+
+ return 0;
+ }
+
+#ifdef SOCK_NONBLOCK
+ auto fd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0);
+ if (fd == -1) {
+ auto error = errno;
+ LOG(FATAL) << "socket() syscall failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return -1;
+ }
+#else // !SOCK_NONBLOCK
+ auto fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd == -1) {
+ auto error = errno;
+ LOG(FATAL) << "socket() syscall failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return -1;
+ }
+ util::make_socket_nonblocking(fd);
+#endif // !SOCK_NONBLOCK
+ int val = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ auto error = errno;
+ LOG(FATAL) << "Failed to set SO_REUSEADDR option to listener socket: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ return -1;
+ }
+
+ sockaddr_union addr;
+ addr.un.sun_family = AF_UNIX;
+ if (faddr.host.size() + 1 > sizeof(addr.un.sun_path)) {
+ LOG(FATAL) << "UNIX domain socket path " << faddr.host << " is too long > "
+ << sizeof(addr.un.sun_path);
+ close(fd);
+ return -1;
+ }
+ // copy path including terminal NULL
+ std::copy_n(faddr.host.c_str(), faddr.host.size() + 1, addr.un.sun_path);
+
+ // unlink (remove) already existing UNIX domain socket path
+ unlink(faddr.host.c_str());
+
+ if (bind(fd, &addr.sa, sizeof(addr.un)) != 0) {
+ auto error = errno;
+ LOG(FATAL) << "Failed to bind UNIX domain socket: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ return -1;
+ }
+
+ auto &listenerconf = get_config()->conn.listener;
+
+ if (listen(fd, listenerconf.backlog) != 0) {
+ auto error = errno;
+ LOG(FATAL) << "Failed to listen to UNIX domain socket: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ return -1;
+ }
+
+ LOG(NOTICE) << "Listening on UNIX domain socket " << faddr.host
+ << (faddr.tls ? ", tls" : "");
+
+ faddr.fd = fd;
+ faddr.hostport = StringRef::from_lit("localhost");
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int create_tcp_server_socket(UpstreamAddr &faddr,
+ std::vector<InheritedAddr> &iaddrs) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ int fd = -1;
+ int rv;
+
+ auto &listenerconf = get_config()->conn.listener;
+
+ auto service = util::utos(faddr.port);
+ addrinfo hints{};
+ hints.ai_family = faddr.family;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_PASSIVE;
+#ifdef AI_ADDRCONFIG
+ hints.ai_flags |= AI_ADDRCONFIG;
+#endif // AI_ADDRCONFIG
+
+ auto node =
+ faddr.host == StringRef::from_lit("*") ? nullptr : faddr.host.c_str();
+
+ addrinfo *res, *rp;
+ rv = getaddrinfo(node, service.c_str(), &hints, &res);
+#ifdef AI_ADDRCONFIG
+ if (rv != 0) {
+ // Retry without AI_ADDRCONFIG
+ hints.ai_flags &= ~AI_ADDRCONFIG;
+ rv = getaddrinfo(node, service.c_str(), &hints, &res);
+ }
+#endif // AI_ADDRCONFIG
+ if (rv != 0) {
+ LOG(FATAL) << "Unable to get IPv" << (faddr.family == AF_INET ? "4" : "6")
+ << " address for " << faddr.host << ", port " << faddr.port
+ << ": " << gai_strerror(rv);
+ return -1;
+ }
+
+ auto res_d = defer(freeaddrinfo, res);
+
+ std::array<char, NI_MAXHOST> host;
+
+ for (rp = res; rp; rp = rp->ai_next) {
+
+ rv = getnameinfo(rp->ai_addr, rp->ai_addrlen, host.data(), host.size(),
+ nullptr, 0, NI_NUMERICHOST);
+
+ if (rv != 0) {
+ LOG(WARN) << "getnameinfo() failed: " << gai_strerror(rv);
+ continue;
+ }
+
+ auto found = std::find_if(std::begin(iaddrs), std::end(iaddrs),
+ [&host, &faddr](const InheritedAddr &ia) {
+ return !ia.used && !ia.host_unix &&
+ ia.host == host.data() &&
+ ia.port == faddr.port;
+ });
+
+ if (found != std::end(iaddrs)) {
+ (*found).used = true;
+ fd = (*found).fd;
+ break;
+ }
+
+#ifdef SOCK_NONBLOCK
+ fd =
+ socket(rp->ai_family, rp->ai_socktype | SOCK_NONBLOCK, rp->ai_protocol);
+ if (fd == -1) {
+ auto error = errno;
+ LOG(WARN) << "socket() syscall failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ continue;
+ }
+#else // !SOCK_NONBLOCK
+ fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if (fd == -1) {
+ auto error = errno;
+ LOG(WARN) << "socket() syscall failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ continue;
+ }
+ util::make_socket_nonblocking(fd);
+#endif // !SOCK_NONBLOCK
+ int val = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ auto error = errno;
+ LOG(WARN) << "Failed to set SO_REUSEADDR option to listener socket: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ continue;
+ }
+
+#ifdef IPV6_V6ONLY
+ if (faddr.family == AF_INET6) {
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ auto error = errno;
+ LOG(WARN) << "Failed to set IPV6_V6ONLY option to listener socket: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ continue;
+ }
+ }
+#endif // IPV6_V6ONLY
+
+#ifdef TCP_DEFER_ACCEPT
+ val = 3;
+ if (setsockopt(fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ auto error = errno;
+ LOG(WARN) << "Failed to set TCP_DEFER_ACCEPT option to listener socket: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ }
+#endif // TCP_DEFER_ACCEPT
+
+ // When we are executing new binary, and the old binary did not
+ // bind privileged port (< 1024) for some reason, binding to those
+ // ports will fail with permission denied error.
+ if (bind(fd, rp->ai_addr, rp->ai_addrlen) == -1) {
+ auto error = errno;
+ LOG(WARN) << "bind() syscall failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ continue;
+ }
+
+ if (listenerconf.fastopen > 0) {
+ val = listenerconf.fastopen;
+ if (setsockopt(fd, SOL_TCP, TCP_FASTOPEN, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ auto error = errno;
+ LOG(WARN) << "Failed to set TCP_FASTOPEN option to listener socket: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ }
+ }
+
+ if (listen(fd, listenerconf.backlog) == -1) {
+ auto error = errno;
+ LOG(WARN) << "listen() syscall failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ continue;
+ }
+
+ break;
+ }
+
+ if (!rp) {
+ LOG(FATAL) << "Listening " << (faddr.family == AF_INET ? "IPv4" : "IPv6")
+ << " socket failed";
+
+ return -1;
+ }
+
+ faddr.fd = fd;
+ faddr.hostport = util::make_http_hostport(mod_config()->balloc,
+ StringRef{host.data()}, faddr.port);
+
+ LOG(NOTICE) << "Listening on " << faddr.hostport
+ << (faddr.tls ? ", tls" : "");
+
+ return 0;
+}
+} // namespace
+
+namespace {
+// Returns array of InheritedAddr constructed from |config|. This
+// function is intended to be used when reloading configuration, and
+// |config| is usually a current configuration.
+std::vector<InheritedAddr>
+get_inherited_addr_from_config(BlockAllocator &balloc, Config *config) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ int rv;
+
+ auto &listenerconf = config->conn.listener;
+
+ std::vector<InheritedAddr> iaddrs(listenerconf.addrs.size());
+
+ size_t idx = 0;
+ for (auto &addr : listenerconf.addrs) {
+ auto &iaddr = iaddrs[idx++];
+
+ if (addr.host_unix) {
+ iaddr.host = addr.host;
+ iaddr.host_unix = true;
+ iaddr.fd = addr.fd;
+
+ continue;
+ }
+
+ iaddr.port = addr.port;
+ iaddr.fd = addr.fd;
+
+ // We have to getsockname/getnameinfo for fd, since we may have
+ // '*' appear in addr.host, which makes comparison against "real"
+ // address fail.
+
+ sockaddr_union su;
+ socklen_t salen = sizeof(su);
+
+ // We already added entry to iaddrs. Even if we got errors, we
+ // don't remove it. This is required because we have to close the
+ // socket if it is not reused. The empty host name usually does
+ // not match anything.
+
+ if (getsockname(addr.fd, &su.sa, &salen) != 0) {
+ auto error = errno;
+ LOG(WARN) << "getsockname() syscall failed (fd=" << addr.fd
+ << "): " << xsi_strerror(error, errbuf.data(), errbuf.size());
+ continue;
+ }
+
+ std::array<char, NI_MAXHOST> host;
+ rv = getnameinfo(&su.sa, salen, host.data(), host.size(), nullptr, 0,
+ NI_NUMERICHOST);
+ if (rv != 0) {
+ LOG(WARN) << "getnameinfo() failed (fd=" << addr.fd
+ << "): " << gai_strerror(rv);
+ continue;
+ }
+
+ iaddr.host = make_string_ref(balloc, StringRef{host.data()});
+ }
+
+ return iaddrs;
+}
+} // namespace
+
+namespace {
+// Returns array of InheritedAddr constructed from environment
+// variables. This function handles the old environment variable
+// names used in 1.7.0 or earlier.
+std::vector<InheritedAddr> get_inherited_addr_from_env(Config *config) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ int rv;
+ std::vector<InheritedAddr> iaddrs;
+
+ {
+ // Upgrade from 1.7.0 or earlier
+ auto portenv = getenv(ENV_PORT.c_str());
+ if (portenv) {
+ size_t i = 1;
+ for (const auto &env_name : {ENV_LISTENER4_FD, ENV_LISTENER6_FD}) {
+ auto fdenv = getenv(env_name.c_str());
+ if (fdenv) {
+ auto name = ENV_ACCEPT_PREFIX.str();
+ name += util::utos(i);
+ std::string value = "tcp,";
+ value += fdenv;
+ setenv(name.c_str(), value.c_str(), 0);
+ ++i;
+ }
+ }
+ } else {
+ // The return value of getenv may be allocated statically.
+ if (getenv(ENV_UNIX_PATH.c_str()) && getenv(ENV_UNIX_FD.c_str())) {
+ auto name = ENV_ACCEPT_PREFIX.str();
+ name += '1';
+ std::string value = "unix,";
+ value += getenv(ENV_UNIX_FD.c_str());
+ value += ',';
+ value += getenv(ENV_UNIX_PATH.c_str());
+ setenv(name.c_str(), value.c_str(), 0);
+ }
+ }
+ }
+
+ for (size_t i = 1;; ++i) {
+ auto name = ENV_ACCEPT_PREFIX.str();
+ name += util::utos(i);
+ auto env = getenv(name.c_str());
+ if (!env) {
+ break;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Read env " << name << "=" << env;
+ }
+
+ auto end_type = strchr(env, ',');
+ if (!end_type) {
+ continue;
+ }
+
+ auto type = StringRef(env, end_type);
+ auto value = end_type + 1;
+
+ if (type == StringRef::from_lit("unix")) {
+ auto endfd = strchr(value, ',');
+ if (!endfd) {
+ continue;
+ }
+ auto fd = util::parse_uint(reinterpret_cast<const uint8_t *>(value),
+ endfd - value);
+ if (fd == -1) {
+ LOG(WARN) << "Could not parse file descriptor from "
+ << std::string(value, endfd - value);
+ continue;
+ }
+
+ auto path = endfd + 1;
+ if (strlen(path) == 0) {
+ LOG(WARN) << "Empty UNIX domain socket path (fd=" << fd << ")";
+ close(fd);
+ continue;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Inherit UNIX domain socket fd=" << fd
+ << ", path=" << path;
+ }
+
+ InheritedAddr addr{};
+ addr.host = make_string_ref(config->balloc, StringRef{path});
+ addr.host_unix = true;
+ addr.fd = static_cast<int>(fd);
+ iaddrs.push_back(std::move(addr));
+ }
+
+ if (type == StringRef::from_lit("tcp")) {
+ auto fd = util::parse_uint(value);
+ if (fd == -1) {
+ LOG(WARN) << "Could not parse file descriptor from " << value;
+ continue;
+ }
+
+ sockaddr_union su;
+ socklen_t salen = sizeof(su);
+
+ if (getsockname(fd, &su.sa, &salen) != 0) {
+ auto error = errno;
+ LOG(WARN) << "getsockname() syscall failed (fd=" << fd
+ << "): " << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ continue;
+ }
+
+ uint16_t port;
+
+ switch (su.storage.ss_family) {
+ case AF_INET:
+ port = ntohs(su.in.sin_port);
+ break;
+ case AF_INET6:
+ port = ntohs(su.in6.sin6_port);
+ break;
+ default:
+ close(fd);
+ continue;
+ }
+
+ std::array<char, NI_MAXHOST> host;
+ rv = getnameinfo(&su.sa, salen, host.data(), host.size(), nullptr, 0,
+ NI_NUMERICHOST);
+ if (rv != 0) {
+ LOG(WARN) << "getnameinfo() failed (fd=" << fd
+ << "): " << gai_strerror(rv);
+ close(fd);
+ continue;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Inherit TCP socket fd=" << fd
+ << ", address=" << host.data() << ", port=" << port;
+ }
+
+ InheritedAddr addr{};
+ addr.host = make_string_ref(config->balloc, StringRef{host.data()});
+ addr.port = static_cast<uint16_t>(port);
+ addr.fd = static_cast<int>(fd);
+ iaddrs.push_back(std::move(addr));
+ continue;
+ }
+ }
+
+ return iaddrs;
+}
+} // namespace
+
+namespace {
+// Closes all sockets which are not reused.
+void close_unused_inherited_addr(const std::vector<InheritedAddr> &iaddrs) {
+ for (auto &ia : iaddrs) {
+ if (ia.used) {
+ continue;
+ }
+
+ close(ia.fd);
+ }
+}
+} // namespace
+
+namespace {
+// Returns the PID of the original main process from environment
+// variable ENV_ORIG_PID.
+pid_t get_orig_pid_from_env() {
+ auto s = getenv(ENV_ORIG_PID.c_str());
+ if (s == nullptr) {
+ return -1;
+ }
+ return util::parse_uint(s);
+}
+} // namespace
+
+#ifdef ENABLE_HTTP3
+namespace {
+std::vector<QUICLingeringWorkerProcess>
+ inherited_quic_lingering_worker_processes;
+} // namespace
+
+namespace {
+std::vector<QUICLingeringWorkerProcess>
+get_inherited_quic_lingering_worker_process_from_env() {
+ std::vector<QUICLingeringWorkerProcess> iwps;
+
+ for (size_t i = 1;; ++i) {
+ auto name = ENV_QUIC_WORKER_PROCESS_PREFIX.str();
+ name += util::utos(i);
+ auto env = getenv(name.c_str());
+ if (!env) {
+ break;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Read env " << name << "=" << env;
+ }
+
+ auto envend = env + strlen(env);
+
+ auto end_fd = std::find(env, envend, ',');
+ if (end_fd == envend) {
+ continue;
+ }
+
+ auto fd =
+ util::parse_uint(reinterpret_cast<const uint8_t *>(env), end_fd - env);
+ if (fd == -1) {
+ LOG(WARN) << "Could not parse file descriptor from "
+ << StringRef{env, static_cast<size_t>(end_fd - env)};
+ continue;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Inherit worker process QUIC IPC socket fd=" << fd;
+ }
+
+ util::make_socket_closeonexec(fd);
+
+ std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>> cid_prefixes;
+
+ auto p = end_fd + 1;
+ for (;;) {
+ auto end = std::find(p, envend, ',');
+
+ auto hex_cid_prefix = StringRef{p, end};
+ if (hex_cid_prefix.size() != SHRPX_QUIC_CID_PREFIXLEN * 2 ||
+ !util::is_hex_string(hex_cid_prefix)) {
+ LOG(WARN) << "Found invalid CID prefix=" << hex_cid_prefix;
+ break;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Inherit worker process CID prefix=" << hex_cid_prefix;
+ }
+
+ cid_prefixes.emplace_back();
+
+ util::decode_hex(std::begin(cid_prefixes.back()), hex_cid_prefix);
+
+ if (end == envend) {
+ break;
+ }
+
+ p = end + 1;
+ }
+
+ iwps.emplace_back(std::move(cid_prefixes), fd);
+ }
+
+ return iwps;
+}
+} // namespace
+#endif // ENABLE_HTTP3
+
+namespace {
+int create_acceptor_socket(Config *config, std::vector<InheritedAddr> &iaddrs) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ auto &listenerconf = config->conn.listener;
+
+ for (auto &addr : listenerconf.addrs) {
+ if (addr.host_unix) {
+ if (create_unix_domain_server_socket(addr, iaddrs) != 0) {
+ return -1;
+ }
+
+ if (config->uid != 0) {
+ // fd is not associated to inode, so we cannot use fchown(2)
+ // here. https://lkml.org/lkml/2004/11/1/84
+ if (chown(addr.host.c_str(), config->uid, config->gid) == -1) {
+ auto error = errno;
+ LOG(WARN) << "Changing owner of UNIX domain socket " << addr.host
+ << " failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ }
+ }
+ continue;
+ }
+
+ if (create_tcp_server_socket(addr, iaddrs) != 0) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int call_daemon() {
+#ifdef __sgi
+ return _daemonize(0, 0, 0, 0);
+#else // !__sgi
+# ifdef HAVE_LIBSYSTEMD
+ if (sd_booted() && (getenv("NOTIFY_SOCKET") != nullptr)) {
+ LOG(NOTICE) << "Daemonising disabled under systemd";
+ chdir("/");
+ return 0;
+ }
+# endif // HAVE_LIBSYSTEMD
+ return util::daemonize(0, 0);
+#endif // !__sgi
+}
+} // namespace
+
+namespace {
+// Opens IPC socket used to communicate with worker proess. The
+// communication is unidirectional; that is main process sends
+// messages to the worker process. On success, ipc_fd[0] is for
+// reading, and ipc_fd[1] for writing, just like pipe(2).
+int create_ipc_socket(std::array<int, 2> &ipc_fd) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ int rv;
+
+ rv = pipe(ipc_fd.data());
+ if (rv == -1) {
+ auto error = errno;
+ LOG(WARN) << "Failed to create pipe to communicate worker process: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return -1;
+ }
+
+ for (int i = 0; i < 2; ++i) {
+ auto fd = ipc_fd[i];
+ util::make_socket_nonblocking(fd);
+ util::make_socket_closeonexec(fd);
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int create_worker_process_ready_ipc_socket(std::array<int, 2> &ipc_fd) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ int rv;
+
+ rv = socketpair(AF_UNIX, SOCK_DGRAM, 0, ipc_fd.data());
+ if (rv == -1) {
+ auto error = errno;
+ LOG(WARN) << "Failed to create socket pair to communicate worker process "
+ "readiness: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return -1;
+ }
+
+ for (auto fd : ipc_fd) {
+ util::make_socket_closeonexec(fd);
+ }
+
+ util::make_socket_nonblocking(ipc_fd[0]);
+
+ return 0;
+}
+} // namespace
+
+#ifdef ENABLE_HTTP3
+namespace {
+int create_quic_ipc_socket(std::array<int, 2> &quic_ipc_fd) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ int rv;
+
+ rv = socketpair(AF_UNIX, SOCK_DGRAM, 0, quic_ipc_fd.data());
+ if (rv == -1) {
+ auto error = errno;
+ LOG(WARN) << "Failed to create socket pair to communicate worker process: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return -1;
+ }
+
+ for (auto fd : quic_ipc_fd) {
+ util::make_socket_nonblocking(fd);
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int generate_cid_prefix(
+ std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>> &cid_prefixes,
+ const Config *config) {
+ auto &apiconf = config->api;
+ auto &quicconf = config->quic;
+
+ size_t num_cid_prefix;
+ if (config->single_thread) {
+ num_cid_prefix = 1;
+ } else {
+ num_cid_prefix = config->num_worker;
+
+ // API endpoint occupies the one dedicated worker thread.
+ // Although such worker never gets QUIC traffic, we create CID
+ // prefix for it to make code a bit simpler.
+ if (apiconf.enabled) {
+ ++num_cid_prefix;
+ }
+ }
+
+ cid_prefixes.resize(num_cid_prefix);
+
+ for (auto &cid_prefix : cid_prefixes) {
+ if (create_cid_prefix(cid_prefix.data(), quicconf.server_id.data()) != 0) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+std::vector<QUICLingeringWorkerProcess>
+collect_quic_lingering_worker_processes() {
+ std::vector<QUICLingeringWorkerProcess> quic_lwps{
+ std::begin(inherited_quic_lingering_worker_processes),
+ std::end(inherited_quic_lingering_worker_processes)};
+
+ for (auto &wp : worker_processes) {
+ quic_lwps.emplace_back(wp->cid_prefixes, wp->quic_ipc_fd);
+ }
+
+ return quic_lwps;
+}
+} // namespace
+#endif // ENABLE_HTTP3
+
+namespace {
+ev_signal reopen_log_signalev;
+ev_signal exec_binary_signalev;
+ev_signal graceful_shutdown_signalev;
+ev_signal reload_signalev;
+} // namespace
+
+namespace {
+void start_signal_watchers(struct ev_loop *loop) {
+ ev_signal_init(&reopen_log_signalev, signal_cb, REOPEN_LOG_SIGNAL);
+ ev_signal_start(loop, &reopen_log_signalev);
+
+ ev_signal_init(&exec_binary_signalev, signal_cb, EXEC_BINARY_SIGNAL);
+ ev_signal_start(loop, &exec_binary_signalev);
+
+ ev_signal_init(&graceful_shutdown_signalev, signal_cb,
+ GRACEFUL_SHUTDOWN_SIGNAL);
+ ev_signal_start(loop, &graceful_shutdown_signalev);
+
+ ev_signal_init(&reload_signalev, signal_cb, RELOAD_SIGNAL);
+ ev_signal_start(loop, &reload_signalev);
+}
+} // namespace
+
+namespace {
+void shutdown_signal_watchers(struct ev_loop *loop) {
+ ev_signal_stop(loop, &reload_signalev);
+ ev_signal_stop(loop, &graceful_shutdown_signalev);
+ ev_signal_stop(loop, &exec_binary_signalev);
+ ev_signal_stop(loop, &reopen_log_signalev);
+}
+} // namespace
+
+namespace {
+// A pair of connected socket with which a worker process tells main
+// process that it is ready for service. A worker process writes its
+// PID to worker_process_ready_ipc_fd[1] and main process reads it
+// from worker_process_ready_ipc_fd[0].
+std::array<int, 2> worker_process_ready_ipc_fd;
+} // namespace
+
+namespace {
+ev_io worker_process_ready_ipcev;
+} // namespace
+
+namespace {
+// PID received via NGHTTPX_ORIG_PID environment variable.
+pid_t orig_pid = -1;
+} // namespace
+
+namespace {
+void worker_process_ready_ipc_readcb(struct ev_loop *loop, ev_io *w,
+ int revents) {
+ std::array<uint8_t, 8> buf;
+ ssize_t nread;
+
+ while ((nread = read(w->fd, buf.data(), buf.size())) == -1 && errno == EINTR)
+ ;
+
+ if (nread == -1) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ auto error = errno;
+
+ LOG(ERROR) << "Failed to read data from worker process ready IPC channel: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+
+ return;
+ }
+
+ if (nread == 0) {
+ return;
+ }
+
+ if (nread != sizeof(pid_t)) {
+ LOG(ERROR) << "Read " << nread
+ << " bytes from worker process ready IPC channel";
+
+ return;
+ }
+
+ pid_t pid;
+
+ memcpy(&pid, buf.data(), sizeof(pid));
+
+ LOG(NOTICE) << "Worker process pid=" << pid << " is ready";
+
+ for (auto &wp : worker_processes) {
+ // Send graceful shutdown signal to all worker processes prior to
+ // pid.
+ if (wp->worker_pid == pid) {
+ break;
+ }
+
+ LOG(INFO) << "Sending graceful shutdown event to worker process pid="
+ << wp->worker_pid;
+
+ ipc_send(wp.get(), SHRPX_IPC_GRACEFUL_SHUTDOWN);
+ worker_process_set_termination_deadline(wp.get(), loop);
+ }
+
+ if (orig_pid != -1) {
+ LOG(NOTICE) << "Send QUIT signal to the original main process to tell "
+ "that we are ready to serve requests.";
+ kill(orig_pid, SIGQUIT);
+
+ orig_pid = -1;
+ }
+}
+} // namespace
+
+namespace {
+void start_worker_process_ready_ipc_watcher(struct ev_loop *loop) {
+ ev_io_init(&worker_process_ready_ipcev, worker_process_ready_ipc_readcb,
+ worker_process_ready_ipc_fd[0], EV_READ);
+ ev_io_start(loop, &worker_process_ready_ipcev);
+}
+} // namespace
+
+namespace {
+void shutdown_worker_process_ready_ipc_watcher(struct ev_loop *loop) {
+ ev_io_stop(loop, &worker_process_ready_ipcev);
+}
+} // namespace
+
+namespace {
+// Creates worker process, and returns PID of worker process. On
+// success, file descriptor for IPC (send only) is assigned to
+// |main_ipc_fd|. In child process, we will close file descriptors
+// which are inherited from previous configuration/process, but not
+// used in the current configuration.
+pid_t fork_worker_process(
+ int &main_ipc_fd
+#ifdef ENABLE_HTTP3
+ ,
+ int &wp_quic_ipc_fd
+#endif // ENABLE_HTTP3
+ ,
+ const std::vector<InheritedAddr> &iaddrs
+#ifdef ENABLE_HTTP3
+ ,
+ const std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>>
+ &cid_prefixes,
+ const std::vector<QUICLingeringWorkerProcess> &quic_lwps
+#endif // ENABLE_HTTP3
+) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ int rv;
+ sigset_t oldset;
+
+ std::array<int, 2> ipc_fd;
+
+ rv = create_ipc_socket(ipc_fd);
+ if (rv != 0) {
+ return -1;
+ }
+
+#ifdef ENABLE_HTTP3
+ std::array<int, 2> quic_ipc_fd;
+
+ rv = create_quic_ipc_socket(quic_ipc_fd);
+ if (rv != 0) {
+ return -1;
+ }
+#endif // ENABLE_HTTP3
+
+ rv = shrpx_signal_block_all(&oldset);
+ if (rv != 0) {
+ auto error = errno;
+ LOG(ERROR) << "Blocking all signals failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+
+ close(ipc_fd[0]);
+ close(ipc_fd[1]);
+
+ return -1;
+ }
+
+ auto config = get_config();
+
+ pid_t pid = 0;
+
+ if (!config->single_process) {
+ pid = fork();
+ }
+
+ if (pid == 0) {
+ // We are in new process now, update pid for logger.
+ log_config()->pid = getpid();
+
+ ev_loop_fork(EV_DEFAULT);
+
+ for (auto &addr : config->conn.listener.addrs) {
+ util::make_socket_closeonexec(addr.fd);
+ }
+
+#ifdef ENABLE_HTTP3
+ util::make_socket_closeonexec(quic_ipc_fd[0]);
+
+ for (auto &lwp : quic_lwps) {
+ util::make_socket_closeonexec(lwp.quic_ipc_fd);
+ }
+
+ for (auto &wp : worker_processes) {
+ util::make_socket_closeonexec(wp->quic_ipc_fd);
+ // Do not close quic_ipc_fd.
+ wp->quic_ipc_fd = -1;
+ }
+#endif // ENABLE_HTTP3
+
+ if (!config->single_process) {
+ close(worker_process_ready_ipc_fd[0]);
+ shutdown_worker_process_ready_ipc_watcher(EV_DEFAULT);
+
+ shutdown_signal_watchers(EV_DEFAULT);
+ }
+
+ // Remove all WorkerProcesses to stop any registered watcher on
+ // default loop.
+ worker_process_remove_all(EV_DEFAULT);
+
+ close_unused_inherited_addr(iaddrs);
+
+ shrpx_signal_set_worker_proc_ign_handler();
+
+ rv = shrpx_signal_unblock_all();
+ if (rv != 0) {
+ auto error = errno;
+ LOG(FATAL) << "Unblocking all signals failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+
+ if (config->single_process) {
+ exit(EXIT_FAILURE);
+ } else {
+ nghttp2_Exit(EXIT_FAILURE);
+ }
+ }
+
+ if (!config->single_process) {
+ close(ipc_fd[1]);
+#ifdef ENABLE_HTTP3
+ close(quic_ipc_fd[1]);
+#endif // ENABLE_HTTP3
+ }
+
+ WorkerProcessConfig wpconf{
+ .ipc_fd = ipc_fd[0],
+ .ready_ipc_fd = worker_process_ready_ipc_fd[1],
+#ifdef ENABLE_HTTP3
+ .cid_prefixes = cid_prefixes,
+ .quic_ipc_fd = quic_ipc_fd[0],
+ .quic_lingering_worker_processes = quic_lwps,
+#endif // ENABLE_HTTP3
+ };
+ rv = worker_process_event_loop(&wpconf);
+ if (rv != 0) {
+ LOG(FATAL) << "Worker process returned error";
+
+ if (config->single_process) {
+ exit(EXIT_FAILURE);
+ } else {
+ nghttp2_Exit(EXIT_FAILURE);
+ }
+ }
+
+ LOG(NOTICE) << "Worker process shutting down momentarily";
+
+ // call exit(...) instead of nghttp2_Exit to get leak sanitizer report
+ if (config->single_process) {
+ exit(EXIT_SUCCESS);
+ } else {
+ nghttp2_Exit(EXIT_SUCCESS);
+ }
+ }
+
+ // parent process
+ if (pid == -1) {
+ auto error = errno;
+ LOG(ERROR) << "Could not spawn worker process: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ }
+
+ rv = shrpx_signal_set(&oldset);
+ if (rv != 0) {
+ auto error = errno;
+ LOG(FATAL) << "Restoring signal mask failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+
+ exit(EXIT_FAILURE);
+ }
+
+ if (pid == -1) {
+ close(ipc_fd[0]);
+ close(ipc_fd[1]);
+#ifdef ENABLE_HTTP3
+ close(quic_ipc_fd[0]);
+ close(quic_ipc_fd[1]);
+#endif // ENABLE_HTTP3
+
+ return -1;
+ }
+
+ close(ipc_fd[0]);
+#ifdef ENABLE_HTTP3
+ close(quic_ipc_fd[0]);
+#endif // ENABLE_HTTP3
+
+ main_ipc_fd = ipc_fd[1];
+#ifdef ENABLE_HTTP3
+ wp_quic_ipc_fd = quic_ipc_fd[1];
+#endif // ENABLE_HTTP3
+
+ LOG(NOTICE) << "Worker process [" << pid << "] spawned";
+
+ return pid;
+}
+} // namespace
+
+namespace {
+int event_loop() {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+
+ shrpx_signal_set_main_proc_ign_handler();
+
+ auto config = mod_config();
+
+ if (config->daemon) {
+ if (call_daemon() == -1) {
+ auto error = errno;
+ LOG(FATAL) << "Failed to daemonize: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return -1;
+ }
+
+ // We get new PID after successful daemon().
+ mod_config()->pid = getpid();
+
+ // daemon redirects stderr file descriptor to /dev/null, so we
+ // need this.
+ redirect_stderr_to_errorlog(config->logging);
+ }
+
+ // update systemd PID tracking
+ shrpx_sd_notifyf(0, "MAINPID=%d\n", config->pid);
+
+ {
+ auto iaddrs = get_inherited_addr_from_env(config);
+
+ if (create_acceptor_socket(config, iaddrs) != 0) {
+ return -1;
+ }
+
+ close_unused_inherited_addr(iaddrs);
+ }
+
+ orig_pid = get_orig_pid_from_env();
+
+#ifdef ENABLE_HTTP3
+ inherited_quic_lingering_worker_processes =
+ get_inherited_quic_lingering_worker_process_from_env();
+#endif // ENABLE_HTTP3
+
+ auto loop = ev_default_loop(config->ev_loop_flags);
+
+ int ipc_fd = 0;
+#ifdef ENABLE_HTTP3
+ int quic_ipc_fd = 0;
+
+ auto quic_lwps = collect_quic_lingering_worker_processes();
+
+ std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>> cid_prefixes;
+
+ if (generate_cid_prefix(cid_prefixes, config) != 0) {
+ return -1;
+ }
+#endif // ENABLE_HTTP3
+
+ if (!config->single_process) {
+ start_signal_watchers(loop);
+ }
+
+ create_worker_process_ready_ipc_socket(worker_process_ready_ipc_fd);
+ start_worker_process_ready_ipc_watcher(loop);
+
+ auto pid = fork_worker_process(ipc_fd
+#ifdef ENABLE_HTTP3
+ ,
+ quic_ipc_fd
+#endif // ENABLE_HTTP3
+ ,
+ {}
+#ifdef ENABLE_HTTP3
+ ,
+ cid_prefixes, quic_lwps
+#endif // ENABLE_HTTP3
+ );
+
+ if (pid == -1) {
+ return -1;
+ }
+
+ ev_timer_init(&worker_process_grace_period_timer,
+ worker_process_grace_period_timercb, 0., 0.);
+
+ worker_process_add(std::make_unique<WorkerProcess>(loop, pid, ipc_fd
+#ifdef ENABLE_HTTP3
+ ,
+ quic_ipc_fd, cid_prefixes
+#endif // ENABLE_HTTP3
+ ));
+
+ // Write PID file when we are ready to accept connection from peer.
+ // This makes easier to write restart script for nghttpx. Because
+ // when we know that PID file is recreated, it means we can send
+ // QUIT signal to the old process to make it shutdown gracefully.
+ if (!config->pid_file.empty()) {
+ save_pid();
+ }
+
+ shrpx_sd_notifyf(0, "READY=1");
+
+ ev_run(loop, 0);
+
+ ev_timer_stop(loop, &worker_process_grace_period_timer);
+
+ shutdown_worker_process_ready_ipc_watcher(loop);
+
+ // config is now stale if reload has happened.
+ if (!get_config()->single_process) {
+ shutdown_signal_watchers(loop);
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+// Returns true if regular file or symbolic link |path| exists.
+bool conf_exists(const char *path) {
+ struct stat buf;
+ int rv = stat(path, &buf);
+ return rv == 0 && (buf.st_mode & (S_IFREG | S_IFLNK));
+}
+} // namespace
+
+namespace {
+constexpr auto DEFAULT_ALPN_LIST =
+ StringRef::from_lit("h2,h2-16,h2-14,http/1.1");
+} // namespace
+
+namespace {
+constexpr auto DEFAULT_TLS_MIN_PROTO_VERSION = StringRef::from_lit("TLSv1.2");
+#ifdef TLS1_3_VERSION
+constexpr auto DEFAULT_TLS_MAX_PROTO_VERSION = StringRef::from_lit("TLSv1.3");
+#else // !TLS1_3_VERSION
+constexpr auto DEFAULT_TLS_MAX_PROTO_VERSION = StringRef::from_lit("TLSv1.2");
+#endif // !TLS1_3_VERSION
+} // namespace
+
+namespace {
+constexpr auto DEFAULT_ACCESSLOG_FORMAT =
+ StringRef::from_lit(R"($remote_addr - - [$time_local] )"
+ R"("$request" $status $body_bytes_sent )"
+ R"("$http_referer" "$http_user_agent")");
+} // namespace
+
+namespace {
+void fill_default_config(Config *config) {
+ config->num_worker = 1;
+ config->conf_path = StringRef::from_lit("/etc/nghttpx/nghttpx.conf");
+ config->pid = getpid();
+
+#ifdef NOTHREADS
+ config->single_thread = true;
+#endif // NOTHREADS
+
+ if (ev_supported_backends() & ~ev_recommended_backends() & EVBACKEND_KQUEUE) {
+ config->ev_loop_flags = ev_recommended_backends() | EVBACKEND_KQUEUE;
+ }
+
+ auto &tlsconf = config->tls;
+ {
+ auto &ticketconf = tlsconf.ticket;
+ {
+ auto &memcachedconf = ticketconf.memcached;
+ memcachedconf.max_retry = 3;
+ memcachedconf.max_fail = 2;
+ memcachedconf.interval = 10_min;
+ memcachedconf.family = AF_UNSPEC;
+ }
+
+ auto &session_cacheconf = tlsconf.session_cache;
+ {
+ auto &memcachedconf = session_cacheconf.memcached;
+ memcachedconf.family = AF_UNSPEC;
+ }
+
+ ticketconf.cipher = EVP_aes_128_cbc();
+ }
+
+ {
+ auto &ocspconf = tlsconf.ocsp;
+ // ocsp update interval = 14400 secs = 4 hours, borrowed from h2o
+ ocspconf.update_interval = 4_h;
+ ocspconf.fetch_ocsp_response_file =
+ StringRef::from_lit(PKGDATADIR "/fetch-ocsp-response");
+ }
+
+ {
+ auto &dyn_recconf = tlsconf.dyn_rec;
+ dyn_recconf.warmup_threshold = 1_m;
+ dyn_recconf.idle_timeout = 1_s;
+ }
+
+ tlsconf.session_timeout = std::chrono::hours(12);
+ tlsconf.ciphers = StringRef::from_lit(nghttp2::tls::DEFAULT_CIPHER_LIST);
+ tlsconf.tls13_ciphers =
+ StringRef::from_lit(nghttp2::tls::DEFAULT_TLS13_CIPHER_LIST);
+ tlsconf.client.ciphers =
+ StringRef::from_lit(nghttp2::tls::DEFAULT_CIPHER_LIST);
+ tlsconf.client.tls13_ciphers =
+ StringRef::from_lit(nghttp2::tls::DEFAULT_TLS13_CIPHER_LIST);
+ tlsconf.min_proto_version =
+ tls::proto_version_from_string(DEFAULT_TLS_MIN_PROTO_VERSION);
+ tlsconf.max_proto_version =
+ tls::proto_version_from_string(DEFAULT_TLS_MAX_PROTO_VERSION);
+ tlsconf.max_early_data = 16_k;
+ tlsconf.ecdh_curves = StringRef::from_lit("X25519:P-256:P-384:P-521");
+
+ auto &httpconf = config->http;
+ httpconf.server_name = StringRef::from_lit("nghttpx");
+ httpconf.no_host_rewrite = true;
+ httpconf.request_header_field_buffer = 64_k;
+ httpconf.max_request_header_fields = 100;
+ httpconf.response_header_field_buffer = 64_k;
+ httpconf.max_response_header_fields = 500;
+ httpconf.redirect_https_port = StringRef::from_lit("443");
+ httpconf.max_requests = std::numeric_limits<size_t>::max();
+ httpconf.xfp.add = true;
+ httpconf.xfp.strip_incoming = true;
+ httpconf.early_data.strip_incoming = true;
+
+ auto &http2conf = config->http2;
+ {
+ auto &upstreamconf = http2conf.upstream;
+
+ {
+ auto &timeoutconf = upstreamconf.timeout;
+ timeoutconf.settings = 10_s;
+ }
+
+ // window size for HTTP/2 upstream connection per stream. 2**16-1
+ // = 64KiB-1, which is HTTP/2 default.
+ upstreamconf.window_size = 64_k - 1;
+ // HTTP/2 has connection-level flow control. The default window
+ // size for HTTP/2 is 64KiB - 1.
+ upstreamconf.connection_window_size = 64_k - 1;
+ upstreamconf.max_concurrent_streams = 100;
+
+ upstreamconf.encoder_dynamic_table_size = 4_k;
+ upstreamconf.decoder_dynamic_table_size = 4_k;
+
+ nghttp2_option_new(&upstreamconf.option);
+ nghttp2_option_set_no_auto_window_update(upstreamconf.option, 1);
+ nghttp2_option_set_no_recv_client_magic(upstreamconf.option, 1);
+ nghttp2_option_set_max_deflate_dynamic_table_size(
+ upstreamconf.option, upstreamconf.encoder_dynamic_table_size);
+ nghttp2_option_set_server_fallback_rfc7540_priorities(upstreamconf.option,
+ 1);
+ nghttp2_option_set_builtin_recv_extension_type(upstreamconf.option,
+ NGHTTP2_PRIORITY_UPDATE);
+
+ // For API endpoint, we enable automatic window update. This is
+ // because we are a sink.
+ nghttp2_option_new(&upstreamconf.alt_mode_option);
+ nghttp2_option_set_no_recv_client_magic(upstreamconf.alt_mode_option, 1);
+ nghttp2_option_set_max_deflate_dynamic_table_size(
+ upstreamconf.alt_mode_option, upstreamconf.encoder_dynamic_table_size);
+ }
+
+ http2conf.timeout.stream_write = 1_min;
+
+ {
+ auto &downstreamconf = http2conf.downstream;
+
+ {
+ auto &timeoutconf = downstreamconf.timeout;
+ timeoutconf.settings = 10_s;
+ }
+
+ downstreamconf.window_size = 64_k - 1;
+ downstreamconf.connection_window_size = (1u << 31) - 1;
+ downstreamconf.max_concurrent_streams = 100;
+
+ downstreamconf.encoder_dynamic_table_size = 4_k;
+ downstreamconf.decoder_dynamic_table_size = 4_k;
+
+ nghttp2_option_new(&downstreamconf.option);
+ nghttp2_option_set_no_auto_window_update(downstreamconf.option, 1);
+ nghttp2_option_set_peer_max_concurrent_streams(downstreamconf.option, 100);
+ nghttp2_option_set_max_deflate_dynamic_table_size(
+ downstreamconf.option, downstreamconf.encoder_dynamic_table_size);
+ }
+
+#ifdef ENABLE_HTTP3
+ auto &quicconf = config->quic;
+ {
+ auto &upstreamconf = quicconf.upstream;
+
+ {
+ auto &timeoutconf = upstreamconf.timeout;
+ timeoutconf.idle = 30_s;
+ }
+
+ auto &bpfconf = quicconf.bpf;
+ bpfconf.prog_file = StringRef::from_lit(PKGLIBDIR "/reuseport_kern.o");
+
+ upstreamconf.congestion_controller = NGTCP2_CC_ALGO_CUBIC;
+
+ upstreamconf.initial_rtt =
+ static_cast<ev_tstamp>(NGTCP2_DEFAULT_INITIAL_RTT) / NGTCP2_SECONDS;
+ }
+
+ if (RAND_bytes(quicconf.server_id.data(), quicconf.server_id.size()) != 1) {
+ assert(0);
+ abort();
+ }
+
+ auto &http3conf = config->http3;
+ {
+ auto &upstreamconf = http3conf.upstream;
+
+ upstreamconf.max_concurrent_streams = 100;
+ upstreamconf.window_size = 256_k;
+ upstreamconf.connection_window_size = 1_m;
+ upstreamconf.max_window_size = 6_m;
+ upstreamconf.max_connection_window_size = 8_m;
+ }
+#endif // ENABLE_HTTP3
+
+ auto &loggingconf = config->logging;
+ {
+ auto &accessconf = loggingconf.access;
+ accessconf.format =
+ parse_log_format(config->balloc, DEFAULT_ACCESSLOG_FORMAT);
+
+ auto &errorconf = loggingconf.error;
+ errorconf.file = StringRef::from_lit("/dev/stderr");
+ }
+
+ loggingconf.syslog_facility = LOG_DAEMON;
+ loggingconf.severity = NOTICE;
+
+ auto &connconf = config->conn;
+ {
+ auto &listenerconf = connconf.listener;
+ {
+ // Default accept() backlog
+ listenerconf.backlog = 65536;
+ listenerconf.timeout.sleep = 30_s;
+ }
+ }
+
+ {
+ auto &upstreamconf = connconf.upstream;
+ {
+ auto &timeoutconf = upstreamconf.timeout;
+ // Read timeout for HTTP2 upstream connection
+ timeoutconf.http2_read = 3_min;
+
+ // Read timeout for HTTP3 upstream connection
+ timeoutconf.http3_read = 3_min;
+
+ // Read timeout for non-HTTP2 upstream connection
+ timeoutconf.read = 1_min;
+
+ // Write timeout for HTTP2/non-HTTP2 upstream connection
+ timeoutconf.write = 30_s;
+
+ // Keep alive timeout for HTTP/1 upstream connection
+ timeoutconf.idle_read = 1_min;
+ }
+ }
+
+ {
+ connconf.downstream = std::make_shared<DownstreamConfig>();
+ auto &downstreamconf = *connconf.downstream;
+ {
+ auto &timeoutconf = downstreamconf.timeout;
+ // Read/Write timeouts for downstream connection
+ timeoutconf.read = 1_min;
+ timeoutconf.write = 30_s;
+ // Timeout for pooled (idle) connections
+ timeoutconf.idle_read = 2_s;
+ timeoutconf.connect = 30_s;
+ timeoutconf.max_backoff = 120_s;
+ }
+
+ downstreamconf.connections_per_host = 8;
+ downstreamconf.request_buffer_size = 16_k;
+ downstreamconf.response_buffer_size = 128_k;
+ downstreamconf.family = AF_UNSPEC;
+ }
+
+ auto &apiconf = config->api;
+ apiconf.max_request_body = 32_m;
+
+ auto &dnsconf = config->dns;
+ {
+ auto &timeoutconf = dnsconf.timeout;
+ timeoutconf.cache = 10_s;
+ timeoutconf.lookup = 5_s;
+ }
+ dnsconf.max_try = 2;
+}
+
+} // namespace
+
+namespace {
+void print_version(std::ostream &out) {
+ out << "nghttpx nghttp2/" NGHTTP2_VERSION
+#ifdef ENABLE_HTTP3
+ " ngtcp2/" NGTCP2_VERSION " nghttp3/" NGHTTP3_VERSION
+#endif // ENABLE_HTTP3
+ << std::endl;
+}
+} // namespace
+
+namespace {
+void print_usage(std::ostream &out) {
+ out << R"(Usage: nghttpx [OPTIONS]... [<PRIVATE_KEY> <CERT>]
+A reverse proxy for HTTP/3, HTTP/2, and HTTP/1.)"
+ << std::endl;
+}
+} // namespace
+
+namespace {
+void print_help(std::ostream &out) {
+ auto config = get_config();
+
+ print_usage(out);
+ out << R"(
+ <PRIVATE_KEY>
+ Set path to server's private key. Required unless
+ "no-tls" parameter is used in --frontend option.
+ <CERT> Set path to server's certificate. Required unless
+ "no-tls" parameter is used in --frontend option. To
+ make OCSP stapling work, this must be an absolute path.
+
+Options:
+ The options are categorized into several groups.
+
+Connections:
+ -b, --backend=(<HOST>,<PORT>|unix:<PATH>)[;[<PATTERN>[:...]][[;<PARAM>]...]
+
+ Set backend host and port. The multiple backend
+ addresses are accepted by repeating this option. UNIX
+ domain socket can be specified by prefixing path name
+ with "unix:" (e.g., unix:/var/run/backend.sock).
+
+ Optionally, if <PATTERN>s are given, the backend address
+ is only used if request matches the pattern. The
+ pattern matching is closely designed to ServeMux in
+ net/http package of Go programming language. <PATTERN>
+ consists of path, host + path or just host. The path
+ must start with "/". If it ends with "/", it matches
+ all request path in its subtree. To deal with the
+ request to the directory without trailing slash, the
+ path which ends with "/" also matches the request path
+ which only lacks trailing '/' (e.g., path "/foo/"
+ matches request path "/foo"). If it does not end with
+ "/", it performs exact match against the request path.
+ If host is given, it performs a match against the
+ request host. For a request received on the frontend
+ listener with "sni-fwd" parameter enabled, SNI host is
+ used instead of a request host. If host alone is given,
+ "/" is appended to it, so that it matches all request
+ paths under the host (e.g., specifying "nghttp2.org"
+ equals to "nghttp2.org/"). CONNECT method is treated
+ specially. It does not have path, and we don't allow
+ empty path. To workaround this, we assume that CONNECT
+ method has "/" as path.
+
+ Patterns with host take precedence over patterns with
+ just path. Then, longer patterns take precedence over
+ shorter ones.
+
+ Host can include "*" in the left most position to
+ indicate wildcard match (only suffix match is done).
+ The "*" must match at least one character. For example,
+ host pattern "*.nghttp2.org" matches against
+ "www.nghttp2.org" and "git.ngttp2.org", but does not
+ match against "nghttp2.org". The exact hosts match
+ takes precedence over the wildcard hosts match.
+
+ If path part ends with "*", it is treated as wildcard
+ path. The wildcard path behaves differently from the
+ normal path. For normal path, match is made around the
+ boundary of path component separator,"/". On the other
+ hand, the wildcard path does not take into account the
+ path component separator. All paths which include the
+ wildcard path without last "*" as prefix, and are
+ strictly longer than wildcard path without last "*" are
+ matched. "*" must match at least one character. For
+ example, the pattern "/foo*" matches "/foo/" and
+ "/foobar". But it does not match "/foo", or "/fo".
+
+ If <PATTERN> is omitted or empty string, "/" is used as
+ pattern, which matches all request paths (catch-all
+ pattern). The catch-all backend must be given.
+
+ When doing a match, nghttpx made some normalization to
+ pattern, request host and path. For host part, they are
+ converted to lower case. For path part, percent-encoded
+ unreserved characters defined in RFC 3986 are decoded,
+ and any dot-segments (".." and ".") are resolved and
+ removed.
+
+ For example, -b'127.0.0.1,8080;nghttp2.org/httpbin/'
+ matches the request host "nghttp2.org" and the request
+ path "/httpbin/get", but does not match the request host
+ "nghttp2.org" and the request path "/index.html".
+
+ The multiple <PATTERN>s can be specified, delimiting
+ them by ":". Specifying
+ -b'127.0.0.1,8080;nghttp2.org:www.nghttp2.org' has the
+ same effect to specify -b'127.0.0.1,8080;nghttp2.org'
+ and -b'127.0.0.1,8080;www.nghttp2.org'.
+
+ The backend addresses sharing same <PATTERN> are grouped
+ together forming load balancing group.
+
+ Several parameters <PARAM> are accepted after <PATTERN>.
+ The parameters are delimited by ";". The available
+ parameters are: "proto=<PROTO>", "tls",
+ "sni=<SNI_HOST>", "fall=<N>", "rise=<N>",
+ "affinity=<METHOD>", "dns", "redirect-if-not-tls",
+ "upgrade-scheme", "mruby=<PATH>",
+ "read-timeout=<DURATION>", "write-timeout=<DURATION>",
+ "group=<GROUP>", "group-weight=<N>", "weight=<N>", and
+ "dnf". The parameter consists of keyword, and
+ optionally followed by "=" and value. For example, the
+ parameter "proto=h2" consists of the keyword "proto" and
+ value "h2". The parameter "tls" consists of the keyword
+ "tls" without value. Each parameter is described as
+ follows.
+
+ The backend application protocol can be specified using
+ optional "proto" parameter, and in the form of
+ "proto=<PROTO>". <PROTO> should be one of the following
+ list without quotes: "h2", "http/1.1". The default
+ value of <PROTO> is "http/1.1". Note that usually "h2"
+ refers to HTTP/2 over TLS. But in this option, it may
+ mean HTTP/2 over cleartext TCP unless "tls" keyword is
+ used (see below).
+
+ TLS can be enabled by specifying optional "tls"
+ parameter. TLS is not enabled by default.
+
+ With "sni=<SNI_HOST>" parameter, it can override the TLS
+ SNI field value with given <SNI_HOST>. This will
+ default to the backend <HOST> name
+
+ The feature to detect whether backend is online or
+ offline can be enabled using optional "fall" and "rise"
+ parameters. Using "fall=<N>" parameter, if nghttpx
+ cannot connect to a this backend <N> times in a row,
+ this backend is assumed to be offline, and it is
+ excluded from load balancing. If <N> is 0, this backend
+ never be excluded from load balancing whatever times
+ nghttpx cannot connect to it, and this is the default.
+ There is also "rise=<N>" parameter. After backend was
+ excluded from load balancing group, nghttpx periodically
+ attempts to make a connection to the failed backend, and
+ if the connection is made successfully <N> times in a
+ row, the backend is assumed to be online, and it is now
+ eligible for load balancing target. If <N> is 0, a
+ backend is permanently offline, once it goes in that
+ state, and this is the default behaviour.
+
+ The session affinity is enabled using
+ "affinity=<METHOD>" parameter. If "ip" is given in
+ <METHOD>, client IP based session affinity is enabled.
+ If "cookie" is given in <METHOD>, cookie based session
+ affinity is enabled. If "none" is given in <METHOD>,
+ session affinity is disabled, and this is the default.
+ The session affinity is enabled per <PATTERN>. If at
+ least one backend has "affinity" parameter, and its
+ <METHOD> is not "none", session affinity is enabled for
+ all backend servers sharing the same <PATTERN>. It is
+ advised to set "affinity" parameter to all backend
+ explicitly if session affinity is desired. The session
+ affinity may break if one of the backend gets
+ unreachable, or backend settings are reloaded or
+ replaced by API.
+
+ If "affinity=cookie" is used, the additional
+ configuration is required.
+ "affinity-cookie-name=<NAME>" must be used to specify a
+ name of cookie to use. Optionally,
+ "affinity-cookie-path=<PATH>" can be used to specify a
+ path which cookie is applied. The optional
+ "affinity-cookie-secure=<SECURE>" controls the Secure
+ attribute of a cookie. The default value is "auto", and
+ the Secure attribute is determined by a request scheme.
+ If a request scheme is "https", then Secure attribute is
+ set. Otherwise, it is not set. If <SECURE> is "yes",
+ the Secure attribute is always set. If <SECURE> is
+ "no", the Secure attribute is always omitted.
+ "affinity-cookie-stickiness=<STICKINESS>" controls
+ stickiness of this affinity. If <STICKINESS> is
+ "loose", removing or adding a backend server might break
+ the affinity and the request might be forwarded to a
+ different backend server. If <STICKINESS> is "strict",
+ removing the designated backend server breaks affinity,
+ but adding new backend server does not cause breakage.
+ If the designated backend server becomes unavailable,
+ new backend server is chosen as if the request does not
+ have an affinity cookie. <STICKINESS> defaults to
+ "loose".
+
+ By default, name resolution of backend host name is done
+ at start up, or reloading configuration. If "dns"
+ parameter is given, name resolution takes place
+ dynamically. This is useful if backend address changes
+ frequently. If "dns" is given, name resolution of
+ backend host name at start up, or reloading
+ configuration is skipped.
+
+ If "redirect-if-not-tls" parameter is used, the matched
+ backend requires that frontend connection is TLS
+ encrypted. If it isn't, nghttpx responds to the request
+ with 308 status code, and https URI the client should
+ use instead is included in Location header field. The
+ port number in redirect URI is 443 by default, and can
+ be changed using --redirect-https-port option. If at
+ least one backend has "redirect-if-not-tls" parameter,
+ this feature is enabled for all backend servers sharing
+ the same <PATTERN>. It is advised to set
+ "redirect-if-no-tls" parameter to all backends
+ explicitly if this feature is desired.
+
+ If "upgrade-scheme" parameter is used along with "tls"
+ parameter, HTTP/2 :scheme pseudo header field is changed
+ to "https" from "http" when forwarding a request to this
+ particular backend. This is a workaround for a backend
+ server which requires "https" :scheme pseudo header
+ field on TLS encrypted connection.
+
+ "mruby=<PATH>" parameter specifies a path to mruby
+ script file which is invoked when this pattern is
+ matched. All backends which share the same pattern must
+ have the same mruby path.
+
+ "read-timeout=<DURATION>" and "write-timeout=<DURATION>"
+ parameters specify the read and write timeout of the
+ backend connection when this pattern is matched. All
+ backends which share the same pattern must have the same
+ timeouts. If these timeouts are entirely omitted for a
+ pattern, --backend-read-timeout and
+ --backend-write-timeout are used.
+
+ "group=<GROUP>" parameter specifies the name of group
+ this backend address belongs to. By default, it belongs
+ to the unnamed default group. The name of group is
+ unique per pattern. "group-weight=<N>" parameter
+ specifies the weight of the group. The higher weight
+ gets more frequently selected by the load balancing
+ algorithm. <N> must be [1, 256] inclusive. The weight
+ 8 has 4 times more weight than 2. <N> must be the same
+ for all addresses which share the same <GROUP>. If
+ "group-weight" is omitted in an address, but the other
+ address which belongs to the same group specifies
+ "group-weight", its weight is used. If no
+ "group-weight" is specified for all addresses, the
+ weight of a group becomes 1. "group" and "group-weight"
+ are ignored if session affinity is enabled.
+
+ "weight=<N>" parameter specifies the weight of the
+ backend address inside a group which this address
+ belongs to. The higher weight gets more frequently
+ selected by the load balancing algorithm. <N> must be
+ [1, 256] inclusive. The weight 8 has 4 times more
+ weight than weight 2. If this parameter is omitted,
+ weight becomes 1. "weight" is ignored if session
+ affinity is enabled.
+
+ If "dnf" parameter is specified, an incoming request is
+ not forwarded to a backend and just consumed along with
+ the request body (actually a backend server never be
+ contacted). It is expected that the HTTP response is
+ generated by mruby script (see "mruby=<PATH>" parameter
+ above). "dnf" is an abbreviation of "do not forward".
+
+ Since ";" and ":" are used as delimiter, <PATTERN> must
+ not contain these characters. In order to include ":"
+ in <PATTERN>, one has to specify "%3A" (which is
+ percent-encoded from of ":") instead. Since ";" has
+ special meaning in shell, the option value must be
+ quoted.
+
+ Default: )"
+ << DEFAULT_DOWNSTREAM_HOST << "," << DEFAULT_DOWNSTREAM_PORT << R"(
+ -f, --frontend=(<HOST>,<PORT>|unix:<PATH>)[[;<PARAM>]...]
+ Set frontend host and port. If <HOST> is '*', it
+ assumes all addresses including both IPv4 and IPv6.
+ UNIX domain socket can be specified by prefixing path
+ name with "unix:" (e.g., unix:/var/run/nghttpx.sock).
+ This option can be used multiple times to listen to
+ multiple addresses.
+
+ This option can take 0 or more parameters, which are
+ described below. Note that "api" and "healthmon"
+ parameters are mutually exclusive.
+
+ Optionally, TLS can be disabled by specifying "no-tls"
+ parameter. TLS is enabled by default.
+
+ If "sni-fwd" parameter is used, when performing a match
+ to select a backend server, SNI host name received from
+ the client is used instead of the request host. See
+ --backend option about the pattern match.
+
+ To make this frontend as API endpoint, specify "api"
+ parameter. This is disabled by default. It is
+ important to limit the access to the API frontend.
+ Otherwise, someone may change the backend server, and
+ break your services, or expose confidential information
+ to the outside the world.
+
+ To make this frontend as health monitor endpoint,
+ specify "healthmon" parameter. This is disabled by
+ default. Any requests which come through this address
+ are replied with 200 HTTP status, without no body.
+
+ To accept PROXY protocol version 1 and 2 on frontend
+ connection, specify "proxyproto" parameter. This is
+ disabled by default.
+
+ To receive HTTP/3 (QUIC) traffic, specify "quic"
+ parameter. It makes nghttpx listen on UDP port rather
+ than TCP port. UNIX domain socket, "api", and
+ "healthmon" parameters cannot be used with "quic"
+ parameter.
+
+ Default: *,3000
+ --backlog=<N>
+ Set listen backlog size.
+ Default: )"
+ << config->conn.listener.backlog << R"(
+ --backend-address-family=(auto|IPv4|IPv6)
+ Specify address family of backend connections. If
+ "auto" is given, both IPv4 and IPv6 are considered. If
+ "IPv4" is given, only IPv4 address is considered. If
+ "IPv6" is given, only IPv6 address is considered.
+ Default: auto
+ --backend-http-proxy-uri=<URI>
+ Specify proxy URI in the form
+ http://[<USER>:<PASS>@]<PROXY>:<PORT>. If a proxy
+ requires authentication, specify <USER> and <PASS>.
+ Note that they must be properly percent-encoded. This
+ proxy is used when the backend connection is HTTP/2.
+ First, make a CONNECT request to the proxy and it
+ connects to the backend on behalf of nghttpx. This
+ forms tunnel. After that, nghttpx performs SSL/TLS
+ handshake with the downstream through the tunnel. The
+ timeouts when connecting and making CONNECT request can
+ be specified by --backend-read-timeout and
+ --backend-write-timeout options.
+
+Performance:
+ -n, --workers=<N>
+ Set the number of worker threads.
+ Default: )"
+ << config->num_worker << R"(
+ --single-thread
+ Run everything in one thread inside the worker process.
+ This feature is provided for better debugging
+ experience, or for the platforms which lack thread
+ support. If threading is disabled, this option is
+ always enabled.
+ --read-rate=<SIZE>
+ Set maximum average read rate on frontend connection.
+ Setting 0 to this option means read rate is unlimited.
+ Default: )"
+ << config->conn.upstream.ratelimit.read.rate << R"(
+ --read-burst=<SIZE>
+ Set maximum read burst size on frontend connection.
+ Setting 0 to this option means read burst size is
+ unlimited.
+ Default: )"
+ << config->conn.upstream.ratelimit.read.burst << R"(
+ --write-rate=<SIZE>
+ Set maximum average write rate on frontend connection.
+ Setting 0 to this option means write rate is unlimited.
+ Default: )"
+ << config->conn.upstream.ratelimit.write.rate << R"(
+ --write-burst=<SIZE>
+ Set maximum write burst size on frontend connection.
+ Setting 0 to this option means write burst size is
+ unlimited.
+ Default: )"
+ << config->conn.upstream.ratelimit.write.burst << R"(
+ --worker-read-rate=<SIZE>
+ Set maximum average read rate on frontend connection per
+ worker. Setting 0 to this option means read rate is
+ unlimited. Not implemented yet.
+ Default: 0
+ --worker-read-burst=<SIZE>
+ Set maximum read burst size on frontend connection per
+ worker. Setting 0 to this option means read burst size
+ is unlimited. Not implemented yet.
+ Default: 0
+ --worker-write-rate=<SIZE>
+ Set maximum average write rate on frontend connection
+ per worker. Setting 0 to this option means write rate
+ is unlimited. Not implemented yet.
+ Default: 0
+ --worker-write-burst=<SIZE>
+ Set maximum write burst size on frontend connection per
+ worker. Setting 0 to this option means write burst size
+ is unlimited. Not implemented yet.
+ Default: 0
+ --worker-frontend-connections=<N>
+ Set maximum number of simultaneous connections frontend
+ accepts. Setting 0 means unlimited.
+ Default: )"
+ << config->conn.upstream.worker_connections << R"(
+ --backend-connections-per-host=<N>
+ Set maximum number of backend concurrent connections
+ (and/or streams in case of HTTP/2) per origin host.
+ This option is meaningful when --http2-proxy option is
+ used. The origin host is determined by authority
+ portion of request URI (or :authority header field for
+ HTTP/2). To limit the number of connections per
+ frontend for default mode, use
+ --backend-connections-per-frontend.
+ Default: )"
+ << config->conn.downstream->connections_per_host << R"(
+ --backend-connections-per-frontend=<N>
+ Set maximum number of backend concurrent connections
+ (and/or streams in case of HTTP/2) per frontend. This
+ option is only used for default mode. 0 means
+ unlimited. To limit the number of connections per host
+ with --http2-proxy option, use
+ --backend-connections-per-host.
+ Default: )"
+ << config->conn.downstream->connections_per_frontend << R"(
+ --rlimit-nofile=<N>
+ Set maximum number of open files (RLIMIT_NOFILE) to <N>.
+ If 0 is given, nghttpx does not set the limit.
+ Default: )"
+ << config->rlimit_nofile << R"(
+ --rlimit-memlock=<N>
+ Set maximum number of bytes of memory that may be locked
+ into RAM. If 0 is given, nghttpx does not set the
+ limit.
+ Default: )"
+ << config->rlimit_memlock << R"(
+ --backend-request-buffer=<SIZE>
+ Set buffer size used to store backend request.
+ Default: )"
+ << util::utos_unit(config->conn.downstream->request_buffer_size) << R"(
+ --backend-response-buffer=<SIZE>
+ Set buffer size used to store backend response.
+ Default: )"
+ << util::utos_unit(config->conn.downstream->response_buffer_size) << R"(
+ --fastopen=<N>
+ Enables "TCP Fast Open" for the listening socket and
+ limits the maximum length for the queue of connections
+ that have not yet completed the three-way handshake. If
+ value is 0 then fast open is disabled.
+ Default: )"
+ << config->conn.listener.fastopen << R"(
+ --no-kqueue Don't use kqueue. This option is only applicable for
+ the platforms which have kqueue. For other platforms,
+ this option will be simply ignored.
+
+Timeout:
+ --frontend-http2-read-timeout=<DURATION>
+ Specify read timeout for HTTP/2 frontend connection.
+ Default: )"
+ << util::duration_str(config->conn.upstream.timeout.http2_read) << R"(
+ --frontend-http3-read-timeout=<DURATION>
+ Specify read timeout for HTTP/3 frontend connection.
+ Default: )"
+ << util::duration_str(config->conn.upstream.timeout.http3_read) << R"(
+ --frontend-read-timeout=<DURATION>
+ Specify read timeout for HTTP/1.1 frontend connection.
+ Default: )"
+ << util::duration_str(config->conn.upstream.timeout.read) << R"(
+ --frontend-write-timeout=<DURATION>
+ Specify write timeout for all frontend connections.
+ Default: )"
+ << util::duration_str(config->conn.upstream.timeout.write) << R"(
+ --frontend-keep-alive-timeout=<DURATION>
+ Specify keep-alive timeout for frontend HTTP/1
+ connection.
+ Default: )"
+ << util::duration_str(config->conn.upstream.timeout.idle_read) << R"(
+ --stream-read-timeout=<DURATION>
+ Specify read timeout for HTTP/2 streams. 0 means no
+ timeout.
+ Default: )"
+ << util::duration_str(config->http2.timeout.stream_read) << R"(
+ --stream-write-timeout=<DURATION>
+ Specify write timeout for HTTP/2 streams. 0 means no
+ timeout.
+ Default: )"
+ << util::duration_str(config->http2.timeout.stream_write) << R"(
+ --backend-read-timeout=<DURATION>
+ Specify read timeout for backend connection.
+ Default: )"
+ << util::duration_str(config->conn.downstream->timeout.read) << R"(
+ --backend-write-timeout=<DURATION>
+ Specify write timeout for backend connection.
+ Default: )"
+ << util::duration_str(config->conn.downstream->timeout.write) << R"(
+ --backend-connect-timeout=<DURATION>
+ Specify timeout before establishing TCP connection to
+ backend.
+ Default: )"
+ << util::duration_str(config->conn.downstream->timeout.connect) << R"(
+ --backend-keep-alive-timeout=<DURATION>
+ Specify keep-alive timeout for backend HTTP/1
+ connection.
+ Default: )"
+ << util::duration_str(config->conn.downstream->timeout.idle_read) << R"(
+ --listener-disable-timeout=<DURATION>
+ After accepting connection failed, connection listener
+ is disabled for a given amount of time. Specifying 0
+ disables this feature.
+ Default: )"
+ << util::duration_str(config->conn.listener.timeout.sleep) << R"(
+ --frontend-http2-setting-timeout=<DURATION>
+ Specify timeout before SETTINGS ACK is received from
+ client.
+ Default: )"
+ << util::duration_str(config->http2.upstream.timeout.settings) << R"(
+ --backend-http2-settings-timeout=<DURATION>
+ Specify timeout before SETTINGS ACK is received from
+ backend server.
+ Default: )"
+ << util::duration_str(config->http2.downstream.timeout.settings) << R"(
+ --backend-max-backoff=<DURATION>
+ Specify maximum backoff interval. This is used when
+ doing health check against offline backend (see "fail"
+ parameter in --backend option). It is also used to
+ limit the maximum interval to temporarily disable
+ backend when nghttpx failed to connect to it. These
+ intervals are calculated using exponential backoff, and
+ consecutive failed attempts increase the interval. This
+ option caps its maximum value.
+ Default: )"
+ << util::duration_str(config->conn.downstream->timeout.max_backoff) << R"(
+
+SSL/TLS:
+ --ciphers=<SUITE>
+ Set allowed cipher list for frontend connection. The
+ format of the string is described in OpenSSL ciphers(1).
+ This option sets cipher suites for TLSv1.2 or earlier.
+ Use --tls13-ciphers for TLSv1.3.
+ Default: )"
+ << config->tls.ciphers << R"(
+ --tls13-ciphers=<SUITE>
+ Set allowed cipher list for frontend connection. The
+ format of the string is described in OpenSSL ciphers(1).
+ This option sets cipher suites for TLSv1.3. Use
+ --ciphers for TLSv1.2 or earlier.
+ Default: )"
+ << config->tls.tls13_ciphers << R"(
+ --client-ciphers=<SUITE>
+ Set allowed cipher list for backend connection. The
+ format of the string is described in OpenSSL ciphers(1).
+ This option sets cipher suites for TLSv1.2 or earlier.
+ Use --tls13-client-ciphers for TLSv1.3.
+ Default: )"
+ << config->tls.client.ciphers << R"(
+ --tls13-client-ciphers=<SUITE>
+ Set allowed cipher list for backend connection. The
+ format of the string is described in OpenSSL ciphers(1).
+ This option sets cipher suites for TLSv1.3. Use
+ --tls13-client-ciphers for TLSv1.2 or earlier.
+ Default: )"
+ << config->tls.client.tls13_ciphers << R"(
+ --ecdh-curves=<LIST>
+ Set supported curve list for frontend connections.
+ <LIST> is a colon separated list of curve NID or names
+ in the preference order. The supported curves depend on
+ the linked OpenSSL library. This function requires
+ OpenSSL >= 1.0.2.
+ Default: )"
+ << config->tls.ecdh_curves << R"(
+ -k, --insecure
+ Don't verify backend server's certificate if TLS is
+ enabled for backend connections.
+ --cacert=<PATH>
+ Set path to trusted CA certificate file. It is used in
+ backend TLS connections to verify peer's certificate.
+ It is also used to verify OCSP response from the script
+ set by --fetch-ocsp-response-file. The file must be in
+ PEM format. It can contain multiple certificates. If
+ the linked OpenSSL is configured to load system wide
+ certificates, they are loaded at startup regardless of
+ this option.
+ --private-key-passwd-file=<PATH>
+ Path to file that contains password for the server's
+ private key. If none is given and the private key is
+ password protected it'll be requested interactively.
+ --subcert=<KEYPATH>:<CERTPATH>[[;<PARAM>]...]
+ Specify additional certificate and private key file.
+ nghttpx will choose certificates based on the hostname
+ indicated by client using TLS SNI extension. If nghttpx
+ is built with OpenSSL >= 1.0.2, the shared elliptic
+ curves (e.g., P-256) between client and server are also
+ taken into consideration. This allows nghttpx to send
+ ECDSA certificate to modern clients, while sending RSA
+ based certificate to older clients. This option can be
+ used multiple times. To make OCSP stapling work,
+ <CERTPATH> must be absolute path.
+
+ Additional parameter can be specified in <PARAM>. The
+ available <PARAM> is "sct-dir=<DIR>".
+
+ "sct-dir=<DIR>" specifies the path to directory which
+ contains *.sct files for TLS
+ signed_certificate_timestamp extension (RFC 6962). This
+ feature requires OpenSSL >= 1.0.2. See also
+ --tls-sct-dir option.
+ --dh-param-file=<PATH>
+ Path to file that contains DH parameters in PEM format.
+ Without this option, DHE cipher suites are not
+ available.
+ --alpn-list=<LIST>
+ Comma delimited list of ALPN protocol identifier sorted
+ in the order of preference. That means most desirable
+ protocol comes first. The parameter must be delimited
+ by a single comma only and any white spaces are treated
+ as a part of protocol string.
+ Default: )"
+ << DEFAULT_ALPN_LIST
+ << R"(
+ --verify-client
+ Require and verify client certificate.
+ --verify-client-cacert=<PATH>
+ Path to file that contains CA certificates to verify
+ client certificate. The file must be in PEM format. It
+ can contain multiple certificates.
+ --verify-client-tolerate-expired
+ Accept expired client certificate. Operator should
+ handle the expired client certificate by some means
+ (e.g., mruby script). Otherwise, this option might
+ cause a security risk.
+ --client-private-key-file=<PATH>
+ Path to file that contains client private key used in
+ backend client authentication.
+ --client-cert-file=<PATH>
+ Path to file that contains client certificate used in
+ backend client authentication.
+ --tls-min-proto-version=<VER>
+ Specify minimum SSL/TLS protocol. The name matching is
+ done in case-insensitive manner. The versions between
+ --tls-min-proto-version and --tls-max-proto-version are
+ enabled. If the protocol list advertised by client does
+ not overlap this range, you will receive the error
+ message "unknown protocol". If a protocol version lower
+ than TLSv1.2 is specified, make sure that the compatible
+ ciphers are included in --ciphers option. The default
+ cipher list only includes ciphers compatible with
+ TLSv1.2 or above. The available versions are:
+ )"
+#ifdef TLS1_3_VERSION
+ "TLSv1.3, "
+#endif // TLS1_3_VERSION
+ "TLSv1.2, TLSv1.1, and TLSv1.0"
+ R"(
+ Default: )"
+ << DEFAULT_TLS_MIN_PROTO_VERSION
+ << R"(
+ --tls-max-proto-version=<VER>
+ Specify maximum SSL/TLS protocol. The name matching is
+ done in case-insensitive manner. The versions between
+ --tls-min-proto-version and --tls-max-proto-version are
+ enabled. If the protocol list advertised by client does
+ not overlap this range, you will receive the error
+ message "unknown protocol". The available versions are:
+ )"
+#ifdef TLS1_3_VERSION
+ "TLSv1.3, "
+#endif // TLS1_3_VERSION
+ "TLSv1.2, TLSv1.1, and TLSv1.0"
+ R"(
+ Default: )"
+ << DEFAULT_TLS_MAX_PROTO_VERSION << R"(
+ --tls-ticket-key-file=<PATH>
+ Path to file that contains random data to construct TLS
+ session ticket parameters. If aes-128-cbc is given in
+ --tls-ticket-key-cipher, the file must contain exactly
+ 48 bytes. If aes-256-cbc is given in
+ --tls-ticket-key-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 are accepted but server
+ will issue new session ticket with first key. This
+ allows session key rotation. Please note that key
+ rotation does not occur automatically. User should
+ rearrange files or change options values and restart
+ nghttpx gracefully. If 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 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-key-memcached=<HOST>,<PORT>[;tls]
+ Specify address of memcached server to get TLS ticket
+ keys for session resumption. This enables shared TLS
+ ticket key between multiple nghttpx instances. nghttpx
+ does not set TLS ticket key to memcached. The external
+ ticket key generator is required. nghttpx just gets TLS
+ ticket keys from memcached, and use them, possibly
+ replacing current set of keys. It is up to extern TLS
+ ticket key generator to rotate keys frequently. See
+ "TLS SESSION TICKET RESUMPTION" section in manual page
+ to know the data format in memcached entry. Optionally,
+ memcached connection can be encrypted with TLS by
+ specifying "tls" parameter.
+ --tls-ticket-key-memcached-address-family=(auto|IPv4|IPv6)
+ Specify address family of memcached connections to get
+ TLS ticket keys. If "auto" is given, both IPv4 and IPv6
+ are considered. If "IPv4" is given, only IPv4 address
+ is considered. If "IPv6" is given, only IPv6 address is
+ considered.
+ Default: auto
+ --tls-ticket-key-memcached-interval=<DURATION>
+ Set interval to get TLS ticket keys from memcached.
+ Default: )"
+ << util::duration_str(config->tls.ticket.memcached.interval) << R"(
+ --tls-ticket-key-memcached-max-retry=<N>
+ Set maximum number of consecutive retries before
+ abandoning TLS ticket key retrieval. If this number is
+ reached, the attempt is considered as failure, and
+ "failure" count is incremented by 1, which contributed
+ to the value controlled
+ --tls-ticket-key-memcached-max-fail option.
+ Default: )"
+ << config->tls.ticket.memcached.max_retry << R"(
+ --tls-ticket-key-memcached-max-fail=<N>
+ Set maximum number of consecutive failure before
+ disabling TLS ticket until next scheduled key retrieval.
+ Default: )"
+ << config->tls.ticket.memcached.max_fail << R"(
+ --tls-ticket-key-cipher=<CIPHER>
+ Specify cipher to encrypt TLS session ticket. Specify
+ either aes-128-cbc or aes-256-cbc. By default,
+ aes-128-cbc is used.
+ --tls-ticket-key-memcached-cert-file=<PATH>
+ Path to client certificate for memcached connections to
+ get TLS ticket keys.
+ --tls-ticket-key-memcached-private-key-file=<PATH>
+ Path to client private key for memcached connections to
+ get TLS ticket keys.
+ --fetch-ocsp-response-file=<PATH>
+ Path to fetch-ocsp-response script file. It should be
+ absolute path.
+ Default: )"
+ << config->tls.ocsp.fetch_ocsp_response_file << R"(
+ --ocsp-update-interval=<DURATION>
+ Set interval to update OCSP response cache.
+ Default: )"
+ << util::duration_str(config->tls.ocsp.update_interval) << R"(
+ --ocsp-startup
+ Start accepting connections after initial attempts to
+ get OCSP responses finish. It does not matter some of
+ the attempts fail. This feature is useful if OCSP
+ responses must be available before accepting
+ connections.
+ --no-verify-ocsp
+ nghttpx does not verify OCSP response.
+ --no-ocsp Disable OCSP stapling.
+ --tls-session-cache-memcached=<HOST>,<PORT>[;tls]
+ Specify address of memcached server to store session
+ cache. This enables shared session cache between
+ multiple nghttpx instances. Optionally, memcached
+ connection can be encrypted with TLS by specifying "tls"
+ parameter.
+ --tls-session-cache-memcached-address-family=(auto|IPv4|IPv6)
+ Specify address family of memcached connections to store
+ session cache. If "auto" is given, both IPv4 and IPv6
+ are considered. If "IPv4" is given, only IPv4 address
+ is considered. If "IPv6" is given, only IPv6 address is
+ considered.
+ Default: auto
+ --tls-session-cache-memcached-cert-file=<PATH>
+ Path to client certificate for memcached connections to
+ store session cache.
+ --tls-session-cache-memcached-private-key-file=<PATH>
+ Path to client private key for memcached connections to
+ store session cache.
+ --tls-dyn-rec-warmup-threshold=<SIZE>
+ Specify the threshold size for TLS dynamic record size
+ behaviour. During a TLS session, after the threshold
+ number of bytes have been written, the TLS record size
+ will be increased to the maximum allowed (16K). The max
+ record size will continue to be used on the active TLS
+ session. After --tls-dyn-rec-idle-timeout has elapsed,
+ the record size is reduced to 1300 bytes. Specify 0 to
+ always use the maximum record size, regardless of idle
+ period. This behaviour applies to all TLS based
+ frontends, and TLS HTTP/2 backends.
+ Default: )"
+ << util::utos_unit(config->tls.dyn_rec.warmup_threshold) << R"(
+ --tls-dyn-rec-idle-timeout=<DURATION>
+ Specify TLS dynamic record size behaviour timeout. See
+ --tls-dyn-rec-warmup-threshold for more information.
+ This behaviour applies to all TLS based frontends, and
+ TLS HTTP/2 backends.
+ Default: )"
+ << util::duration_str(config->tls.dyn_rec.idle_timeout) << R"(
+ --no-http2-cipher-block-list
+ Allow block listed cipher suite on frontend HTTP/2
+ connection. See
+ https://tools.ietf.org/html/rfc7540#appendix-A for the
+ complete HTTP/2 cipher suites block list.
+ --client-no-http2-cipher-block-list
+ Allow block listed cipher suite on backend HTTP/2
+ connection. See
+ https://tools.ietf.org/html/rfc7540#appendix-A for the
+ complete HTTP/2 cipher suites block list.
+ --tls-sct-dir=<DIR>
+ Specifies the directory where *.sct files exist. All
+ *.sct files in <DIR> are read, and sent as
+ extension_data of TLS signed_certificate_timestamp (RFC
+ 6962) to client. These *.sct files are for the
+ certificate specified in positional command-line
+ argument <CERT>, or certificate option in configuration
+ file. For additional certificates, use --subcert
+ option. This option requires OpenSSL >= 1.0.2.
+ --psk-secrets=<PATH>
+ Read list of PSK identity and secrets from <PATH>. This
+ is used for frontend connection. The each line of input
+ file is formatted as <identity>:<hex-secret>, where
+ <identity> is PSK identity, and <hex-secret> is secret
+ in hex. An empty line, and line which starts with '#'
+ are skipped. The default enabled cipher list might not
+ contain any PSK cipher suite. In that case, desired PSK
+ cipher suites must be enabled using --ciphers option.
+ The desired PSK cipher suite may be block listed by
+ HTTP/2. To use those cipher suites with HTTP/2,
+ consider to use --no-http2-cipher-block-list option.
+ But be aware its implications.
+ --client-psk-secrets=<PATH>
+ Read PSK identity and secrets from <PATH>. This is used
+ for backend connection. The each line of input file is
+ formatted as <identity>:<hex-secret>, where <identity>
+ is PSK identity, and <hex-secret> is secret in hex. An
+ empty line, and line which starts with '#' are skipped.
+ The first identity and secret pair encountered is used.
+ The default enabled cipher list might not contain any
+ PSK cipher suite. In that case, desired PSK cipher
+ suites must be enabled using --client-ciphers option.
+ The desired PSK cipher suite may be block listed by
+ HTTP/2. To use those cipher suites with HTTP/2,
+ consider to use --client-no-http2-cipher-block-list
+ option. But be aware its implications.
+ --tls-no-postpone-early-data
+ By default, except for QUIC connections, nghttpx
+ postpones forwarding HTTP requests sent in early data,
+ including those sent in partially in it, until TLS
+ handshake finishes. If all backend server recognizes
+ "Early-Data" header field, using this option makes
+ nghttpx not postpone forwarding request and get full
+ potential of 0-RTT data.
+ --tls-max-early-data=<SIZE>
+ Sets the maximum amount of 0-RTT data that server
+ accepts.
+ Default: )"
+ << util::utos_unit(config->tls.max_early_data) << R"(
+ --tls-ktls Enable ktls. For server, ktls is enable if
+ --tls-session-cache-memcached is not configured.
+
+HTTP/2:
+ -c, --frontend-http2-max-concurrent-streams=<N>
+ Set the maximum number of the concurrent streams in one
+ frontend HTTP/2 session.
+ Default: )"
+ << config->http2.upstream.max_concurrent_streams << R"(
+ --backend-http2-max-concurrent-streams=<N>
+ Set the maximum number of the concurrent streams in one
+ backend HTTP/2 session. This sets maximum number of
+ concurrent opened pushed streams. The maximum number of
+ concurrent requests are set by a remote server.
+ Default: )"
+ << config->http2.downstream.max_concurrent_streams << R"(
+ --frontend-http2-window-size=<SIZE>
+ Sets the per-stream initial window size of HTTP/2
+ frontend connection.
+ Default: )"
+ << config->http2.upstream.window_size << R"(
+ --frontend-http2-connection-window-size=<SIZE>
+ Sets the per-connection window size of HTTP/2 frontend
+ connection.
+ Default: )"
+ << config->http2.upstream.connection_window_size << R"(
+ --backend-http2-window-size=<SIZE>
+ Sets the initial window size of HTTP/2 backend
+ connection.
+ Default: )"
+ << config->http2.downstream.window_size << R"(
+ --backend-http2-connection-window-size=<SIZE>
+ Sets the per-connection window size of HTTP/2 backend
+ connection.
+ Default: )"
+ << config->http2.downstream.connection_window_size << R"(
+ --http2-no-cookie-crumbling
+ Don't crumble cookie header field.
+ --padding=<N>
+ Add at most <N> bytes to a HTTP/2 frame payload as
+ padding. Specify 0 to disable padding. This option is
+ meant for debugging purpose and not intended to enhance
+ protocol security.
+ --no-server-push
+ Disable HTTP/2 server push. Server push is supported by
+ default mode and HTTP/2 frontend via Link header field.
+ It is also supported if both frontend and backend are
+ HTTP/2 in default mode. In this case, server push from
+ backend session is relayed to frontend, and server push
+ via Link header field is also supported.
+ --frontend-http2-optimize-write-buffer-size
+ (Experimental) Enable write buffer size optimization in
+ frontend HTTP/2 TLS connection. This optimization aims
+ to reduce write buffer size so that it only contains
+ bytes which can send immediately. This makes server
+ more responsive to prioritized HTTP/2 stream because the
+ buffering of lower priority stream is reduced. This
+ option is only effective on recent Linux platform.
+ --frontend-http2-optimize-window-size
+ (Experimental) Automatically tune connection level
+ window size of frontend HTTP/2 TLS connection. If this
+ feature is enabled, connection window size starts with
+ the default window size, 65535 bytes. nghttpx
+ automatically adjusts connection window size based on
+ TCP receiving window size. The maximum window size is
+ capped by the value specified by
+ --frontend-http2-connection-window-size. Since the
+ stream is subject to stream level window size, it should
+ be adjusted using --frontend-http2-window-size option as
+ well. This option is only effective on recent Linux
+ platform.
+ --frontend-http2-encoder-dynamic-table-size=<SIZE>
+ Specify the maximum dynamic table size of HPACK encoder
+ in the frontend HTTP/2 connection. The decoder (client)
+ specifies the maximum dynamic table size it accepts.
+ Then the negotiated dynamic table size is the minimum of
+ this option value and the value which client specified.
+ Default: )"
+ << util::utos_unit(config->http2.upstream.encoder_dynamic_table_size)
+ << R"(
+ --frontend-http2-decoder-dynamic-table-size=<SIZE>
+ Specify the maximum dynamic table size of HPACK decoder
+ in the frontend HTTP/2 connection.
+ Default: )"
+ << util::utos_unit(config->http2.upstream.decoder_dynamic_table_size)
+ << R"(
+ --backend-http2-encoder-dynamic-table-size=<SIZE>
+ Specify the maximum dynamic table size of HPACK encoder
+ in the backend HTTP/2 connection. The decoder (backend)
+ specifies the maximum dynamic table size it accepts.
+ Then the negotiated dynamic table size is the minimum of
+ this option value and the value which backend specified.
+ Default: )"
+ << util::utos_unit(config->http2.downstream.encoder_dynamic_table_size)
+ << R"(
+ --backend-http2-decoder-dynamic-table-size=<SIZE>
+ Specify the maximum dynamic table size of HPACK decoder
+ in the backend HTTP/2 connection.
+ Default: )"
+ << util::utos_unit(config->http2.downstream.decoder_dynamic_table_size)
+ << R"(
+
+Mode:
+ (default mode)
+ Accept HTTP/2, and HTTP/1.1 over SSL/TLS. "no-tls"
+ parameter is used in --frontend option, accept HTTP/2
+ and HTTP/1.1 over cleartext TCP. The incoming HTTP/1.1
+ connection can be upgraded to HTTP/2 through HTTP
+ Upgrade.
+ -s, --http2-proxy
+ Like default mode, but enable forward proxy. This is so
+ called HTTP/2 proxy mode.
+
+Logging:
+ -L, --log-level=<LEVEL>
+ Set the severity level of log output. <LEVEL> must be
+ one of INFO, NOTICE, WARN, ERROR and FATAL.
+ Default: NOTICE
+ --accesslog-file=<PATH>
+ Set path to write access log. To reopen file, send USR1
+ signal to nghttpx.
+ --accesslog-syslog
+ Send access log to syslog. If this option is used,
+ --accesslog-file option is ignored.
+ --accesslog-format=<FORMAT>
+ Specify format string for access log. The default
+ format is combined format. The following variables are
+ available:
+
+ * $remote_addr: client IP address.
+ * $time_local: local time in Common Log format.
+ * $time_iso8601: local time in ISO 8601 format.
+ * $request: HTTP request line.
+ * $status: HTTP response status code.
+ * $body_bytes_sent: the number of bytes sent to client
+ as response body.
+ * $http_<VAR>: value of HTTP request header <VAR> where
+ '_' in <VAR> is replaced with '-'.
+ * $remote_port: client port.
+ * $server_port: server port.
+ * $request_time: request processing time in seconds with
+ milliseconds resolution.
+ * $pid: PID of the running process.
+ * $alpn: ALPN identifier of the protocol which generates
+ the response. For HTTP/1, ALPN is always http/1.1,
+ regardless of minor version.
+ * $tls_cipher: cipher used for SSL/TLS connection.
+ * $tls_client_fingerprint_sha256: SHA-256 fingerprint of
+ client certificate.
+ * $tls_client_fingerprint_sha1: SHA-1 fingerprint of
+ client certificate.
+ * $tls_client_subject_name: subject name in client
+ certificate.
+ * $tls_client_issuer_name: issuer name in client
+ certificate.
+ * $tls_client_serial: serial number in client
+ certificate.
+ * $tls_protocol: protocol for SSL/TLS connection.
+ * $tls_session_id: session ID for SSL/TLS connection.
+ * $tls_session_reused: "r" if SSL/TLS session was
+ reused. Otherwise, "."
+ * $tls_sni: SNI server name for SSL/TLS connection.
+ * $backend_host: backend host used to fulfill the
+ request. "-" if backend host is not available.
+ * $backend_port: backend port used to fulfill the
+ request. "-" if backend host is not available.
+ * $method: HTTP method
+ * $path: Request path including query. For CONNECT
+ request, authority is recorded.
+ * $path_without_query: $path up to the first '?'
+ character. For CONNECT request, authority is
+ recorded.
+ * $protocol_version: HTTP version (e.g., HTTP/1.1,
+ HTTP/2)
+
+ The variable can be enclosed by "{" and "}" for
+ disambiguation (e.g., ${remote_addr}).
+
+ Default: )"
+ << DEFAULT_ACCESSLOG_FORMAT << R"(
+ --accesslog-write-early
+ Write access log when response header fields are
+ received from backend rather than when request
+ transaction finishes.
+ --errorlog-file=<PATH>
+ Set path to write error log. To reopen file, send USR1
+ signal to nghttpx. stderr will be redirected to the
+ error log file unless --errorlog-syslog is used.
+ Default: )"
+ << config->logging.error.file << R"(
+ --errorlog-syslog
+ Send error log to syslog. If this option is used,
+ --errorlog-file option is ignored.
+ --syslog-facility=<FACILITY>
+ Set syslog facility to <FACILITY>.
+ Default: )"
+ << str_syslog_facility(config->logging.syslog_facility) << R"(
+
+HTTP:
+ --add-x-forwarded-for
+ Append X-Forwarded-For header field to the downstream
+ request.
+ --strip-incoming-x-forwarded-for
+ Strip X-Forwarded-For header field from inbound client
+ requests.
+ --no-add-x-forwarded-proto
+ Don't append additional X-Forwarded-Proto header field
+ to the backend request. If inbound client sets
+ X-Forwarded-Proto, and
+ --no-strip-incoming-x-forwarded-proto option is used,
+ they are passed to the backend.
+ --no-strip-incoming-x-forwarded-proto
+ Don't strip X-Forwarded-Proto header field from inbound
+ client requests.
+ --add-forwarded=<LIST>
+ Append RFC 7239 Forwarded header field with parameters
+ specified in comma delimited list <LIST>. The supported
+ parameters are "by", "for", "host", and "proto". By
+ default, the value of "by" and "for" parameters are
+ obfuscated string. See --forwarded-by and
+ --forwarded-for options respectively. Note that nghttpx
+ does not translate non-standard X-Forwarded-* header
+ fields into Forwarded header field, and vice versa.
+ --strip-incoming-forwarded
+ Strip Forwarded header field from inbound client
+ requests.
+ --forwarded-by=(obfuscated|ip|<VALUE>)
+ Specify the parameter value sent out with "by" parameter
+ of Forwarded header field. If "obfuscated" is given,
+ the string is randomly generated at startup. If "ip" is
+ given, the interface address of the connection,
+ including port number, is sent with "by" parameter. In
+ case of UNIX domain socket, "localhost" is used instead
+ of address and port. User can also specify the static
+ obfuscated string. The limitation is that it must start
+ with "_", and only consists of character set
+ [A-Za-z0-9._-], as described in RFC 7239.
+ Default: obfuscated
+ --forwarded-for=(obfuscated|ip)
+ Specify the parameter value sent out with "for"
+ parameter of Forwarded header field. If "obfuscated" is
+ given, the string is randomly generated for each client
+ connection. If "ip" is given, the remote client address
+ of the connection, without port number, is sent with
+ "for" parameter. In case of UNIX domain socket,
+ "localhost" is used instead of address.
+ Default: obfuscated
+ --no-via Don't append to Via header field. If Via header field
+ is received, it is left unaltered.
+ --no-strip-incoming-early-data
+ Don't strip Early-Data header field from inbound client
+ requests.
+ --no-location-rewrite
+ Don't rewrite location header field in default mode.
+ When --http2-proxy is used, location header field will
+ not be altered regardless of this option.
+ --host-rewrite
+ Rewrite host and :authority header fields in default
+ mode. When --http2-proxy is used, these headers will
+ not be altered regardless of this option.
+ --altsvc=<PROTOID,PORT[,HOST,[ORIGIN[,PARAMS]]]>
+ Specify protocol ID, port, host and origin of
+ alternative service. <HOST>, <ORIGIN> and <PARAMS> are
+ optional. Empty <HOST> and <ORIGIN> are allowed and
+ they are treated as nothing is specified. They are
+ advertised in alt-svc header field only in HTTP/1.1
+ frontend. This option can be used multiple times to
+ specify multiple alternative services.
+ Example: --altsvc="h2,443,,,ma=3600; persist=1"
+ --http2-altsvc=<PROTOID,PORT[,HOST,[ORIGIN[,PARAMS]]]>
+ Just like --altsvc option, but this altsvc is only sent
+ in HTTP/2 frontend.
+ --add-request-header=<HEADER>
+ Specify additional header field to add to request header
+ set. The field name must be lowercase. This option
+ just appends header field and won't replace anything
+ already set. This option can be used several times to
+ specify multiple header fields.
+ Example: --add-request-header="foo: bar"
+ --add-response-header=<HEADER>
+ Specify additional header field to add to response
+ header set. The field name must be lowercase. This
+ option just appends header field and won't replace
+ anything already set. This option can be used several
+ times to specify multiple header fields.
+ Example: --add-response-header="foo: bar"
+ --request-header-field-buffer=<SIZE>
+ Set maximum buffer size for incoming HTTP request header
+ field list. This is the sum of header name and value in
+ bytes. If trailer fields exist, they are counted
+ towards this number.
+ Default: )"
+ << util::utos_unit(config->http.request_header_field_buffer) << R"(
+ --max-request-header-fields=<N>
+ Set maximum number of incoming HTTP request header
+ fields. If trailer fields exist, they are counted
+ towards this number.
+ Default: )"
+ << config->http.max_request_header_fields << R"(
+ --response-header-field-buffer=<SIZE>
+ Set maximum buffer size for incoming HTTP response
+ header field list. This is the sum of header name and
+ value in bytes. If trailer fields exist, they are
+ counted towards this number.
+ Default: )"
+ << util::utos_unit(config->http.response_header_field_buffer) << R"(
+ --max-response-header-fields=<N>
+ Set maximum number of incoming HTTP response header
+ fields. If trailer fields exist, they are counted
+ towards this number.
+ Default: )"
+ << config->http.max_response_header_fields << R"(
+ --error-page=(<CODE>|*)=<PATH>
+ Set file path to custom error page served when nghttpx
+ originally generates HTTP error status code <CODE>.
+ <CODE> must be greater than or equal to 400, and at most
+ 599. If "*" is used instead of <CODE>, it matches all
+ HTTP status code. If error status code comes from
+ backend server, the custom error pages are not used.
+ --server-name=<NAME>
+ Change server response header field value to <NAME>.
+ Default: )"
+ << config->http.server_name << R"(
+ --no-server-rewrite
+ Don't rewrite server header field in default mode. When
+ --http2-proxy is used, these headers will not be altered
+ regardless of this option.
+ --redirect-https-port=<PORT>
+ Specify the port number which appears in Location header
+ field when redirect to HTTPS URI is made due to
+ "redirect-if-not-tls" parameter in --backend option.
+ Default: )"
+ << config->http.redirect_https_port << R"(
+ --require-http-scheme
+ Always require http or https scheme in HTTP request. It
+ also requires that https scheme must be used for an
+ encrypted connection. Otherwise, http scheme must be
+ used. This option is recommended for a server
+ deployment which directly faces clients and the services
+ it provides only require http or https scheme.
+
+API:
+ --api-max-request-body=<SIZE>
+ Set the maximum size of request body for API request.
+ Default: )"
+ << util::utos_unit(config->api.max_request_body) << R"(
+
+DNS:
+ --dns-cache-timeout=<DURATION>
+ Set duration that cached DNS results remain valid. Note
+ that nghttpx caches the unsuccessful results as well.
+ Default: )"
+ << util::duration_str(config->dns.timeout.cache) << R"(
+ --dns-lookup-timeout=<DURATION>
+ Set timeout that DNS server is given to respond to the
+ initial DNS query. For the 2nd and later queries,
+ server is given time based on this timeout, and it is
+ scaled linearly.
+ Default: )"
+ << util::duration_str(config->dns.timeout.lookup) << R"(
+ --dns-max-try=<N>
+ Set the number of DNS query before nghttpx gives up name
+ lookup.
+ Default: )"
+ << config->dns.max_try << R"(
+ --frontend-max-requests=<N>
+ The number of requests that single frontend connection
+ can process. For HTTP/2, this is the number of streams
+ in one HTTP/2 connection. For HTTP/1, this is the
+ number of keep alive requests. This is hint to nghttpx,
+ and it may allow additional few requests. The default
+ value is unlimited.
+
+Debug:
+ --frontend-http2-dump-request-header=<PATH>
+ Dumps request headers received by HTTP/2 frontend to the
+ file denoted in <PATH>. The output is done in HTTP/1
+ header field format and each header block is followed by
+ an empty line. This option is not thread safe and MUST
+ NOT be used with option -n<N>, where <N> >= 2.
+ --frontend-http2-dump-response-header=<PATH>
+ Dumps response headers sent from HTTP/2 frontend to the
+ file denoted in <PATH>. The output is done in HTTP/1
+ header field format and each header block is followed by
+ an empty line. This option is not thread safe and MUST
+ NOT be used with option -n<N>, where <N> >= 2.
+ -o, --frontend-frame-debug
+ Print HTTP/2 frames in frontend to stderr. This option
+ is not thread safe and MUST NOT be used with option
+ -n=N, where N >= 2.
+
+Process:
+ -D, --daemon
+ Run in a background. If -D is used, the current working
+ directory is changed to '/'.
+ --pid-file=<PATH>
+ Set path to save PID of this program.
+ --user=<USER>
+ Run this program as <USER>. This option is intended to
+ be used to drop root privileges.
+ --single-process
+ Run this program in a single process mode for debugging
+ purpose. Without this option, nghttpx creates at least
+ 2 processes: main and worker processes. If this option
+ is used, main and worker are unified into a single
+ process. nghttpx still spawns additional process if
+ neverbleed is used. In the single process mode, the
+ signal handling feature is disabled.
+ --max-worker-processes=<N>
+ The maximum number of worker processes. nghttpx spawns
+ new worker process when it reloads its configuration.
+ The previous worker process enters graceful termination
+ period and will terminate when it finishes handling the
+ existing connections. However, if reloading
+ configurations happen very frequently, the worker
+ processes might be piled up if they take a bit long time
+ to finish the existing connections. With this option,
+ if the number of worker processes exceeds the given
+ value, the oldest worker process is terminated
+ immediately. Specifying 0 means no limit and it is the
+ default behaviour.
+ --worker-process-grace-shutdown-period=<DURATION>
+ Maximum period for a worker process to terminate
+ gracefully. When a worker process enters in graceful
+ shutdown period (e.g., when nghttpx reloads its
+ configuration) and it does not finish handling the
+ existing connections in the given period of time, it is
+ immediately terminated. Specifying 0 means no limit and
+ it is the default behaviour.
+
+Scripting:
+ --mruby-file=<PATH>
+ Set mruby script file
+ --ignore-per-pattern-mruby-error
+ Ignore mruby compile error for per-pattern mruby script
+ file. If error occurred, it is treated as if no mruby
+ file were specified for the pattern.
+)";
+
+#ifdef ENABLE_HTTP3
+ out << R"(
+HTTP/3 and QUIC:
+ --frontend-quic-idle-timeout=<DURATION>
+ Specify an idle timeout for QUIC connection.
+ Default: )"
+ << util::duration_str(config->quic.upstream.timeout.idle) << R"(
+ --frontend-quic-debug-log
+ Output QUIC debug log to /dev/stderr.
+ --quic-bpf-program-file=<PATH>
+ Specify a path to eBPF program file reuseport_kern.o to
+ direct an incoming QUIC UDP datagram to a correct
+ socket.
+ Default: )"
+ << config->quic.bpf.prog_file << R"(
+ --frontend-quic-early-data
+ Enable early data on frontend QUIC connections. nghttpx
+ sends "Early-Data" header field to a backend server if a
+ request is received in early data and handshake has not
+ finished. All backend servers should deal with possibly
+ replayed requests.
+ --frontend-quic-qlog-dir=<DIR>
+ Specify a directory where a qlog file is written for
+ frontend QUIC connections. A qlog file is created per
+ each QUIC connection. The file name is ISO8601 basic
+ format, followed by "-", server Source Connection ID and
+ ".sqlog".
+ --frontend-quic-require-token
+ Require an address validation token for a frontend QUIC
+ connection. Server sends a token in Retry packet or
+ NEW_TOKEN frame in the previous connection.
+ --frontend-quic-congestion-controller=<CC>
+ Specify a congestion controller algorithm for a frontend
+ QUIC connection. <CC> should be either "cubic" or
+ "bbr".
+ Default: )"
+ << (config->quic.upstream.congestion_controller == NGTCP2_CC_ALGO_CUBIC
+ ? "cubic"
+ : "bbr")
+ << R"(
+ --frontend-quic-secret-file=<PATH>
+ Path to file that contains secure random data to be used
+ as QUIC keying materials. It is used to derive keys for
+ encrypting tokens and Connection IDs. It is not used to
+ encrypt QUIC packets. Each line of this file must
+ contain exactly 136 bytes hex-encoded string (when
+ decoded the byte string is 68 bytes long). The first 2
+ bits of decoded byte string are used to identify the
+ keying material. An empty line or a line which starts
+ '#' is ignored. The file can contain more than one
+ keying materials. Because the identifier is 2 bits, at
+ most 4 keying materials are read and the remaining data
+ is discarded. The first keying material in the file is
+ primarily used for encryption and decryption for new
+ connection. The other ones are used to decrypt data for
+ the existing connections. Specifying multiple keying
+ materials enables key rotation. Please note that key
+ rotation does not occur automatically. User should
+ update files or change options values and restart
+ nghttpx gracefully. If opening or reading given file
+ fails, all loaded keying materials 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, a keying material is generated
+ internally on startup and reload.
+ --quic-server-id=<HEXSTRING>
+ Specify server ID encoded in Connection ID to identify
+ this particular server instance. Connection ID is
+ encrypted and this part is not visible in public. It
+ must be 4 bytes long and must be encoded in hex string
+ (which is 8 bytes long). If this option is omitted, a
+ random server ID is generated on startup and
+ configuration reload.
+ --frontend-quic-initial-rtt=<DURATION>
+ Specify the initial RTT of the frontend QUIC connection.
+ Default: )"
+ << util::duration_str(config->quic.upstream.initial_rtt) << R"(
+ --no-quic-bpf
+ Disable eBPF.
+ --frontend-http3-window-size=<SIZE>
+ Sets the per-stream initial window size of HTTP/3
+ frontend connection.
+ Default: )"
+ << util::utos_unit(config->http3.upstream.window_size) << R"(
+ --frontend-http3-connection-window-size=<SIZE>
+ Sets the per-connection window size of HTTP/3 frontend
+ connection.
+ Default: )"
+ << util::utos_unit(config->http3.upstream.connection_window_size) << R"(
+ --frontend-http3-max-window-size=<SIZE>
+ Sets the maximum per-stream window size of HTTP/3
+ frontend connection. The window size is adjusted based
+ on the receiving rate of stream data. The initial value
+ is the value specified by --frontend-http3-window-size
+ and the window size grows up to <SIZE> bytes.
+ Default: )"
+ << util::utos_unit(config->http3.upstream.max_window_size) << R"(
+ --frontend-http3-max-connection-window-size=<SIZE>
+ Sets the maximum per-connection window size of HTTP/3
+ frontend connection. The window size is adjusted based
+ on the receiving rate of stream data. The initial value
+ is the value specified by
+ --frontend-http3-connection-window-size and the window
+ size grows up to <SIZE> bytes.
+ Default: )"
+ << util::utos_unit(config->http3.upstream.max_connection_window_size)
+ << R"(
+ --frontend-http3-max-concurrent-streams=<N>
+ Set the maximum number of the concurrent streams in one
+ frontend HTTP/3 connection.
+ Default: )"
+ << config->http3.upstream.max_concurrent_streams << R"(
+)";
+#endif // ENABLE_HTTP3
+
+ out << R"(
+Misc:
+ --conf=<PATH>
+ Load configuration from <PATH>. Please note that
+ nghttpx always tries to read the default configuration
+ file if --conf is not given.
+ Default: )"
+ << config->conf_path << R"(
+ --include=<PATH>
+ Load additional configurations from <PATH>. File <PATH>
+ is read when configuration parser encountered this
+ option. This option can be used multiple times, or even
+ recursively.
+ -v, --version
+ Print version and exit.
+ -h, --help Print this help and exit.
+
+--
+
+ The <SIZE> argument is an integer and an optional unit (e.g., 10K is
+ 10 * 1024). Units are K, M and G (powers of 1024).
+
+ The <DURATION> argument is an integer and an optional unit (e.g., 1s
+ is 1 second and 500ms is 500 milliseconds). Units are h, m, s or ms
+ (hours, minutes, seconds and milliseconds, respectively). If a unit
+ is omitted, a second is used as unit.)"
+ << std::endl;
+}
+} // namespace
+
+namespace {
+int process_options(Config *config,
+ std::vector<std::pair<StringRef, StringRef>> &cmdcfgs) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ std::map<StringRef, size_t> pattern_addr_indexer;
+ if (conf_exists(config->conf_path.c_str())) {
+ LOG(NOTICE) << "Loading configuration from " << config->conf_path;
+ std::set<StringRef> include_set;
+ if (load_config(config, config->conf_path.c_str(), include_set,
+ pattern_addr_indexer) == -1) {
+ LOG(FATAL) << "Failed to load configuration from " << config->conf_path;
+ return -1;
+ }
+ assert(include_set.empty());
+ }
+
+ // Reopen log files using configurations in file
+ reopen_log_files(config->logging);
+
+ {
+ std::set<StringRef> include_set;
+
+ for (auto &p : cmdcfgs) {
+ if (parse_config(config, p.first, p.second, include_set,
+ pattern_addr_indexer) == -1) {
+ LOG(FATAL) << "Failed to parse command-line argument.";
+ return -1;
+ }
+ }
+
+ assert(include_set.empty());
+ }
+
+ Log::set_severity_level(config->logging.severity);
+
+ auto &loggingconf = config->logging;
+
+ if (loggingconf.access.syslog || loggingconf.error.syslog) {
+ openlog("nghttpx", LOG_NDELAY | LOG_NOWAIT | LOG_PID,
+ loggingconf.syslog_facility);
+ }
+
+ if (reopen_log_files(config->logging) != 0) {
+ LOG(FATAL) << "Failed to open log file";
+ return -1;
+ }
+
+ redirect_stderr_to_errorlog(loggingconf);
+
+ if (config->uid != 0) {
+ if (log_config()->accesslog_fd != -1 &&
+ fchown(log_config()->accesslog_fd, config->uid, config->gid) == -1) {
+ auto error = errno;
+ LOG(WARN) << "Changing owner of access log file failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ }
+ if (log_config()->errorlog_fd != -1 &&
+ fchown(log_config()->errorlog_fd, config->uid, config->gid) == -1) {
+ auto error = errno;
+ LOG(WARN) << "Changing owner of error log file failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ }
+ }
+
+ if (config->single_thread) {
+ LOG(WARN) << "single-thread: Set workers to 1";
+ config->num_worker = 1;
+ }
+
+ auto &http2conf = config->http2;
+ {
+ auto &dumpconf = http2conf.upstream.debug.dump;
+
+ if (!dumpconf.request_header_file.empty()) {
+ auto path = dumpconf.request_header_file.c_str();
+ auto f = open_file_for_write(path);
+
+ if (f == nullptr) {
+ LOG(FATAL) << "Failed to open http2 upstream request header file: "
+ << path;
+ return -1;
+ }
+
+ dumpconf.request_header = f;
+
+ if (config->uid != 0) {
+ if (chown(path, config->uid, config->gid) == -1) {
+ auto error = errno;
+ LOG(WARN) << "Changing owner of http2 upstream request header file "
+ << path << " failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ }
+ }
+ }
+
+ if (!dumpconf.response_header_file.empty()) {
+ auto path = dumpconf.response_header_file.c_str();
+ auto f = open_file_for_write(path);
+
+ if (f == nullptr) {
+ LOG(FATAL) << "Failed to open http2 upstream response header file: "
+ << path;
+ return -1;
+ }
+
+ dumpconf.response_header = f;
+
+ if (config->uid != 0) {
+ if (chown(path, config->uid, config->gid) == -1) {
+ auto error = errno;
+ LOG(WARN) << "Changing owner of http2 upstream response header file"
+ << " " << path << " failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ }
+ }
+ }
+ }
+
+ auto &tlsconf = config->tls;
+
+ if (tlsconf.alpn_list.empty()) {
+ tlsconf.alpn_list = util::split_str(DEFAULT_ALPN_LIST, ',');
+ }
+
+ if (!tlsconf.tls_proto_list.empty()) {
+ tlsconf.tls_proto_mask = tls::create_tls_proto_mask(tlsconf.tls_proto_list);
+ }
+
+ // TODO We depends on the ordering of protocol version macro in
+ // OpenSSL.
+ if (tlsconf.min_proto_version > tlsconf.max_proto_version) {
+ LOG(ERROR) << "tls-max-proto-version must be equal to or larger than "
+ "tls-min-proto-version";
+ return -1;
+ }
+
+ if (tls::set_alpn_prefs(tlsconf.alpn_prefs, tlsconf.alpn_list) != 0) {
+ return -1;
+ }
+
+ tlsconf.bio_method = create_bio_method();
+
+ auto &listenerconf = config->conn.listener;
+ auto &upstreamconf = config->conn.upstream;
+
+ if (listenerconf.addrs.empty()) {
+ UpstreamAddr addr{};
+ addr.host = StringRef::from_lit("*");
+ addr.port = 3000;
+ addr.tls = true;
+ addr.family = AF_INET;
+ addr.index = 0;
+ listenerconf.addrs.push_back(addr);
+ addr.family = AF_INET6;
+ addr.index = 1;
+ listenerconf.addrs.push_back(std::move(addr));
+ }
+
+ if (upstreamconf.worker_connections == 0) {
+ upstreamconf.worker_connections = std::numeric_limits<size_t>::max();
+ }
+
+ if (tls::upstream_tls_enabled(config->conn) &&
+ (tlsconf.private_key_file.empty() || tlsconf.cert_file.empty())) {
+ LOG(FATAL) << "TLS private key and certificate files are required. "
+ "Specify them in command-line, or in configuration file "
+ "using private-key-file and certificate-file options.";
+ return -1;
+ }
+
+ if (tls::upstream_tls_enabled(config->conn) && !tlsconf.ocsp.disabled) {
+ struct stat buf;
+ if (stat(tlsconf.ocsp.fetch_ocsp_response_file.c_str(), &buf) != 0) {
+ tlsconf.ocsp.disabled = true;
+ LOG(WARN) << "--fetch-ocsp-response-file: "
+ << tlsconf.ocsp.fetch_ocsp_response_file
+ << " not found. OCSP stapling has been disabled.";
+ }
+ }
+
+ if (configure_downstream_group(config, config->http2_proxy, false, tlsconf) !=
+ 0) {
+ return -1;
+ }
+
+ std::array<char, util::max_hostport> hostport_buf;
+
+ auto &proxy = config->downstream_http_proxy;
+ if (!proxy.host.empty()) {
+ auto hostport = util::make_hostport(std::begin(hostport_buf),
+ StringRef{proxy.host}, proxy.port);
+ if (resolve_hostname(&proxy.addr, proxy.host.c_str(), proxy.port,
+ AF_UNSPEC) == -1) {
+ LOG(FATAL) << "Resolving backend HTTP proxy address failed: " << hostport;
+ return -1;
+ }
+ LOG(NOTICE) << "Backend HTTP proxy address: " << hostport << " -> "
+ << util::to_numeric_addr(&proxy.addr);
+ }
+
+ {
+ auto &memcachedconf = tlsconf.session_cache.memcached;
+ if (!memcachedconf.host.empty()) {
+ auto hostport = util::make_hostport(std::begin(hostport_buf),
+ StringRef{memcachedconf.host},
+ memcachedconf.port);
+ if (resolve_hostname(&memcachedconf.addr, memcachedconf.host.c_str(),
+ memcachedconf.port, memcachedconf.family) == -1) {
+ LOG(FATAL)
+ << "Resolving memcached address for TLS session cache failed: "
+ << hostport;
+ return -1;
+ }
+ LOG(NOTICE) << "Memcached address for TLS session cache: " << hostport
+ << " -> " << util::to_numeric_addr(&memcachedconf.addr);
+ if (memcachedconf.tls) {
+ LOG(NOTICE) << "Connection to memcached for TLS session cache will be "
+ "encrypted by TLS";
+ }
+ }
+ }
+
+ {
+ auto &memcachedconf = tlsconf.ticket.memcached;
+ if (!memcachedconf.host.empty()) {
+ auto hostport = util::make_hostport(std::begin(hostport_buf),
+ StringRef{memcachedconf.host},
+ memcachedconf.port);
+ if (resolve_hostname(&memcachedconf.addr, memcachedconf.host.c_str(),
+ memcachedconf.port, memcachedconf.family) == -1) {
+ LOG(FATAL) << "Resolving memcached address for TLS ticket key failed: "
+ << hostport;
+ return -1;
+ }
+ LOG(NOTICE) << "Memcached address for TLS ticket key: " << hostport
+ << " -> " << util::to_numeric_addr(&memcachedconf.addr);
+ if (memcachedconf.tls) {
+ LOG(NOTICE) << "Connection to memcached for TLS ticket key will be "
+ "encrypted by TLS";
+ }
+ }
+ }
+
+ if (config->rlimit_nofile) {
+ struct rlimit lim = {static_cast<rlim_t>(config->rlimit_nofile),
+ static_cast<rlim_t>(config->rlimit_nofile)};
+ if (setrlimit(RLIMIT_NOFILE, &lim) != 0) {
+ auto error = errno;
+ LOG(WARN) << "Setting rlimit-nofile failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ }
+ }
+
+#ifdef RLIMIT_MEMLOCK
+ if (config->rlimit_memlock) {
+ struct rlimit lim = {static_cast<rlim_t>(config->rlimit_memlock),
+ static_cast<rlim_t>(config->rlimit_memlock)};
+ if (setrlimit(RLIMIT_MEMLOCK, &lim) != 0) {
+ auto error = errno;
+ LOG(WARN) << "Setting rlimit-memlock failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ }
+ }
+#endif // RLIMIT_MEMLOCK
+
+ auto &fwdconf = config->http.forwarded;
+
+ if (fwdconf.by_node_type == ForwardedNode::OBFUSCATED &&
+ fwdconf.by_obfuscated.empty()) {
+ // 2 for '_' and terminal NULL
+ auto iov = make_byte_ref(config->balloc, SHRPX_OBFUSCATED_NODE_LENGTH + 2);
+ auto p = iov.base;
+ *p++ = '_';
+ auto gen = util::make_mt19937();
+ p = util::random_alpha_digit(p, p + SHRPX_OBFUSCATED_NODE_LENGTH, gen);
+ *p = '\0';
+ fwdconf.by_obfuscated = StringRef{iov.base, p};
+ }
+
+ if (config->http2.upstream.debug.frame_debug) {
+ // To make it sync to logging
+ set_output(stderr);
+ if (isatty(fileno(stdout))) {
+ set_color_output(true);
+ }
+ reset_timer();
+ }
+
+ config->http2.upstream.callbacks = create_http2_upstream_callbacks();
+ config->http2.downstream.callbacks = create_http2_downstream_callbacks();
+
+ if (!config->http.altsvcs.empty()) {
+ config->http.altsvc_header_value =
+ http::create_altsvc_header_value(config->balloc, config->http.altsvcs);
+ }
+
+ if (!config->http.http2_altsvcs.empty()) {
+ config->http.http2_altsvc_header_value = http::create_altsvc_header_value(
+ config->balloc, config->http.http2_altsvcs);
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+// Closes file descriptor which are opened for listeners in config,
+// and are not inherited from |iaddrs|.
+void close_not_inherited_fd(Config *config,
+ const std::vector<InheritedAddr> &iaddrs) {
+ auto &listenerconf = config->conn.listener;
+
+ for (auto &addr : listenerconf.addrs) {
+ auto inherited = std::find_if(
+ std::begin(iaddrs), std::end(iaddrs),
+ [&addr](const InheritedAddr &iaddr) { return addr.fd == iaddr.fd; });
+
+ if (inherited != std::end(iaddrs)) {
+ continue;
+ }
+
+ close(addr.fd);
+ }
+}
+} // namespace
+
+namespace {
+void reload_config() {
+ int rv;
+
+ LOG(NOTICE) << "Reloading configuration";
+
+ auto cur_config = mod_config();
+ auto new_config = std::make_unique<Config>();
+
+ fill_default_config(new_config.get());
+
+ new_config->conf_path =
+ make_string_ref(new_config->balloc, cur_config->conf_path);
+ // daemon option is ignored here.
+ new_config->daemon = cur_config->daemon;
+ // loop is reused, and ev_loop_flags gets ignored
+ new_config->ev_loop_flags = cur_config->ev_loop_flags;
+ new_config->config_revision = cur_config->config_revision + 1;
+
+ rv = process_options(new_config.get(), suconfig.cmdcfgs);
+ if (rv != 0) {
+ LOG(ERROR) << "Failed to process new configuration";
+ return;
+ }
+
+ auto iaddrs = get_inherited_addr_from_config(new_config->balloc, cur_config);
+
+ if (create_acceptor_socket(new_config.get(), iaddrs) != 0) {
+ close_not_inherited_fd(new_config.get(), iaddrs);
+ return;
+ }
+
+ // According to libev documentation, flags are ignored since we have
+ // already created first default loop.
+ auto loop = ev_default_loop(new_config->ev_loop_flags);
+
+ int ipc_fd = 0;
+#ifdef ENABLE_HTTP3
+ int quic_ipc_fd = 0;
+
+ auto quic_lwps = collect_quic_lingering_worker_processes();
+
+ std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>> cid_prefixes;
+
+ if (generate_cid_prefix(cid_prefixes, new_config.get()) != 0) {
+ close_not_inherited_fd(new_config.get(), iaddrs);
+ return;
+ }
+#endif // ENABLE_HTTP3
+
+ // fork_worker_process and forked child process assumes new
+ // configuration can be obtained from get_config().
+
+ auto old_config = replace_config(std::move(new_config));
+
+ auto pid = fork_worker_process(ipc_fd
+#ifdef ENABLE_HTTP3
+ ,
+ quic_ipc_fd
+#endif // ENABLE_HTTP3
+
+ ,
+ iaddrs
+#ifdef ENABLE_HTTP3
+ ,
+ cid_prefixes, quic_lwps
+#endif // ENABLE_HTTP3
+ );
+
+ if (pid == -1) {
+ LOG(ERROR) << "Failed to process new configuration";
+
+ new_config = replace_config(std::move(old_config));
+ close_not_inherited_fd(new_config.get(), iaddrs);
+
+ return;
+ }
+
+ close_unused_inherited_addr(iaddrs);
+
+ worker_process_add(std::make_unique<WorkerProcess>(loop, pid, ipc_fd
+#ifdef ENABLE_HTTP3
+ ,
+ quic_ipc_fd, cid_prefixes
+#endif // ENABLE_HTTP3
+ ));
+
+ worker_process_adjust_limit();
+
+ if (!get_config()->pid_file.empty()) {
+ save_pid();
+ }
+}
+} // namespace
+
+int main(int argc, char **argv) {
+ int rv;
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+
+#ifdef HAVE_LIBBPF
+ libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
+#endif // HAVE_LIBBPF
+
+ Log::set_severity_level(NOTICE);
+ create_config();
+ fill_default_config(mod_config());
+
+ // make copy of stderr
+ store_original_fds();
+
+ // First open log files with default configuration, so that we can
+ // log errors/warnings while reading configuration files.
+ reopen_log_files(get_config()->logging);
+
+ suconfig.original_argv = argv;
+
+ // We have to copy argv, since getopt_long may change its content.
+ suconfig.argc = argc;
+ suconfig.argv = new char *[argc];
+
+ for (int i = 0; i < argc; ++i) {
+ suconfig.argv[i] = strdup(argv[i]);
+ if (suconfig.argv[i] == nullptr) {
+ auto error = errno;
+ LOG(FATAL) << "failed to copy argv: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ suconfig.cwd = getcwd(nullptr, 0);
+ if (suconfig.cwd == nullptr) {
+ auto error = errno;
+ LOG(FATAL) << "failed to get current working directory: errno=" << error;
+ exit(EXIT_FAILURE);
+ }
+
+ auto &cmdcfgs = suconfig.cmdcfgs;
+
+ while (1) {
+ static int flag = 0;
+ static constexpr option long_options[] = {
+ {SHRPX_OPT_DAEMON.c_str(), no_argument, nullptr, 'D'},
+ {SHRPX_OPT_LOG_LEVEL.c_str(), required_argument, nullptr, 'L'},
+ {SHRPX_OPT_BACKEND.c_str(), required_argument, nullptr, 'b'},
+ {SHRPX_OPT_HTTP2_MAX_CONCURRENT_STREAMS.c_str(), required_argument,
+ nullptr, 'c'},
+ {SHRPX_OPT_FRONTEND.c_str(), required_argument, nullptr, 'f'},
+ {"help", no_argument, nullptr, 'h'},
+ {SHRPX_OPT_INSECURE.c_str(), no_argument, nullptr, 'k'},
+ {SHRPX_OPT_WORKERS.c_str(), required_argument, nullptr, 'n'},
+ {SHRPX_OPT_CLIENT_PROXY.c_str(), no_argument, nullptr, 'p'},
+ {SHRPX_OPT_HTTP2_PROXY.c_str(), no_argument, nullptr, 's'},
+ {"version", no_argument, nullptr, 'v'},
+ {SHRPX_OPT_FRONTEND_FRAME_DEBUG.c_str(), no_argument, nullptr, 'o'},
+ {SHRPX_OPT_ADD_X_FORWARDED_FOR.c_str(), no_argument, &flag, 1},
+ {SHRPX_OPT_FRONTEND_HTTP2_READ_TIMEOUT.c_str(), required_argument,
+ &flag, 2},
+ {SHRPX_OPT_FRONTEND_READ_TIMEOUT.c_str(), required_argument, &flag, 3},
+ {SHRPX_OPT_FRONTEND_WRITE_TIMEOUT.c_str(), required_argument, &flag, 4},
+ {SHRPX_OPT_BACKEND_READ_TIMEOUT.c_str(), required_argument, &flag, 5},
+ {SHRPX_OPT_BACKEND_WRITE_TIMEOUT.c_str(), required_argument, &flag, 6},
+ {SHRPX_OPT_ACCESSLOG_FILE.c_str(), required_argument, &flag, 7},
+ {SHRPX_OPT_BACKEND_KEEP_ALIVE_TIMEOUT.c_str(), required_argument, &flag,
+ 8},
+ {SHRPX_OPT_FRONTEND_HTTP2_WINDOW_BITS.c_str(), required_argument, &flag,
+ 9},
+ {SHRPX_OPT_PID_FILE.c_str(), required_argument, &flag, 10},
+ {SHRPX_OPT_USER.c_str(), required_argument, &flag, 11},
+ {"conf", required_argument, &flag, 12},
+ {SHRPX_OPT_SYSLOG_FACILITY.c_str(), required_argument, &flag, 14},
+ {SHRPX_OPT_BACKLOG.c_str(), required_argument, &flag, 15},
+ {SHRPX_OPT_CIPHERS.c_str(), required_argument, &flag, 16},
+ {SHRPX_OPT_CLIENT.c_str(), no_argument, &flag, 17},
+ {SHRPX_OPT_BACKEND_HTTP2_WINDOW_BITS.c_str(), required_argument, &flag,
+ 18},
+ {SHRPX_OPT_CACERT.c_str(), required_argument, &flag, 19},
+ {SHRPX_OPT_BACKEND_IPV4.c_str(), no_argument, &flag, 20},
+ {SHRPX_OPT_BACKEND_IPV6.c_str(), no_argument, &flag, 21},
+ {SHRPX_OPT_PRIVATE_KEY_PASSWD_FILE.c_str(), required_argument, &flag,
+ 22},
+ {SHRPX_OPT_NO_VIA.c_str(), no_argument, &flag, 23},
+ {SHRPX_OPT_SUBCERT.c_str(), required_argument, &flag, 24},
+ {SHRPX_OPT_HTTP2_BRIDGE.c_str(), no_argument, &flag, 25},
+ {SHRPX_OPT_BACKEND_HTTP_PROXY_URI.c_str(), required_argument, &flag,
+ 26},
+ {SHRPX_OPT_BACKEND_NO_TLS.c_str(), no_argument, &flag, 27},
+ {SHRPX_OPT_OCSP_STARTUP.c_str(), no_argument, &flag, 28},
+ {SHRPX_OPT_FRONTEND_NO_TLS.c_str(), no_argument, &flag, 29},
+ {SHRPX_OPT_NO_VERIFY_OCSP.c_str(), no_argument, &flag, 30},
+ {SHRPX_OPT_BACKEND_TLS_SNI_FIELD.c_str(), required_argument, &flag, 31},
+ {SHRPX_OPT_DH_PARAM_FILE.c_str(), required_argument, &flag, 33},
+ {SHRPX_OPT_READ_RATE.c_str(), required_argument, &flag, 34},
+ {SHRPX_OPT_READ_BURST.c_str(), required_argument, &flag, 35},
+ {SHRPX_OPT_WRITE_RATE.c_str(), required_argument, &flag, 36},
+ {SHRPX_OPT_WRITE_BURST.c_str(), required_argument, &flag, 37},
+ {SHRPX_OPT_NPN_LIST.c_str(), required_argument, &flag, 38},
+ {SHRPX_OPT_VERIFY_CLIENT.c_str(), no_argument, &flag, 39},
+ {SHRPX_OPT_VERIFY_CLIENT_CACERT.c_str(), required_argument, &flag, 40},
+ {SHRPX_OPT_CLIENT_PRIVATE_KEY_FILE.c_str(), required_argument, &flag,
+ 41},
+ {SHRPX_OPT_CLIENT_CERT_FILE.c_str(), required_argument, &flag, 42},
+ {SHRPX_OPT_FRONTEND_HTTP2_DUMP_REQUEST_HEADER.c_str(),
+ required_argument, &flag, 43},
+ {SHRPX_OPT_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER.c_str(),
+ required_argument, &flag, 44},
+ {SHRPX_OPT_HTTP2_NO_COOKIE_CRUMBLING.c_str(), no_argument, &flag, 45},
+ {SHRPX_OPT_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS.c_str(),
+ required_argument, &flag, 46},
+ {SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_BITS.c_str(),
+ required_argument, &flag, 47},
+ {SHRPX_OPT_TLS_PROTO_LIST.c_str(), required_argument, &flag, 48},
+ {SHRPX_OPT_PADDING.c_str(), required_argument, &flag, 49},
+ {SHRPX_OPT_WORKER_READ_RATE.c_str(), required_argument, &flag, 50},
+ {SHRPX_OPT_WORKER_READ_BURST.c_str(), required_argument, &flag, 51},
+ {SHRPX_OPT_WORKER_WRITE_RATE.c_str(), required_argument, &flag, 52},
+ {SHRPX_OPT_WORKER_WRITE_BURST.c_str(), required_argument, &flag, 53},
+ {SHRPX_OPT_ALTSVC.c_str(), required_argument, &flag, 54},
+ {SHRPX_OPT_ADD_RESPONSE_HEADER.c_str(), required_argument, &flag, 55},
+ {SHRPX_OPT_WORKER_FRONTEND_CONNECTIONS.c_str(), required_argument,
+ &flag, 56},
+ {SHRPX_OPT_ACCESSLOG_SYSLOG.c_str(), no_argument, &flag, 57},
+ {SHRPX_OPT_ERRORLOG_FILE.c_str(), required_argument, &flag, 58},
+ {SHRPX_OPT_ERRORLOG_SYSLOG.c_str(), no_argument, &flag, 59},
+ {SHRPX_OPT_STREAM_READ_TIMEOUT.c_str(), required_argument, &flag, 60},
+ {SHRPX_OPT_STREAM_WRITE_TIMEOUT.c_str(), required_argument, &flag, 61},
+ {SHRPX_OPT_NO_LOCATION_REWRITE.c_str(), no_argument, &flag, 62},
+ {SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_HOST.c_str(),
+ required_argument, &flag, 63},
+ {SHRPX_OPT_LISTENER_DISABLE_TIMEOUT.c_str(), required_argument, &flag,
+ 64},
+ {SHRPX_OPT_STRIP_INCOMING_X_FORWARDED_FOR.c_str(), no_argument, &flag,
+ 65},
+ {SHRPX_OPT_ACCESSLOG_FORMAT.c_str(), required_argument, &flag, 66},
+ {SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND.c_str(),
+ required_argument, &flag, 67},
+ {SHRPX_OPT_TLS_TICKET_KEY_FILE.c_str(), required_argument, &flag, 68},
+ {SHRPX_OPT_RLIMIT_NOFILE.c_str(), required_argument, &flag, 69},
+ {SHRPX_OPT_BACKEND_RESPONSE_BUFFER.c_str(), required_argument, &flag,
+ 71},
+ {SHRPX_OPT_BACKEND_REQUEST_BUFFER.c_str(), required_argument, &flag,
+ 72},
+ {SHRPX_OPT_NO_HOST_REWRITE.c_str(), no_argument, &flag, 73},
+ {SHRPX_OPT_NO_SERVER_PUSH.c_str(), no_argument, &flag, 74},
+ {SHRPX_OPT_BACKEND_HTTP2_CONNECTIONS_PER_WORKER.c_str(),
+ required_argument, &flag, 76},
+ {SHRPX_OPT_FETCH_OCSP_RESPONSE_FILE.c_str(), required_argument, &flag,
+ 77},
+ {SHRPX_OPT_OCSP_UPDATE_INTERVAL.c_str(), required_argument, &flag, 78},
+ {SHRPX_OPT_NO_OCSP.c_str(), no_argument, &flag, 79},
+ {SHRPX_OPT_HEADER_FIELD_BUFFER.c_str(), required_argument, &flag, 80},
+ {SHRPX_OPT_MAX_HEADER_FIELDS.c_str(), required_argument, &flag, 81},
+ {SHRPX_OPT_ADD_REQUEST_HEADER.c_str(), required_argument, &flag, 82},
+ {SHRPX_OPT_INCLUDE.c_str(), required_argument, &flag, 83},
+ {SHRPX_OPT_TLS_TICKET_KEY_CIPHER.c_str(), required_argument, &flag, 84},
+ {SHRPX_OPT_HOST_REWRITE.c_str(), no_argument, &flag, 85},
+ {SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED.c_str(), required_argument,
+ &flag, 86},
+ {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED.c_str(), required_argument, &flag,
+ 87},
+ {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_INTERVAL.c_str(), required_argument,
+ &flag, 88},
+ {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_RETRY.c_str(),
+ required_argument, &flag, 89},
+ {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL.c_str(), required_argument,
+ &flag, 90},
+ {SHRPX_OPT_MRUBY_FILE.c_str(), required_argument, &flag, 91},
+ {SHRPX_OPT_ACCEPT_PROXY_PROTOCOL.c_str(), no_argument, &flag, 93},
+ {SHRPX_OPT_FASTOPEN.c_str(), required_argument, &flag, 94},
+ {SHRPX_OPT_TLS_DYN_REC_WARMUP_THRESHOLD.c_str(), required_argument,
+ &flag, 95},
+ {SHRPX_OPT_TLS_DYN_REC_IDLE_TIMEOUT.c_str(), required_argument, &flag,
+ 96},
+ {SHRPX_OPT_ADD_FORWARDED.c_str(), required_argument, &flag, 97},
+ {SHRPX_OPT_STRIP_INCOMING_FORWARDED.c_str(), no_argument, &flag, 98},
+ {SHRPX_OPT_FORWARDED_BY.c_str(), required_argument, &flag, 99},
+ {SHRPX_OPT_FORWARDED_FOR.c_str(), required_argument, &flag, 100},
+ {SHRPX_OPT_RESPONSE_HEADER_FIELD_BUFFER.c_str(), required_argument,
+ &flag, 101},
+ {SHRPX_OPT_MAX_RESPONSE_HEADER_FIELDS.c_str(), required_argument, &flag,
+ 102},
+ {SHRPX_OPT_NO_HTTP2_CIPHER_BLACK_LIST.c_str(), no_argument, &flag, 103},
+ {SHRPX_OPT_REQUEST_HEADER_FIELD_BUFFER.c_str(), required_argument,
+ &flag, 104},
+ {SHRPX_OPT_MAX_REQUEST_HEADER_FIELDS.c_str(), required_argument, &flag,
+ 105},
+ {SHRPX_OPT_BACKEND_HTTP1_TLS.c_str(), no_argument, &flag, 106},
+ {SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_TLS.c_str(), no_argument, &flag,
+ 108},
+ {SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_CERT_FILE.c_str(),
+ required_argument, &flag, 109},
+ {SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_PRIVATE_KEY_FILE.c_str(),
+ required_argument, &flag, 110},
+ {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_TLS.c_str(), no_argument, &flag,
+ 111},
+ {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_CERT_FILE.c_str(),
+ required_argument, &flag, 112},
+ {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_PRIVATE_KEY_FILE.c_str(),
+ required_argument, &flag, 113},
+ {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_ADDRESS_FAMILY.c_str(),
+ required_argument, &flag, 114},
+ {SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_ADDRESS_FAMILY.c_str(),
+ required_argument, &flag, 115},
+ {SHRPX_OPT_BACKEND_ADDRESS_FAMILY.c_str(), required_argument, &flag,
+ 116},
+ {SHRPX_OPT_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS.c_str(),
+ required_argument, &flag, 117},
+ {SHRPX_OPT_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS.c_str(),
+ required_argument, &flag, 118},
+ {SHRPX_OPT_BACKEND_CONNECTIONS_PER_FRONTEND.c_str(), required_argument,
+ &flag, 119},
+ {SHRPX_OPT_BACKEND_TLS.c_str(), no_argument, &flag, 120},
+ {SHRPX_OPT_BACKEND_CONNECTIONS_PER_HOST.c_str(), required_argument,
+ &flag, 121},
+ {SHRPX_OPT_ERROR_PAGE.c_str(), required_argument, &flag, 122},
+ {SHRPX_OPT_NO_KQUEUE.c_str(), no_argument, &flag, 123},
+ {SHRPX_OPT_FRONTEND_HTTP2_SETTINGS_TIMEOUT.c_str(), required_argument,
+ &flag, 124},
+ {SHRPX_OPT_BACKEND_HTTP2_SETTINGS_TIMEOUT.c_str(), required_argument,
+ &flag, 125},
+ {SHRPX_OPT_API_MAX_REQUEST_BODY.c_str(), required_argument, &flag, 126},
+ {SHRPX_OPT_BACKEND_MAX_BACKOFF.c_str(), required_argument, &flag, 127},
+ {SHRPX_OPT_SERVER_NAME.c_str(), required_argument, &flag, 128},
+ {SHRPX_OPT_NO_SERVER_REWRITE.c_str(), no_argument, &flag, 129},
+ {SHRPX_OPT_FRONTEND_HTTP2_OPTIMIZE_WRITE_BUFFER_SIZE.c_str(),
+ no_argument, &flag, 130},
+ {SHRPX_OPT_FRONTEND_HTTP2_OPTIMIZE_WINDOW_SIZE.c_str(), no_argument,
+ &flag, 131},
+ {SHRPX_OPT_FRONTEND_HTTP2_WINDOW_SIZE.c_str(), required_argument, &flag,
+ 132},
+ {SHRPX_OPT_FRONTEND_HTTP2_CONNECTION_WINDOW_SIZE.c_str(),
+ required_argument, &flag, 133},
+ {SHRPX_OPT_BACKEND_HTTP2_WINDOW_SIZE.c_str(), required_argument, &flag,
+ 134},
+ {SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE.c_str(),
+ required_argument, &flag, 135},
+ {SHRPX_OPT_FRONTEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE.c_str(),
+ required_argument, &flag, 136},
+ {SHRPX_OPT_FRONTEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE.c_str(),
+ required_argument, &flag, 137},
+ {SHRPX_OPT_BACKEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE.c_str(),
+ required_argument, &flag, 138},
+ {SHRPX_OPT_BACKEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE.c_str(),
+ required_argument, &flag, 139},
+ {SHRPX_OPT_ECDH_CURVES.c_str(), required_argument, &flag, 140},
+ {SHRPX_OPT_TLS_SCT_DIR.c_str(), required_argument, &flag, 141},
+ {SHRPX_OPT_BACKEND_CONNECT_TIMEOUT.c_str(), required_argument, &flag,
+ 142},
+ {SHRPX_OPT_DNS_CACHE_TIMEOUT.c_str(), required_argument, &flag, 143},
+ {SHRPX_OPT_DNS_LOOKUP_TIMEOUT.c_str(), required_argument, &flag, 144},
+ {SHRPX_OPT_DNS_MAX_TRY.c_str(), required_argument, &flag, 145},
+ {SHRPX_OPT_FRONTEND_KEEP_ALIVE_TIMEOUT.c_str(), required_argument,
+ &flag, 146},
+ {SHRPX_OPT_PSK_SECRETS.c_str(), required_argument, &flag, 147},
+ {SHRPX_OPT_CLIENT_PSK_SECRETS.c_str(), required_argument, &flag, 148},
+ {SHRPX_OPT_CLIENT_NO_HTTP2_CIPHER_BLACK_LIST.c_str(), no_argument,
+ &flag, 149},
+ {SHRPX_OPT_CLIENT_CIPHERS.c_str(), required_argument, &flag, 150},
+ {SHRPX_OPT_ACCESSLOG_WRITE_EARLY.c_str(), no_argument, &flag, 151},
+ {SHRPX_OPT_TLS_MIN_PROTO_VERSION.c_str(), required_argument, &flag,
+ 152},
+ {SHRPX_OPT_TLS_MAX_PROTO_VERSION.c_str(), required_argument, &flag,
+ 153},
+ {SHRPX_OPT_REDIRECT_HTTPS_PORT.c_str(), required_argument, &flag, 154},
+ {SHRPX_OPT_FRONTEND_MAX_REQUESTS.c_str(), required_argument, &flag,
+ 155},
+ {SHRPX_OPT_SINGLE_THREAD.c_str(), no_argument, &flag, 156},
+ {SHRPX_OPT_NO_ADD_X_FORWARDED_PROTO.c_str(), no_argument, &flag, 157},
+ {SHRPX_OPT_NO_STRIP_INCOMING_X_FORWARDED_PROTO.c_str(), no_argument,
+ &flag, 158},
+ {SHRPX_OPT_SINGLE_PROCESS.c_str(), no_argument, &flag, 159},
+ {SHRPX_OPT_VERIFY_CLIENT_TOLERATE_EXPIRED.c_str(), no_argument, &flag,
+ 160},
+ {SHRPX_OPT_IGNORE_PER_PATTERN_MRUBY_ERROR.c_str(), no_argument, &flag,
+ 161},
+ {SHRPX_OPT_TLS_NO_POSTPONE_EARLY_DATA.c_str(), no_argument, &flag, 162},
+ {SHRPX_OPT_TLS_MAX_EARLY_DATA.c_str(), required_argument, &flag, 163},
+ {SHRPX_OPT_TLS13_CIPHERS.c_str(), required_argument, &flag, 164},
+ {SHRPX_OPT_TLS13_CLIENT_CIPHERS.c_str(), required_argument, &flag, 165},
+ {SHRPX_OPT_NO_STRIP_INCOMING_EARLY_DATA.c_str(), no_argument, &flag,
+ 166},
+ {SHRPX_OPT_NO_HTTP2_CIPHER_BLOCK_LIST.c_str(), no_argument, &flag, 167},
+ {SHRPX_OPT_CLIENT_NO_HTTP2_CIPHER_BLOCK_LIST.c_str(), no_argument,
+ &flag, 168},
+ {SHRPX_OPT_QUIC_BPF_PROGRAM_FILE.c_str(), required_argument, &flag,
+ 169},
+ {SHRPX_OPT_NO_QUIC_BPF.c_str(), no_argument, &flag, 170},
+ {SHRPX_OPT_HTTP2_ALTSVC.c_str(), required_argument, &flag, 171},
+ {SHRPX_OPT_FRONTEND_HTTP3_READ_TIMEOUT.c_str(), required_argument,
+ &flag, 172},
+ {SHRPX_OPT_FRONTEND_QUIC_IDLE_TIMEOUT.c_str(), required_argument, &flag,
+ 173},
+ {SHRPX_OPT_FRONTEND_QUIC_DEBUG_LOG.c_str(), no_argument, &flag, 174},
+ {SHRPX_OPT_FRONTEND_HTTP3_WINDOW_SIZE.c_str(), required_argument, &flag,
+ 175},
+ {SHRPX_OPT_FRONTEND_HTTP3_CONNECTION_WINDOW_SIZE.c_str(),
+ required_argument, &flag, 176},
+ {SHRPX_OPT_FRONTEND_HTTP3_MAX_WINDOW_SIZE.c_str(), required_argument,
+ &flag, 177},
+ {SHRPX_OPT_FRONTEND_HTTP3_MAX_CONNECTION_WINDOW_SIZE.c_str(),
+ required_argument, &flag, 178},
+ {SHRPX_OPT_FRONTEND_HTTP3_MAX_CONCURRENT_STREAMS.c_str(),
+ required_argument, &flag, 179},
+ {SHRPX_OPT_FRONTEND_QUIC_EARLY_DATA.c_str(), no_argument, &flag, 180},
+ {SHRPX_OPT_FRONTEND_QUIC_QLOG_DIR.c_str(), required_argument, &flag,
+ 181},
+ {SHRPX_OPT_FRONTEND_QUIC_REQUIRE_TOKEN.c_str(), no_argument, &flag,
+ 182},
+ {SHRPX_OPT_FRONTEND_QUIC_CONGESTION_CONTROLLER.c_str(),
+ required_argument, &flag, 183},
+ {SHRPX_OPT_QUIC_SERVER_ID.c_str(), required_argument, &flag, 185},
+ {SHRPX_OPT_FRONTEND_QUIC_SECRET_FILE.c_str(), required_argument, &flag,
+ 186},
+ {SHRPX_OPT_RLIMIT_MEMLOCK.c_str(), required_argument, &flag, 187},
+ {SHRPX_OPT_MAX_WORKER_PROCESSES.c_str(), required_argument, &flag, 188},
+ {SHRPX_OPT_WORKER_PROCESS_GRACE_SHUTDOWN_PERIOD.c_str(),
+ required_argument, &flag, 189},
+ {SHRPX_OPT_FRONTEND_QUIC_INITIAL_RTT.c_str(), required_argument, &flag,
+ 190},
+ {SHRPX_OPT_REQUIRE_HTTP_SCHEME.c_str(), no_argument, &flag, 191},
+ {SHRPX_OPT_TLS_KTLS.c_str(), no_argument, &flag, 192},
+ {SHRPX_OPT_ALPN_LIST.c_str(), required_argument, &flag, 193},
+ {nullptr, 0, nullptr, 0}};
+
+ int option_index = 0;
+ int c = getopt_long(argc, argv, "DL:b:c:f:hkn:opsv", long_options,
+ &option_index);
+ if (c == -1) {
+ break;
+ }
+ switch (c) {
+ case 'D':
+ cmdcfgs.emplace_back(SHRPX_OPT_DAEMON, StringRef::from_lit("yes"));
+ break;
+ case 'L':
+ cmdcfgs.emplace_back(SHRPX_OPT_LOG_LEVEL, StringRef{optarg});
+ break;
+ case 'b':
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND, StringRef{optarg});
+ break;
+ case 'c':
+ cmdcfgs.emplace_back(SHRPX_OPT_HTTP2_MAX_CONCURRENT_STREAMS,
+ StringRef{optarg});
+ break;
+ case 'f':
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND, StringRef{optarg});
+ break;
+ case 'h':
+ print_help(std::cout);
+ exit(EXIT_SUCCESS);
+ case 'k':
+ cmdcfgs.emplace_back(SHRPX_OPT_INSECURE, StringRef::from_lit("yes"));
+ break;
+ case 'n':
+ cmdcfgs.emplace_back(SHRPX_OPT_WORKERS, StringRef{optarg});
+ break;
+ case 'o':
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_FRAME_DEBUG,
+ StringRef::from_lit("yes"));
+ break;
+ case 'p':
+ cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_PROXY, StringRef::from_lit("yes"));
+ break;
+ case 's':
+ cmdcfgs.emplace_back(SHRPX_OPT_HTTP2_PROXY, StringRef::from_lit("yes"));
+ break;
+ case 'v':
+ print_version(std::cout);
+ exit(EXIT_SUCCESS);
+ case '?':
+ util::show_candidates(argv[optind - 1], long_options);
+ exit(EXIT_FAILURE);
+ case 0:
+ switch (flag) {
+ case 1:
+ // --add-x-forwarded-for
+ cmdcfgs.emplace_back(SHRPX_OPT_ADD_X_FORWARDED_FOR,
+ StringRef::from_lit("yes"));
+ break;
+ case 2:
+ // --frontend-http2-read-timeout
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_READ_TIMEOUT,
+ StringRef{optarg});
+ break;
+ case 3:
+ // --frontend-read-timeout
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_READ_TIMEOUT,
+ StringRef{optarg});
+ break;
+ case 4:
+ // --frontend-write-timeout
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_WRITE_TIMEOUT,
+ StringRef{optarg});
+ break;
+ case 5:
+ // --backend-read-timeout
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_READ_TIMEOUT, StringRef{optarg});
+ break;
+ case 6:
+ // --backend-write-timeout
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_WRITE_TIMEOUT,
+ StringRef{optarg});
+ break;
+ case 7:
+ cmdcfgs.emplace_back(SHRPX_OPT_ACCESSLOG_FILE, StringRef{optarg});
+ break;
+ case 8:
+ // --backend-keep-alive-timeout
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_KEEP_ALIVE_TIMEOUT,
+ StringRef{optarg});
+ break;
+ case 9:
+ // --frontend-http2-window-bits
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_WINDOW_BITS,
+ StringRef{optarg});
+ break;
+ case 10:
+ cmdcfgs.emplace_back(SHRPX_OPT_PID_FILE, StringRef{optarg});
+ break;
+ case 11:
+ cmdcfgs.emplace_back(SHRPX_OPT_USER, StringRef{optarg});
+ break;
+ case 12:
+ // --conf
+ mod_config()->conf_path =
+ make_string_ref(mod_config()->balloc, StringRef{optarg});
+ break;
+ case 14:
+ // --syslog-facility
+ cmdcfgs.emplace_back(SHRPX_OPT_SYSLOG_FACILITY, StringRef{optarg});
+ break;
+ case 15:
+ // --backlog
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKLOG, StringRef{optarg});
+ break;
+ case 16:
+ // --ciphers
+ cmdcfgs.emplace_back(SHRPX_OPT_CIPHERS, StringRef{optarg});
+ break;
+ case 17:
+ // --client
+ cmdcfgs.emplace_back(SHRPX_OPT_CLIENT, StringRef::from_lit("yes"));
+ break;
+ case 18:
+ // --backend-http2-window-bits
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_WINDOW_BITS,
+ StringRef{optarg});
+ break;
+ case 19:
+ // --cacert
+ cmdcfgs.emplace_back(SHRPX_OPT_CACERT, StringRef{optarg});
+ break;
+ case 20:
+ // --backend-ipv4
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_IPV4,
+ StringRef::from_lit("yes"));
+ break;
+ case 21:
+ // --backend-ipv6
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_IPV6,
+ StringRef::from_lit("yes"));
+ break;
+ case 22:
+ // --private-key-passwd-file
+ cmdcfgs.emplace_back(SHRPX_OPT_PRIVATE_KEY_PASSWD_FILE,
+ StringRef{optarg});
+ break;
+ case 23:
+ // --no-via
+ cmdcfgs.emplace_back(SHRPX_OPT_NO_VIA, StringRef::from_lit("yes"));
+ break;
+ case 24:
+ // --subcert
+ cmdcfgs.emplace_back(SHRPX_OPT_SUBCERT, StringRef{optarg});
+ break;
+ case 25:
+ // --http2-bridge
+ cmdcfgs.emplace_back(SHRPX_OPT_HTTP2_BRIDGE,
+ StringRef::from_lit("yes"));
+ break;
+ case 26:
+ // --backend-http-proxy-uri
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP_PROXY_URI,
+ StringRef{optarg});
+ break;
+ case 27:
+ // --backend-no-tls
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_NO_TLS,
+ StringRef::from_lit("yes"));
+ break;
+ case 28:
+ // --ocsp-startup
+ cmdcfgs.emplace_back(SHRPX_OPT_OCSP_STARTUP,
+ StringRef::from_lit("yes"));
+ break;
+ case 29:
+ // --frontend-no-tls
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_NO_TLS,
+ StringRef::from_lit("yes"));
+ break;
+ case 30:
+ // --no-verify-ocsp
+ cmdcfgs.emplace_back(SHRPX_OPT_NO_VERIFY_OCSP,
+ StringRef::from_lit("yes"));
+ break;
+ case 31:
+ // --backend-tls-sni-field
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_TLS_SNI_FIELD,
+ StringRef{optarg});
+ break;
+ case 33:
+ // --dh-param-file
+ cmdcfgs.emplace_back(SHRPX_OPT_DH_PARAM_FILE, StringRef{optarg});
+ break;
+ case 34:
+ // --read-rate
+ cmdcfgs.emplace_back(SHRPX_OPT_READ_RATE, StringRef{optarg});
+ break;
+ case 35:
+ // --read-burst
+ cmdcfgs.emplace_back(SHRPX_OPT_READ_BURST, StringRef{optarg});
+ break;
+ case 36:
+ // --write-rate
+ cmdcfgs.emplace_back(SHRPX_OPT_WRITE_RATE, StringRef{optarg});
+ break;
+ case 37:
+ // --write-burst
+ cmdcfgs.emplace_back(SHRPX_OPT_WRITE_BURST, StringRef{optarg});
+ break;
+ case 38:
+ // --npn-list
+ cmdcfgs.emplace_back(SHRPX_OPT_NPN_LIST, StringRef{optarg});
+ break;
+ case 39:
+ // --verify-client
+ cmdcfgs.emplace_back(SHRPX_OPT_VERIFY_CLIENT,
+ StringRef::from_lit("yes"));
+ break;
+ case 40:
+ // --verify-client-cacert
+ cmdcfgs.emplace_back(SHRPX_OPT_VERIFY_CLIENT_CACERT, StringRef{optarg});
+ break;
+ case 41:
+ // --client-private-key-file
+ cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_PRIVATE_KEY_FILE,
+ StringRef{optarg});
+ break;
+ case 42:
+ // --client-cert-file
+ cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_CERT_FILE, StringRef{optarg});
+ break;
+ case 43:
+ // --frontend-http2-dump-request-header
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_DUMP_REQUEST_HEADER,
+ StringRef{optarg});
+ break;
+ case 44:
+ // --frontend-http2-dump-response-header
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER,
+ StringRef{optarg});
+ break;
+ case 45:
+ // --http2-no-cookie-crumbling
+ cmdcfgs.emplace_back(SHRPX_OPT_HTTP2_NO_COOKIE_CRUMBLING,
+ StringRef::from_lit("yes"));
+ break;
+ case 46:
+ // --frontend-http2-connection-window-bits
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS,
+ StringRef{optarg});
+ break;
+ case 47:
+ // --backend-http2-connection-window-bits
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_BITS,
+ StringRef{optarg});
+ break;
+ case 48:
+ // --tls-proto-list
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_PROTO_LIST, StringRef{optarg});
+ break;
+ case 49:
+ // --padding
+ cmdcfgs.emplace_back(SHRPX_OPT_PADDING, StringRef{optarg});
+ break;
+ case 50:
+ // --worker-read-rate
+ cmdcfgs.emplace_back(SHRPX_OPT_WORKER_READ_RATE, StringRef{optarg});
+ break;
+ case 51:
+ // --worker-read-burst
+ cmdcfgs.emplace_back(SHRPX_OPT_WORKER_READ_BURST, StringRef{optarg});
+ break;
+ case 52:
+ // --worker-write-rate
+ cmdcfgs.emplace_back(SHRPX_OPT_WORKER_WRITE_RATE, StringRef{optarg});
+ break;
+ case 53:
+ // --worker-write-burst
+ cmdcfgs.emplace_back(SHRPX_OPT_WORKER_WRITE_BURST, StringRef{optarg});
+ break;
+ case 54:
+ // --altsvc
+ cmdcfgs.emplace_back(SHRPX_OPT_ALTSVC, StringRef{optarg});
+ break;
+ case 55:
+ // --add-response-header
+ cmdcfgs.emplace_back(SHRPX_OPT_ADD_RESPONSE_HEADER, StringRef{optarg});
+ break;
+ case 56:
+ // --worker-frontend-connections
+ cmdcfgs.emplace_back(SHRPX_OPT_WORKER_FRONTEND_CONNECTIONS,
+ StringRef{optarg});
+ break;
+ case 57:
+ // --accesslog-syslog
+ cmdcfgs.emplace_back(SHRPX_OPT_ACCESSLOG_SYSLOG,
+ StringRef::from_lit("yes"));
+ break;
+ case 58:
+ // --errorlog-file
+ cmdcfgs.emplace_back(SHRPX_OPT_ERRORLOG_FILE, StringRef{optarg});
+ break;
+ case 59:
+ // --errorlog-syslog
+ cmdcfgs.emplace_back(SHRPX_OPT_ERRORLOG_SYSLOG,
+ StringRef::from_lit("yes"));
+ break;
+ case 60:
+ // --stream-read-timeout
+ cmdcfgs.emplace_back(SHRPX_OPT_STREAM_READ_TIMEOUT, StringRef{optarg});
+ break;
+ case 61:
+ // --stream-write-timeout
+ cmdcfgs.emplace_back(SHRPX_OPT_STREAM_WRITE_TIMEOUT, StringRef{optarg});
+ break;
+ case 62:
+ // --no-location-rewrite
+ cmdcfgs.emplace_back(SHRPX_OPT_NO_LOCATION_REWRITE,
+ StringRef::from_lit("yes"));
+ break;
+ case 63:
+ // --backend-http1-connections-per-host
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_HOST,
+ StringRef{optarg});
+ break;
+ case 64:
+ // --listener-disable-timeout
+ cmdcfgs.emplace_back(SHRPX_OPT_LISTENER_DISABLE_TIMEOUT,
+ StringRef{optarg});
+ break;
+ case 65:
+ // --strip-incoming-x-forwarded-for
+ cmdcfgs.emplace_back(SHRPX_OPT_STRIP_INCOMING_X_FORWARDED_FOR,
+ StringRef::from_lit("yes"));
+ break;
+ case 66:
+ // --accesslog-format
+ cmdcfgs.emplace_back(SHRPX_OPT_ACCESSLOG_FORMAT, StringRef{optarg});
+ break;
+ case 67:
+ // --backend-http1-connections-per-frontend
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND,
+ StringRef{optarg});
+ break;
+ case 68:
+ // --tls-ticket-key-file
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_FILE, StringRef{optarg});
+ break;
+ case 69:
+ // --rlimit-nofile
+ cmdcfgs.emplace_back(SHRPX_OPT_RLIMIT_NOFILE, StringRef{optarg});
+ break;
+ case 71:
+ // --backend-response-buffer
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_RESPONSE_BUFFER,
+ StringRef{optarg});
+ break;
+ case 72:
+ // --backend-request-buffer
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_REQUEST_BUFFER,
+ StringRef{optarg});
+ break;
+ case 73:
+ // --no-host-rewrite
+ cmdcfgs.emplace_back(SHRPX_OPT_NO_HOST_REWRITE,
+ StringRef::from_lit("yes"));
+ break;
+ case 74:
+ // --no-server-push
+ cmdcfgs.emplace_back(SHRPX_OPT_NO_SERVER_PUSH,
+ StringRef::from_lit("yes"));
+ break;
+ case 76:
+ // --backend-http2-connections-per-worker
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_CONNECTIONS_PER_WORKER,
+ StringRef{optarg});
+ break;
+ case 77:
+ // --fetch-ocsp-response-file
+ cmdcfgs.emplace_back(SHRPX_OPT_FETCH_OCSP_RESPONSE_FILE,
+ StringRef{optarg});
+ break;
+ case 78:
+ // --ocsp-update-interval
+ cmdcfgs.emplace_back(SHRPX_OPT_OCSP_UPDATE_INTERVAL, StringRef{optarg});
+ break;
+ case 79:
+ // --no-ocsp
+ cmdcfgs.emplace_back(SHRPX_OPT_NO_OCSP, StringRef::from_lit("yes"));
+ break;
+ case 80:
+ // --header-field-buffer
+ cmdcfgs.emplace_back(SHRPX_OPT_HEADER_FIELD_BUFFER, StringRef{optarg});
+ break;
+ case 81:
+ // --max-header-fields
+ cmdcfgs.emplace_back(SHRPX_OPT_MAX_HEADER_FIELDS, StringRef{optarg});
+ break;
+ case 82:
+ // --add-request-header
+ cmdcfgs.emplace_back(SHRPX_OPT_ADD_REQUEST_HEADER, StringRef{optarg});
+ break;
+ case 83:
+ // --include
+ cmdcfgs.emplace_back(SHRPX_OPT_INCLUDE, StringRef{optarg});
+ break;
+ case 84:
+ // --tls-ticket-key-cipher
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_CIPHER,
+ StringRef{optarg});
+ break;
+ case 85:
+ // --host-rewrite
+ cmdcfgs.emplace_back(SHRPX_OPT_HOST_REWRITE,
+ StringRef::from_lit("yes"));
+ break;
+ case 86:
+ // --tls-session-cache-memcached
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED,
+ StringRef{optarg});
+ break;
+ case 87:
+ // --tls-ticket-key-memcached
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED,
+ StringRef{optarg});
+ break;
+ case 88:
+ // --tls-ticket-key-memcached-interval
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_INTERVAL,
+ StringRef{optarg});
+ break;
+ case 89:
+ // --tls-ticket-key-memcached-max-retry
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_RETRY,
+ StringRef{optarg});
+ break;
+ case 90:
+ // --tls-ticket-key-memcached-max-fail
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL,
+ StringRef{optarg});
+ break;
+ case 91:
+ // --mruby-file
+ cmdcfgs.emplace_back(SHRPX_OPT_MRUBY_FILE, StringRef{optarg});
+ break;
+ case 93:
+ // --accept-proxy-protocol
+ cmdcfgs.emplace_back(SHRPX_OPT_ACCEPT_PROXY_PROTOCOL,
+ StringRef::from_lit("yes"));
+ break;
+ case 94:
+ // --fastopen
+ cmdcfgs.emplace_back(SHRPX_OPT_FASTOPEN, StringRef{optarg});
+ break;
+ case 95:
+ // --tls-dyn-rec-warmup-threshold
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_DYN_REC_WARMUP_THRESHOLD,
+ StringRef{optarg});
+ break;
+ case 96:
+ // --tls-dyn-rec-idle-timeout
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_DYN_REC_IDLE_TIMEOUT,
+ StringRef{optarg});
+ break;
+ case 97:
+ // --add-forwarded
+ cmdcfgs.emplace_back(SHRPX_OPT_ADD_FORWARDED, StringRef{optarg});
+ break;
+ case 98:
+ // --strip-incoming-forwarded
+ cmdcfgs.emplace_back(SHRPX_OPT_STRIP_INCOMING_FORWARDED,
+ StringRef::from_lit("yes"));
+ break;
+ case 99:
+ // --forwarded-by
+ cmdcfgs.emplace_back(SHRPX_OPT_FORWARDED_BY, StringRef{optarg});
+ break;
+ case 100:
+ // --forwarded-for
+ cmdcfgs.emplace_back(SHRPX_OPT_FORWARDED_FOR, StringRef{optarg});
+ break;
+ case 101:
+ // --response-header-field-buffer
+ cmdcfgs.emplace_back(SHRPX_OPT_RESPONSE_HEADER_FIELD_BUFFER,
+ StringRef{optarg});
+ break;
+ case 102:
+ // --max-response-header-fields
+ cmdcfgs.emplace_back(SHRPX_OPT_MAX_RESPONSE_HEADER_FIELDS,
+ StringRef{optarg});
+ break;
+ case 103:
+ // --no-http2-cipher-black-list
+ cmdcfgs.emplace_back(SHRPX_OPT_NO_HTTP2_CIPHER_BLACK_LIST,
+ StringRef::from_lit("yes"));
+ break;
+ case 104:
+ // --request-header-field-buffer
+ cmdcfgs.emplace_back(SHRPX_OPT_REQUEST_HEADER_FIELD_BUFFER,
+ StringRef{optarg});
+ break;
+ case 105:
+ // --max-request-header-fields
+ cmdcfgs.emplace_back(SHRPX_OPT_MAX_REQUEST_HEADER_FIELDS,
+ StringRef{optarg});
+ break;
+ case 106:
+ // --backend-http1-tls
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP1_TLS,
+ StringRef::from_lit("yes"));
+ break;
+ case 108:
+ // --tls-session-cache-memcached-tls
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_TLS,
+ StringRef::from_lit("yes"));
+ break;
+ case 109:
+ // --tls-session-cache-memcached-cert-file
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_CERT_FILE,
+ StringRef{optarg});
+ break;
+ case 110:
+ // --tls-session-cache-memcached-private-key-file
+ cmdcfgs.emplace_back(
+ SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_PRIVATE_KEY_FILE,
+ StringRef{optarg});
+ break;
+ case 111:
+ // --tls-ticket-key-memcached-tls
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_TLS,
+ StringRef::from_lit("yes"));
+ break;
+ case 112:
+ // --tls-ticket-key-memcached-cert-file
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_CERT_FILE,
+ StringRef{optarg});
+ break;
+ case 113:
+ // --tls-ticket-key-memcached-private-key-file
+ cmdcfgs.emplace_back(
+ SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_PRIVATE_KEY_FILE,
+ StringRef{optarg});
+ break;
+ case 114:
+ // --tls-ticket-key-memcached-address-family
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_ADDRESS_FAMILY,
+ StringRef{optarg});
+ break;
+ case 115:
+ // --tls-session-cache-memcached-address-family
+ cmdcfgs.emplace_back(
+ SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_ADDRESS_FAMILY,
+ StringRef{optarg});
+ break;
+ case 116:
+ // --backend-address-family
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_ADDRESS_FAMILY,
+ StringRef{optarg});
+ break;
+ case 117:
+ // --frontend-http2-max-concurrent-streams
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS,
+ StringRef{optarg});
+ break;
+ case 118:
+ // --backend-http2-max-concurrent-streams
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS,
+ StringRef{optarg});
+ break;
+ case 119:
+ // --backend-connections-per-frontend
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_CONNECTIONS_PER_FRONTEND,
+ StringRef{optarg});
+ break;
+ case 120:
+ // --backend-tls
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_TLS, StringRef::from_lit("yes"));
+ break;
+ case 121:
+ // --backend-connections-per-host
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_CONNECTIONS_PER_HOST,
+ StringRef{optarg});
+ break;
+ case 122:
+ // --error-page
+ cmdcfgs.emplace_back(SHRPX_OPT_ERROR_PAGE, StringRef{optarg});
+ break;
+ case 123:
+ // --no-kqueue
+ cmdcfgs.emplace_back(SHRPX_OPT_NO_KQUEUE, StringRef::from_lit("yes"));
+ break;
+ case 124:
+ // --frontend-http2-settings-timeout
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_SETTINGS_TIMEOUT,
+ StringRef{optarg});
+ break;
+ case 125:
+ // --backend-http2-settings-timeout
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_SETTINGS_TIMEOUT,
+ StringRef{optarg});
+ break;
+ case 126:
+ // --api-max-request-body
+ cmdcfgs.emplace_back(SHRPX_OPT_API_MAX_REQUEST_BODY, StringRef{optarg});
+ break;
+ case 127:
+ // --backend-max-backoff
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_MAX_BACKOFF, StringRef{optarg});
+ break;
+ case 128:
+ // --server-name
+ cmdcfgs.emplace_back(SHRPX_OPT_SERVER_NAME, StringRef{optarg});
+ break;
+ case 129:
+ // --no-server-rewrite
+ cmdcfgs.emplace_back(SHRPX_OPT_NO_SERVER_REWRITE,
+ StringRef::from_lit("yes"));
+ break;
+ case 130:
+ // --frontend-http2-optimize-write-buffer-size
+ cmdcfgs.emplace_back(
+ SHRPX_OPT_FRONTEND_HTTP2_OPTIMIZE_WRITE_BUFFER_SIZE,
+ StringRef::from_lit("yes"));
+ break;
+ case 131:
+ // --frontend-http2-optimize-window-size
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_OPTIMIZE_WINDOW_SIZE,
+ StringRef::from_lit("yes"));
+ break;
+ case 132:
+ // --frontend-http2-window-size
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_WINDOW_SIZE,
+ StringRef{optarg});
+ break;
+ case 133:
+ // --frontend-http2-connection-window-size
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_CONNECTION_WINDOW_SIZE,
+ StringRef{optarg});
+ break;
+ case 134:
+ // --backend-http2-window-size
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_WINDOW_SIZE,
+ StringRef{optarg});
+ break;
+ case 135:
+ // --backend-http2-connection-window-size
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE,
+ StringRef{optarg});
+ break;
+ case 136:
+ // --frontend-http2-encoder-dynamic-table-size
+ cmdcfgs.emplace_back(
+ SHRPX_OPT_FRONTEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE,
+ StringRef{optarg});
+ break;
+ case 137:
+ // --frontend-http2-decoder-dynamic-table-size
+ cmdcfgs.emplace_back(
+ SHRPX_OPT_FRONTEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE,
+ StringRef{optarg});
+ break;
+ case 138:
+ // --backend-http2-encoder-dynamic-table-size
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE,
+ StringRef{optarg});
+ break;
+ case 139:
+ // --backend-http2-decoder-dynamic-table-size
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE,
+ StringRef{optarg});
+ break;
+ case 140:
+ // --ecdh-curves
+ cmdcfgs.emplace_back(SHRPX_OPT_ECDH_CURVES, StringRef{optarg});
+ break;
+ case 141:
+ // --tls-sct-dir
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_SCT_DIR, StringRef{optarg});
+ break;
+ case 142:
+ // --backend-connect-timeout
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_CONNECT_TIMEOUT,
+ StringRef{optarg});
+ break;
+ case 143:
+ // --dns-cache-timeout
+ cmdcfgs.emplace_back(SHRPX_OPT_DNS_CACHE_TIMEOUT, StringRef{optarg});
+ break;
+ case 144:
+ // --dns-lookup-timeou
+ cmdcfgs.emplace_back(SHRPX_OPT_DNS_LOOKUP_TIMEOUT, StringRef{optarg});
+ break;
+ case 145:
+ // --dns-max-try
+ cmdcfgs.emplace_back(SHRPX_OPT_DNS_MAX_TRY, StringRef{optarg});
+ break;
+ case 146:
+ // --frontend-keep-alive-timeout
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_KEEP_ALIVE_TIMEOUT,
+ StringRef{optarg});
+ break;
+ case 147:
+ // --psk-secrets
+ cmdcfgs.emplace_back(SHRPX_OPT_PSK_SECRETS, StringRef{optarg});
+ break;
+ case 148:
+ // --client-psk-secrets
+ cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_PSK_SECRETS, StringRef{optarg});
+ break;
+ case 149:
+ // --client-no-http2-cipher-black-list
+ cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_NO_HTTP2_CIPHER_BLACK_LIST,
+ StringRef::from_lit("yes"));
+ break;
+ case 150:
+ // --client-ciphers
+ cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_CIPHERS, StringRef{optarg});
+ break;
+ case 151:
+ // --accesslog-write-early
+ cmdcfgs.emplace_back(SHRPX_OPT_ACCESSLOG_WRITE_EARLY,
+ StringRef::from_lit("yes"));
+ break;
+ case 152:
+ // --tls-min-proto-version
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_MIN_PROTO_VERSION,
+ StringRef{optarg});
+ break;
+ case 153:
+ // --tls-max-proto-version
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_MAX_PROTO_VERSION,
+ StringRef{optarg});
+ break;
+ case 154:
+ // --redirect-https-port
+ cmdcfgs.emplace_back(SHRPX_OPT_REDIRECT_HTTPS_PORT, StringRef{optarg});
+ break;
+ case 155:
+ // --frontend-max-requests
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_MAX_REQUESTS,
+ StringRef{optarg});
+ break;
+ case 156:
+ // --single-thread
+ cmdcfgs.emplace_back(SHRPX_OPT_SINGLE_THREAD,
+ StringRef::from_lit("yes"));
+ break;
+ case 157:
+ // --no-add-x-forwarded-proto
+ cmdcfgs.emplace_back(SHRPX_OPT_NO_ADD_X_FORWARDED_PROTO,
+ StringRef::from_lit("yes"));
+ break;
+ case 158:
+ // --no-strip-incoming-x-forwarded-proto
+ cmdcfgs.emplace_back(SHRPX_OPT_NO_STRIP_INCOMING_X_FORWARDED_PROTO,
+ StringRef::from_lit("yes"));
+ break;
+ case 159:
+ // --single-process
+ cmdcfgs.emplace_back(SHRPX_OPT_SINGLE_PROCESS,
+ StringRef::from_lit("yes"));
+ break;
+ case 160:
+ // --verify-client-tolerate-expired
+ cmdcfgs.emplace_back(SHRPX_OPT_VERIFY_CLIENT_TOLERATE_EXPIRED,
+ StringRef::from_lit("yes"));
+ break;
+ case 161:
+ // --ignore-per-pattern-mruby-error
+ cmdcfgs.emplace_back(SHRPX_OPT_IGNORE_PER_PATTERN_MRUBY_ERROR,
+ StringRef::from_lit("yes"));
+ break;
+ case 162:
+ // --tls-no-postpone-early-data
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_NO_POSTPONE_EARLY_DATA,
+ StringRef::from_lit("yes"));
+ break;
+ case 163:
+ // --tls-max-early-data
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_MAX_EARLY_DATA, StringRef{optarg});
+ break;
+ case 164:
+ // --tls13-ciphers
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS13_CIPHERS, StringRef{optarg});
+ break;
+ case 165:
+ // --tls13-client-ciphers
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS13_CLIENT_CIPHERS, StringRef{optarg});
+ break;
+ case 166:
+ // --no-strip-incoming-early-data
+ cmdcfgs.emplace_back(SHRPX_OPT_NO_STRIP_INCOMING_EARLY_DATA,
+ StringRef::from_lit("yes"));
+ break;
+ case 167:
+ // --no-http2-cipher-block-list
+ cmdcfgs.emplace_back(SHRPX_OPT_NO_HTTP2_CIPHER_BLOCK_LIST,
+ StringRef::from_lit("yes"));
+ break;
+ case 168:
+ // --client-no-http2-cipher-block-list
+ cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_NO_HTTP2_CIPHER_BLOCK_LIST,
+ StringRef::from_lit("yes"));
+ break;
+ case 169:
+ // --quic-bpf-program-file
+ cmdcfgs.emplace_back(SHRPX_OPT_QUIC_BPF_PROGRAM_FILE,
+ StringRef{optarg});
+ break;
+ case 170:
+ // --no-quic-bpf
+ cmdcfgs.emplace_back(SHRPX_OPT_NO_QUIC_BPF, StringRef::from_lit("yes"));
+ break;
+ case 171:
+ // --http2-altsvc
+ cmdcfgs.emplace_back(SHRPX_OPT_HTTP2_ALTSVC, StringRef{optarg});
+ break;
+ case 172:
+ // --frontend-http3-read-timeout
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP3_READ_TIMEOUT,
+ StringRef{optarg});
+ break;
+ case 173:
+ // --frontend-quic-idle-timeout
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_IDLE_TIMEOUT,
+ StringRef{optarg});
+ break;
+ case 174:
+ // --frontend-quic-debug-log
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_DEBUG_LOG,
+ StringRef::from_lit("yes"));
+ break;
+ case 175:
+ // --frontend-http3-window-size
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP3_WINDOW_SIZE,
+ StringRef{optarg});
+ break;
+ case 176:
+ // --frontend-http3-connection-window-size
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP3_CONNECTION_WINDOW_SIZE,
+ StringRef{optarg});
+ break;
+ case 177:
+ // --frontend-http3-max-window-size
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP3_MAX_WINDOW_SIZE,
+ StringRef{optarg});
+ break;
+ case 178:
+ // --frontend-http3-max-connection-window-size
+ cmdcfgs.emplace_back(
+ SHRPX_OPT_FRONTEND_HTTP3_MAX_CONNECTION_WINDOW_SIZE,
+ StringRef{optarg});
+ break;
+ case 179:
+ // --frontend-http3-max-concurrent-streams
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP3_MAX_CONCURRENT_STREAMS,
+ StringRef{optarg});
+ break;
+ case 180:
+ // --frontend-quic-early-data
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_EARLY_DATA,
+ StringRef::from_lit("yes"));
+ break;
+ case 181:
+ // --frontend-quic-qlog-dir
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_QLOG_DIR,
+ StringRef{optarg});
+ break;
+ case 182:
+ // --frontend-quic-require-token
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_REQUIRE_TOKEN,
+ StringRef::from_lit("yes"));
+ break;
+ case 183:
+ // --frontend-quic-congestion-controller
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_CONGESTION_CONTROLLER,
+ StringRef{optarg});
+ break;
+ case 185:
+ // --quic-server-id
+ cmdcfgs.emplace_back(SHRPX_OPT_QUIC_SERVER_ID, StringRef{optarg});
+ break;
+ case 186:
+ // --frontend-quic-secret-file
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_SECRET_FILE,
+ StringRef{optarg});
+ break;
+ case 187:
+ // --rlimit-memlock
+ cmdcfgs.emplace_back(SHRPX_OPT_RLIMIT_MEMLOCK, StringRef{optarg});
+ break;
+ case 188:
+ // --max-worker-processes
+ cmdcfgs.emplace_back(SHRPX_OPT_MAX_WORKER_PROCESSES, StringRef{optarg});
+ break;
+ case 189:
+ // --worker-process-grace-shutdown-period
+ cmdcfgs.emplace_back(SHRPX_OPT_WORKER_PROCESS_GRACE_SHUTDOWN_PERIOD,
+ StringRef{optarg});
+ break;
+ case 190:
+ // --frontend-quic-initial-rtt
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_INITIAL_RTT,
+ StringRef{optarg});
+ break;
+ case 191:
+ // --require-http-scheme
+ cmdcfgs.emplace_back(SHRPX_OPT_REQUIRE_HTTP_SCHEME,
+ StringRef::from_lit("yes"));
+ break;
+ case 192:
+ // --tls-ktls
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_KTLS, StringRef::from_lit("yes"));
+ break;
+ case 193:
+ // --alpn-list
+ cmdcfgs.emplace_back(SHRPX_OPT_ALPN_LIST, StringRef{optarg});
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (argc - optind >= 2) {
+ cmdcfgs.emplace_back(SHRPX_OPT_PRIVATE_KEY_FILE, StringRef{argv[optind++]});
+ cmdcfgs.emplace_back(SHRPX_OPT_CERTIFICATE_FILE, StringRef{argv[optind++]});
+ }
+
+ rv = process_options(mod_config(), cmdcfgs);
+ if (rv != 0) {
+ return -1;
+ }
+
+ if (event_loop() != 0) {
+ return -1;
+ }
+
+ LOG(NOTICE) << "Shutdown momentarily";
+
+ delete_log_config();
+
+ return 0;
+}
+
+} // namespace shrpx
+
+int main(int argc, char **argv) { return run_app(shrpx::main, argc, argv); }
diff --git a/src/shrpx.h b/src/shrpx.h
new file mode 100644
index 0000000..d881ef5
--- /dev/null
+++ b/src/shrpx.h
@@ -0,0 +1,58 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_H
+#define SHRPX_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include <sys/types.h>
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif // HAVE_SYS_SOCKET_H
+
+#include <cassert>
+
+#ifndef HAVE__EXIT
+# define nghttp2_Exit(status) _exit(status)
+#else // HAVE__EXIT
+# define nghttp2_Exit(status) _Exit(status)
+#endif // HAVE__EXIT
+
+#define DIE() nghttp2_Exit(EXIT_FAILURE)
+
+#if defined(HAVE_DECL_INITGROUPS) && !HAVE_DECL_INITGROUPS
+inline int initgroups(const char *user, gid_t group) { return 0; }
+#endif // defined(HAVE_DECL_INITGROUPS) && !HAVE_DECL_INITGROUPS
+
+#ifndef HAVE_BPF_STATS_TYPE
+/* Newer kernel should have this defined in linux/bpf.h */
+enum bpf_stats_type {
+ BPF_STATS_RUN_TIME = 0,
+};
+#endif // !HAVE_BPF_STATS_TYPE
+
+#endif // SHRPX_H
diff --git a/src/shrpx_accept_handler.cc b/src/shrpx_accept_handler.cc
new file mode 100644
index 0000000..01b6415
--- /dev/null
+++ b/src/shrpx_accept_handler.cc
@@ -0,0 +1,111 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_accept_handler.h"
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+
+#include <cerrno>
+
+#include "shrpx_connection_handler.h"
+#include "shrpx_config.h"
+#include "shrpx_log.h"
+#include "util.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+namespace {
+void acceptcb(struct ev_loop *loop, ev_io *w, int revent) {
+ auto h = static_cast<AcceptHandler *>(w->data);
+ h->accept_connection();
+}
+} // namespace
+
+AcceptHandler::AcceptHandler(const UpstreamAddr *faddr, ConnectionHandler *h)
+ : conn_hnr_(h), faddr_(faddr) {
+ ev_io_init(&wev_, acceptcb, faddr_->fd, EV_READ);
+ wev_.data = this;
+ ev_io_start(conn_hnr_->get_loop(), &wev_);
+}
+
+AcceptHandler::~AcceptHandler() {
+ ev_io_stop(conn_hnr_->get_loop(), &wev_);
+ close(faddr_->fd);
+}
+
+void AcceptHandler::accept_connection() {
+ sockaddr_union sockaddr;
+ socklen_t addrlen = sizeof(sockaddr);
+
+#ifdef HAVE_ACCEPT4
+ auto cfd =
+ accept4(faddr_->fd, &sockaddr.sa, &addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC);
+#else // !HAVE_ACCEPT4
+ auto cfd = accept(faddr_->fd, &sockaddr.sa, &addrlen);
+#endif // !HAVE_ACCEPT4
+
+ if (cfd == -1) {
+ switch (errno) {
+ case EINTR:
+ case ENETDOWN:
+ case EPROTO:
+ case ENOPROTOOPT:
+ case EHOSTDOWN:
+#ifdef ENONET
+ case ENONET:
+#endif // ENONET
+ case EHOSTUNREACH:
+ case EOPNOTSUPP:
+ case ENETUNREACH:
+ return;
+ case EMFILE:
+ case ENFILE:
+ LOG(WARN) << "acceptor: running out file descriptor; disable acceptor "
+ "temporarily";
+ conn_hnr_->sleep_acceptor(get_config()->conn.listener.timeout.sleep);
+ return;
+ default:
+ return;
+ }
+ }
+
+#ifndef HAVE_ACCEPT4
+ util::make_socket_nonblocking(cfd);
+ util::make_socket_closeonexec(cfd);
+#endif // !HAVE_ACCEPT4
+
+ conn_hnr_->handle_connection(cfd, &sockaddr.sa, addrlen, faddr_);
+}
+
+void AcceptHandler::enable() { ev_io_start(conn_hnr_->get_loop(), &wev_); }
+
+void AcceptHandler::disable() { ev_io_stop(conn_hnr_->get_loop(), &wev_); }
+
+int AcceptHandler::get_fd() const { return faddr_->fd; }
+
+} // namespace shrpx
diff --git a/src/shrpx_accept_handler.h b/src/shrpx_accept_handler.h
new file mode 100644
index 0000000..853e9a2
--- /dev/null
+++ b/src/shrpx_accept_handler.h
@@ -0,0 +1,54 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_ACCEPT_HANDLER_H
+#define SHRPX_ACCEPT_HANDLER_H
+
+#include "shrpx.h"
+
+#include <ev.h>
+
+namespace shrpx {
+
+class ConnectionHandler;
+struct UpstreamAddr;
+
+class AcceptHandler {
+public:
+ AcceptHandler(const UpstreamAddr *faddr, ConnectionHandler *h);
+ ~AcceptHandler();
+ void accept_connection();
+ void enable();
+ void disable();
+ int get_fd() const;
+
+private:
+ ev_io wev_;
+ ConnectionHandler *conn_hnr_;
+ const UpstreamAddr *faddr_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_ACCEPT_HANDLER_H
diff --git a/src/shrpx_api_downstream_connection.cc b/src/shrpx_api_downstream_connection.cc
new file mode 100644
index 0000000..254ab59
--- /dev/null
+++ b/src/shrpx_api_downstream_connection.cc
@@ -0,0 +1,478 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_api_downstream_connection.h"
+
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <cstdlib>
+
+#include "shrpx_client_handler.h"
+#include "shrpx_upstream.h"
+#include "shrpx_downstream.h"
+#include "shrpx_worker.h"
+#include "shrpx_connection_handler.h"
+#include "shrpx_log.h"
+
+namespace shrpx {
+
+namespace {
+// List of API endpoints
+const std::array<APIEndpoint, 2> &apis() {
+ static const auto apis = new std::array<APIEndpoint, 2>{
+ APIEndpoint{
+ StringRef::from_lit("/api/v1beta1/backendconfig"),
+ true,
+ (1 << API_METHOD_POST) | (1 << API_METHOD_PUT),
+ &APIDownstreamConnection::handle_backendconfig,
+ },
+ APIEndpoint{
+ StringRef::from_lit("/api/v1beta1/configrevision"),
+ true,
+ (1 << API_METHOD_GET),
+ &APIDownstreamConnection::handle_configrevision,
+ },
+ };
+
+ return *apis;
+}
+} // namespace
+
+namespace {
+// The method string. This must be same order of APIMethod.
+constexpr StringRef API_METHOD_STRING[] = {
+ StringRef::from_lit("GET"),
+ StringRef::from_lit("POST"),
+ StringRef::from_lit("PUT"),
+};
+} // namespace
+
+APIDownstreamConnection::APIDownstreamConnection(Worker *worker)
+ : worker_(worker), api_(nullptr), fd_(-1), shutdown_read_(false) {}
+
+APIDownstreamConnection::~APIDownstreamConnection() {
+ if (fd_ != -1) {
+ close(fd_);
+ }
+}
+
+int APIDownstreamConnection::attach_downstream(Downstream *downstream) {
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "Attaching to DOWNSTREAM:" << downstream;
+ }
+
+ downstream_ = downstream;
+
+ return 0;
+}
+
+void APIDownstreamConnection::detach_downstream(Downstream *downstream) {
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "Detaching from DOWNSTREAM:" << downstream;
+ }
+ downstream_ = nullptr;
+}
+
+int APIDownstreamConnection::send_reply(unsigned int http_status,
+ APIStatusCode api_status,
+ const StringRef &data) {
+ shutdown_read_ = true;
+
+ auto upstream = downstream_->get_upstream();
+
+ auto &resp = downstream_->response();
+
+ resp.http_status = http_status;
+
+ auto &balloc = downstream_->get_block_allocator();
+
+ StringRef api_status_str;
+
+ switch (api_status) {
+ case APIStatusCode::SUCCESS:
+ api_status_str = StringRef::from_lit("Success");
+ break;
+ case APIStatusCode::FAILURE:
+ api_status_str = StringRef::from_lit("Failure");
+ break;
+ default:
+ assert(0);
+ }
+
+ constexpr auto M1 = StringRef::from_lit("{\"status\":\"");
+ constexpr auto M2 = StringRef::from_lit("\",\"code\":");
+ constexpr auto M3 = StringRef::from_lit("}");
+
+ // 3 is the number of digits in http_status, assuming it is 3 digits
+ // number.
+ auto buflen = M1.size() + M2.size() + M3.size() + data.size() +
+ api_status_str.size() + 3;
+
+ auto buf = make_byte_ref(balloc, buflen);
+ auto p = buf.base;
+
+ p = std::copy(std::begin(M1), std::end(M1), p);
+ p = std::copy(std::begin(api_status_str), std::end(api_status_str), p);
+ p = std::copy(std::begin(M2), std::end(M2), p);
+ p = util::utos(p, http_status);
+ p = std::copy(std::begin(data), std::end(data), p);
+ p = std::copy(std::begin(M3), std::end(M3), p);
+
+ buf.len = p - buf.base;
+
+ auto content_length = util::make_string_ref_uint(balloc, buf.len);
+
+ resp.fs.add_header_token(StringRef::from_lit("content-length"),
+ content_length, false, http2::HD_CONTENT_LENGTH);
+
+ switch (http_status) {
+ case 400:
+ case 405:
+ case 413:
+ resp.fs.add_header_token(StringRef::from_lit("connection"),
+ StringRef::from_lit("close"), false,
+ http2::HD_CONNECTION);
+ break;
+ }
+
+ if (upstream->send_reply(downstream_, buf.base, buf.len) != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+namespace {
+const APIEndpoint *lookup_api(const StringRef &path) {
+ switch (path.size()) {
+ case 26:
+ switch (path[25]) {
+ case 'g':
+ if (util::streq_l("/api/v1beta1/backendconfi", std::begin(path), 25)) {
+ return &apis()[0];
+ }
+ break;
+ }
+ break;
+ case 27:
+ switch (path[26]) {
+ case 'n':
+ if (util::streq_l("/api/v1beta1/configrevisio", std::begin(path), 26)) {
+ return &apis()[1];
+ }
+ break;
+ }
+ break;
+ }
+ return nullptr;
+}
+} // namespace
+
+int APIDownstreamConnection::push_request_headers() {
+ auto &req = downstream_->request();
+
+ auto path =
+ StringRef{std::begin(req.path),
+ std::find(std::begin(req.path), std::end(req.path), '?')};
+
+ api_ = lookup_api(path);
+
+ if (!api_) {
+ send_reply(404, APIStatusCode::FAILURE);
+
+ return 0;
+ }
+
+ switch (req.method) {
+ case HTTP_GET:
+ if (!(api_->allowed_methods & (1 << API_METHOD_GET))) {
+ error_method_not_allowed();
+ return 0;
+ }
+ break;
+ case HTTP_POST:
+ if (!(api_->allowed_methods & (1 << API_METHOD_POST))) {
+ error_method_not_allowed();
+ return 0;
+ }
+ break;
+ case HTTP_PUT:
+ if (!(api_->allowed_methods & (1 << API_METHOD_PUT))) {
+ error_method_not_allowed();
+ return 0;
+ }
+ break;
+ default:
+ error_method_not_allowed();
+ return 0;
+ }
+
+ // This works with req.fs.content_length == -1
+ if (req.fs.content_length >
+ static_cast<int64_t>(get_config()->api.max_request_body)) {
+ send_reply(413, APIStatusCode::FAILURE);
+
+ return 0;
+ }
+
+ switch (req.method) {
+ case HTTP_POST:
+ case HTTP_PUT: {
+ char tempname[] = "/tmp/nghttpx-api.XXXXXX";
+#ifdef HAVE_MKOSTEMP
+ fd_ = mkostemp(tempname, O_CLOEXEC);
+#else // !HAVE_MKOSTEMP
+ fd_ = mkstemp(tempname);
+#endif // !HAVE_MKOSTEMP
+ if (fd_ == -1) {
+ send_reply(500, APIStatusCode::FAILURE);
+
+ return 0;
+ }
+#ifndef HAVE_MKOSTEMP
+ util::make_socket_closeonexec(fd_);
+#endif // HAVE_MKOSTEMP
+ unlink(tempname);
+ break;
+ }
+ }
+
+ downstream_->set_request_header_sent(true);
+ auto src = downstream_->get_blocked_request_buf();
+ auto dest = downstream_->get_request_buf();
+ src->remove(*dest);
+
+ return 0;
+}
+
+int APIDownstreamConnection::error_method_not_allowed() {
+ auto &resp = downstream_->response();
+
+ size_t len = 0;
+ for (uint8_t i = 0; i < API_METHOD_MAX; ++i) {
+ if (api_->allowed_methods & (1 << i)) {
+ // The length of method + ", "
+ len += API_METHOD_STRING[i].size() + 2;
+ }
+ }
+
+ assert(len > 0);
+
+ auto &balloc = downstream_->get_block_allocator();
+
+ auto iov = make_byte_ref(balloc, len + 1);
+ auto p = iov.base;
+ for (uint8_t i = 0; i < API_METHOD_MAX; ++i) {
+ if (api_->allowed_methods & (1 << i)) {
+ auto &s = API_METHOD_STRING[i];
+ p = std::copy(std::begin(s), std::end(s), p);
+ p = std::copy_n(", ", 2, p);
+ }
+ }
+
+ p -= 2;
+ *p = '\0';
+
+ resp.fs.add_header_token(StringRef::from_lit("allow"), StringRef{iov.base, p},
+ false, -1);
+ return send_reply(405, APIStatusCode::FAILURE);
+}
+
+int APIDownstreamConnection::push_upload_data_chunk(const uint8_t *data,
+ size_t datalen) {
+ if (shutdown_read_ || !api_->require_body) {
+ return 0;
+ }
+
+ auto &req = downstream_->request();
+ auto &apiconf = get_config()->api;
+
+ if (static_cast<size_t>(req.recv_body_length) > apiconf.max_request_body) {
+ send_reply(413, APIStatusCode::FAILURE);
+
+ return 0;
+ }
+
+ ssize_t nwrite;
+ while ((nwrite = write(fd_, data, datalen)) == -1 && errno == EINTR)
+ ;
+ if (nwrite == -1) {
+ auto error = errno;
+ LOG(ERROR) << "Could not write API request body: errno=" << error;
+ send_reply(500, APIStatusCode::FAILURE);
+
+ return 0;
+ }
+
+ // We don't have to call Upstream::resume_read() here, because
+ // request buffer is effectively unlimited. Actually, we cannot
+ // call it here since it could recursively call this function again.
+
+ return 0;
+}
+
+int APIDownstreamConnection::end_upload_data() {
+ if (shutdown_read_) {
+ return 0;
+ }
+
+ return api_->handler(*this);
+}
+
+int APIDownstreamConnection::handle_backendconfig() {
+ auto &req = downstream_->request();
+
+ if (req.recv_body_length == 0) {
+ send_reply(200, APIStatusCode::SUCCESS);
+
+ return 0;
+ }
+
+ auto rp = mmap(nullptr, req.recv_body_length, PROT_READ, MAP_SHARED, fd_, 0);
+ if (rp == reinterpret_cast<void *>(-1)) {
+ send_reply(500, APIStatusCode::FAILURE);
+ return 0;
+ }
+
+ auto unmapper = defer(munmap, rp, req.recv_body_length);
+
+ Config new_config{};
+ new_config.conn.downstream = std::make_shared<DownstreamConfig>();
+ const auto &downstreamconf = new_config.conn.downstream;
+
+ auto config = get_config();
+ auto &src = config->conn.downstream;
+
+ downstreamconf->timeout = src->timeout;
+ downstreamconf->connections_per_host = src->connections_per_host;
+ downstreamconf->connections_per_frontend = src->connections_per_frontend;
+ downstreamconf->request_buffer_size = src->request_buffer_size;
+ downstreamconf->response_buffer_size = src->response_buffer_size;
+ downstreamconf->family = src->family;
+
+ std::set<StringRef> include_set;
+ std::map<StringRef, size_t> pattern_addr_indexer;
+
+ for (auto first = reinterpret_cast<const uint8_t *>(rp),
+ last = first + req.recv_body_length;
+ first != last;) {
+ auto eol = std::find(first, last, '\n');
+ if (eol == last) {
+ break;
+ }
+
+ if (first == eol || *first == '#') {
+ first = ++eol;
+ continue;
+ }
+
+ auto eq = std::find(first, eol, '=');
+ if (eq == eol) {
+ send_reply(400, APIStatusCode::FAILURE);
+ return 0;
+ }
+
+ auto opt = StringRef{first, eq};
+ auto optval = StringRef{eq + 1, eol};
+
+ auto optid = option_lookup_token(opt.c_str(), opt.size());
+
+ switch (optid) {
+ case SHRPX_OPTID_BACKEND:
+ break;
+ default:
+ first = ++eol;
+ continue;
+ }
+
+ if (parse_config(&new_config, optid, opt, optval, include_set,
+ pattern_addr_indexer) != 0) {
+ send_reply(400, APIStatusCode::FAILURE);
+ return 0;
+ }
+
+ first = ++eol;
+ }
+
+ auto &tlsconf = config->tls;
+ if (configure_downstream_group(&new_config, config->http2_proxy, true,
+ tlsconf) != 0) {
+ send_reply(400, APIStatusCode::FAILURE);
+ return 0;
+ }
+
+ auto conn_handler = worker_->get_connection_handler();
+
+ conn_handler->send_replace_downstream(downstreamconf);
+
+ send_reply(200, APIStatusCode::SUCCESS);
+
+ return 0;
+}
+
+int APIDownstreamConnection::handle_configrevision() {
+ auto config = get_config();
+ auto &balloc = downstream_->get_block_allocator();
+
+ // Construct the following string:
+ // ,
+ // "data":{
+ // "configRevision": N
+ // }
+ auto data = concat_string_ref(
+ balloc, StringRef::from_lit(R"(,"data":{"configRevision":)"),
+ util::make_string_ref_uint(balloc, config->config_revision),
+ StringRef::from_lit("}"));
+
+ send_reply(200, APIStatusCode::SUCCESS, data);
+
+ return 0;
+}
+
+void APIDownstreamConnection::pause_read(IOCtrlReason reason) {}
+
+int APIDownstreamConnection::resume_read(IOCtrlReason reason, size_t consumed) {
+ return 0;
+}
+
+void APIDownstreamConnection::force_resume_read() {}
+
+int APIDownstreamConnection::on_read() { return 0; }
+
+int APIDownstreamConnection::on_write() { return 0; }
+
+void APIDownstreamConnection::on_upstream_change(Upstream *upstream) {}
+
+bool APIDownstreamConnection::poolable() const { return false; }
+
+const std::shared_ptr<DownstreamAddrGroup> &
+APIDownstreamConnection::get_downstream_addr_group() const {
+ static std::shared_ptr<DownstreamAddrGroup> s;
+ return s;
+}
+
+DownstreamAddr *APIDownstreamConnection::get_addr() const { return nullptr; }
+
+} // namespace shrpx
diff --git a/src/shrpx_api_downstream_connection.h b/src/shrpx_api_downstream_connection.h
new file mode 100644
index 0000000..5d4182f
--- /dev/null
+++ b/src/shrpx_api_downstream_connection.h
@@ -0,0 +1,114 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_API_DOWNSTREAM_CONNECTION_H
+#define SHRPX_API_DOWNSTREAM_CONNECTION_H
+
+#include "shrpx_downstream_connection.h"
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+class Worker;
+
+// If new method is added, don't forget to update API_METHOD_STRING as
+// well.
+enum APIMethod {
+ API_METHOD_GET,
+ API_METHOD_POST,
+ API_METHOD_PUT,
+ API_METHOD_MAX,
+};
+
+// API status code, which is independent from HTTP status code. But
+// generally, 2xx code for SUCCESS, and otherwise FAILURE.
+enum class APIStatusCode {
+ SUCCESS,
+ FAILURE,
+};
+
+class APIDownstreamConnection;
+
+struct APIEndpoint {
+ // Endpoint path. It must start with "/api/".
+ StringRef path;
+ // true if we evaluate request body.
+ bool require_body;
+ // Allowed methods. This is bitwise OR of one or more of (1 <<
+ // APIMethod value).
+ uint8_t allowed_methods;
+ std::function<int(APIDownstreamConnection &)> handler;
+};
+
+class APIDownstreamConnection : public DownstreamConnection {
+public:
+ APIDownstreamConnection(Worker *worker);
+ virtual ~APIDownstreamConnection();
+ virtual int attach_downstream(Downstream *downstream);
+ virtual void detach_downstream(Downstream *downstream);
+
+ virtual int push_request_headers();
+ virtual int push_upload_data_chunk(const uint8_t *data, size_t datalen);
+ virtual int end_upload_data();
+
+ virtual void pause_read(IOCtrlReason reason);
+ virtual int resume_read(IOCtrlReason reason, size_t consumed);
+ virtual void force_resume_read();
+
+ virtual int on_read();
+ virtual int on_write();
+
+ virtual void on_upstream_change(Upstream *upstream);
+
+ // true if this object is poolable.
+ virtual bool poolable() const;
+
+ virtual const std::shared_ptr<DownstreamAddrGroup> &
+ get_downstream_addr_group() const;
+ virtual DownstreamAddr *get_addr() const;
+
+ int send_reply(unsigned int http_status, APIStatusCode api_status,
+ const StringRef &data = StringRef{});
+ int error_method_not_allowed();
+
+ // Handles backendconfig API request.
+ int handle_backendconfig();
+ // Handles configrevision API request.
+ int handle_configrevision();
+
+private:
+ Worker *worker_;
+ // This points to the requested APIEndpoint struct.
+ const APIEndpoint *api_;
+ // The file descriptor for temporary file to store request body.
+ int fd_;
+ // true if we stop reading request body.
+ bool shutdown_read_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_API_DOWNSTREAM_CONNECTION_H
diff --git a/src/shrpx_client_handler.cc b/src/shrpx_client_handler.cc
new file mode 100644
index 0000000..1f0c01c
--- /dev/null
+++ b/src/shrpx_client_handler.cc
@@ -0,0 +1,1703 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_client_handler.h"
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif // HAVE_SYS_SOCKET_H
+#ifdef HAVE_NETDB_H
+# include <netdb.h>
+#endif // HAVE_NETDB_H
+
+#include <cerrno>
+
+#include "shrpx_upstream.h"
+#include "shrpx_http2_upstream.h"
+#include "shrpx_https_upstream.h"
+#include "shrpx_config.h"
+#include "shrpx_http_downstream_connection.h"
+#include "shrpx_http2_downstream_connection.h"
+#include "shrpx_tls.h"
+#include "shrpx_worker.h"
+#include "shrpx_downstream_connection_pool.h"
+#include "shrpx_downstream.h"
+#include "shrpx_http2_session.h"
+#include "shrpx_connect_blocker.h"
+#include "shrpx_api_downstream_connection.h"
+#include "shrpx_health_monitor_downstream_connection.h"
+#include "shrpx_null_downstream_connection.h"
+#ifdef ENABLE_HTTP3
+# include "shrpx_http3_upstream.h"
+#endif // ENABLE_HTTP3
+#include "shrpx_log.h"
+#include "util.h"
+#include "template.h"
+#include "tls.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+namespace {
+void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto conn = static_cast<Connection *>(w->data);
+ auto handler = static_cast<ClientHandler *>(conn->data);
+
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, handler) << "Time out";
+ }
+
+ delete handler;
+}
+} // namespace
+
+namespace {
+void shutdowncb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto handler = static_cast<ClientHandler *>(w->data);
+
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, handler) << "Close connection due to TLS renegotiation";
+ }
+
+ delete handler;
+}
+} // namespace
+
+namespace {
+void readcb(struct ev_loop *loop, ev_io *w, int revents) {
+ auto conn = static_cast<Connection *>(w->data);
+ auto handler = static_cast<ClientHandler *>(conn->data);
+
+ if (handler->do_read() != 0) {
+ delete handler;
+ return;
+ }
+}
+} // namespace
+
+namespace {
+void writecb(struct ev_loop *loop, ev_io *w, int revents) {
+ auto conn = static_cast<Connection *>(w->data);
+ auto handler = static_cast<ClientHandler *>(conn->data);
+
+ if (handler->do_write() != 0) {
+ delete handler;
+ return;
+ }
+}
+} // namespace
+
+int ClientHandler::noop() { return 0; }
+
+int ClientHandler::read_clear() {
+ auto should_break = false;
+ rb_.ensure_chunk();
+ for (;;) {
+ if (rb_.rleft() && on_read() != 0) {
+ return -1;
+ }
+ if (rb_.rleft() == 0) {
+ rb_.reset();
+ } else if (rb_.wleft() == 0) {
+ conn_.rlimit.stopw();
+ return 0;
+ }
+
+ if (!ev_is_active(&conn_.rev) || should_break) {
+ return 0;
+ }
+
+ auto nread = conn_.read_clear(rb_.last(), rb_.wleft());
+
+ if (nread == 0) {
+ if (rb_.rleft() == 0) {
+ rb_.release_chunk();
+ }
+ return 0;
+ }
+
+ if (nread < 0) {
+ return -1;
+ }
+
+ rb_.write(nread);
+ should_break = true;
+ }
+}
+
+int ClientHandler::write_clear() {
+ std::array<iovec, 2> iov;
+
+ for (;;) {
+ if (on_write() != 0) {
+ return -1;
+ }
+
+ auto iovcnt = upstream_->response_riovec(iov.data(), iov.size());
+ if (iovcnt == 0) {
+ break;
+ }
+
+ auto nwrite = conn_.writev_clear(iov.data(), iovcnt);
+ if (nwrite < 0) {
+ return -1;
+ }
+
+ if (nwrite == 0) {
+ return 0;
+ }
+
+ upstream_->response_drain(nwrite);
+ }
+
+ conn_.wlimit.stopw();
+ ev_timer_stop(conn_.loop, &conn_.wt);
+
+ return 0;
+}
+
+int ClientHandler::proxy_protocol_peek_clear() {
+ rb_.ensure_chunk();
+
+ assert(rb_.rleft() == 0);
+
+ auto nread = conn_.peek_clear(rb_.last(), rb_.wleft());
+ if (nread < 0) {
+ return -1;
+ }
+ if (nread == 0) {
+ return 0;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol: Peek " << nread
+ << " bytes from socket";
+ }
+
+ rb_.write(nread);
+
+ if (on_read() != 0) {
+ return -1;
+ }
+
+ rb_.reset();
+
+ return 0;
+}
+
+int ClientHandler::tls_handshake() {
+ ev_timer_again(conn_.loop, &conn_.rt);
+
+ ERR_clear_error();
+
+ auto rv = conn_.tls_handshake();
+
+ if (rv == SHRPX_ERR_INPROGRESS) {
+ return 0;
+ }
+
+ if (rv < 0) {
+ return -1;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "SSL/TLS handshake completed";
+ }
+
+ if (validate_next_proto() != 0) {
+ return -1;
+ }
+
+ read_ = &ClientHandler::read_tls;
+ write_ = &ClientHandler::write_tls;
+
+ return 0;
+}
+
+int ClientHandler::read_tls() {
+ auto should_break = false;
+
+ ERR_clear_error();
+
+ rb_.ensure_chunk();
+
+ for (;;) {
+ // we should process buffered data first before we read EOF.
+ if (rb_.rleft() && on_read() != 0) {
+ return -1;
+ }
+ if (rb_.rleft() == 0) {
+ rb_.reset();
+ } else if (rb_.wleft() == 0) {
+ conn_.rlimit.stopw();
+ return 0;
+ }
+
+ if (!ev_is_active(&conn_.rev) || should_break) {
+ return 0;
+ }
+
+ auto nread = conn_.read_tls(rb_.last(), rb_.wleft());
+
+ if (nread == 0) {
+ if (rb_.rleft() == 0) {
+ rb_.release_chunk();
+ }
+ return 0;
+ }
+
+ if (nread < 0) {
+ return -1;
+ }
+
+ rb_.write(nread);
+ should_break = true;
+ }
+}
+
+int ClientHandler::write_tls() {
+ struct iovec iov;
+
+ ERR_clear_error();
+
+ if (on_write() != 0) {
+ return -1;
+ }
+
+ auto iovcnt = upstream_->response_riovec(&iov, 1);
+ if (iovcnt == 0) {
+ conn_.start_tls_write_idle();
+
+ conn_.wlimit.stopw();
+ ev_timer_stop(conn_.loop, &conn_.wt);
+
+ return 0;
+ }
+
+ for (;;) {
+ auto nwrite = conn_.write_tls(iov.iov_base, iov.iov_len);
+ if (nwrite < 0) {
+ return -1;
+ }
+
+ if (nwrite == 0) {
+ return 0;
+ }
+
+ upstream_->response_drain(nwrite);
+
+ iovcnt = upstream_->response_riovec(&iov, 1);
+ if (iovcnt == 0) {
+ return 0;
+ }
+ }
+}
+
+#ifdef ENABLE_HTTP3
+int ClientHandler::read_quic(const UpstreamAddr *faddr,
+ const Address &remote_addr,
+ const Address &local_addr,
+ const ngtcp2_pkt_info &pi, const uint8_t *data,
+ size_t datalen) {
+ auto upstream = static_cast<Http3Upstream *>(upstream_.get());
+
+ return upstream->on_read(faddr, remote_addr, local_addr, pi, data, datalen);
+}
+
+int ClientHandler::write_quic() { return upstream_->on_write(); }
+#endif // ENABLE_HTTP3
+
+int ClientHandler::upstream_noop() { return 0; }
+
+int ClientHandler::upstream_read() {
+ assert(upstream_);
+ if (upstream_->on_read() != 0) {
+ return -1;
+ }
+ return 0;
+}
+
+int ClientHandler::upstream_write() {
+ assert(upstream_);
+ if (upstream_->on_write() != 0) {
+ return -1;
+ }
+
+ if (get_should_close_after_write() && upstream_->response_empty()) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int ClientHandler::upstream_http2_connhd_read() {
+ auto nread = std::min(left_connhd_len_, rb_.rleft());
+ if (memcmp(&NGHTTP2_CLIENT_MAGIC[NGHTTP2_CLIENT_MAGIC_LEN - left_connhd_len_],
+ rb_.pos(), nread) != 0) {
+ // There is no downgrade path here. Just drop the connection.
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "invalid client connection header";
+ }
+
+ return -1;
+ }
+
+ left_connhd_len_ -= nread;
+ rb_.drain(nread);
+ conn_.rlimit.startw();
+
+ if (left_connhd_len_ == 0) {
+ on_read_ = &ClientHandler::upstream_read;
+ // Run on_read to process data left in buffer since they are not
+ // notified further
+ if (on_read() != 0) {
+ return -1;
+ }
+ return 0;
+ }
+
+ return 0;
+}
+
+int ClientHandler::upstream_http1_connhd_read() {
+ auto nread = std::min(left_connhd_len_, rb_.rleft());
+ if (memcmp(&NGHTTP2_CLIENT_MAGIC[NGHTTP2_CLIENT_MAGIC_LEN - left_connhd_len_],
+ rb_.pos(), nread) != 0) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "This is HTTP/1.1 connection, "
+ << "but may be upgraded to HTTP/2 later.";
+ }
+
+ // Reset header length for later HTTP/2 upgrade
+ left_connhd_len_ = NGHTTP2_CLIENT_MAGIC_LEN;
+ on_read_ = &ClientHandler::upstream_read;
+ on_write_ = &ClientHandler::upstream_write;
+
+ if (on_read() != 0) {
+ return -1;
+ }
+
+ return 0;
+ }
+
+ left_connhd_len_ -= nread;
+ rb_.drain(nread);
+ conn_.rlimit.startw();
+
+ if (left_connhd_len_ == 0) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "direct HTTP/2 connection";
+ }
+
+ direct_http2_upgrade();
+ on_read_ = &ClientHandler::upstream_read;
+ on_write_ = &ClientHandler::upstream_write;
+
+ // Run on_read to process data left in buffer since they are not
+ // notified further
+ if (on_read() != 0) {
+ return -1;
+ }
+
+ return 0;
+ }
+
+ return 0;
+}
+
+ClientHandler::ClientHandler(Worker *worker, int fd, SSL *ssl,
+ const StringRef &ipaddr, const StringRef &port,
+ int family, const UpstreamAddr *faddr)
+ : // We use balloc_ for TLS session ID (64), ipaddr (IPv6) (39),
+ // port (5), forwarded-for (IPv6) (41), alpn (5), proxyproto
+ // ipaddr (15), proxyproto port (5), sni (32, estimated). we
+ // need terminal NULL byte for each. We also require 8 bytes
+ // header for each allocation. We align at 16 bytes boundary,
+ // so the required space is 64 + 48 + 16 + 48 + 16 + 16 + 16 +
+ // 32 + 8 + 8 * 8 = 328.
+ balloc_(512, 512),
+ rb_(worker->get_mcpool()),
+ conn_(worker->get_loop(), fd, ssl, worker->get_mcpool(),
+ get_config()->conn.upstream.timeout.write,
+ get_config()->conn.upstream.timeout.read,
+ get_config()->conn.upstream.ratelimit.write,
+ get_config()->conn.upstream.ratelimit.read, writecb, readcb,
+ timeoutcb, this, get_config()->tls.dyn_rec.warmup_threshold,
+ get_config()->tls.dyn_rec.idle_timeout,
+ faddr->quic ? Proto::HTTP3 : Proto::NONE),
+ ipaddr_(make_string_ref(balloc_, ipaddr)),
+ port_(make_string_ref(balloc_, port)),
+ faddr_(faddr),
+ worker_(worker),
+ left_connhd_len_(NGHTTP2_CLIENT_MAGIC_LEN),
+ affinity_hash_(0),
+ should_close_after_write_(false),
+ affinity_hash_computed_(false) {
+
+ ++worker_->get_worker_stat()->num_connections;
+
+ ev_timer_init(&reneg_shutdown_timer_, shutdowncb, 0., 0.);
+
+ reneg_shutdown_timer_.data = this;
+
+ if (!faddr->quic) {
+ conn_.rlimit.startw();
+ }
+ ev_timer_again(conn_.loop, &conn_.rt);
+
+ auto config = get_config();
+
+ if (!faddr->quic) {
+ if (faddr_->accept_proxy_protocol ||
+ config->conn.upstream.accept_proxy_protocol) {
+ read_ = &ClientHandler::proxy_protocol_peek_clear;
+ write_ = &ClientHandler::noop;
+ on_read_ = &ClientHandler::proxy_protocol_read;
+ on_write_ = &ClientHandler::upstream_noop;
+ } else {
+ setup_upstream_io_callback();
+ }
+ }
+
+ auto &fwdconf = config->http.forwarded;
+
+ if (fwdconf.params & FORWARDED_FOR) {
+ if (fwdconf.for_node_type == ForwardedNode::OBFUSCATED) {
+ // 1 for '_'
+ auto len = SHRPX_OBFUSCATED_NODE_LENGTH + 1;
+ // 1 for terminating NUL.
+ auto buf = make_byte_ref(balloc_, len + 1);
+ auto p = buf.base;
+ *p++ = '_';
+ p = util::random_alpha_digit(p, p + SHRPX_OBFUSCATED_NODE_LENGTH,
+ worker_->get_randgen());
+ *p = '\0';
+
+ forwarded_for_ = StringRef{buf.base, p};
+ } else {
+ init_forwarded_for(family, ipaddr_);
+ }
+ }
+}
+
+void ClientHandler::init_forwarded_for(int family, const StringRef &ipaddr) {
+ if (family == AF_INET6) {
+ // 2 for '[' and ']'
+ auto len = 2 + ipaddr.size();
+ // 1 for terminating NUL.
+ auto buf = make_byte_ref(balloc_, len + 1);
+ auto p = buf.base;
+ *p++ = '[';
+ p = std::copy(std::begin(ipaddr), std::end(ipaddr), p);
+ *p++ = ']';
+ *p = '\0';
+
+ forwarded_for_ = StringRef{buf.base, p};
+ } else {
+ // family == AF_INET or family == AF_UNIX
+ forwarded_for_ = ipaddr;
+ }
+}
+
+void ClientHandler::setup_upstream_io_callback() {
+ if (conn_.tls.ssl) {
+ conn_.prepare_server_handshake();
+ read_ = write_ = &ClientHandler::tls_handshake;
+ on_read_ = &ClientHandler::upstream_noop;
+ on_write_ = &ClientHandler::upstream_write;
+ } else {
+ // For non-TLS version, first create HttpsUpstream. It may be
+ // upgraded to HTTP/2 through HTTP Upgrade or direct HTTP/2
+ // connection.
+ upstream_ = std::make_unique<HttpsUpstream>(this);
+ alpn_ = StringRef::from_lit("http/1.1");
+ read_ = &ClientHandler::read_clear;
+ write_ = &ClientHandler::write_clear;
+ on_read_ = &ClientHandler::upstream_http1_connhd_read;
+ on_write_ = &ClientHandler::upstream_noop;
+ }
+}
+
+#ifdef ENABLE_HTTP3
+void ClientHandler::setup_http3_upstream(
+ std::unique_ptr<Http3Upstream> &&upstream) {
+ upstream_ = std::move(upstream);
+ write_ = &ClientHandler::write_quic;
+
+ auto config = get_config();
+
+ reset_upstream_read_timeout(config->conn.upstream.timeout.http3_read);
+}
+#endif // ENABLE_HTTP3
+
+ClientHandler::~ClientHandler() {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "Deleting";
+ }
+
+ if (upstream_) {
+ upstream_->on_handler_delete();
+ }
+
+ auto worker_stat = worker_->get_worker_stat();
+ --worker_stat->num_connections;
+
+ if (worker_stat->num_connections == 0) {
+ worker_->schedule_clear_mcpool();
+ }
+
+ ev_timer_stop(conn_.loop, &reneg_shutdown_timer_);
+
+ // TODO If backend is http/2, and it is in CONNECTED state, signal
+ // it and make it loopbreak when output is zero.
+ if (worker_->get_graceful_shutdown() && worker_stat->num_connections == 0 &&
+ worker_stat->num_close_waits == 0) {
+ ev_break(conn_.loop);
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "Deleted";
+ }
+}
+
+Upstream *ClientHandler::get_upstream() { return upstream_.get(); }
+
+struct ev_loop *ClientHandler::get_loop() const { return conn_.loop; }
+
+void ClientHandler::reset_upstream_read_timeout(ev_tstamp t) {
+ conn_.rt.repeat = t;
+ if (ev_is_active(&conn_.rt)) {
+ ev_timer_again(conn_.loop, &conn_.rt);
+ }
+}
+
+void ClientHandler::reset_upstream_write_timeout(ev_tstamp t) {
+ conn_.wt.repeat = t;
+ if (ev_is_active(&conn_.wt)) {
+ ev_timer_again(conn_.loop, &conn_.wt);
+ }
+}
+
+void ClientHandler::repeat_read_timer() {
+ ev_timer_again(conn_.loop, &conn_.rt);
+}
+
+void ClientHandler::stop_read_timer() { ev_timer_stop(conn_.loop, &conn_.rt); }
+
+int ClientHandler::validate_next_proto() {
+ const unsigned char *next_proto = nullptr;
+ unsigned int next_proto_len = 0;
+
+ // First set callback for catch all cases
+ on_read_ = &ClientHandler::upstream_read;
+
+ SSL_get0_alpn_selected(conn_.tls.ssl, &next_proto, &next_proto_len);
+
+ StringRef proto;
+
+ if (next_proto) {
+ proto = StringRef{next_proto, next_proto_len};
+
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "The negotiated next protocol: " << proto;
+ }
+ } else {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "No protocol negotiated. Fallback to HTTP/1.1";
+ }
+
+ proto = StringRef::from_lit("http/1.1");
+ }
+
+ if (!tls::in_proto_list(get_config()->tls.alpn_list, proto)) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "The negotiated protocol is not supported: " << proto;
+ }
+ return -1;
+ }
+
+ if (util::check_h2_is_selected(proto)) {
+ on_read_ = &ClientHandler::upstream_http2_connhd_read;
+
+ auto http2_upstream = std::make_unique<Http2Upstream>(this);
+
+ upstream_ = std::move(http2_upstream);
+ alpn_ = make_string_ref(balloc_, proto);
+
+ // At this point, input buffer is already filled with some bytes.
+ // The read callback is not called until new data come. So consume
+ // input buffer here.
+ if (on_read() != 0) {
+ return -1;
+ }
+
+ return 0;
+ }
+
+ if (proto == StringRef::from_lit("http/1.1")) {
+ upstream_ = std::make_unique<HttpsUpstream>(this);
+ alpn_ = StringRef::from_lit("http/1.1");
+
+ // At this point, input buffer is already filled with some bytes.
+ // The read callback is not called until new data come. So consume
+ // input buffer here.
+ if (on_read() != 0) {
+ return -1;
+ }
+
+ return 0;
+ }
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "The negotiated protocol is not supported";
+ }
+ return -1;
+}
+
+int ClientHandler::do_read() { return read_(*this); }
+int ClientHandler::do_write() { return write_(*this); }
+
+int ClientHandler::on_read() {
+ if (rb_.chunk_avail()) {
+ auto rv = on_read_(*this);
+ if (rv != 0) {
+ return rv;
+ }
+ }
+ conn_.handle_tls_pending_read();
+ return 0;
+}
+int ClientHandler::on_write() { return on_write_(*this); }
+
+const StringRef &ClientHandler::get_ipaddr() const { return ipaddr_; }
+
+bool ClientHandler::get_should_close_after_write() const {
+ return should_close_after_write_;
+}
+
+void ClientHandler::set_should_close_after_write(bool f) {
+ should_close_after_write_ = f;
+}
+
+void ClientHandler::pool_downstream_connection(
+ std::unique_ptr<DownstreamConnection> dconn) {
+ if (!dconn->poolable()) {
+ return;
+ }
+
+ dconn->set_client_handler(nullptr);
+
+ auto &group = dconn->get_downstream_addr_group();
+
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "Pooling downstream connection DCONN:" << dconn.get()
+ << " in group " << group;
+ }
+
+ auto addr = dconn->get_addr();
+ auto &dconn_pool = addr->dconn_pool;
+ dconn_pool->add_downstream_connection(std::move(dconn));
+}
+
+namespace {
+// Computes 32bits hash for session affinity for IP address |ip|.
+uint32_t compute_affinity_from_ip(const StringRef &ip) {
+ int rv;
+ std::array<uint8_t, 32> buf;
+
+ rv = util::sha256(buf.data(), ip);
+ if (rv != 0) {
+ // Not sure when sha256 failed. Just fall back to another
+ // function.
+ return util::hash32(ip);
+ }
+
+ return (static_cast<uint32_t>(buf[0]) << 24) |
+ (static_cast<uint32_t>(buf[1]) << 16) |
+ (static_cast<uint32_t>(buf[2]) << 8) | static_cast<uint32_t>(buf[3]);
+}
+} // namespace
+
+Http2Session *ClientHandler::get_http2_session(
+ const std::shared_ptr<DownstreamAddrGroup> &group, DownstreamAddr *addr) {
+ auto &shared_addr = group->shared_addr;
+
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "Selected DownstreamAddr=" << addr
+ << ", index=" << (addr - shared_addr->addrs.data());
+ }
+
+ for (auto session = addr->http2_extra_freelist.head; session;) {
+ auto next = session->dlnext;
+
+ if (session->max_concurrency_reached(0)) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this)
+ << "Maximum streams have been reached for Http2Session(" << session
+ << "). Skip it";
+ }
+
+ session->remove_from_freelist();
+ session = next;
+
+ continue;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "Use Http2Session " << session
+ << " from http2_extra_freelist";
+ }
+
+ if (session->max_concurrency_reached(1)) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "Maximum streams are reached for Http2Session("
+ << session << ").";
+ }
+
+ session->remove_from_freelist();
+ }
+ return session;
+ }
+
+ auto session = new Http2Session(conn_.loop, worker_->get_cl_ssl_ctx(),
+ worker_, group, addr);
+
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "Create new Http2Session " << session;
+ }
+
+ session->add_to_extra_freelist();
+
+ return session;
+}
+
+uint32_t ClientHandler::get_affinity_cookie(Downstream *downstream,
+ const StringRef &cookie_name) {
+ auto h = downstream->find_affinity_cookie(cookie_name);
+ if (h) {
+ return h;
+ }
+
+ auto d = std::uniform_int_distribution<uint32_t>(1);
+ auto rh = d(worker_->get_randgen());
+ h = util::hash32(StringRef{reinterpret_cast<uint8_t *>(&rh),
+ reinterpret_cast<uint8_t *>(&rh) + sizeof(rh)});
+
+ downstream->renew_affinity_cookie(h);
+
+ return h;
+}
+
+namespace {
+void reschedule_addr(
+ std::priority_queue<DownstreamAddrEntry, std::vector<DownstreamAddrEntry>,
+ DownstreamAddrEntryGreater> &pq,
+ DownstreamAddr *addr) {
+ auto penalty = MAX_DOWNSTREAM_ADDR_WEIGHT + addr->pending_penalty;
+ addr->cycle += penalty / addr->weight;
+ addr->pending_penalty = penalty % addr->weight;
+
+ pq.push(DownstreamAddrEntry{addr, addr->seq, addr->cycle});
+ addr->queued = true;
+}
+} // namespace
+
+namespace {
+void reschedule_wg(
+ std::priority_queue<WeightGroupEntry, std::vector<WeightGroupEntry>,
+ WeightGroupEntryGreater> &pq,
+ WeightGroup *wg) {
+ auto penalty = MAX_DOWNSTREAM_ADDR_WEIGHT + wg->pending_penalty;
+ wg->cycle += penalty / wg->weight;
+ wg->pending_penalty = penalty % wg->weight;
+
+ pq.push(WeightGroupEntry{wg, wg->seq, wg->cycle});
+ wg->queued = true;
+}
+} // namespace
+
+DownstreamAddr *ClientHandler::get_downstream_addr(int &err,
+ DownstreamAddrGroup *group,
+ Downstream *downstream) {
+ err = 0;
+
+ switch (faddr_->alt_mode) {
+ case UpstreamAltMode::API:
+ case UpstreamAltMode::HEALTHMON:
+ assert(0);
+ default:
+ break;
+ }
+
+ auto &shared_addr = group->shared_addr;
+
+ if (shared_addr->affinity.type != SessionAffinity::NONE) {
+ uint32_t hash;
+ switch (shared_addr->affinity.type) {
+ case SessionAffinity::IP:
+ if (!affinity_hash_computed_) {
+ affinity_hash_ = compute_affinity_from_ip(ipaddr_);
+ affinity_hash_computed_ = true;
+ }
+ hash = affinity_hash_;
+ break;
+ case SessionAffinity::COOKIE:
+ if (shared_addr->affinity.cookie.stickiness ==
+ SessionAffinityCookieStickiness::STRICT) {
+ return get_downstream_addr_strict_affinity(err, shared_addr,
+ downstream);
+ }
+
+ hash = get_affinity_cookie(downstream, shared_addr->affinity.cookie.name);
+ break;
+ default:
+ assert(0);
+ }
+
+ const auto &affinity_hash = shared_addr->affinity_hash;
+
+ auto it = std::lower_bound(
+ std::begin(affinity_hash), std::end(affinity_hash), hash,
+ [](const AffinityHash &lhs, uint32_t rhs) { return lhs.hash < rhs; });
+
+ if (it == std::end(affinity_hash)) {
+ it = std::begin(affinity_hash);
+ }
+
+ auto aff_idx =
+ static_cast<size_t>(std::distance(std::begin(affinity_hash), it));
+ auto idx = (*it).idx;
+ auto addr = &shared_addr->addrs[idx];
+
+ if (addr->connect_blocker->blocked()) {
+ size_t i;
+ for (i = aff_idx + 1; i != aff_idx; ++i) {
+ if (i == shared_addr->affinity_hash.size()) {
+ i = 0;
+ }
+ addr = &shared_addr->addrs[shared_addr->affinity_hash[i].idx];
+ if (addr->connect_blocker->blocked()) {
+ continue;
+ }
+ break;
+ }
+ if (i == aff_idx) {
+ err = -1;
+ return nullptr;
+ }
+ }
+
+ return addr;
+ }
+
+ auto &wgpq = shared_addr->pq;
+
+ for (;;) {
+ if (wgpq.empty()) {
+ CLOG(INFO, this) << "No working downstream address found";
+ err = -1;
+ return nullptr;
+ }
+
+ auto wg = wgpq.top().wg;
+ wgpq.pop();
+ wg->queued = false;
+
+ for (;;) {
+ if (wg->pq.empty()) {
+ break;
+ }
+
+ auto addr = wg->pq.top().addr;
+ wg->pq.pop();
+ addr->queued = false;
+
+ if (addr->connect_blocker->blocked()) {
+ continue;
+ }
+
+ reschedule_addr(wg->pq, addr);
+ reschedule_wg(wgpq, wg);
+
+ return addr;
+ }
+ }
+}
+
+DownstreamAddr *ClientHandler::get_downstream_addr_strict_affinity(
+ int &err, const std::shared_ptr<SharedDownstreamAddr> &shared_addr,
+ Downstream *downstream) {
+ const auto &affinity_hash = shared_addr->affinity_hash;
+
+ auto h = downstream->find_affinity_cookie(shared_addr->affinity.cookie.name);
+ if (h) {
+ auto it = shared_addr->affinity_hash_map.find(h);
+ if (it != std::end(shared_addr->affinity_hash_map)) {
+ auto addr = &shared_addr->addrs[(*it).second];
+ if (!addr->connect_blocker->blocked()) {
+ return addr;
+ }
+ }
+ } else {
+ auto d = std::uniform_int_distribution<uint32_t>(1);
+ auto rh = d(worker_->get_randgen());
+ h = util::hash32(StringRef{reinterpret_cast<uint8_t *>(&rh),
+ reinterpret_cast<uint8_t *>(&rh) + sizeof(rh)});
+ }
+
+ // Client is not bound to a particular backend, or the bound backend
+ // is not found, or is blocked. Find new backend using h. Using
+ // existing h allows us to find new server in a deterministic way.
+ // It is preferable because multiple concurrent requests with the
+ // stale cookie might be in-flight.
+ auto it = std::lower_bound(
+ std::begin(affinity_hash), std::end(affinity_hash), h,
+ [](const AffinityHash &lhs, uint32_t rhs) { return lhs.hash < rhs; });
+
+ if (it == std::end(affinity_hash)) {
+ it = std::begin(affinity_hash);
+ }
+
+ auto aff_idx =
+ static_cast<size_t>(std::distance(std::begin(affinity_hash), it));
+ auto idx = (*it).idx;
+ auto addr = &shared_addr->addrs[idx];
+
+ if (addr->connect_blocker->blocked()) {
+ size_t i;
+ for (i = aff_idx + 1; i != aff_idx; ++i) {
+ if (i == shared_addr->affinity_hash.size()) {
+ i = 0;
+ }
+ addr = &shared_addr->addrs[shared_addr->affinity_hash[i].idx];
+ if (addr->connect_blocker->blocked()) {
+ continue;
+ }
+ break;
+ }
+ if (i == aff_idx) {
+ err = -1;
+ return nullptr;
+ }
+ }
+
+ downstream->renew_affinity_cookie(addr->affinity_hash);
+
+ return addr;
+}
+
+std::unique_ptr<DownstreamConnection>
+ClientHandler::get_downstream_connection(int &err, Downstream *downstream) {
+ size_t group_idx;
+ auto &downstreamconf = *worker_->get_downstream_config();
+ auto &routerconf = downstreamconf.router;
+
+ auto catch_all = downstreamconf.addr_group_catch_all;
+ auto &groups = worker_->get_downstream_addr_groups();
+
+ auto &req = downstream->request();
+
+ err = 0;
+
+ switch (faddr_->alt_mode) {
+ case UpstreamAltMode::API: {
+ auto dconn = std::make_unique<APIDownstreamConnection>(worker_);
+ dconn->set_client_handler(this);
+ return dconn;
+ }
+ case UpstreamAltMode::HEALTHMON: {
+ auto dconn = std::make_unique<HealthMonitorDownstreamConnection>();
+ dconn->set_client_handler(this);
+ return dconn;
+ }
+ default:
+ break;
+ }
+
+ auto &balloc = downstream->get_block_allocator();
+
+ StringRef authority, path;
+
+ if (req.forwarded_once) {
+ if (groups.size() != 1) {
+ authority = req.orig_authority;
+ path = req.orig_path;
+ }
+ } else {
+ if (faddr_->sni_fwd) {
+ authority = sni_;
+ } else if (!req.authority.empty()) {
+ authority = req.authority;
+ } else {
+ auto h = req.fs.header(http2::HD_HOST);
+ if (h) {
+ authority = h->value;
+ }
+ }
+
+ // CONNECT method does not have path. But we requires path in
+ // host-path mapping. As workaround, we assume that path is
+ // "/".
+ if (!req.regular_connect_method()) {
+ path = req.path;
+ }
+
+ // Cache the authority and path used for the first-time backend
+ // selection because per-pattern mruby script can change them.
+ req.orig_authority = authority;
+ req.orig_path = path;
+ req.forwarded_once = true;
+ }
+
+ // Fast path. If we have one group, it must be catch-all group.
+ if (groups.size() == 1) {
+ group_idx = 0;
+ } else {
+ group_idx = match_downstream_addr_group(routerconf, authority, path, groups,
+ catch_all, balloc);
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "Downstream address group_idx: " << group_idx;
+ }
+
+ if (groups[group_idx]->shared_addr->redirect_if_not_tls && !conn_.tls.ssl) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "Downstream address group " << group_idx
+ << " requires frontend TLS connection.";
+ }
+ err = SHRPX_ERR_TLS_REQUIRED;
+ return nullptr;
+ }
+
+ auto &group = groups[group_idx];
+
+ if (group->shared_addr->dnf) {
+ auto dconn = std::make_unique<NullDownstreamConnection>(group);
+ dconn->set_client_handler(this);
+ return dconn;
+ }
+
+ auto addr = get_downstream_addr(err, group.get(), downstream);
+ if (addr == nullptr) {
+ return nullptr;
+ }
+
+ if (addr->proto == Proto::HTTP1) {
+ auto dconn = addr->dconn_pool->pop_downstream_connection();
+ if (dconn) {
+ dconn->set_client_handler(this);
+ return dconn;
+ }
+
+ if (worker_->get_connect_blocker()->blocked()) {
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this)
+ << "Worker wide backend connection was blocked temporarily";
+ }
+ return nullptr;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "Downstream connection pool is empty."
+ << " Create new one";
+ }
+
+ dconn = std::make_unique<HttpDownstreamConnection>(group, addr, conn_.loop,
+ worker_);
+ dconn->set_client_handler(this);
+ return dconn;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "Downstream connection pool is empty."
+ << " Create new one";
+ }
+
+ auto http2session = get_http2_session(group, addr);
+ auto dconn = std::make_unique<Http2DownstreamConnection>(http2session);
+ dconn->set_client_handler(this);
+ return dconn;
+}
+
+MemchunkPool *ClientHandler::get_mcpool() { return worker_->get_mcpool(); }
+
+SSL *ClientHandler::get_ssl() const { return conn_.tls.ssl; }
+
+void ClientHandler::direct_http2_upgrade() {
+ upstream_ = std::make_unique<Http2Upstream>(this);
+ alpn_ = StringRef::from_lit(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID);
+ on_read_ = &ClientHandler::upstream_read;
+ write_ = &ClientHandler::write_clear;
+}
+
+int ClientHandler::perform_http2_upgrade(HttpsUpstream *http) {
+ auto upstream = std::make_unique<Http2Upstream>(this);
+
+ auto output = upstream->get_response_buf();
+
+ // We might have written non-final header in response_buf, in this
+ // case, response_state is still INITIAL. If this non-final header
+ // and upgrade header fit in output buffer, do upgrade. Otherwise,
+ // to avoid to send this non-final header as response body in HTTP/2
+ // upstream, fail upgrade.
+ auto downstream = http->get_downstream();
+ auto input = downstream->get_response_buf();
+
+ if (upstream->upgrade_upstream(http) != 0) {
+ return -1;
+ }
+ // http pointer is now owned by upstream.
+ upstream_.release();
+ // TODO We might get other version id in HTTP2-settings, if we
+ // support aliasing for h2, but we just use library default for now.
+ alpn_ = StringRef::from_lit(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID);
+ on_read_ = &ClientHandler::upstream_http2_connhd_read;
+ write_ = &ClientHandler::write_clear;
+
+ input->remove(*output, input->rleft());
+
+ constexpr auto res =
+ StringRef::from_lit("HTTP/1.1 101 Switching Protocols\r\n"
+ "Connection: Upgrade\r\n"
+ "Upgrade: " NGHTTP2_CLEARTEXT_PROTO_VERSION_ID "\r\n"
+ "\r\n");
+
+ output->append(res);
+ upstream_ = std::move(upstream);
+
+ signal_write();
+ return 0;
+}
+
+bool ClientHandler::get_http2_upgrade_allowed() const { return !conn_.tls.ssl; }
+
+StringRef ClientHandler::get_upstream_scheme() const {
+ if (conn_.tls.ssl) {
+ return StringRef::from_lit("https");
+ } else {
+ return StringRef::from_lit("http");
+ }
+}
+
+void ClientHandler::start_immediate_shutdown() {
+ ev_timer_start(conn_.loop, &reneg_shutdown_timer_);
+}
+
+void ClientHandler::write_accesslog(Downstream *downstream) {
+ auto &req = downstream->request();
+
+ auto config = get_config();
+
+ if (!req.tstamp) {
+ auto lgconf = log_config();
+ lgconf->update_tstamp(std::chrono::system_clock::now());
+ req.tstamp = lgconf->tstamp;
+ }
+
+ upstream_accesslog(
+ config->logging.access.format,
+ LogSpec{
+ downstream,
+ ipaddr_,
+ alpn_,
+ sni_,
+ conn_.tls.ssl,
+ std::chrono::high_resolution_clock::now(), // request_end_time
+ port_,
+ faddr_->port,
+ config->pid,
+ });
+}
+
+ClientHandler::ReadBuf *ClientHandler::get_rb() { return &rb_; }
+
+void ClientHandler::signal_write() { conn_.wlimit.startw(); }
+
+RateLimit *ClientHandler::get_rlimit() { return &conn_.rlimit; }
+RateLimit *ClientHandler::get_wlimit() { return &conn_.wlimit; }
+
+ev_io *ClientHandler::get_wev() { return &conn_.wev; }
+
+Worker *ClientHandler::get_worker() const { return worker_; }
+
+namespace {
+ssize_t parse_proxy_line_port(const uint8_t *first, const uint8_t *last) {
+ auto p = first;
+ int32_t port = 0;
+
+ if (p == last) {
+ return -1;
+ }
+
+ if (*p == '0') {
+ if (p + 1 != last && util::is_digit(*(p + 1))) {
+ return -1;
+ }
+ return 1;
+ }
+
+ for (; p != last && util::is_digit(*p); ++p) {
+ port *= 10;
+ port += *p - '0';
+
+ if (port > 65535) {
+ return -1;
+ }
+ }
+
+ return p - first;
+}
+} // namespace
+
+int ClientHandler::on_proxy_protocol_finish() {
+ auto len = rb_.pos() - rb_.begin();
+
+ assert(len);
+
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol: Draining " << len
+ << " bytes from socket";
+ }
+
+ rb_.reset();
+
+ if (conn_.read_nolim_clear(rb_.pos(), len) < 0) {
+ return -1;
+ }
+
+ rb_.reset();
+
+ setup_upstream_io_callback();
+
+ return 0;
+}
+
+namespace {
+// PROXY-protocol v2 header signature
+constexpr uint8_t PROXY_PROTO_V2_SIG[] =
+ "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A";
+
+// PROXY-protocol v2 header length
+constexpr size_t PROXY_PROTO_V2_HDLEN =
+ str_size(PROXY_PROTO_V2_SIG) + /* ver_cmd(1) + fam(1) + len(2) = */ 4;
+} // namespace
+
+// http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt
+int ClientHandler::proxy_protocol_read() {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol: Started";
+ }
+
+ auto first = rb_.pos();
+
+ if (rb_.rleft() >= PROXY_PROTO_V2_HDLEN &&
+ (*(first + str_size(PROXY_PROTO_V2_SIG)) & 0xf0) == 0x20) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol: Detected v2 header signature";
+ }
+ return proxy_protocol_v2_read();
+ }
+
+ // NULL character really destroys functions which expects NULL
+ // terminated string. We won't expect it in PROXY protocol line, so
+ // find it here.
+ auto chrs = std::array<char, 2>{'\n', '\0'};
+
+ constexpr size_t MAX_PROXY_LINELEN = 107;
+
+ auto bufend = rb_.pos() + std::min(MAX_PROXY_LINELEN, rb_.rleft());
+
+ auto end =
+ std::find_first_of(rb_.pos(), bufend, std::begin(chrs), std::end(chrs));
+
+ if (end == bufend || *end == '\0' || end == rb_.pos() || *(end - 1) != '\r') {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v1: No ending CR LF sequence found";
+ }
+ return -1;
+ }
+
+ --end;
+
+ constexpr auto HEADER = StringRef::from_lit("PROXY ");
+
+ if (static_cast<size_t>(end - rb_.pos()) < HEADER.size()) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v1: PROXY version 1 ID not found";
+ }
+ return -1;
+ }
+
+ if (!util::streq(HEADER, StringRef{rb_.pos(), HEADER.size()})) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v1: Bad PROXY protocol version 1 ID";
+ }
+ return -1;
+ }
+
+ rb_.drain(HEADER.size());
+
+ int family;
+
+ if (rb_.pos()[0] == 'T') {
+ if (end - rb_.pos() < 5) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v1: INET protocol family not found";
+ }
+ return -1;
+ }
+
+ if (rb_.pos()[1] != 'C' || rb_.pos()[2] != 'P') {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v1: Unknown INET protocol family";
+ }
+ return -1;
+ }
+
+ switch (rb_.pos()[3]) {
+ case '4':
+ family = AF_INET;
+ break;
+ case '6':
+ family = AF_INET6;
+ break;
+ default:
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v1: Unknown INET protocol family";
+ }
+ return -1;
+ }
+
+ rb_.drain(5);
+ } else {
+ if (end - rb_.pos() < 7) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v1: INET protocol family not found";
+ }
+ return -1;
+ }
+ if (!util::streq_l("UNKNOWN", rb_.pos(), 7)) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v1: Unknown INET protocol family";
+ }
+ return -1;
+ }
+
+ rb_.drain(end + 2 - rb_.pos());
+
+ return on_proxy_protocol_finish();
+ }
+
+ // source address
+ auto token_end = std::find(rb_.pos(), end, ' ');
+ if (token_end == end) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v1: Source address not found";
+ }
+ return -1;
+ }
+
+ *token_end = '\0';
+ if (!util::numeric_host(reinterpret_cast<const char *>(rb_.pos()), family)) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v1: Invalid source address";
+ }
+ return -1;
+ }
+
+ auto src_addr = rb_.pos();
+ auto src_addrlen = token_end - rb_.pos();
+
+ rb_.drain(token_end - rb_.pos() + 1);
+
+ // destination address
+ token_end = std::find(rb_.pos(), end, ' ');
+ if (token_end == end) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v1: Destination address not found";
+ }
+ return -1;
+ }
+
+ *token_end = '\0';
+ if (!util::numeric_host(reinterpret_cast<const char *>(rb_.pos()), family)) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v1: Invalid destination address";
+ }
+ return -1;
+ }
+
+ // Currently we don't use destination address
+
+ rb_.drain(token_end - rb_.pos() + 1);
+
+ // source port
+ auto n = parse_proxy_line_port(rb_.pos(), end);
+ if (n <= 0 || *(rb_.pos() + n) != ' ') {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v1: Invalid source port";
+ }
+ return -1;
+ }
+
+ rb_.pos()[n] = '\0';
+ auto src_port = rb_.pos();
+ auto src_portlen = n;
+
+ rb_.drain(n + 1);
+
+ // destination port
+ n = parse_proxy_line_port(rb_.pos(), end);
+ if (n <= 0 || rb_.pos() + n != end) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v1: Invalid destination port";
+ }
+ return -1;
+ }
+
+ // Currently we don't use destination port
+
+ rb_.drain(end + 2 - rb_.pos());
+
+ ipaddr_ =
+ make_string_ref(balloc_, StringRef{src_addr, src_addr + src_addrlen});
+ port_ = make_string_ref(balloc_, StringRef{src_port, src_port + src_portlen});
+
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v1: Finished, " << (rb_.pos() - first)
+ << " bytes read";
+ }
+
+ auto config = get_config();
+ auto &fwdconf = config->http.forwarded;
+
+ if ((fwdconf.params & FORWARDED_FOR) &&
+ fwdconf.for_node_type == ForwardedNode::IP) {
+ init_forwarded_for(family, ipaddr_);
+ }
+
+ return on_proxy_protocol_finish();
+}
+
+int ClientHandler::proxy_protocol_v2_read() {
+ // Assume that first str_size(PROXY_PROTO_V2_SIG) octets match v2
+ // protocol signature and followed by the bytes which indicates v2.
+ assert(rb_.rleft() >= PROXY_PROTO_V2_HDLEN);
+
+ auto p = rb_.pos() + str_size(PROXY_PROTO_V2_SIG);
+
+ assert(((*p) & 0xf0) == 0x20);
+
+ enum { LOCAL, PROXY } cmd;
+
+ auto cmd_bits = (*p++) & 0xf;
+ switch (cmd_bits) {
+ case 0x0:
+ cmd = LOCAL;
+ break;
+ case 0x01:
+ cmd = PROXY;
+ break;
+ default:
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v2: Unknown command " << log::hex
+ << cmd_bits;
+ }
+ return -1;
+ }
+
+ auto fam = *p++;
+ uint16_t len;
+ memcpy(&len, p, sizeof(len));
+ len = ntohs(len);
+
+ p += sizeof(len);
+
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v2: Detected family=" << log::hex << fam
+ << ", len=" << log::dec << len;
+ }
+
+ if (rb_.last() - p < len) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this)
+ << "PROXY-protocol-v2: Prematurely truncated header block; require "
+ << len << " bytes, " << rb_.last() - p << " bytes left";
+ }
+ return -1;
+ }
+
+ int family;
+ std::array<char, std::max(INET_ADDRSTRLEN, INET6_ADDRSTRLEN)> src_addr,
+ dst_addr;
+ size_t addrlen;
+
+ switch (fam) {
+ case 0x11:
+ case 0x12:
+ if (len < 12) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v2: Too short AF_INET addresses";
+ }
+ return -1;
+ }
+ family = AF_INET;
+ addrlen = 4;
+ break;
+ case 0x21:
+ case 0x22:
+ if (len < 36) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v2: Too short AF_INET6 addresses";
+ }
+ return -1;
+ }
+ family = AF_INET6;
+ addrlen = 16;
+ break;
+ case 0x31:
+ case 0x32:
+ if (len < 216) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v2: Too short AF_UNIX addresses";
+ }
+ return -1;
+ }
+ // fall through
+ case 0x00: {
+ // UNSPEC and UNIX are just ignored.
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v2: Ignore combination of address "
+ "family and protocol "
+ << log::hex << fam;
+ }
+ rb_.drain(PROXY_PROTO_V2_HDLEN + len);
+ return on_proxy_protocol_finish();
+ }
+ default:
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v2: Unknown combination of address "
+ "family and protocol "
+ << log::hex << fam;
+ }
+ return -1;
+ }
+
+ if (cmd != PROXY) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v2: Ignore non-PROXY command";
+ }
+ rb_.drain(PROXY_PROTO_V2_HDLEN + len);
+ return on_proxy_protocol_finish();
+ }
+
+ if (inet_ntop(family, p, src_addr.data(), src_addr.size()) == nullptr) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v2: Unable to parse source address";
+ }
+ return -1;
+ }
+
+ p += addrlen;
+
+ if (inet_ntop(family, p, dst_addr.data(), dst_addr.size()) == nullptr) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this)
+ << "PROXY-protocol-v2: Unable to parse destination address";
+ }
+ return -1;
+ }
+
+ p += addrlen;
+
+ uint16_t src_port;
+
+ memcpy(&src_port, p, sizeof(src_port));
+ src_port = ntohs(src_port);
+
+ // We don't use destination port.
+ p += 4;
+
+ ipaddr_ = make_string_ref(balloc_, StringRef{src_addr.data()});
+ port_ = util::make_string_ref_uint(balloc_, src_port);
+
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v2: Finished reading proxy addresses, "
+ << p - rb_.pos() << " bytes read, "
+ << PROXY_PROTO_V2_HDLEN + len - (p - rb_.pos())
+ << " bytes left";
+ }
+
+ auto config = get_config();
+ auto &fwdconf = config->http.forwarded;
+
+ if ((fwdconf.params & FORWARDED_FOR) &&
+ fwdconf.for_node_type == ForwardedNode::IP) {
+ init_forwarded_for(family, ipaddr_);
+ }
+
+ rb_.drain(PROXY_PROTO_V2_HDLEN + len);
+ return on_proxy_protocol_finish();
+}
+
+StringRef ClientHandler::get_forwarded_by() const {
+ auto &fwdconf = get_config()->http.forwarded;
+
+ if (fwdconf.by_node_type == ForwardedNode::OBFUSCATED) {
+ return fwdconf.by_obfuscated;
+ }
+
+ return faddr_->hostport;
+}
+
+StringRef ClientHandler::get_forwarded_for() const { return forwarded_for_; }
+
+const UpstreamAddr *ClientHandler::get_upstream_addr() const { return faddr_; }
+
+Connection *ClientHandler::get_connection() { return &conn_; };
+
+void ClientHandler::set_tls_sni(const StringRef &sni) {
+ sni_ = make_string_ref(balloc_, sni);
+}
+
+StringRef ClientHandler::get_tls_sni() const { return sni_; }
+
+StringRef ClientHandler::get_alpn() const { return alpn_; }
+
+BlockAllocator &ClientHandler::get_block_allocator() { return balloc_; }
+
+void ClientHandler::set_alpn_from_conn() {
+ const unsigned char *alpn;
+ unsigned int alpnlen;
+
+ SSL_get0_alpn_selected(conn_.tls.ssl, &alpn, &alpnlen);
+
+ alpn_ = make_string_ref(balloc_, StringRef{alpn, alpnlen});
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_client_handler.h b/src/shrpx_client_handler.h
new file mode 100644
index 0000000..511dd91
--- /dev/null
+++ b/src/shrpx_client_handler.h
@@ -0,0 +1,236 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_CLIENT_HANDLER_H
+#define SHRPX_CLIENT_HANDLER_H
+
+#include "shrpx.h"
+
+#include <memory>
+
+#include <ev.h>
+
+#include <openssl/ssl.h>
+
+#include "shrpx_rate_limit.h"
+#include "shrpx_connection.h"
+#include "buffer.h"
+#include "memchunk.h"
+#include "allocator.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+class Upstream;
+class DownstreamConnection;
+class HttpsUpstream;
+class ConnectBlocker;
+class DownstreamConnectionPool;
+class Worker;
+class Downstream;
+struct WorkerStat;
+struct DownstreamAddrGroup;
+struct SharedDownstreamAddr;
+struct DownstreamAddr;
+#ifdef ENABLE_HTTP3
+class Http3Upstream;
+#endif // ENABLE_HTTP3
+
+class ClientHandler {
+public:
+ ClientHandler(Worker *worker, int fd, SSL *ssl, const StringRef &ipaddr,
+ const StringRef &port, int family, const UpstreamAddr *faddr);
+ ~ClientHandler();
+
+ int noop();
+ // Performs clear text I/O
+ int read_clear();
+ int write_clear();
+ // Specialized for PROXY-protocol use; peek data from socket.
+ int proxy_protocol_peek_clear();
+ // Performs TLS handshake
+ int tls_handshake();
+ // Performs TLS I/O
+ int read_tls();
+ int write_tls();
+
+ int upstream_noop();
+ int upstream_read();
+ int upstream_http2_connhd_read();
+ int upstream_http1_connhd_read();
+ int upstream_write();
+
+ int proxy_protocol_read();
+ int proxy_protocol_v2_read();
+ int on_proxy_protocol_finish();
+
+ // Performs I/O operation. Internally calls on_read()/on_write().
+ int do_read();
+ int do_write();
+
+ // Processes buffers. No underlying I/O operation will be done.
+ int on_read();
+ int on_write();
+
+ struct ev_loop *get_loop() const;
+ void reset_upstream_read_timeout(ev_tstamp t);
+ void reset_upstream_write_timeout(ev_tstamp t);
+
+ int validate_next_proto();
+ const StringRef &get_ipaddr() const;
+ bool get_should_close_after_write() const;
+ void set_should_close_after_write(bool f);
+ Upstream *get_upstream();
+
+ void pool_downstream_connection(std::unique_ptr<DownstreamConnection> dconn);
+ void remove_downstream_connection(DownstreamConnection *dconn);
+ DownstreamAddr *get_downstream_addr(int &err, DownstreamAddrGroup *group,
+ Downstream *downstream);
+ // Returns DownstreamConnection object based on request path. This
+ // function returns non-null DownstreamConnection, and assigns 0 to
+ // |err| if it succeeds, or returns nullptr, and assigns negative
+ // error code to |err|.
+ std::unique_ptr<DownstreamConnection>
+ get_downstream_connection(int &err, Downstream *downstream);
+ MemchunkPool *get_mcpool();
+ SSL *get_ssl() const;
+ // Call this function when HTTP/2 connection header is received at
+ // the start of the connection.
+ void direct_http2_upgrade();
+ // Performs HTTP/2 Upgrade from the connection managed by
+ // |http|. If this function fails, the connection must be
+ // terminated. This function returns 0 if it succeeds, or -1.
+ int perform_http2_upgrade(HttpsUpstream *http);
+ bool get_http2_upgrade_allowed() const;
+ // Returns upstream scheme, either "http" or "https"
+ StringRef get_upstream_scheme() const;
+ void start_immediate_shutdown();
+
+ // Writes upstream accesslog using |downstream|. The |downstream|
+ // must not be nullptr.
+ void write_accesslog(Downstream *downstream);
+
+ Worker *get_worker() const;
+
+ // Initializes forwarded_for_.
+ void init_forwarded_for(int family, const StringRef &ipaddr);
+
+ using ReadBuf = DefaultMemchunkBuffer;
+
+ ReadBuf *get_rb();
+
+ RateLimit *get_rlimit();
+ RateLimit *get_wlimit();
+
+ void signal_write();
+ ev_io *get_wev();
+
+ void setup_upstream_io_callback();
+
+#ifdef ENABLE_HTTP3
+ void setup_http3_upstream(std::unique_ptr<Http3Upstream> &&upstream);
+ int read_quic(const UpstreamAddr *faddr, const Address &remote_addr,
+ const Address &local_addr, const ngtcp2_pkt_info &pi,
+ const uint8_t *data, size_t datalen);
+ int write_quic();
+#endif // ENABLE_HTTP3
+
+ // Returns string suitable for use in "by" parameter of Forwarded
+ // header field.
+ StringRef get_forwarded_by() const;
+ // Returns string suitable for use in "for" parameter of Forwarded
+ // header field.
+ StringRef get_forwarded_for() const;
+
+ Http2Session *
+ get_http2_session(const std::shared_ptr<DownstreamAddrGroup> &group,
+ DownstreamAddr *addr);
+
+ // Returns an affinity cookie value for |downstream|. |cookie_name|
+ // is used to inspect cookie header field in request header fields.
+ uint32_t get_affinity_cookie(Downstream *downstream,
+ const StringRef &cookie_name);
+
+ DownstreamAddr *get_downstream_addr_strict_affinity(
+ int &err, const std::shared_ptr<SharedDownstreamAddr> &shared_addr,
+ Downstream *downstream);
+
+ const UpstreamAddr *get_upstream_addr() const;
+
+ void repeat_read_timer();
+ void stop_read_timer();
+
+ Connection *get_connection();
+
+ // Stores |sni| which is TLS SNI extension value client sent in this
+ // connection.
+ void set_tls_sni(const StringRef &sni);
+ // Returns TLS SNI extension value client sent in this connection.
+ StringRef get_tls_sni() const;
+
+ // Returns ALPN negotiated in this connection.
+ StringRef get_alpn() const;
+
+ BlockAllocator &get_block_allocator();
+
+ void set_alpn_from_conn();
+
+private:
+ // Allocator to allocate memory for connection-wide objects. Make
+ // sure that the allocations must be bounded, and not proportional
+ // to the number of requests.
+ BlockAllocator balloc_;
+ DefaultMemchunkBuffer rb_;
+ Connection conn_;
+ ev_timer reneg_shutdown_timer_;
+ std::unique_ptr<Upstream> upstream_;
+ // IP address of client. If UNIX domain socket is used, this is
+ // "localhost".
+ StringRef ipaddr_;
+ StringRef port_;
+ // The ALPN identifier negotiated for this connection.
+ StringRef alpn_;
+ // The client address used in "for" parameter of Forwarded header
+ // field.
+ StringRef forwarded_for_;
+ // lowercased TLS SNI which client sent.
+ StringRef sni_;
+ std::function<int(ClientHandler &)> read_, write_;
+ std::function<int(ClientHandler &)> on_read_, on_write_;
+ // Address of frontend listening socket
+ const UpstreamAddr *faddr_;
+ Worker *worker_;
+ // The number of bytes of HTTP/2 client connection header to read
+ size_t left_connhd_len_;
+ // hash for session affinity using client IP
+ uint32_t affinity_hash_;
+ bool should_close_after_write_;
+ // true if affinity_hash_ is computed
+ bool affinity_hash_computed_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_CLIENT_HANDLER_H
diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc
new file mode 100644
index 0000000..d6d0d07
--- /dev/null
+++ b/src/shrpx_config.cc
@@ -0,0 +1,4694 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_config.h"
+
+#ifdef HAVE_PWD_H
+# include <pwd.h>
+#endif // HAVE_PWD_H
+#ifdef HAVE_NETDB_H
+# include <netdb.h>
+#endif // HAVE_NETDB_H
+#ifdef HAVE_SYSLOG_H
+# include <syslog.h>
+#endif // HAVE_SYSLOG_H
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifdef HAVE_FCNTL_H
+# include <fcntl.h>
+#endif // HAVE_FCNTL_H
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+#include <dirent.h>
+
+#include <cstring>
+#include <cerrno>
+#include <limits>
+#include <fstream>
+#include <unordered_map>
+
+#include <nghttp2/nghttp2.h>
+
+#include "url-parser/url_parser.h"
+
+#include "shrpx_log.h"
+#include "shrpx_tls.h"
+#include "shrpx_http.h"
+#ifdef HAVE_MRUBY
+# include "shrpx_mruby.h"
+#endif // HAVE_MRUBY
+#include "util.h"
+#include "base64.h"
+#include "ssl_compat.h"
+#include "xsi_strerror.h"
+
+namespace shrpx {
+
+namespace {
+Config *config;
+} // namespace
+
+constexpr auto SHRPX_UNIX_PATH_PREFIX = StringRef::from_lit("unix:");
+
+const Config *get_config() { return config; }
+
+Config *mod_config() { return config; }
+
+std::unique_ptr<Config> replace_config(std::unique_ptr<Config> another) {
+ auto p = config;
+ config = another.release();
+ return std::unique_ptr<Config>(p);
+}
+
+void create_config() { config = new Config(); }
+
+Config::~Config() {
+ auto &upstreamconf = http2.upstream;
+
+ nghttp2_option_del(upstreamconf.option);
+ nghttp2_option_del(upstreamconf.alt_mode_option);
+ nghttp2_session_callbacks_del(upstreamconf.callbacks);
+
+ auto &downstreamconf = http2.downstream;
+
+ nghttp2_option_del(downstreamconf.option);
+ nghttp2_session_callbacks_del(downstreamconf.callbacks);
+
+ auto &dumpconf = http2.upstream.debug.dump;
+
+ if (dumpconf.request_header) {
+ fclose(dumpconf.request_header);
+ }
+
+ if (dumpconf.response_header) {
+ fclose(dumpconf.response_header);
+ }
+}
+
+TicketKeys::~TicketKeys() {
+ /* Erase keys from memory */
+ for (auto &key : keys) {
+ memset(&key, 0, sizeof(key));
+ }
+}
+
+namespace {
+int split_host_port(char *host, size_t hostlen, uint16_t *port_ptr,
+ const StringRef &hostport, const StringRef &opt) {
+ // host and port in |hostport| is separated by single ','.
+ auto sep = std::find(std::begin(hostport), std::end(hostport), ',');
+ if (sep == std::end(hostport)) {
+ LOG(ERROR) << opt << ": Invalid host, port: " << hostport;
+ return -1;
+ }
+ size_t len = sep - std::begin(hostport);
+ if (hostlen < len + 1) {
+ LOG(ERROR) << opt << ": Hostname too long: " << hostport;
+ return -1;
+ }
+ std::copy(std::begin(hostport), sep, host);
+ host[len] = '\0';
+
+ auto portstr = StringRef{sep + 1, std::end(hostport)};
+ auto d = util::parse_uint(portstr);
+ if (1 <= d && d <= std::numeric_limits<uint16_t>::max()) {
+ *port_ptr = d;
+ return 0;
+ }
+
+ LOG(ERROR) << opt << ": Port is invalid: " << portstr;
+ return -1;
+}
+} // namespace
+
+namespace {
+bool is_secure(const StringRef &filename) {
+ struct stat buf;
+ int rv = stat(filename.c_str(), &buf);
+ if (rv == 0) {
+ if ((buf.st_mode & S_IRWXU) && !(buf.st_mode & S_IRWXG) &&
+ !(buf.st_mode & S_IRWXO)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+} // namespace
+
+std::unique_ptr<TicketKeys>
+read_tls_ticket_key_file(const std::vector<StringRef> &files,
+ const EVP_CIPHER *cipher, const EVP_MD *hmac) {
+ auto ticket_keys = std::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 = keys[0].data.name.size() + 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;
+ }
+
+ 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++];
+ key.cipher = cipher;
+ key.hmac = hmac;
+ key.hmac_keylen = hmac_keylen;
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "enc_keylen=" << enc_keylen
+ << ", hmac_keylen=" << key.hmac_keylen;
+ }
+
+ auto p = buf;
+ std::copy_n(p, key.data.name.size(), std::begin(key.data.name));
+ p += key.data.name.size();
+ std::copy_n(p, enc_keylen, std::begin(key.data.enc_key));
+ p += enc_keylen;
+ std::copy_n(p, hmac_keylen, std::begin(key.data.hmac_key));
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "session ticket key: " << util::format_hex(key.data.name);
+ }
+ }
+ return ticket_keys;
+}
+
+#ifdef ENABLE_HTTP3
+std::shared_ptr<QUICKeyingMaterials>
+read_quic_secret_file(const StringRef &path) {
+ constexpr size_t expectedlen =
+ SHRPX_QUIC_SECRET_RESERVEDLEN + SHRPX_QUIC_SECRETLEN + SHRPX_QUIC_SALTLEN;
+
+ auto qkms = std::make_shared<QUICKeyingMaterials>();
+ auto &kms = qkms->keying_materials;
+
+ std::ifstream f(path.c_str());
+ if (!f) {
+ LOG(ERROR) << "frontend-quic-secret-file: could not open file " << path;
+ return nullptr;
+ }
+
+ std::array<char, 4096> buf;
+
+ while (f.getline(buf.data(), buf.size())) {
+ auto len = strlen(buf.data());
+ if (len == 0 || buf[0] == '#') {
+ continue;
+ }
+
+ auto s = StringRef{std::begin(buf), std::begin(buf) + len};
+ if (s.size() != expectedlen * 2 || !util::is_hex_string(s)) {
+ LOG(ERROR) << "frontend-quic-secret-file: each line must be a "
+ << expectedlen * 2 << " bytes hex encoded string";
+ return nullptr;
+ }
+
+ kms.emplace_back();
+ auto &qkm = kms.back();
+
+ auto p = std::begin(s);
+
+ util::decode_hex(std::begin(qkm.reserved),
+ StringRef{p, p + qkm.reserved.size()});
+ p += qkm.reserved.size() * 2;
+ util::decode_hex(std::begin(qkm.secret),
+ StringRef{p, p + qkm.secret.size()});
+ p += qkm.secret.size() * 2;
+ util::decode_hex(std::begin(qkm.salt), StringRef{p, p + qkm.salt.size()});
+ p += qkm.salt.size() * 2;
+
+ assert(static_cast<size_t>(p - std::begin(s)) == expectedlen * 2);
+
+ qkm.id = qkm.reserved[0] & 0xc0;
+
+ if (kms.size() == 4) {
+ break;
+ }
+ }
+
+ if (f.bad() || (!f.eof() && f.fail())) {
+ LOG(ERROR)
+ << "frontend-quic-secret-file: error occurred while reading file "
+ << path;
+ return nullptr;
+ }
+
+ if (kms.empty()) {
+ LOG(WARN)
+ << "frontend-quic-secret-file: no keying materials are present in file "
+ << path;
+ return nullptr;
+ }
+
+ return qkms;
+}
+#endif // ENABLE_HTTP3
+
+FILE *open_file_for_write(const char *filename) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+
+#ifdef O_CLOEXEC
+ auto fd = open(filename, O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC,
+ S_IRUSR | S_IWUSR);
+#else
+ auto fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+
+ // We get race condition if execve is called at the same time.
+ if (fd != -1) {
+ util::make_socket_closeonexec(fd);
+ }
+#endif
+ if (fd == -1) {
+ auto error = errno;
+ LOG(ERROR) << "Failed to open " << filename << " for writing. Cause: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return nullptr;
+ }
+ auto f = fdopen(fd, "wb");
+ if (f == nullptr) {
+ auto error = errno;
+ LOG(ERROR) << "Failed to open " << filename << " for writing. Cause: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return nullptr;
+ }
+
+ return f;
+}
+
+namespace {
+// Read passwd from |filename|
+std::string read_passwd_from_file(const StringRef &opt,
+ const StringRef &filename) {
+ std::string line;
+
+ if (!is_secure(filename)) {
+ LOG(ERROR) << opt << ": Private key passwd file " << filename
+ << " has insecure mode.";
+ return line;
+ }
+
+ std::ifstream in(filename.c_str(), std::ios::binary);
+ if (!in) {
+ LOG(ERROR) << opt << ": Could not open key passwd file " << filename;
+ return line;
+ }
+
+ std::getline(in, line);
+ return line;
+}
+} // namespace
+
+HeaderRefs::value_type parse_header(BlockAllocator &balloc,
+ const StringRef &optarg) {
+ auto colon = std::find(std::begin(optarg), std::end(optarg), ':');
+
+ if (colon == std::end(optarg) || colon == std::begin(optarg)) {
+ return {};
+ }
+
+ auto value = colon + 1;
+ for (; *value == '\t' || *value == ' '; ++value)
+ ;
+
+ auto name_iov =
+ make_byte_ref(balloc, std::distance(std::begin(optarg), colon) + 1);
+ auto p = name_iov.base;
+ p = std::copy(std::begin(optarg), colon, p);
+ util::inp_strlower(name_iov.base, p);
+ *p = '\0';
+
+ auto nv =
+ HeaderRef(StringRef{name_iov.base, p},
+ make_string_ref(balloc, StringRef{value, std::end(optarg)}));
+
+ if (!nghttp2_check_header_name(nv.name.byte(), nv.name.size()) ||
+ !nghttp2_check_header_value_rfc9113(nv.value.byte(), nv.value.size())) {
+ return {};
+ }
+
+ return nv;
+}
+
+template <typename T>
+int parse_uint(T *dest, const StringRef &opt, const StringRef &optarg) {
+ auto val = util::parse_uint(optarg);
+ if (val == -1) {
+ LOG(ERROR) << opt << ": bad value. Specify an integer >= 0.";
+ return -1;
+ }
+
+ *dest = val;
+
+ return 0;
+}
+
+namespace {
+template <typename T>
+int parse_uint_with_unit(T *dest, const StringRef &opt,
+ const StringRef &optarg) {
+ auto n = util::parse_uint_with_unit(optarg);
+ if (n == -1) {
+ LOG(ERROR) << opt << ": bad value: '" << optarg << "'";
+ return -1;
+ }
+
+ if (static_cast<uint64_t>(std::numeric_limits<T>::max()) <
+ static_cast<uint64_t>(n)) {
+ LOG(ERROR) << opt
+ << ": too large. The value should be less than or equal to "
+ << std::numeric_limits<T>::max();
+ return -1;
+ }
+
+ *dest = n;
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int parse_altsvc(AltSvc &altsvc, const StringRef &opt,
+ const StringRef &optarg) {
+ // PROTOID, PORT, HOST, ORIGIN, PARAMS.
+ auto tokens = util::split_str(optarg, ',', 5);
+
+ if (tokens.size() < 2) {
+ // Requires at least protocol_id and port
+ LOG(ERROR) << opt << ": too few parameters: " << optarg;
+ return -1;
+ }
+
+ int port;
+
+ if (parse_uint(&port, opt, tokens[1]) != 0) {
+ return -1;
+ }
+
+ if (port < 1 ||
+ port > static_cast<int>(std::numeric_limits<uint16_t>::max())) {
+ LOG(ERROR) << opt << ": port is invalid: " << tokens[1];
+ return -1;
+ }
+
+ altsvc.protocol_id = make_string_ref(config->balloc, tokens[0]);
+
+ altsvc.port = port;
+ altsvc.service = make_string_ref(config->balloc, tokens[1]);
+
+ if (tokens.size() > 2) {
+ if (!tokens[2].empty()) {
+ altsvc.host = make_string_ref(config->balloc, tokens[2]);
+ }
+
+ if (tokens.size() > 3) {
+ if (!tokens[3].empty()) {
+ altsvc.origin = make_string_ref(config->balloc, tokens[3]);
+ }
+
+ if (tokens.size() > 4) {
+ if (!tokens[4].empty()) {
+ altsvc.params = make_string_ref(config->balloc, tokens[4]);
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+// generated by gennghttpxfun.py
+LogFragmentType log_var_lookup_token(const char *name, size_t namelen) {
+ switch (namelen) {
+ case 3:
+ switch (name[2]) {
+ case 'd':
+ if (util::strieq_l("pi", name, 2)) {
+ return LogFragmentType::PID;
+ }
+ break;
+ }
+ break;
+ case 4:
+ switch (name[3]) {
+ case 'h':
+ if (util::strieq_l("pat", name, 3)) {
+ return LogFragmentType::PATH;
+ }
+ break;
+ case 'n':
+ if (util::strieq_l("alp", name, 3)) {
+ return LogFragmentType::ALPN;
+ }
+ break;
+ }
+ break;
+ case 6:
+ switch (name[5]) {
+ case 'd':
+ if (util::strieq_l("metho", name, 5)) {
+ return LogFragmentType::METHOD;
+ }
+ break;
+ case 's':
+ if (util::strieq_l("statu", name, 5)) {
+ return LogFragmentType::STATUS;
+ }
+ break;
+ }
+ break;
+ case 7:
+ switch (name[6]) {
+ case 'i':
+ if (util::strieq_l("tls_sn", name, 6)) {
+ return LogFragmentType::TLS_SNI;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("reques", name, 6)) {
+ return LogFragmentType::REQUEST;
+ }
+ break;
+ }
+ break;
+ case 10:
+ switch (name[9]) {
+ case 'l':
+ if (util::strieq_l("time_loca", name, 9)) {
+ return LogFragmentType::TIME_LOCAL;
+ }
+ break;
+ case 'r':
+ if (util::strieq_l("ssl_ciphe", name, 9)) {
+ return LogFragmentType::SSL_CIPHER;
+ }
+ if (util::strieq_l("tls_ciphe", name, 9)) {
+ return LogFragmentType::TLS_CIPHER;
+ }
+ break;
+ }
+ break;
+ case 11:
+ switch (name[10]) {
+ case 'r':
+ if (util::strieq_l("remote_add", name, 10)) {
+ return LogFragmentType::REMOTE_ADDR;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("remote_por", name, 10)) {
+ return LogFragmentType::REMOTE_PORT;
+ }
+ if (util::strieq_l("server_por", name, 10)) {
+ return LogFragmentType::SERVER_PORT;
+ }
+ break;
+ }
+ break;
+ case 12:
+ switch (name[11]) {
+ case '1':
+ if (util::strieq_l("time_iso860", name, 11)) {
+ return LogFragmentType::TIME_ISO8601;
+ }
+ break;
+ case 'e':
+ if (util::strieq_l("request_tim", name, 11)) {
+ return LogFragmentType::REQUEST_TIME;
+ }
+ break;
+ case 'l':
+ if (util::strieq_l("ssl_protoco", name, 11)) {
+ return LogFragmentType::SSL_PROTOCOL;
+ }
+ if (util::strieq_l("tls_protoco", name, 11)) {
+ return LogFragmentType::TLS_PROTOCOL;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("backend_hos", name, 11)) {
+ return LogFragmentType::BACKEND_HOST;
+ }
+ if (util::strieq_l("backend_por", name, 11)) {
+ return LogFragmentType::BACKEND_PORT;
+ }
+ break;
+ }
+ break;
+ case 14:
+ switch (name[13]) {
+ case 'd':
+ if (util::strieq_l("ssl_session_i", name, 13)) {
+ return LogFragmentType::SSL_SESSION_ID;
+ }
+ if (util::strieq_l("tls_session_i", name, 13)) {
+ return LogFragmentType::TLS_SESSION_ID;
+ }
+ break;
+ }
+ break;
+ case 15:
+ switch (name[14]) {
+ case 't':
+ if (util::strieq_l("body_bytes_sen", name, 14)) {
+ return LogFragmentType::BODY_BYTES_SENT;
+ }
+ break;
+ }
+ break;
+ case 16:
+ switch (name[15]) {
+ case 'n':
+ if (util::strieq_l("protocol_versio", name, 15)) {
+ return LogFragmentType::PROTOCOL_VERSION;
+ }
+ break;
+ }
+ break;
+ case 17:
+ switch (name[16]) {
+ case 'l':
+ if (util::strieq_l("tls_client_seria", name, 16)) {
+ return LogFragmentType::TLS_CLIENT_SERIAL;
+ }
+ break;
+ }
+ break;
+ case 18:
+ switch (name[17]) {
+ case 'd':
+ if (util::strieq_l("ssl_session_reuse", name, 17)) {
+ return LogFragmentType::SSL_SESSION_REUSED;
+ }
+ if (util::strieq_l("tls_session_reuse", name, 17)) {
+ return LogFragmentType::TLS_SESSION_REUSED;
+ }
+ break;
+ case 'y':
+ if (util::strieq_l("path_without_quer", name, 17)) {
+ return LogFragmentType::PATH_WITHOUT_QUERY;
+ }
+ break;
+ }
+ break;
+ case 22:
+ switch (name[21]) {
+ case 'e':
+ if (util::strieq_l("tls_client_issuer_nam", name, 21)) {
+ return LogFragmentType::TLS_CLIENT_ISSUER_NAME;
+ }
+ break;
+ }
+ break;
+ case 23:
+ switch (name[22]) {
+ case 'e':
+ if (util::strieq_l("tls_client_subject_nam", name, 22)) {
+ return LogFragmentType::TLS_CLIENT_SUBJECT_NAME;
+ }
+ break;
+ }
+ break;
+ case 27:
+ switch (name[26]) {
+ case '1':
+ if (util::strieq_l("tls_client_fingerprint_sha", name, 26)) {
+ return LogFragmentType::TLS_CLIENT_FINGERPRINT_SHA1;
+ }
+ break;
+ }
+ break;
+ case 29:
+ switch (name[28]) {
+ case '6':
+ if (util::strieq_l("tls_client_fingerprint_sha25", name, 28)) {
+ return LogFragmentType::TLS_CLIENT_FINGERPRINT_SHA256;
+ }
+ break;
+ }
+ break;
+ }
+ return LogFragmentType::NONE;
+}
+} // namespace
+
+namespace {
+bool var_token(char c) {
+ return util::is_alpha(c) || util::is_digit(c) || c == '_';
+}
+} // namespace
+
+std::vector<LogFragment> parse_log_format(BlockAllocator &balloc,
+ const StringRef &optarg) {
+ auto literal_start = std::begin(optarg);
+ auto p = literal_start;
+ auto eop = std::end(optarg);
+
+ auto res = std::vector<LogFragment>();
+
+ for (; p != eop;) {
+ if (*p != '$') {
+ ++p;
+ continue;
+ }
+
+ auto var_start = p;
+
+ ++p;
+
+ const char *var_name;
+ size_t var_namelen;
+ if (p != eop && *p == '{') {
+ var_name = ++p;
+ for (; p != eop && var_token(*p); ++p)
+ ;
+
+ if (p == eop || *p != '}') {
+ LOG(WARN) << "Missing '}' after " << StringRef{var_start, p};
+ continue;
+ }
+
+ var_namelen = p - var_name;
+ ++p;
+ } else {
+ var_name = p;
+ for (; p != eop && var_token(*p); ++p)
+ ;
+
+ var_namelen = p - var_name;
+ }
+
+ const char *value = nullptr;
+
+ auto type = log_var_lookup_token(var_name, var_namelen);
+
+ if (type == LogFragmentType::NONE) {
+ if (util::istarts_with_l(StringRef{var_name, var_namelen}, "http_")) {
+ if (util::streq_l("host", StringRef{var_name + str_size("http_"),
+ var_namelen - str_size("http_")})) {
+ // Special handling of host header field. We will use
+ // :authority header field if host header is missing. This
+ // is a typical case in HTTP/2.
+ type = LogFragmentType::AUTHORITY;
+ } else {
+ type = LogFragmentType::HTTP;
+ value = var_name + str_size("http_");
+ }
+ } else {
+ LOG(WARN) << "Unrecognized log format variable: "
+ << StringRef{var_name, var_namelen};
+ continue;
+ }
+ }
+
+ if (literal_start < var_start) {
+ res.emplace_back(
+ LogFragmentType::LITERAL,
+ make_string_ref(balloc, StringRef{literal_start, var_start}));
+ }
+
+ literal_start = p;
+
+ if (value == nullptr) {
+ res.emplace_back(type);
+ continue;
+ }
+
+ {
+ auto iov = make_byte_ref(
+ balloc, std::distance(value, var_name + var_namelen) + 1);
+ auto p = iov.base;
+ p = std::copy(value, var_name + var_namelen, p);
+ for (auto cp = iov.base; cp != p; ++cp) {
+ if (*cp == '_') {
+ *cp = '-';
+ }
+ }
+ *p = '\0';
+ res.emplace_back(type, StringRef{iov.base, p});
+ }
+ }
+
+ if (literal_start != eop) {
+ res.emplace_back(LogFragmentType::LITERAL,
+ make_string_ref(balloc, StringRef{literal_start, eop}));
+ }
+
+ return res;
+}
+
+namespace {
+int parse_address_family(int *dest, const StringRef &opt,
+ const StringRef &optarg) {
+ if (util::strieq_l("auto", optarg)) {
+ *dest = AF_UNSPEC;
+ return 0;
+ }
+ if (util::strieq_l("IPv4", optarg)) {
+ *dest = AF_INET;
+ return 0;
+ }
+ if (util::strieq_l("IPv6", optarg)) {
+ *dest = AF_INET6;
+ return 0;
+ }
+
+ LOG(ERROR) << opt << ": bad value: '" << optarg << "'";
+ return -1;
+}
+} // namespace
+
+namespace {
+int parse_duration(ev_tstamp *dest, const StringRef &opt,
+ const StringRef &optarg) {
+ auto t = util::parse_duration_with_unit(optarg);
+ if (t == std::numeric_limits<double>::infinity()) {
+ LOG(ERROR) << opt << ": bad value: '" << optarg << "'";
+ return -1;
+ }
+
+ *dest = t;
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int parse_tls_proto_version(int &dest, const StringRef &opt,
+ const StringRef &optarg) {
+ auto v = tls::proto_version_from_string(optarg);
+ if (v == -1) {
+ LOG(ERROR) << opt << ": invalid TLS protocol version: " << optarg;
+ return -1;
+ }
+
+ dest = v;
+
+ return 0;
+}
+} // namespace
+
+struct MemcachedConnectionParams {
+ bool tls;
+};
+
+namespace {
+// Parses memcached connection configuration parameter |src_params|,
+// and stores parsed results into |out|. This function returns 0 if
+// it succeeds, or -1.
+int parse_memcached_connection_params(MemcachedConnectionParams &out,
+ const StringRef &src_params,
+ const StringRef &opt) {
+ auto last = std::end(src_params);
+ for (auto first = std::begin(src_params); first != last;) {
+ auto end = std::find(first, last, ';');
+ auto param = StringRef{first, end};
+
+ if (util::strieq_l("tls", param)) {
+ out.tls = true;
+ } else if (util::strieq_l("no-tls", param)) {
+ out.tls = false;
+ } else if (!param.empty()) {
+ LOG(ERROR) << opt << ": " << param << ": unknown keyword";
+ return -1;
+ }
+
+ if (end == last) {
+ break;
+ }
+
+ first = end + 1;
+ }
+
+ return 0;
+}
+} // namespace
+
+struct UpstreamParams {
+ UpstreamAltMode alt_mode;
+ bool tls;
+ bool sni_fwd;
+ bool proxyproto;
+ bool quic;
+};
+
+namespace {
+// Parses upstream configuration parameter |src_params|, and stores
+// parsed results into |out|. This function returns 0 if it succeeds,
+// or -1.
+int parse_upstream_params(UpstreamParams &out, const StringRef &src_params) {
+ auto last = std::end(src_params);
+ for (auto first = std::begin(src_params); first != last;) {
+ auto end = std::find(first, last, ';');
+ auto param = StringRef{first, end};
+
+ if (util::strieq_l("tls", param)) {
+ out.tls = true;
+ } else if (util::strieq_l("sni-fwd", param)) {
+ out.sni_fwd = true;
+ } else if (util::strieq_l("no-tls", param)) {
+ out.tls = false;
+ } else if (util::strieq_l("api", param)) {
+ if (out.alt_mode != UpstreamAltMode::NONE &&
+ out.alt_mode != UpstreamAltMode::API) {
+ LOG(ERROR) << "frontend: api and healthmon are mutually exclusive";
+ return -1;
+ }
+ out.alt_mode = UpstreamAltMode::API;
+ } else if (util::strieq_l("healthmon", param)) {
+ if (out.alt_mode != UpstreamAltMode::NONE &&
+ out.alt_mode != UpstreamAltMode::HEALTHMON) {
+ LOG(ERROR) << "frontend: api and healthmon are mutually exclusive";
+ return -1;
+ }
+ out.alt_mode = UpstreamAltMode::HEALTHMON;
+ } else if (util::strieq_l("proxyproto", param)) {
+ out.proxyproto = true;
+ } else if (util::strieq_l("quic", param)) {
+#ifdef ENABLE_HTTP3
+ out.quic = true;
+#else // !ENABLE_HTTP3
+ LOG(ERROR) << "quic: QUIC is disabled at compile time";
+ return -1;
+#endif // !ENABLE_HTTP3
+ } else if (!param.empty()) {
+ LOG(ERROR) << "frontend: " << param << ": unknown keyword";
+ return -1;
+ }
+
+ if (end == last) {
+ break;
+ }
+
+ first = end + 1;
+ }
+
+ return 0;
+}
+} // namespace
+
+struct DownstreamParams {
+ StringRef sni;
+ StringRef mruby;
+ StringRef group;
+ AffinityConfig affinity;
+ ev_tstamp read_timeout;
+ ev_tstamp write_timeout;
+ size_t fall;
+ size_t rise;
+ uint32_t weight;
+ uint32_t group_weight;
+ Proto proto;
+ bool tls;
+ bool dns;
+ bool redirect_if_not_tls;
+ bool upgrade_scheme;
+ bool dnf;
+};
+
+namespace {
+// Parses |value| of parameter named |name| as duration. This
+// function returns 0 if it succeeds and the parsed value is assigned
+// to |dest|, or -1.
+int parse_downstream_param_duration(ev_tstamp &dest, const StringRef &name,
+ const StringRef &value) {
+ auto t = util::parse_duration_with_unit(value);
+ if (t == std::numeric_limits<double>::infinity()) {
+ LOG(ERROR) << "backend: " << name << ": bad value: '" << value << "'";
+ return -1;
+ }
+ dest = t;
+ return 0;
+}
+} // namespace
+
+namespace {
+// Parses downstream configuration parameter |src_params|, and stores
+// parsed results into |out|. This function returns 0 if it succeeds,
+// or -1.
+int parse_downstream_params(DownstreamParams &out,
+ const StringRef &src_params) {
+ auto last = std::end(src_params);
+ for (auto first = std::begin(src_params); first != last;) {
+ auto end = std::find(first, last, ';');
+ auto param = StringRef{first, end};
+
+ if (util::istarts_with_l(param, "proto=")) {
+ auto protostr = StringRef{first + str_size("proto="), end};
+ if (protostr.empty()) {
+ LOG(ERROR) << "backend: proto: protocol is empty";
+ return -1;
+ }
+
+ if (util::streq_l("h2", std::begin(protostr), protostr.size())) {
+ out.proto = Proto::HTTP2;
+ } else if (util::streq_l("http/1.1", std::begin(protostr),
+ protostr.size())) {
+ out.proto = Proto::HTTP1;
+ } else {
+ LOG(ERROR) << "backend: proto: unknown protocol " << protostr;
+ return -1;
+ }
+ } else if (util::istarts_with_l(param, "fall=")) {
+ auto valstr = StringRef{first + str_size("fall="), end};
+ if (valstr.empty()) {
+ LOG(ERROR) << "backend: fall: non-negative integer is expected";
+ return -1;
+ }
+
+ auto n = util::parse_uint(valstr);
+ if (n == -1) {
+ LOG(ERROR) << "backend: fall: non-negative integer is expected";
+ return -1;
+ }
+
+ out.fall = n;
+ } else if (util::istarts_with_l(param, "rise=")) {
+ auto valstr = StringRef{first + str_size("rise="), end};
+ if (valstr.empty()) {
+ LOG(ERROR) << "backend: rise: non-negative integer is expected";
+ return -1;
+ }
+
+ auto n = util::parse_uint(valstr);
+ if (n == -1) {
+ LOG(ERROR) << "backend: rise: non-negative integer is expected";
+ return -1;
+ }
+
+ out.rise = n;
+ } else if (util::strieq_l("tls", param)) {
+ out.tls = true;
+ } else if (util::strieq_l("no-tls", param)) {
+ out.tls = false;
+ } else if (util::istarts_with_l(param, "sni=")) {
+ out.sni = StringRef{first + str_size("sni="), end};
+ } else if (util::istarts_with_l(param, "affinity=")) {
+ auto valstr = StringRef{first + str_size("affinity="), end};
+ if (util::strieq_l("none", valstr)) {
+ out.affinity.type = SessionAffinity::NONE;
+ } else if (util::strieq_l("ip", valstr)) {
+ out.affinity.type = SessionAffinity::IP;
+ } else if (util::strieq_l("cookie", valstr)) {
+ out.affinity.type = SessionAffinity::COOKIE;
+ } else {
+ LOG(ERROR)
+ << "backend: affinity: value must be one of none, ip, and cookie";
+ return -1;
+ }
+ } else if (util::istarts_with_l(param, "affinity-cookie-name=")) {
+ auto val = StringRef{first + str_size("affinity-cookie-name="), end};
+ if (val.empty()) {
+ LOG(ERROR)
+ << "backend: affinity-cookie-name: non empty string is expected";
+ return -1;
+ }
+ out.affinity.cookie.name = val;
+ } else if (util::istarts_with_l(param, "affinity-cookie-path=")) {
+ out.affinity.cookie.path =
+ StringRef{first + str_size("affinity-cookie-path="), end};
+ } else if (util::istarts_with_l(param, "affinity-cookie-secure=")) {
+ auto valstr = StringRef{first + str_size("affinity-cookie-secure="), end};
+ if (util::strieq_l("auto", valstr)) {
+ out.affinity.cookie.secure = SessionAffinityCookieSecure::AUTO;
+ } else if (util::strieq_l("yes", valstr)) {
+ out.affinity.cookie.secure = SessionAffinityCookieSecure::YES;
+ } else if (util::strieq_l("no", valstr)) {
+ out.affinity.cookie.secure = SessionAffinityCookieSecure::NO;
+ } else {
+ LOG(ERROR) << "backend: affinity-cookie-secure: value must be one of "
+ "auto, yes, and no";
+ return -1;
+ }
+ } else if (util::istarts_with_l(param, "affinity-cookie-stickiness=")) {
+ auto valstr =
+ StringRef{first + str_size("affinity-cookie-stickiness="), end};
+ if (util::strieq_l("loose", valstr)) {
+ out.affinity.cookie.stickiness = SessionAffinityCookieStickiness::LOOSE;
+ } else if (util::strieq_l("strict", valstr)) {
+ out.affinity.cookie.stickiness =
+ SessionAffinityCookieStickiness::STRICT;
+ } else {
+ LOG(ERROR) << "backend: affinity-cookie-stickiness: value must be "
+ "either loose or strict";
+ return -1;
+ }
+ } else if (util::strieq_l("dns", param)) {
+ out.dns = true;
+ } else if (util::strieq_l("redirect-if-not-tls", param)) {
+ out.redirect_if_not_tls = true;
+ } else if (util::strieq_l("upgrade-scheme", param)) {
+ out.upgrade_scheme = true;
+ } else if (util::istarts_with_l(param, "mruby=")) {
+ auto valstr = StringRef{first + str_size("mruby="), end};
+ out.mruby = valstr;
+ } else if (util::istarts_with_l(param, "read-timeout=")) {
+ if (parse_downstream_param_duration(
+ out.read_timeout, StringRef::from_lit("read-timeout"),
+ StringRef{first + str_size("read-timeout="), end}) == -1) {
+ return -1;
+ }
+ } else if (util::istarts_with_l(param, "write-timeout=")) {
+ if (parse_downstream_param_duration(
+ out.write_timeout, StringRef::from_lit("write-timeout"),
+ StringRef{first + str_size("write-timeout="), end}) == -1) {
+ return -1;
+ }
+ } else if (util::istarts_with_l(param, "weight=")) {
+ auto valstr = StringRef{first + str_size("weight="), end};
+ if (valstr.empty()) {
+ LOG(ERROR)
+ << "backend: weight: non-negative integer [1, 256] is expected";
+ return -1;
+ }
+
+ auto n = util::parse_uint(valstr);
+ if (n < 1 || n > 256) {
+ LOG(ERROR)
+ << "backend: weight: non-negative integer [1, 256] is expected";
+ return -1;
+ }
+ out.weight = n;
+ } else if (util::istarts_with_l(param, "group=")) {
+ auto valstr = StringRef{first + str_size("group="), end};
+ if (valstr.empty()) {
+ LOG(ERROR) << "backend: group: empty string is not allowed";
+ return -1;
+ }
+ out.group = valstr;
+ } else if (util::istarts_with_l(param, "group-weight=")) {
+ auto valstr = StringRef{first + str_size("group-weight="), end};
+ if (valstr.empty()) {
+ LOG(ERROR) << "backend: group-weight: non-negative integer [1, 256] is "
+ "expected";
+ return -1;
+ }
+
+ auto n = util::parse_uint(valstr);
+ if (n < 1 || n > 256) {
+ LOG(ERROR) << "backend: group-weight: non-negative integer [1, 256] is "
+ "expected";
+ return -1;
+ }
+ out.group_weight = n;
+ } else if (util::strieq_l("dnf", param)) {
+ out.dnf = true;
+ } else if (!param.empty()) {
+ LOG(ERROR) << "backend: " << param << ": unknown keyword";
+ return -1;
+ }
+
+ if (end == last) {
+ break;
+ }
+
+ first = end + 1;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+// Parses host-path mapping patterns in |src_pattern|, and stores
+// mappings in config. We will store each host-path pattern found in
+// |src| with |addr|. |addr| will be copied accordingly. Also we
+// make a group based on the pattern. The "/" pattern is considered
+// as catch-all. We also parse protocol specified in |src_proto|.
+//
+// This function returns 0 if it succeeds, or -1.
+int parse_mapping(Config *config, DownstreamAddrConfig &addr,
+ std::map<StringRef, size_t> &pattern_addr_indexer,
+ const StringRef &src_pattern, const StringRef &src_params) {
+ // 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 = util::split_str(src_pattern, ':');
+ assert(!mapping.empty());
+ auto &downstreamconf = *config->conn.downstream;
+ auto &addr_groups = downstreamconf.addr_groups;
+
+ DownstreamParams params{};
+ params.proto = Proto::HTTP1;
+ params.weight = 1;
+
+ if (parse_downstream_params(params, src_params) != 0) {
+ return -1;
+ }
+
+ if (addr.host_unix && params.dns) {
+ LOG(ERROR) << "backend: dns: cannot be used for UNIX domain socket";
+ return -1;
+ }
+
+ if (params.affinity.type == SessionAffinity::COOKIE &&
+ params.affinity.cookie.name.empty()) {
+ LOG(ERROR) << "backend: affinity-cookie-name is mandatory if "
+ "affinity=cookie is specified";
+ return -1;
+ }
+
+ addr.fall = params.fall;
+ addr.rise = params.rise;
+ addr.weight = params.weight;
+ addr.group = make_string_ref(downstreamconf.balloc, params.group);
+ addr.group_weight = params.group_weight;
+ addr.proto = params.proto;
+ addr.tls = params.tls;
+ addr.sni = make_string_ref(downstreamconf.balloc, params.sni);
+ addr.dns = params.dns;
+ addr.upgrade_scheme = params.upgrade_scheme;
+ addr.dnf = params.dnf;
+
+ auto &routerconf = downstreamconf.router;
+ auto &router = routerconf.router;
+ auto &rw_router = routerconf.rev_wildcard_router;
+ auto &wildcard_patterns = routerconf.wildcard_patterns;
+
+ for (const auto &raw_pattern : mapping) {
+ StringRef pattern;
+ auto slash = std::find(std::begin(raw_pattern), std::end(raw_pattern), '/');
+ if (slash == std::end(raw_pattern)) {
+ // This effectively makes empty pattern to "/". 2 for '/' and
+ // terminal NULL character.
+ auto iov = make_byte_ref(downstreamconf.balloc, raw_pattern.size() + 2);
+ auto p = iov.base;
+ p = std::copy(std::begin(raw_pattern), std::end(raw_pattern), p);
+ util::inp_strlower(iov.base, p);
+ *p++ = '/';
+ *p = '\0';
+ pattern = StringRef{iov.base, p};
+ } else {
+ auto path = http2::normalize_path_colon(
+ downstreamconf.balloc, StringRef{slash, std::end(raw_pattern)},
+ StringRef{});
+ auto iov = make_byte_ref(downstreamconf.balloc,
+ std::distance(std::begin(raw_pattern), slash) +
+ path.size() + 1);
+ auto p = iov.base;
+ p = std::copy(std::begin(raw_pattern), slash, p);
+ util::inp_strlower(iov.base, p);
+ p = std::copy(std::begin(path), std::end(path), p);
+ *p = '\0';
+ pattern = StringRef{iov.base, p};
+ }
+ auto it = pattern_addr_indexer.find(pattern);
+ if (it != std::end(pattern_addr_indexer)) {
+ auto &g = addr_groups[(*it).second];
+ // Last value wins if we have multiple different affinity
+ // value under one group.
+ if (params.affinity.type != SessionAffinity::NONE) {
+ if (g.affinity.type == SessionAffinity::NONE) {
+ g.affinity.type = params.affinity.type;
+ if (params.affinity.type == SessionAffinity::COOKIE) {
+ g.affinity.cookie.name = make_string_ref(
+ downstreamconf.balloc, params.affinity.cookie.name);
+ if (!params.affinity.cookie.path.empty()) {
+ g.affinity.cookie.path = make_string_ref(
+ downstreamconf.balloc, params.affinity.cookie.path);
+ }
+ g.affinity.cookie.secure = params.affinity.cookie.secure;
+ g.affinity.cookie.stickiness = params.affinity.cookie.stickiness;
+ }
+ } else if (g.affinity.type != params.affinity.type ||
+ g.affinity.cookie.name != params.affinity.cookie.name ||
+ g.affinity.cookie.path != params.affinity.cookie.path ||
+ g.affinity.cookie.secure != params.affinity.cookie.secure ||
+ g.affinity.cookie.stickiness !=
+ params.affinity.cookie.stickiness) {
+ LOG(ERROR) << "backend: affinity: multiple different affinity "
+ "configurations found in a single group";
+ return -1;
+ }
+ }
+ // If at least one backend requires frontend TLS connection,
+ // enable it for all backends sharing the same pattern.
+ if (params.redirect_if_not_tls) {
+ g.redirect_if_not_tls = true;
+ }
+ // All backends in the same group must have the same mruby path.
+ // If some backends do not specify mruby file, and there is at
+ // least one backend with mruby file, it is used for all
+ // backends in the group.
+ if (!params.mruby.empty()) {
+ if (g.mruby_file.empty()) {
+ g.mruby_file = make_string_ref(downstreamconf.balloc, params.mruby);
+ } else if (g.mruby_file != params.mruby) {
+ LOG(ERROR) << "backend: mruby: multiple different mruby file found "
+ "in a single group";
+ return -1;
+ }
+ }
+ // All backends in the same group must have the same read/write
+ // timeout. If some backends do not specify read/write timeout,
+ // and there is at least one backend with read/write timeout, it
+ // is used for all backends in the group.
+ if (params.read_timeout > 1e-9) {
+ if (g.timeout.read < 1e-9) {
+ g.timeout.read = params.read_timeout;
+ } else if (fabs(g.timeout.read - params.read_timeout) > 1e-9) {
+ LOG(ERROR)
+ << "backend: read-timeout: multiple different read-timeout "
+ "found in a single group";
+ return -1;
+ }
+ }
+ if (params.write_timeout > 1e-9) {
+ if (g.timeout.write < 1e-9) {
+ g.timeout.write = params.write_timeout;
+ } else if (fabs(g.timeout.write - params.write_timeout) > 1e-9) {
+ LOG(ERROR) << "backend: write-timeout: multiple different "
+ "write-timeout found in a single group";
+ return -1;
+ }
+ }
+ // All backends in the same group must have the same dnf
+ // setting. If some backends do not specify dnf, and there is
+ // at least one backend with dnf, it is used for all backends in
+ // the group. In general, multiple backends are not necessary
+ // for dnf because there is no need for load balancing.
+ if (params.dnf) {
+ g.dnf = true;
+ }
+
+ g.addrs.push_back(addr);
+ continue;
+ }
+
+ auto idx = addr_groups.size();
+ pattern_addr_indexer.emplace(pattern, idx);
+ addr_groups.emplace_back(pattern);
+ auto &g = addr_groups.back();
+ g.addrs.push_back(addr);
+ g.affinity.type = params.affinity.type;
+ if (params.affinity.type == SessionAffinity::COOKIE) {
+ g.affinity.cookie.name =
+ make_string_ref(downstreamconf.balloc, params.affinity.cookie.name);
+ if (!params.affinity.cookie.path.empty()) {
+ g.affinity.cookie.path =
+ make_string_ref(downstreamconf.balloc, params.affinity.cookie.path);
+ }
+ g.affinity.cookie.secure = params.affinity.cookie.secure;
+ g.affinity.cookie.stickiness = params.affinity.cookie.stickiness;
+ }
+ g.redirect_if_not_tls = params.redirect_if_not_tls;
+ g.mruby_file = make_string_ref(downstreamconf.balloc, params.mruby);
+ g.timeout.read = params.read_timeout;
+ g.timeout.write = params.write_timeout;
+ g.dnf = params.dnf;
+
+ if (pattern[0] == '*') {
+ // wildcard pattern
+ auto path_first =
+ std::find(std::begin(g.pattern), std::end(g.pattern), '/');
+
+ auto host = StringRef{std::begin(g.pattern) + 1, path_first};
+ auto path = StringRef{path_first, std::end(g.pattern)};
+
+ auto path_is_wildcard = false;
+ if (path[path.size() - 1] == '*') {
+ path = StringRef{std::begin(path), std::begin(path) + path.size() - 1};
+ path_is_wildcard = true;
+ }
+
+ auto it = std::find_if(
+ std::begin(wildcard_patterns), std::end(wildcard_patterns),
+ [&host](const WildcardPattern &wp) { return wp.host == host; });
+
+ if (it == std::end(wildcard_patterns)) {
+ wildcard_patterns.emplace_back(host);
+
+ auto &router = wildcard_patterns.back().router;
+ router.add_route(path, idx, path_is_wildcard);
+
+ auto iov = make_byte_ref(downstreamconf.balloc, host.size() + 1);
+ auto p = iov.base;
+ p = std::reverse_copy(std::begin(host), std::end(host), p);
+ *p = '\0';
+ auto rev_host = StringRef{iov.base, p};
+
+ rw_router.add_route(rev_host, wildcard_patterns.size() - 1);
+ } else {
+ (*it).router.add_route(path, idx, path_is_wildcard);
+ }
+
+ continue;
+ }
+
+ auto path_is_wildcard = false;
+ if (pattern[pattern.size() - 1] == '*') {
+ pattern = StringRef{std::begin(pattern),
+ std::begin(pattern) + pattern.size() - 1};
+ path_is_wildcard = true;
+ }
+
+ router.add_route(pattern, idx, path_is_wildcard);
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+ForwardedNode parse_forwarded_node_type(const StringRef &optarg) {
+ if (util::strieq_l("obfuscated", optarg)) {
+ return ForwardedNode::OBFUSCATED;
+ }
+
+ if (util::strieq_l("ip", optarg)) {
+ return ForwardedNode::IP;
+ }
+
+ if (optarg.size() < 2 || optarg[0] != '_') {
+ return static_cast<ForwardedNode>(-1);
+ }
+
+ if (std::find_if_not(std::begin(optarg), std::end(optarg), [](char c) {
+ return util::is_alpha(c) || util::is_digit(c) || c == '.' || c == '_' ||
+ c == '-';
+ }) != std::end(optarg)) {
+ return static_cast<ForwardedNode>(-1);
+ }
+
+ return ForwardedNode::OBFUSCATED;
+}
+} // namespace
+
+namespace {
+int parse_error_page(std::vector<ErrorPage> &error_pages, const StringRef &opt,
+ const StringRef &optarg) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+
+ auto eq = std::find(std::begin(optarg), std::end(optarg), '=');
+ if (eq == std::end(optarg) || eq + 1 == std::end(optarg)) {
+ LOG(ERROR) << opt << ": bad value: '" << optarg << "'";
+ return -1;
+ }
+
+ auto codestr = StringRef{std::begin(optarg), eq};
+ unsigned int code;
+
+ if (codestr == StringRef::from_lit("*")) {
+ code = 0;
+ } else {
+ auto n = util::parse_uint(codestr);
+
+ if (n == -1 || n < 400 || n > 599) {
+ LOG(ERROR) << opt << ": bad code: '" << codestr << "'";
+ return -1;
+ }
+
+ code = static_cast<unsigned int>(n);
+ }
+
+ auto path = StringRef{eq + 1, std::end(optarg)};
+
+ std::vector<uint8_t> content;
+ auto fd = open(path.c_str(), O_RDONLY);
+ if (fd == -1) {
+ auto error = errno;
+ LOG(ERROR) << opt << ": " << optarg << ": "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return -1;
+ }
+
+ auto fd_closer = defer(close, fd);
+
+ std::array<uint8_t, 4096> buf;
+ for (;;) {
+ auto n = read(fd, buf.data(), buf.size());
+ if (n == -1) {
+ auto error = errno;
+ LOG(ERROR) << opt << ": " << optarg << ": "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return -1;
+ }
+ if (n == 0) {
+ break;
+ }
+ content.insert(std::end(content), std::begin(buf), std::begin(buf) + n);
+ }
+
+ error_pages.push_back(ErrorPage{std::move(content), code});
+
+ return 0;
+}
+} // namespace
+
+namespace {
+// Maximum size of SCT extension payload length.
+constexpr size_t MAX_SCT_EXT_LEN = 16_k;
+} // namespace
+
+struct SubcertParams {
+ StringRef sct_dir;
+};
+
+namespace {
+// Parses subcert parameter |src_params|, and stores parsed results
+// into |out|. This function returns 0 if it succeeds, or -1.
+int parse_subcert_params(SubcertParams &out, const StringRef &src_params) {
+ auto last = std::end(src_params);
+ for (auto first = std::begin(src_params); first != last;) {
+ auto end = std::find(first, last, ';');
+ auto param = StringRef{first, end};
+
+ if (util::istarts_with_l(param, "sct-dir=")) {
+#if defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_BORINGSSL)
+ auto sct_dir =
+ StringRef{std::begin(param) + str_size("sct-dir="), std::end(param)};
+ if (sct_dir.empty()) {
+ LOG(ERROR) << "subcert: " << param << ": empty sct-dir";
+ return -1;
+ }
+ out.sct_dir = sct_dir;
+#else // !NGHTTP2_GENUINE_OPENSSL && !NGHTTP2_OPENSSL_IS_BORINGSSL
+ LOG(WARN) << "subcert: sct-dir is ignored because underlying TLS library "
+ "does not support SCT";
+#endif // !NGHTTP2_GENUINE_OPENSSL && !NGHTTP2_OPENSSL_IS_BORINGSSL
+ } else if (!param.empty()) {
+ LOG(ERROR) << "subcert: " << param << ": unknown keyword";
+ return -1;
+ }
+
+ if (end == last) {
+ break;
+ }
+
+ first = end + 1;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+// Reads *.sct files from directory denoted by |dir_path|. |dir_path|
+// must be NULL-terminated string.
+int read_tls_sct_from_dir(std::vector<uint8_t> &dst, const StringRef &opt,
+ const StringRef &dir_path) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+
+ auto dir = opendir(dir_path.c_str());
+ if (dir == nullptr) {
+ auto error = errno;
+ LOG(ERROR) << opt << ": " << dir_path << ": "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return -1;
+ }
+
+ auto closer = defer(closedir, dir);
+
+ // 2 bytes total length field
+ auto len_idx = std::distance(std::begin(dst), std::end(dst));
+ dst.insert(std::end(dst), 2, 0);
+
+ for (;;) {
+ errno = 0;
+ auto ent = readdir(dir);
+ if (ent == nullptr) {
+ if (errno != 0) {
+ auto error = errno;
+ LOG(ERROR) << opt << ": failed to read directory " << dir_path << ": "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return -1;
+ }
+ break;
+ }
+
+ auto name = StringRef{ent->d_name};
+
+ if (name[0] == '.' || !util::iends_with_l(name, ".sct")) {
+ continue;
+ }
+
+ std::string path;
+ path.resize(dir_path.size() + 1 + name.size());
+ {
+ auto p = std::begin(path);
+ p = std::copy(std::begin(dir_path), std::end(dir_path), p);
+ *p++ = '/';
+ std::copy(std::begin(name), std::end(name), p);
+ }
+
+ auto fd = open(path.c_str(), O_RDONLY);
+ if (fd == -1) {
+ auto error = errno;
+ LOG(ERROR) << opt << ": failed to read SCT from " << path << ": "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return -1;
+ }
+
+ auto closer = defer(close, fd);
+
+ // 2 bytes length field for this SCT.
+ auto len_idx = std::distance(std::begin(dst), std::end(dst));
+ dst.insert(std::end(dst), 2, 0);
+
+ // *.sct file tends to be small; around 110+ bytes.
+ std::array<char, 256> buf;
+ for (;;) {
+ ssize_t nread;
+ while ((nread = read(fd, buf.data(), buf.size())) == -1 && errno == EINTR)
+ ;
+
+ if (nread == -1) {
+ auto error = errno;
+ LOG(ERROR) << opt << ": failed to read SCT data from " << path << ": "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return -1;
+ }
+
+ if (nread == 0) {
+ break;
+ }
+
+ dst.insert(std::end(dst), std::begin(buf), std::begin(buf) + nread);
+
+ if (dst.size() > MAX_SCT_EXT_LEN) {
+ LOG(ERROR) << opt << ": the concatenated SCT data from " << dir_path
+ << " is too large. Max " << MAX_SCT_EXT_LEN;
+ return -1;
+ }
+ }
+
+ auto len = dst.size() - len_idx - 2;
+
+ if (len == 0) {
+ dst.resize(dst.size() - 2);
+ continue;
+ }
+
+ dst[len_idx] = len >> 8;
+ dst[len_idx + 1] = len;
+ }
+
+ auto len = dst.size() - len_idx - 2;
+
+ if (len == 0) {
+ dst.resize(dst.size() - 2);
+ return 0;
+ }
+
+ dst[len_idx] = len >> 8;
+ dst[len_idx + 1] = len;
+
+ return 0;
+}
+} // namespace
+
+#ifndef OPENSSL_NO_PSK
+namespace {
+// Reads PSK secrets from path, and parses each line. The result is
+// directly stored into config->tls.psk_secrets. This function
+// returns 0 if it succeeds, or -1.
+int parse_psk_secrets(Config *config, const StringRef &path) {
+ auto &tlsconf = config->tls;
+
+ std::ifstream f(path.c_str(), std::ios::binary);
+ if (!f) {
+ LOG(ERROR) << SHRPX_OPT_PSK_SECRETS << ": could not open file " << path;
+ return -1;
+ }
+
+ size_t lineno = 0;
+ std::string line;
+ while (std::getline(f, line)) {
+ ++lineno;
+ if (line.empty() || line[0] == '#') {
+ continue;
+ }
+
+ auto sep_it = std::find(std::begin(line), std::end(line), ':');
+ if (sep_it == std::end(line)) {
+ LOG(ERROR) << SHRPX_OPT_PSK_SECRETS
+ << ": could not fine separator at line " << lineno;
+ return -1;
+ }
+
+ if (sep_it == std::begin(line)) {
+ LOG(ERROR) << SHRPX_OPT_PSK_SECRETS << ": empty identity at line "
+ << lineno;
+ return -1;
+ }
+
+ if (sep_it + 1 == std::end(line)) {
+ LOG(ERROR) << SHRPX_OPT_PSK_SECRETS << ": empty secret at line "
+ << lineno;
+ return -1;
+ }
+
+ if (!util::is_hex_string(StringRef{sep_it + 1, std::end(line)})) {
+ LOG(ERROR) << SHRPX_OPT_PSK_SECRETS
+ << ": secret must be hex string at line " << lineno;
+ return -1;
+ }
+
+ auto identity =
+ make_string_ref(config->balloc, StringRef{std::begin(line), sep_it});
+
+ auto secret =
+ util::decode_hex(config->balloc, StringRef{sep_it + 1, std::end(line)});
+
+ auto rv = tlsconf.psk_secrets.emplace(identity, secret);
+ if (!rv.second) {
+ LOG(ERROR) << SHRPX_OPT_PSK_SECRETS
+ << ": identity has already been registered at line " << lineno;
+ return -1;
+ }
+ }
+
+ return 0;
+}
+} // namespace
+#endif // !OPENSSL_NO_PSK
+
+#ifndef OPENSSL_NO_PSK
+namespace {
+// Reads PSK secrets from path, and parses each line. The result is
+// directly stored into config->tls.client.psk. This function returns
+// 0 if it succeeds, or -1.
+int parse_client_psk_secrets(Config *config, const StringRef &path) {
+ auto &tlsconf = config->tls;
+
+ std::ifstream f(path.c_str(), std::ios::binary);
+ if (!f) {
+ LOG(ERROR) << SHRPX_OPT_CLIENT_PSK_SECRETS << ": could not open file "
+ << path;
+ return -1;
+ }
+
+ size_t lineno = 0;
+ std::string line;
+ while (std::getline(f, line)) {
+ ++lineno;
+ if (line.empty() || line[0] == '#') {
+ continue;
+ }
+
+ auto sep_it = std::find(std::begin(line), std::end(line), ':');
+ if (sep_it == std::end(line)) {
+ LOG(ERROR) << SHRPX_OPT_CLIENT_PSK_SECRETS
+ << ": could not find separator at line " << lineno;
+ return -1;
+ }
+
+ if (sep_it == std::begin(line)) {
+ LOG(ERROR) << SHRPX_OPT_CLIENT_PSK_SECRETS << ": empty identity at line "
+ << lineno;
+ return -1;
+ }
+
+ if (sep_it + 1 == std::end(line)) {
+ LOG(ERROR) << SHRPX_OPT_CLIENT_PSK_SECRETS << ": empty secret at line "
+ << lineno;
+ return -1;
+ }
+
+ if (!util::is_hex_string(StringRef{sep_it + 1, std::end(line)})) {
+ LOG(ERROR) << SHRPX_OPT_CLIENT_PSK_SECRETS
+ << ": secret must be hex string at line " << lineno;
+ return -1;
+ }
+
+ tlsconf.client.psk.identity =
+ make_string_ref(config->balloc, StringRef{std::begin(line), sep_it});
+
+ tlsconf.client.psk.secret =
+ util::decode_hex(config->balloc, StringRef{sep_it + 1, std::end(line)});
+
+ return 0;
+ }
+
+ return 0;
+}
+} // namespace
+#endif // !OPENSSL_NO_PSK
+
+// generated by gennghttpxfun.py
+int option_lookup_token(const char *name, size_t namelen) {
+ switch (namelen) {
+ case 4:
+ switch (name[3]) {
+ case 'f':
+ if (util::strieq_l("con", name, 3)) {
+ return SHRPX_OPTID_CONF;
+ }
+ break;
+ case 'r':
+ if (util::strieq_l("use", name, 3)) {
+ return SHRPX_OPTID_USER;
+ }
+ break;
+ }
+ break;
+ case 6:
+ switch (name[5]) {
+ case 'a':
+ if (util::strieq_l("no-vi", name, 5)) {
+ return SHRPX_OPTID_NO_VIA;
+ }
+ break;
+ case 'c':
+ if (util::strieq_l("altsv", name, 5)) {
+ return SHRPX_OPTID_ALTSVC;
+ }
+ break;
+ case 'n':
+ if (util::strieq_l("daemo", name, 5)) {
+ return SHRPX_OPTID_DAEMON;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("cacer", name, 5)) {
+ return SHRPX_OPTID_CACERT;
+ }
+ if (util::strieq_l("clien", name, 5)) {
+ return SHRPX_OPTID_CLIENT;
+ }
+ break;
+ }
+ break;
+ case 7:
+ switch (name[6]) {
+ case 'd':
+ if (util::strieq_l("backen", name, 6)) {
+ return SHRPX_OPTID_BACKEND;
+ }
+ break;
+ case 'e':
+ if (util::strieq_l("includ", name, 6)) {
+ return SHRPX_OPTID_INCLUDE;
+ }
+ break;
+ case 'g':
+ if (util::strieq_l("backlo", name, 6)) {
+ return SHRPX_OPTID_BACKLOG;
+ }
+ if (util::strieq_l("paddin", name, 6)) {
+ return SHRPX_OPTID_PADDING;
+ }
+ break;
+ case 'p':
+ if (util::strieq_l("no-ocs", name, 6)) {
+ return SHRPX_OPTID_NO_OCSP;
+ }
+ break;
+ case 's':
+ if (util::strieq_l("cipher", name, 6)) {
+ return SHRPX_OPTID_CIPHERS;
+ }
+ if (util::strieq_l("worker", name, 6)) {
+ return SHRPX_OPTID_WORKERS;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("subcer", name, 6)) {
+ return SHRPX_OPTID_SUBCERT;
+ }
+ break;
+ }
+ break;
+ case 8:
+ switch (name[7]) {
+ case 'd':
+ if (util::strieq_l("fronten", name, 7)) {
+ return SHRPX_OPTID_FRONTEND;
+ }
+ break;
+ case 'e':
+ if (util::strieq_l("insecur", name, 7)) {
+ return SHRPX_OPTID_INSECURE;
+ }
+ if (util::strieq_l("pid-fil", name, 7)) {
+ return SHRPX_OPTID_PID_FILE;
+ }
+ break;
+ case 'n':
+ if (util::strieq_l("fastope", name, 7)) {
+ return SHRPX_OPTID_FASTOPEN;
+ }
+ break;
+ case 's':
+ if (util::strieq_l("tls-ktl", name, 7)) {
+ return SHRPX_OPTID_TLS_KTLS;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("npn-lis", name, 7)) {
+ return SHRPX_OPTID_NPN_LIST;
+ }
+ break;
+ }
+ break;
+ case 9:
+ switch (name[8]) {
+ case 'e':
+ if (util::strieq_l("no-kqueu", name, 8)) {
+ return SHRPX_OPTID_NO_KQUEUE;
+ }
+ if (util::strieq_l("read-rat", name, 8)) {
+ return SHRPX_OPTID_READ_RATE;
+ }
+ break;
+ case 'l':
+ if (util::strieq_l("log-leve", name, 8)) {
+ return SHRPX_OPTID_LOG_LEVEL;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("alpn-lis", name, 8)) {
+ return SHRPX_OPTID_ALPN_LIST;
+ }
+ break;
+ }
+ break;
+ case 10:
+ switch (name[9]) {
+ case 'e':
+ if (util::strieq_l("error-pag", name, 9)) {
+ return SHRPX_OPTID_ERROR_PAGE;
+ }
+ if (util::strieq_l("mruby-fil", name, 9)) {
+ return SHRPX_OPTID_MRUBY_FILE;
+ }
+ if (util::strieq_l("write-rat", name, 9)) {
+ return SHRPX_OPTID_WRITE_RATE;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("read-burs", name, 9)) {
+ return SHRPX_OPTID_READ_BURST;
+ }
+ break;
+ }
+ break;
+ case 11:
+ switch (name[10]) {
+ case 'e':
+ if (util::strieq_l("server-nam", name, 10)) {
+ return SHRPX_OPTID_SERVER_NAME;
+ }
+ break;
+ case 'f':
+ if (util::strieq_l("no-quic-bp", name, 10)) {
+ return SHRPX_OPTID_NO_QUIC_BPF;
+ }
+ break;
+ case 'r':
+ if (util::strieq_l("tls-sct-di", name, 10)) {
+ return SHRPX_OPTID_TLS_SCT_DIR;
+ }
+ break;
+ case 's':
+ if (util::strieq_l("backend-tl", name, 10)) {
+ return SHRPX_OPTID_BACKEND_TLS;
+ }
+ if (util::strieq_l("ecdh-curve", name, 10)) {
+ return SHRPX_OPTID_ECDH_CURVES;
+ }
+ if (util::strieq_l("psk-secret", name, 10)) {
+ return SHRPX_OPTID_PSK_SECRETS;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("write-burs", name, 10)) {
+ return SHRPX_OPTID_WRITE_BURST;
+ }
+ break;
+ case 'y':
+ if (util::strieq_l("dns-max-tr", name, 10)) {
+ return SHRPX_OPTID_DNS_MAX_TRY;
+ }
+ if (util::strieq_l("http2-prox", name, 10)) {
+ return SHRPX_OPTID_HTTP2_PROXY;
+ }
+ break;
+ }
+ break;
+ case 12:
+ switch (name[11]) {
+ case '4':
+ if (util::strieq_l("backend-ipv", name, 11)) {
+ return SHRPX_OPTID_BACKEND_IPV4;
+ }
+ break;
+ case '6':
+ if (util::strieq_l("backend-ipv", name, 11)) {
+ return SHRPX_OPTID_BACKEND_IPV6;
+ }
+ break;
+ case 'c':
+ if (util::strieq_l("http2-altsv", name, 11)) {
+ return SHRPX_OPTID_HTTP2_ALTSVC;
+ }
+ 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;
+ }
+ break;
+ case 'p':
+ if (util::strieq_l("ocsp-startu", name, 11)) {
+ return SHRPX_OPTID_OCSP_STARTUP;
+ }
+ break;
+ case 'y':
+ if (util::strieq_l("client-prox", name, 11)) {
+ return SHRPX_OPTID_CLIENT_PROXY;
+ }
+ if (util::strieq_l("forwarded-b", name, 11)) {
+ return SHRPX_OPTID_FORWARDED_BY;
+ }
+ break;
+ }
+ break;
+ case 13:
+ switch (name[12]) {
+ case 'd':
+ if (util::strieq_l("add-forwarde", name, 12)) {
+ return SHRPX_OPTID_ADD_FORWARDED;
+ }
+ if (util::strieq_l("single-threa", name, 12)) {
+ return SHRPX_OPTID_SINGLE_THREAD;
+ }
+ break;
+ case 'e':
+ if (util::strieq_l("dh-param-fil", name, 12)) {
+ return SHRPX_OPTID_DH_PARAM_FILE;
+ }
+ if (util::strieq_l("errorlog-fil", name, 12)) {
+ return SHRPX_OPTID_ERRORLOG_FILE;
+ }
+ if (util::strieq_l("rlimit-nofil", name, 12)) {
+ return SHRPX_OPTID_RLIMIT_NOFILE;
+ }
+ break;
+ case 'r':
+ if (util::strieq_l("forwarded-fo", name, 12)) {
+ return SHRPX_OPTID_FORWARDED_FOR;
+ }
+ break;
+ case 's':
+ if (util::strieq_l("tls13-cipher", name, 12)) {
+ return SHRPX_OPTID_TLS13_CIPHERS;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("verify-clien", name, 12)) {
+ return SHRPX_OPTID_VERIFY_CLIENT;
+ }
+ break;
+ }
+ break;
+ case 14:
+ switch (name[13]) {
+ case 'd':
+ if (util::strieq_l("quic-server-i", name, 13)) {
+ return SHRPX_OPTID_QUIC_SERVER_ID;
+ }
+ break;
+ case 'e':
+ if (util::strieq_l("accesslog-fil", name, 13)) {
+ return SHRPX_OPTID_ACCESSLOG_FILE;
+ }
+ break;
+ case 'h':
+ if (util::strieq_l("no-server-pus", name, 13)) {
+ return SHRPX_OPTID_NO_SERVER_PUSH;
+ }
+ break;
+ case 'k':
+ if (util::strieq_l("rlimit-memloc", name, 13)) {
+ return SHRPX_OPTID_RLIMIT_MEMLOCK;
+ }
+ break;
+ case 'p':
+ if (util::strieq_l("no-verify-ocs", name, 13)) {
+ return SHRPX_OPTID_NO_VERIFY_OCSP;
+ }
+ break;
+ case 's':
+ if (util::strieq_l("backend-no-tl", name, 13)) {
+ return SHRPX_OPTID_BACKEND_NO_TLS;
+ }
+ if (util::strieq_l("client-cipher", name, 13)) {
+ return SHRPX_OPTID_CLIENT_CIPHERS;
+ }
+ if (util::strieq_l("single-proces", name, 13)) {
+ return SHRPX_OPTID_SINGLE_PROCESS;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("tls-proto-lis", name, 13)) {
+ return SHRPX_OPTID_TLS_PROTO_LIST;
+ }
+ break;
+ }
+ break;
+ case 15:
+ switch (name[14]) {
+ case 'e':
+ if (util::strieq_l("no-host-rewrit", name, 14)) {
+ return SHRPX_OPTID_NO_HOST_REWRITE;
+ }
+ break;
+ case 'g':
+ if (util::strieq_l("errorlog-syslo", name, 14)) {
+ return SHRPX_OPTID_ERRORLOG_SYSLOG;
+ }
+ break;
+ case 's':
+ if (util::strieq_l("frontend-no-tl", name, 14)) {
+ return SHRPX_OPTID_FRONTEND_NO_TLS;
+ }
+ break;
+ case 'y':
+ if (util::strieq_l("syslog-facilit", name, 14)) {
+ return SHRPX_OPTID_SYSLOG_FACILITY;
+ }
+ break;
+ }
+ break;
+ case 16:
+ switch (name[15]) {
+ case 'e':
+ if (util::strieq_l("certificate-fil", name, 15)) {
+ return SHRPX_OPTID_CERTIFICATE_FILE;
+ }
+ if (util::strieq_l("client-cert-fil", name, 15)) {
+ return SHRPX_OPTID_CLIENT_CERT_FILE;
+ }
+ if (util::strieq_l("private-key-fil", name, 15)) {
+ return SHRPX_OPTID_PRIVATE_KEY_FILE;
+ }
+ if (util::strieq_l("worker-read-rat", name, 15)) {
+ return SHRPX_OPTID_WORKER_READ_RATE;
+ }
+ break;
+ case 'g':
+ if (util::strieq_l("accesslog-syslo", name, 15)) {
+ return SHRPX_OPTID_ACCESSLOG_SYSLOG;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("accesslog-forma", name, 15)) {
+ return SHRPX_OPTID_ACCESSLOG_FORMAT;
+ }
+ break;
+ }
+ break;
+ case 17:
+ switch (name[16]) {
+ case 'e':
+ if (util::strieq_l("no-server-rewrit", name, 16)) {
+ return SHRPX_OPTID_NO_SERVER_REWRITE;
+ }
+ if (util::strieq_l("worker-write-rat", name, 16)) {
+ return SHRPX_OPTID_WORKER_WRITE_RATE;
+ }
+ break;
+ case 's':
+ if (util::strieq_l("backend-http1-tl", name, 16)) {
+ return SHRPX_OPTID_BACKEND_HTTP1_TLS;
+ }
+ if (util::strieq_l("max-header-field", name, 16)) {
+ return SHRPX_OPTID_MAX_HEADER_FIELDS;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("dns-cache-timeou", name, 16)) {
+ return SHRPX_OPTID_DNS_CACHE_TIMEOUT;
+ }
+ if (util::strieq_l("worker-read-burs", name, 16)) {
+ return SHRPX_OPTID_WORKER_READ_BURST;
+ }
+ break;
+ }
+ break;
+ case 18:
+ switch (name[17]) {
+ case 'a':
+ if (util::strieq_l("tls-max-early-dat", name, 17)) {
+ return SHRPX_OPTID_TLS_MAX_EARLY_DATA;
+ }
+ break;
+ case 'r':
+ if (util::strieq_l("add-request-heade", name, 17)) {
+ return SHRPX_OPTID_ADD_REQUEST_HEADER;
+ }
+ break;
+ case 's':
+ if (util::strieq_l("client-psk-secret", name, 17)) {
+ return SHRPX_OPTID_CLIENT_PSK_SECRETS;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("dns-lookup-timeou", name, 17)) {
+ return SHRPX_OPTID_DNS_LOOKUP_TIMEOUT;
+ }
+ if (util::strieq_l("worker-write-burs", name, 17)) {
+ return SHRPX_OPTID_WORKER_WRITE_BURST;
+ }
+ break;
+ }
+ break;
+ case 19:
+ switch (name[18]) {
+ case 'e':
+ if (util::strieq_l("no-location-rewrit", name, 18)) {
+ return SHRPX_OPTID_NO_LOCATION_REWRITE;
+ }
+ if (util::strieq_l("require-http-schem", name, 18)) {
+ return SHRPX_OPTID_REQUIRE_HTTP_SCHEME;
+ }
+ if (util::strieq_l("tls-ticket-key-fil", name, 18)) {
+ return SHRPX_OPTID_TLS_TICKET_KEY_FILE;
+ }
+ break;
+ case 'f':
+ if (util::strieq_l("backend-max-backof", name, 18)) {
+ return SHRPX_OPTID_BACKEND_MAX_BACKOFF;
+ }
+ break;
+ case 'r':
+ if (util::strieq_l("add-response-heade", name, 18)) {
+ return SHRPX_OPTID_ADD_RESPONSE_HEADER;
+ }
+ if (util::strieq_l("add-x-forwarded-fo", name, 18)) {
+ return SHRPX_OPTID_ADD_X_FORWARDED_FOR;
+ }
+ if (util::strieq_l("header-field-buffe", name, 18)) {
+ return SHRPX_OPTID_HEADER_FIELD_BUFFER;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("redirect-https-por", name, 18)) {
+ return SHRPX_OPTID_REDIRECT_HTTPS_PORT;
+ }
+ if (util::strieq_l("stream-read-timeou", name, 18)) {
+ return SHRPX_OPTID_STREAM_READ_TIMEOUT;
+ }
+ break;
+ }
+ break;
+ case 20:
+ switch (name[19]) {
+ case 'g':
+ if (util::strieq_l("frontend-frame-debu", name, 19)) {
+ return SHRPX_OPTID_FRONTEND_FRAME_DEBUG;
+ }
+ break;
+ case 'l':
+ if (util::strieq_l("ocsp-update-interva", name, 19)) {
+ return SHRPX_OPTID_OCSP_UPDATE_INTERVAL;
+ }
+ break;
+ case 's':
+ if (util::strieq_l("max-worker-processe", name, 19)) {
+ return SHRPX_OPTID_MAX_WORKER_PROCESSES;
+ }
+ if (util::strieq_l("tls13-client-cipher", name, 19)) {
+ return SHRPX_OPTID_TLS13_CLIENT_CIPHERS;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("backend-read-timeou", name, 19)) {
+ return SHRPX_OPTID_BACKEND_READ_TIMEOUT;
+ }
+ if (util::strieq_l("stream-write-timeou", name, 19)) {
+ return SHRPX_OPTID_STREAM_WRITE_TIMEOUT;
+ }
+ if (util::strieq_l("verify-client-cacer", name, 19)) {
+ return SHRPX_OPTID_VERIFY_CLIENT_CACERT;
+ }
+ break;
+ case 'y':
+ if (util::strieq_l("api-max-request-bod", name, 19)) {
+ return SHRPX_OPTID_API_MAX_REQUEST_BODY;
+ }
+ break;
+ }
+ break;
+ case 21:
+ switch (name[20]) {
+ case 'd':
+ if (util::strieq_l("backend-tls-sni-fiel", name, 20)) {
+ return SHRPX_OPTID_BACKEND_TLS_SNI_FIELD;
+ }
+ break;
+ case 'e':
+ if (util::strieq_l("quic-bpf-program-fil", name, 20)) {
+ return SHRPX_OPTID_QUIC_BPF_PROGRAM_FILE;
+ }
+ break;
+ case 'l':
+ if (util::strieq_l("accept-proxy-protoco", name, 20)) {
+ return SHRPX_OPTID_ACCEPT_PROXY_PROTOCOL;
+ }
+ break;
+ case 'n':
+ if (util::strieq_l("tls-max-proto-versio", name, 20)) {
+ return SHRPX_OPTID_TLS_MAX_PROTO_VERSION;
+ }
+ if (util::strieq_l("tls-min-proto-versio", name, 20)) {
+ return SHRPX_OPTID_TLS_MIN_PROTO_VERSION;
+ }
+ break;
+ case 'r':
+ if (util::strieq_l("tls-ticket-key-ciphe", name, 20)) {
+ return SHRPX_OPTID_TLS_TICKET_KEY_CIPHER;
+ }
+ break;
+ case 's':
+ if (util::strieq_l("frontend-max-request", name, 20)) {
+ return SHRPX_OPTID_FRONTEND_MAX_REQUESTS;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("backend-write-timeou", name, 20)) {
+ return SHRPX_OPTID_BACKEND_WRITE_TIMEOUT;
+ }
+ if (util::strieq_l("frontend-read-timeou", name, 20)) {
+ return SHRPX_OPTID_FRONTEND_READ_TIMEOUT;
+ }
+ break;
+ case 'y':
+ if (util::strieq_l("accesslog-write-earl", name, 20)) {
+ return SHRPX_OPTID_ACCESSLOG_WRITE_EARLY;
+ }
+ break;
+ }
+ break;
+ case 22:
+ switch (name[21]) {
+ case 'i':
+ if (util::strieq_l("backend-http-proxy-ur", name, 21)) {
+ return SHRPX_OPTID_BACKEND_HTTP_PROXY_URI;
+ }
+ break;
+ case 'r':
+ if (util::strieq_l("backend-request-buffe", name, 21)) {
+ return SHRPX_OPTID_BACKEND_REQUEST_BUFFER;
+ }
+ if (util::strieq_l("frontend-quic-qlog-di", name, 21)) {
+ return SHRPX_OPTID_FRONTEND_QUIC_QLOG_DIR;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("frontend-write-timeou", name, 21)) {
+ return SHRPX_OPTID_FRONTEND_WRITE_TIMEOUT;
+ }
+ break;
+ case 'y':
+ if (util::strieq_l("backend-address-famil", name, 21)) {
+ return SHRPX_OPTID_BACKEND_ADDRESS_FAMILY;
+ }
+ break;
+ }
+ break;
+ case 23:
+ switch (name[22]) {
+ case 'e':
+ if (util::strieq_l("client-private-key-fil", name, 22)) {
+ return SHRPX_OPTID_CLIENT_PRIVATE_KEY_FILE;
+ }
+ if (util::strieq_l("private-key-passwd-fil", name, 22)) {
+ return SHRPX_OPTID_PRIVATE_KEY_PASSWD_FILE;
+ }
+ break;
+ case 'g':
+ if (util::strieq_l("frontend-quic-debug-lo", name, 22)) {
+ return SHRPX_OPTID_FRONTEND_QUIC_DEBUG_LOG;
+ }
+ break;
+ case 'r':
+ if (util::strieq_l("backend-response-buffe", name, 22)) {
+ return SHRPX_OPTID_BACKEND_RESPONSE_BUFFER;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("backend-connect-timeou", name, 22)) {
+ return SHRPX_OPTID_BACKEND_CONNECT_TIMEOUT;
+ }
+ break;
+ }
+ break;
+ case 24:
+ switch (name[23]) {
+ case 'a':
+ if (util::strieq_l("frontend-quic-early-dat", name, 23)) {
+ return SHRPX_OPTID_FRONTEND_QUIC_EARLY_DATA;
+ }
+ break;
+ case 'd':
+ if (util::strieq_l("strip-incoming-forwarde", name, 23)) {
+ return SHRPX_OPTID_STRIP_INCOMING_FORWARDED;
+ }
+ if (util::strieq_l("tls-ticket-key-memcache", name, 23)) {
+ return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED;
+ }
+ break;
+ case 'e':
+ if (util::strieq_l("fetch-ocsp-response-fil", name, 23)) {
+ return SHRPX_OPTID_FETCH_OCSP_RESPONSE_FILE;
+ }
+ break;
+ case 'o':
+ if (util::strieq_l("no-add-x-forwarded-prot", name, 23)) {
+ return SHRPX_OPTID_NO_ADD_X_FORWARDED_PROTO;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("listener-disable-timeou", name, 23)) {
+ return SHRPX_OPTID_LISTENER_DISABLE_TIMEOUT;
+ }
+ if (util::strieq_l("tls-dyn-rec-idle-timeou", name, 23)) {
+ return SHRPX_OPTID_TLS_DYN_REC_IDLE_TIMEOUT;
+ }
+ break;
+ }
+ break;
+ case 25:
+ switch (name[24]) {
+ case 'e':
+ if (util::strieq_l("backend-http2-window-siz", name, 24)) {
+ return SHRPX_OPTID_BACKEND_HTTP2_WINDOW_SIZE;
+ }
+ if (util::strieq_l("frontend-quic-secret-fil", name, 24)) {
+ return SHRPX_OPTID_FRONTEND_QUIC_SECRET_FILE;
+ }
+ break;
+ case 'g':
+ if (util::strieq_l("http2-no-cookie-crumblin", name, 24)) {
+ return SHRPX_OPTID_HTTP2_NO_COOKIE_CRUMBLING;
+ }
+ break;
+ case 's':
+ if (util::strieq_l("backend-http2-window-bit", name, 24)) {
+ return SHRPX_OPTID_BACKEND_HTTP2_WINDOW_BITS;
+ }
+ if (util::strieq_l("max-request-header-field", name, 24)) {
+ return SHRPX_OPTID_MAX_REQUEST_HEADER_FIELDS;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("frontend-quic-initial-rt", name, 24)) {
+ return SHRPX_OPTID_FRONTEND_QUIC_INITIAL_RTT;
+ }
+ break;
+ }
+ break;
+ case 26:
+ switch (name[25]) {
+ case 'a':
+ if (util::strieq_l("tls-no-postpone-early-dat", name, 25)) {
+ return SHRPX_OPTID_TLS_NO_POSTPONE_EARLY_DATA;
+ }
+ break;
+ case 'e':
+ if (util::strieq_l("frontend-http2-window-siz", name, 25)) {
+ return SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_SIZE;
+ }
+ if (util::strieq_l("frontend-http3-window-siz", name, 25)) {
+ return SHRPX_OPTID_FRONTEND_HTTP3_WINDOW_SIZE;
+ }
+ break;
+ case 's':
+ if (util::strieq_l("frontend-http2-window-bit", name, 25)) {
+ return SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_BITS;
+ }
+ if (util::strieq_l("max-response-header-field", name, 25)) {
+ return SHRPX_OPTID_MAX_RESPONSE_HEADER_FIELDS;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("backend-keep-alive-timeou", name, 25)) {
+ return SHRPX_OPTID_BACKEND_KEEP_ALIVE_TIMEOUT;
+ }
+ if (util::strieq_l("frontend-quic-idle-timeou", name, 25)) {
+ return SHRPX_OPTID_FRONTEND_QUIC_IDLE_TIMEOUT;
+ }
+ if (util::strieq_l("no-http2-cipher-black-lis", name, 25)) {
+ return SHRPX_OPTID_NO_HTTP2_CIPHER_BLACK_LIST;
+ }
+ if (util::strieq_l("no-http2-cipher-block-lis", name, 25)) {
+ return SHRPX_OPTID_NO_HTTP2_CIPHER_BLOCK_LIST;
+ }
+ break;
+ }
+ break;
+ case 27:
+ switch (name[26]) {
+ case 'd':
+ if (util::strieq_l("tls-session-cache-memcache", name, 26)) {
+ return SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED;
+ }
+ break;
+ case 'n':
+ if (util::strieq_l("frontend-quic-require-toke", name, 26)) {
+ return SHRPX_OPTID_FRONTEND_QUIC_REQUIRE_TOKEN;
+ }
+ break;
+ case 'r':
+ if (util::strieq_l("request-header-field-buffe", name, 26)) {
+ return SHRPX_OPTID_REQUEST_HEADER_FIELD_BUFFER;
+ }
+ break;
+ case 's':
+ if (util::strieq_l("worker-frontend-connection", name, 26)) {
+ return SHRPX_OPTID_WORKER_FRONTEND_CONNECTIONS;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("frontend-http2-read-timeou", name, 26)) {
+ return SHRPX_OPTID_FRONTEND_HTTP2_READ_TIMEOUT;
+ }
+ if (util::strieq_l("frontend-http3-read-timeou", name, 26)) {
+ return SHRPX_OPTID_FRONTEND_HTTP3_READ_TIMEOUT;
+ }
+ if (util::strieq_l("frontend-keep-alive-timeou", name, 26)) {
+ return SHRPX_OPTID_FRONTEND_KEEP_ALIVE_TIMEOUT;
+ }
+ break;
+ }
+ break;
+ case 28:
+ switch (name[27]) {
+ case 'a':
+ if (util::strieq_l("no-strip-incoming-early-dat", name, 27)) {
+ return SHRPX_OPTID_NO_STRIP_INCOMING_EARLY_DATA;
+ }
+ break;
+ case 'd':
+ if (util::strieq_l("tls-dyn-rec-warmup-threshol", name, 27)) {
+ return SHRPX_OPTID_TLS_DYN_REC_WARMUP_THRESHOLD;
+ }
+ break;
+ case 'r':
+ if (util::strieq_l("response-header-field-buffe", name, 27)) {
+ return SHRPX_OPTID_RESPONSE_HEADER_FIELD_BUFFER;
+ }
+ break;
+ case 's':
+ if (util::strieq_l("http2-max-concurrent-stream", name, 27)) {
+ return SHRPX_OPTID_HTTP2_MAX_CONCURRENT_STREAMS;
+ }
+ if (util::strieq_l("tls-ticket-key-memcached-tl", name, 27)) {
+ return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_TLS;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("backend-connections-per-hos", name, 27)) {
+ return SHRPX_OPTID_BACKEND_CONNECTIONS_PER_HOST;
+ }
+ break;
+ }
+ break;
+ case 30:
+ switch (name[29]) {
+ case 'd':
+ if (util::strieq_l("verify-client-tolerate-expire", name, 29)) {
+ return SHRPX_OPTID_VERIFY_CLIENT_TOLERATE_EXPIRED;
+ }
+ break;
+ case 'e':
+ if (util::strieq_l("frontend-http3-max-window-siz", name, 29)) {
+ return SHRPX_OPTID_FRONTEND_HTTP3_MAX_WINDOW_SIZE;
+ }
+ break;
+ case 'r':
+ if (util::strieq_l("ignore-per-pattern-mruby-erro", name, 29)) {
+ return SHRPX_OPTID_IGNORE_PER_PATTERN_MRUBY_ERROR;
+ }
+ if (util::strieq_l("strip-incoming-x-forwarded-fo", name, 29)) {
+ return SHRPX_OPTID_STRIP_INCOMING_X_FORWARDED_FOR;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("backend-http2-settings-timeou", name, 29)) {
+ return SHRPX_OPTID_BACKEND_HTTP2_SETTINGS_TIMEOUT;
+ }
+ break;
+ }
+ break;
+ case 31:
+ switch (name[30]) {
+ case 's':
+ if (util::strieq_l("tls-session-cache-memcached-tl", name, 30)) {
+ return SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_TLS;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("frontend-http2-settings-timeou", name, 30)) {
+ return SHRPX_OPTID_FRONTEND_HTTP2_SETTINGS_TIMEOUT;
+ }
+ break;
+ }
+ break;
+ case 32:
+ switch (name[31]) {
+ case 'd':
+ if (util::strieq_l("backend-connections-per-fronten", name, 31)) {
+ return SHRPX_OPTID_BACKEND_CONNECTIONS_PER_FRONTEND;
+ }
+ break;
+ }
+ break;
+ case 33:
+ switch (name[32]) {
+ case 'l':
+ if (util::strieq_l("tls-ticket-key-memcached-interva", name, 32)) {
+ return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_INTERVAL;
+ }
+ if (util::strieq_l("tls-ticket-key-memcached-max-fai", name, 32)) {
+ return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("client-no-http2-cipher-black-lis", name, 32)) {
+ return SHRPX_OPTID_CLIENT_NO_HTTP2_CIPHER_BLACK_LIST;
+ }
+ if (util::strieq_l("client-no-http2-cipher-block-lis", name, 32)) {
+ return SHRPX_OPTID_CLIENT_NO_HTTP2_CIPHER_BLOCK_LIST;
+ }
+ break;
+ }
+ break;
+ case 34:
+ switch (name[33]) {
+ case 'e':
+ if (util::strieq_l("tls-ticket-key-memcached-cert-fil", name, 33)) {
+ return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_CERT_FILE;
+ }
+ break;
+ case 'r':
+ if (util::strieq_l("frontend-http2-dump-request-heade", name, 33)) {
+ return SHRPX_OPTID_FRONTEND_HTTP2_DUMP_REQUEST_HEADER;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("backend-http1-connections-per-hos", name, 33)) {
+ return SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_HOST;
+ }
+ break;
+ case 'y':
+ if (util::strieq_l("tls-ticket-key-memcached-max-retr", name, 33)) {
+ return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_MAX_RETRY;
+ }
+ break;
+ }
+ break;
+ case 35:
+ switch (name[34]) {
+ case 'e':
+ if (util::strieq_l("frontend-http2-optimize-window-siz", name, 34)) {
+ return SHRPX_OPTID_FRONTEND_HTTP2_OPTIMIZE_WINDOW_SIZE;
+ }
+ break;
+ case 'o':
+ if (util::strieq_l("no-strip-incoming-x-forwarded-prot", name, 34)) {
+ return SHRPX_OPTID_NO_STRIP_INCOMING_X_FORWARDED_PROTO;
+ }
+ break;
+ case 'r':
+ if (util::strieq_l("frontend-http2-dump-response-heade", name, 34)) {
+ return SHRPX_OPTID_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER;
+ }
+ if (util::strieq_l("frontend-quic-congestion-controlle", name, 34)) {
+ return SHRPX_OPTID_FRONTEND_QUIC_CONGESTION_CONTROLLER;
+ }
+ break;
+ }
+ break;
+ case 36:
+ switch (name[35]) {
+ case 'd':
+ if (util::strieq_l("worker-process-grace-shutdown-perio", name, 35)) {
+ return SHRPX_OPTID_WORKER_PROCESS_GRACE_SHUTDOWN_PERIOD;
+ }
+ break;
+ case 'e':
+ if (util::strieq_l("backend-http2-connection-window-siz", name, 35)) {
+ return SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE;
+ }
+ break;
+ case 'r':
+ if (util::strieq_l("backend-http2-connections-per-worke", name, 35)) {
+ return SHRPX_OPTID_BACKEND_HTTP2_CONNECTIONS_PER_WORKER;
+ }
+ break;
+ case 's':
+ if (util::strieq_l("backend-http2-connection-window-bit", name, 35)) {
+ return SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_BITS;
+ }
+ if (util::strieq_l("backend-http2-max-concurrent-stream", name, 35)) {
+ return SHRPX_OPTID_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS;
+ }
+ break;
+ }
+ break;
+ case 37:
+ switch (name[36]) {
+ case 'e':
+ if (util::strieq_l("frontend-http2-connection-window-siz", name, 36)) {
+ return SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_SIZE;
+ }
+ if (util::strieq_l("frontend-http3-connection-window-siz", name, 36)) {
+ return SHRPX_OPTID_FRONTEND_HTTP3_CONNECTION_WINDOW_SIZE;
+ }
+ if (util::strieq_l("tls-session-cache-memcached-cert-fil", name, 36)) {
+ return SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_CERT_FILE;
+ }
+ break;
+ case 's':
+ if (util::strieq_l("frontend-http2-connection-window-bit", name, 36)) {
+ return SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS;
+ }
+ if (util::strieq_l("frontend-http2-max-concurrent-stream", name, 36)) {
+ return SHRPX_OPTID_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS;
+ }
+ if (util::strieq_l("frontend-http3-max-concurrent-stream", name, 36)) {
+ return SHRPX_OPTID_FRONTEND_HTTP3_MAX_CONCURRENT_STREAMS;
+ }
+ break;
+ }
+ break;
+ case 38:
+ switch (name[37]) {
+ case 'd':
+ if (util::strieq_l("backend-http1-connections-per-fronten", name, 37)) {
+ return SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND;
+ }
+ break;
+ }
+ break;
+ case 39:
+ switch (name[38]) {
+ case 'y':
+ if (util::strieq_l("tls-ticket-key-memcached-address-famil", name, 38)) {
+ return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_ADDRESS_FAMILY;
+ }
+ break;
+ }
+ break;
+ case 40:
+ switch (name[39]) {
+ case 'e':
+ if (util::strieq_l("backend-http2-decoder-dynamic-table-siz", name, 39)) {
+ return SHRPX_OPTID_BACKEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE;
+ }
+ if (util::strieq_l("backend-http2-encoder-dynamic-table-siz", name, 39)) {
+ return SHRPX_OPTID_BACKEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE;
+ }
+ break;
+ }
+ break;
+ case 41:
+ switch (name[40]) {
+ case 'e':
+ if (util::strieq_l("frontend-http2-decoder-dynamic-table-siz", name,
+ 40)) {
+ return SHRPX_OPTID_FRONTEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE;
+ }
+ if (util::strieq_l("frontend-http2-encoder-dynamic-table-siz", name,
+ 40)) {
+ return SHRPX_OPTID_FRONTEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE;
+ }
+ if (util::strieq_l("frontend-http2-optimize-write-buffer-siz", name,
+ 40)) {
+ return SHRPX_OPTID_FRONTEND_HTTP2_OPTIMIZE_WRITE_BUFFER_SIZE;
+ }
+ if (util::strieq_l("frontend-http3-max-connection-window-siz", name,
+ 40)) {
+ return SHRPX_OPTID_FRONTEND_HTTP3_MAX_CONNECTION_WINDOW_SIZE;
+ }
+ if (util::strieq_l("tls-ticket-key-memcached-private-key-fil", name,
+ 40)) {
+ return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_PRIVATE_KEY_FILE;
+ }
+ break;
+ }
+ break;
+ case 42:
+ switch (name[41]) {
+ case 'y':
+ if (util::strieq_l("tls-session-cache-memcached-address-famil", name,
+ 41)) {
+ return SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_ADDRESS_FAMILY;
+ }
+ break;
+ }
+ break;
+ case 44:
+ switch (name[43]) {
+ case 'e':
+ if (util::strieq_l("tls-session-cache-memcached-private-key-fil", name,
+ 43)) {
+ return SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_PRIVATE_KEY_FILE;
+ }
+ break;
+ }
+ break;
+ }
+ return -1;
+}
+
+int parse_config(Config *config, const StringRef &opt, const StringRef &optarg,
+ std::set<StringRef> &included_set,
+ std::map<StringRef, size_t> &pattern_addr_indexer) {
+ auto optid = option_lookup_token(opt.c_str(), opt.size());
+ return parse_config(config, optid, opt, optarg, included_set,
+ pattern_addr_indexer);
+}
+
+int parse_config(Config *config, int optid, const StringRef &opt,
+ const StringRef &optarg, std::set<StringRef> &included_set,
+ std::map<StringRef, size_t> &pattern_addr_indexer) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ char host[NI_MAXHOST];
+ uint16_t port;
+
+ switch (optid) {
+ case SHRPX_OPTID_BACKEND: {
+ auto &downstreamconf = *config->conn.downstream;
+ auto addr_end = std::find(std::begin(optarg), std::end(optarg), ';');
+
+ DownstreamAddrConfig addr{};
+ if (util::istarts_with(optarg, SHRPX_UNIX_PATH_PREFIX)) {
+ auto path = std::begin(optarg) + SHRPX_UNIX_PATH_PREFIX.size();
+ addr.host =
+ make_string_ref(downstreamconf.balloc, StringRef{path, addr_end});
+ addr.host_unix = true;
+ } else {
+ if (split_host_port(host, sizeof(host), &port,
+ StringRef{std::begin(optarg), addr_end}, opt) == -1) {
+ return -1;
+ }
+
+ addr.host = make_string_ref(downstreamconf.balloc, StringRef{host});
+ addr.port = port;
+ }
+
+ auto mapping = addr_end == std::end(optarg) ? addr_end : addr_end + 1;
+ auto mapping_end = std::find(mapping, std::end(optarg), ';');
+
+ auto params =
+ mapping_end == std::end(optarg) ? mapping_end : mapping_end + 1;
+
+ if (parse_mapping(config, addr, pattern_addr_indexer,
+ StringRef{mapping, mapping_end},
+ StringRef{params, std::end(optarg)}) != 0) {
+ return -1;
+ }
+
+ return 0;
+ }
+ case SHRPX_OPTID_FRONTEND: {
+ auto &apiconf = config->api;
+
+ auto addr_end = std::find(std::begin(optarg), std::end(optarg), ';');
+ auto src_params = StringRef{addr_end, std::end(optarg)};
+
+ UpstreamParams params{};
+ params.tls = true;
+
+ if (parse_upstream_params(params, src_params) != 0) {
+ return -1;
+ }
+
+ if (params.sni_fwd && !params.tls) {
+ LOG(ERROR) << "frontend: sni_fwd requires tls";
+ return -1;
+ }
+
+ if (params.quic) {
+ if (params.alt_mode != UpstreamAltMode::NONE) {
+ LOG(ERROR) << "frontend: api or healthmon cannot be used with quic";
+ return -1;
+ }
+
+ if (!params.tls) {
+ LOG(ERROR) << "frontend: quic requires TLS";
+ return -1;
+ }
+ }
+
+ UpstreamAddr addr{};
+ addr.fd = -1;
+ addr.tls = params.tls;
+ addr.sni_fwd = params.sni_fwd;
+ addr.alt_mode = params.alt_mode;
+ addr.accept_proxy_protocol = params.proxyproto;
+ addr.quic = params.quic;
+
+ if (addr.alt_mode == UpstreamAltMode::API) {
+ apiconf.enabled = true;
+ }
+
+#ifdef ENABLE_HTTP3
+ auto &addrs = params.quic ? config->conn.quic_listener.addrs
+ : config->conn.listener.addrs;
+#else // !ENABLE_HTTP3
+ auto &addrs = config->conn.listener.addrs;
+#endif // !ENABLE_HTTP3
+
+ if (util::istarts_with(optarg, SHRPX_UNIX_PATH_PREFIX)) {
+ if (addr.quic) {
+ LOG(ERROR) << "frontend: quic cannot be used on UNIX domain socket";
+ return -1;
+ }
+
+ auto path = std::begin(optarg) + SHRPX_UNIX_PATH_PREFIX.size();
+ addr.host = make_string_ref(config->balloc, StringRef{path, addr_end});
+ addr.host_unix = true;
+ addr.index = addrs.size();
+
+ addrs.push_back(std::move(addr));
+
+ return 0;
+ }
+
+ if (split_host_port(host, sizeof(host), &port,
+ StringRef{std::begin(optarg), addr_end}, opt) == -1) {
+ return -1;
+ }
+
+ addr.host = make_string_ref(config->balloc, StringRef{host});
+ addr.port = port;
+
+ if (util::numeric_host(host, AF_INET)) {
+ addr.family = AF_INET;
+ addr.index = addrs.size();
+ addrs.push_back(std::move(addr));
+ return 0;
+ }
+
+ if (util::numeric_host(host, AF_INET6)) {
+ addr.family = AF_INET6;
+ addr.index = addrs.size();
+ addrs.push_back(std::move(addr));
+ return 0;
+ }
+
+ addr.family = AF_INET;
+ addr.index = addrs.size();
+ addrs.push_back(addr);
+
+ addr.family = AF_INET6;
+ addr.index = addrs.size();
+ addrs.push_back(std::move(addr));
+
+ return 0;
+ }
+ case SHRPX_OPTID_WORKERS:
+#ifdef NOTHREADS
+ LOG(WARN) << "Threading disabled at build time, no threads created.";
+ return 0;
+#else // !NOTHREADS
+ return parse_uint(&config->num_worker, opt, optarg);
+#endif // !NOTHREADS
+ case SHRPX_OPTID_HTTP2_MAX_CONCURRENT_STREAMS: {
+ LOG(WARN) << opt << ": deprecated. Use "
+ << SHRPX_OPT_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS << " and "
+ << SHRPX_OPT_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS << " instead.";
+ size_t n;
+ if (parse_uint(&n, opt, optarg) != 0) {
+ return -1;
+ }
+ auto &http2conf = config->http2;
+ http2conf.upstream.max_concurrent_streams = n;
+ http2conf.downstream.max_concurrent_streams = n;
+
+ return 0;
+ }
+ case SHRPX_OPTID_LOG_LEVEL: {
+ auto level = Log::get_severity_level_by_name(optarg);
+ if (level == -1) {
+ LOG(ERROR) << opt << ": Invalid severity level: " << optarg;
+ return -1;
+ }
+ config->logging.severity = level;
+
+ return 0;
+ }
+ case SHRPX_OPTID_DAEMON:
+ config->daemon = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_HTTP2_PROXY:
+ config->http2_proxy = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_HTTP2_BRIDGE:
+ LOG(ERROR) << opt
+ << ": deprecated. Use backend=<addr>,<port>;;proto=h2;tls";
+ return -1;
+ case SHRPX_OPTID_CLIENT_PROXY:
+ LOG(ERROR)
+ << opt
+ << ": deprecated. Use http2-proxy, frontend=<addr>,<port>;no-tls "
+ "and backend=<addr>,<port>;;proto=h2;tls";
+ return -1;
+ case SHRPX_OPTID_ADD_X_FORWARDED_FOR:
+ config->http.xff.add = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_STRIP_INCOMING_X_FORWARDED_FOR:
+ config->http.xff.strip_incoming = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_NO_VIA:
+ config->http.no_via = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_HTTP2_READ_TIMEOUT:
+ return parse_duration(&config->conn.upstream.timeout.http2_read, opt,
+ optarg);
+ case SHRPX_OPTID_FRONTEND_READ_TIMEOUT:
+ return parse_duration(&config->conn.upstream.timeout.read, opt, optarg);
+ case SHRPX_OPTID_FRONTEND_WRITE_TIMEOUT:
+ return parse_duration(&config->conn.upstream.timeout.write, opt, optarg);
+ case SHRPX_OPTID_BACKEND_READ_TIMEOUT:
+ return parse_duration(&config->conn.downstream->timeout.read, opt, optarg);
+ case SHRPX_OPTID_BACKEND_WRITE_TIMEOUT:
+ return parse_duration(&config->conn.downstream->timeout.write, opt, optarg);
+ case SHRPX_OPTID_BACKEND_CONNECT_TIMEOUT:
+ return parse_duration(&config->conn.downstream->timeout.connect, opt,
+ optarg);
+ case SHRPX_OPTID_STREAM_READ_TIMEOUT:
+ return parse_duration(&config->http2.timeout.stream_read, opt, optarg);
+ case SHRPX_OPTID_STREAM_WRITE_TIMEOUT:
+ return parse_duration(&config->http2.timeout.stream_write, opt, optarg);
+ case SHRPX_OPTID_ACCESSLOG_FILE:
+ config->logging.access.file = make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_ACCESSLOG_SYSLOG:
+ config->logging.access.syslog = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_ACCESSLOG_FORMAT:
+ config->logging.access.format = parse_log_format(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_ERRORLOG_FILE:
+ config->logging.error.file = make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_ERRORLOG_SYSLOG:
+ config->logging.error.syslog = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_FASTOPEN:
+ return parse_uint(&config->conn.listener.fastopen, opt, optarg);
+ case SHRPX_OPTID_BACKEND_KEEP_ALIVE_TIMEOUT:
+ return parse_duration(&config->conn.downstream->timeout.idle_read, opt,
+ optarg);
+ case SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_BITS:
+ case SHRPX_OPTID_BACKEND_HTTP2_WINDOW_BITS: {
+ LOG(WARN) << opt << ": deprecated. Use "
+ << (optid == SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_BITS
+ ? SHRPX_OPT_FRONTEND_HTTP2_WINDOW_SIZE
+ : SHRPX_OPT_BACKEND_HTTP2_WINDOW_SIZE);
+ int32_t *resp;
+
+ if (optid == SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_BITS) {
+ resp = &config->http2.upstream.window_size;
+ } else {
+ resp = &config->http2.downstream.window_size;
+ }
+
+ errno = 0;
+
+ int n;
+
+ if (parse_uint(&n, opt, optarg) != 0) {
+ return -1;
+ }
+
+ if (n >= 31) {
+ LOG(ERROR) << opt
+ << ": specify the integer in the range [0, 30], inclusive";
+ return -1;
+ }
+
+ // Make 16 bits to the HTTP/2 default 64KiB - 1. This is the same
+ // behaviour of previous code.
+ *resp = (1 << n) - 1;
+
+ return 0;
+ }
+ case SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS:
+ case SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_BITS: {
+ LOG(WARN) << opt << ": deprecated. Use "
+ << (optid == SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS
+ ? SHRPX_OPT_FRONTEND_HTTP2_CONNECTION_WINDOW_SIZE
+ : SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE);
+ int32_t *resp;
+
+ if (optid == SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS) {
+ resp = &config->http2.upstream.connection_window_size;
+ } else {
+ resp = &config->http2.downstream.connection_window_size;
+ }
+
+ errno = 0;
+
+ int n;
+
+ if (parse_uint(&n, opt, optarg) != 0) {
+ return -1;
+ }
+
+ if (n < 16 || n >= 31) {
+ LOG(ERROR) << opt
+ << ": specify the integer in the range [16, 30], inclusive";
+ return -1;
+ }
+
+ *resp = (1 << n) - 1;
+
+ return 0;
+ }
+ case SHRPX_OPTID_FRONTEND_NO_TLS:
+ LOG(WARN) << opt << ": deprecated. Use no-tls keyword in "
+ << SHRPX_OPT_FRONTEND;
+ return 0;
+ case SHRPX_OPTID_BACKEND_NO_TLS:
+ LOG(WARN) << opt
+ << ": deprecated. backend connection is not encrypted by "
+ "default. See also "
+ << SHRPX_OPT_BACKEND_TLS;
+ return 0;
+ case SHRPX_OPTID_BACKEND_TLS_SNI_FIELD:
+ LOG(WARN) << opt
+ << ": deprecated. Use sni keyword in --backend option. "
+ "For now, all sni values of all backends are "
+ "overridden by the given value "
+ << optarg;
+ config->tls.backend_sni_name = make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_PID_FILE:
+ config->pid_file = make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_USER: {
+ auto pwd = getpwnam(optarg.c_str());
+ if (!pwd) {
+ LOG(ERROR) << opt << ": failed to get uid from " << optarg << ": "
+ << xsi_strerror(errno, errbuf.data(), errbuf.size());
+ return -1;
+ }
+ config->user = make_string_ref(config->balloc, StringRef{pwd->pw_name});
+ config->uid = pwd->pw_uid;
+ config->gid = pwd->pw_gid;
+
+ return 0;
+ }
+ case SHRPX_OPTID_PRIVATE_KEY_FILE:
+ config->tls.private_key_file = make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_PRIVATE_KEY_PASSWD_FILE: {
+ auto passwd = read_passwd_from_file(opt, optarg);
+ if (passwd.empty()) {
+ LOG(ERROR) << opt << ": Couldn't read key file's passwd from " << optarg;
+ return -1;
+ }
+ config->tls.private_key_passwd =
+ make_string_ref(config->balloc, StringRef{passwd});
+
+ return 0;
+ }
+ case SHRPX_OPTID_CERTIFICATE_FILE:
+ config->tls.cert_file = make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_DH_PARAM_FILE:
+ config->tls.dh_param_file = make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_SUBCERT: {
+ auto end_keys = std::find(std::begin(optarg), std::end(optarg), ';');
+ auto src_params = StringRef{end_keys, std::end(optarg)};
+
+ SubcertParams params;
+ if (parse_subcert_params(params, src_params) != 0) {
+ return -1;
+ }
+
+ std::vector<uint8_t> sct_data;
+
+ if (!params.sct_dir.empty()) {
+ // Make sure that dir_path is NULL terminated string.
+ if (read_tls_sct_from_dir(sct_data, opt,
+ StringRef{params.sct_dir.str()}) != 0) {
+ return -1;
+ }
+ }
+
+ // Private Key file and certificate file separated by ':'.
+ auto sp = std::find(std::begin(optarg), end_keys, ':');
+ if (sp == end_keys) {
+ LOG(ERROR) << opt << ": missing ':' in "
+ << StringRef{std::begin(optarg), end_keys};
+ return -1;
+ }
+
+ auto private_key_file = StringRef{std::begin(optarg), sp};
+
+ if (private_key_file.empty()) {
+ LOG(ERROR) << opt << ": missing private key file: "
+ << StringRef{std::begin(optarg), end_keys};
+ return -1;
+ }
+
+ auto cert_file = StringRef{sp + 1, end_keys};
+
+ if (cert_file.empty()) {
+ LOG(ERROR) << opt << ": missing certificate file: "
+ << StringRef{std::begin(optarg), end_keys};
+ return -1;
+ }
+
+ config->tls.subcerts.emplace_back(
+ make_string_ref(config->balloc, private_key_file),
+ make_string_ref(config->balloc, cert_file), std::move(sct_data));
+
+ return 0;
+ }
+ case SHRPX_OPTID_SYSLOG_FACILITY: {
+ int facility = int_syslog_facility(optarg);
+ if (facility == -1) {
+ LOG(ERROR) << opt << ": Unknown syslog facility: " << optarg;
+ return -1;
+ }
+ config->logging.syslog_facility = facility;
+
+ return 0;
+ }
+ case SHRPX_OPTID_BACKLOG:
+ return parse_uint(&config->conn.listener.backlog, opt, optarg);
+ case SHRPX_OPTID_CIPHERS:
+ config->tls.ciphers = make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_TLS13_CIPHERS:
+ config->tls.tls13_ciphers = make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_CLIENT:
+ LOG(ERROR) << opt
+ << ": deprecated. Use frontend=<addr>,<port>;no-tls, "
+ "backend=<addr>,<port>;;proto=h2;tls";
+ return -1;
+ case SHRPX_OPTID_INSECURE:
+ config->tls.insecure = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_CACERT:
+ config->tls.cacert = make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_BACKEND_IPV4:
+ LOG(WARN) << opt
+ << ": deprecated. Use backend-address-family=IPv4 instead.";
+
+ config->conn.downstream->family = AF_INET;
+
+ return 0;
+ case SHRPX_OPTID_BACKEND_IPV6:
+ LOG(WARN) << opt
+ << ": deprecated. Use backend-address-family=IPv6 instead.";
+
+ config->conn.downstream->family = AF_INET6;
+
+ return 0;
+ case SHRPX_OPTID_BACKEND_HTTP_PROXY_URI: {
+ auto &proxy = config->downstream_http_proxy;
+ // Reset here so that multiple option occurrence does not merge
+ // the results.
+ proxy = {};
+ // parse URI and get hostname, port and optionally userinfo.
+ http_parser_url u{};
+ int rv = http_parser_parse_url(optarg.c_str(), optarg.size(), 0, &u);
+ if (rv == 0) {
+ if (u.field_set & UF_USERINFO) {
+ auto uf = util::get_uri_field(optarg.c_str(), u, UF_USERINFO);
+ // Surprisingly, u.field_set & UF_USERINFO is nonzero even if
+ // userinfo component is empty string.
+ if (!uf.empty()) {
+ proxy.userinfo = util::percent_decode(config->balloc, uf);
+ }
+ }
+ if (u.field_set & UF_HOST) {
+ proxy.host = make_string_ref(
+ config->balloc, util::get_uri_field(optarg.c_str(), u, UF_HOST));
+ } else {
+ LOG(ERROR) << opt << ": no hostname specified";
+ return -1;
+ }
+ if (u.field_set & UF_PORT) {
+ proxy.port = u.port;
+ } else {
+ LOG(ERROR) << opt << ": no port specified";
+ return -1;
+ }
+ } else {
+ LOG(ERROR) << opt << ": parse error";
+ return -1;
+ }
+
+ return 0;
+ }
+ case SHRPX_OPTID_READ_RATE:
+ return parse_uint_with_unit(&config->conn.upstream.ratelimit.read.rate, opt,
+ optarg);
+ case SHRPX_OPTID_READ_BURST:
+ return parse_uint_with_unit(&config->conn.upstream.ratelimit.read.burst,
+ opt, optarg);
+ case SHRPX_OPTID_WRITE_RATE:
+ return parse_uint_with_unit(&config->conn.upstream.ratelimit.write.rate,
+ opt, optarg);
+ case SHRPX_OPTID_WRITE_BURST:
+ return parse_uint_with_unit(&config->conn.upstream.ratelimit.write.burst,
+ opt, optarg);
+ case SHRPX_OPTID_WORKER_READ_RATE:
+ LOG(WARN) << opt << ": not implemented yet";
+ return 0;
+ case SHRPX_OPTID_WORKER_READ_BURST:
+ LOG(WARN) << opt << ": not implemented yet";
+ return 0;
+ case SHRPX_OPTID_WORKER_WRITE_RATE:
+ LOG(WARN) << opt << ": not implemented yet";
+ return 0;
+ case SHRPX_OPTID_WORKER_WRITE_BURST:
+ LOG(WARN) << opt << ": not implemented yet";
+ return 0;
+ case SHRPX_OPTID_TLS_PROTO_LIST: {
+ LOG(WARN) << opt
+ << ": deprecated. Use tls-min-proto-version and "
+ "tls-max-proto-version instead.";
+ auto list = util::split_str(optarg, ',');
+ config->tls.tls_proto_list.resize(list.size());
+ for (size_t i = 0; i < list.size(); ++i) {
+ config->tls.tls_proto_list[i] = make_string_ref(config->balloc, list[i]);
+ }
+
+ return 0;
+ }
+ case SHRPX_OPTID_VERIFY_CLIENT:
+ config->tls.client_verify.enabled = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_VERIFY_CLIENT_CACERT:
+ config->tls.client_verify.cacert = make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_CLIENT_PRIVATE_KEY_FILE:
+ config->tls.client.private_key_file =
+ make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_CLIENT_CERT_FILE:
+ config->tls.client.cert_file = make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_HTTP2_DUMP_REQUEST_HEADER:
+ config->http2.upstream.debug.dump.request_header_file =
+ make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER:
+ config->http2.upstream.debug.dump.response_header_file =
+ make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_HTTP2_NO_COOKIE_CRUMBLING:
+ config->http2.no_cookie_crumbling = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_FRAME_DEBUG:
+ config->http2.upstream.debug.frame_debug = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_PADDING:
+ return parse_uint(&config->padding, opt, optarg);
+ case SHRPX_OPTID_ALTSVC: {
+ AltSvc altsvc{};
+
+ if (parse_altsvc(altsvc, opt, optarg) != 0) {
+ return -1;
+ }
+
+ config->http.altsvcs.push_back(std::move(altsvc));
+
+ return 0;
+ }
+ case SHRPX_OPTID_ADD_REQUEST_HEADER:
+ case SHRPX_OPTID_ADD_RESPONSE_HEADER: {
+ auto p = parse_header(config->balloc, optarg);
+ if (p.name.empty()) {
+ LOG(ERROR) << opt << ": invalid header field: " << optarg;
+ return -1;
+ }
+ if (optid == SHRPX_OPTID_ADD_REQUEST_HEADER) {
+ config->http.add_request_headers.push_back(std::move(p));
+ } else {
+ config->http.add_response_headers.push_back(std::move(p));
+ }
+ return 0;
+ }
+ case SHRPX_OPTID_WORKER_FRONTEND_CONNECTIONS:
+ return parse_uint(&config->conn.upstream.worker_connections, opt, optarg);
+ case SHRPX_OPTID_NO_LOCATION_REWRITE:
+ config->http.no_location_rewrite = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_NO_HOST_REWRITE:
+ 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:
+ LOG(WARN) << opt
+ << ": deprecated. Use backend-connections-per-host instead.";
+ // fall through
+ case SHRPX_OPTID_BACKEND_CONNECTIONS_PER_HOST: {
+ int n;
+
+ if (parse_uint(&n, opt, optarg) != 0) {
+ return -1;
+ }
+
+ if (n == 0) {
+ LOG(ERROR) << opt << ": specify an integer strictly more than 0";
+
+ return -1;
+ }
+
+ config->conn.downstream->connections_per_host = n;
+
+ return 0;
+ }
+ case SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND:
+ LOG(WARN) << opt << ": deprecated. Use "
+ << SHRPX_OPT_BACKEND_CONNECTIONS_PER_FRONTEND << " instead.";
+ // fall through
+ case SHRPX_OPTID_BACKEND_CONNECTIONS_PER_FRONTEND:
+ return parse_uint(&config->conn.downstream->connections_per_frontend, opt,
+ optarg);
+ case SHRPX_OPTID_LISTENER_DISABLE_TIMEOUT:
+ return parse_duration(&config->conn.listener.timeout.sleep, opt, optarg);
+ case SHRPX_OPTID_TLS_TICKET_KEY_FILE:
+ config->tls.ticket.files.emplace_back(
+ make_string_ref(config->balloc, optarg));
+ return 0;
+ case SHRPX_OPTID_RLIMIT_NOFILE: {
+ int n;
+
+ if (parse_uint(&n, opt, optarg) != 0) {
+ return -1;
+ }
+
+ if (n < 0) {
+ LOG(ERROR) << opt << ": specify the integer more than or equal to 0";
+
+ return -1;
+ }
+
+ config->rlimit_nofile = n;
+
+ return 0;
+ }
+ case SHRPX_OPTID_BACKEND_REQUEST_BUFFER:
+ case SHRPX_OPTID_BACKEND_RESPONSE_BUFFER: {
+ size_t n;
+ if (parse_uint_with_unit(&n, opt, optarg) != 0) {
+ return -1;
+ }
+
+ if (n == 0) {
+ LOG(ERROR) << opt << ": specify an integer strictly more than 0";
+
+ return -1;
+ }
+
+ if (optid == SHRPX_OPTID_BACKEND_REQUEST_BUFFER) {
+ config->conn.downstream->request_buffer_size = n;
+ } else {
+ config->conn.downstream->response_buffer_size = n;
+ }
+
+ return 0;
+ }
+
+ case SHRPX_OPTID_NO_SERVER_PUSH:
+ config->http2.no_server_push = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_BACKEND_HTTP2_CONNECTIONS_PER_WORKER:
+ LOG(WARN) << opt << ": deprecated.";
+ return 0;
+ case SHRPX_OPTID_FETCH_OCSP_RESPONSE_FILE:
+ config->tls.ocsp.fetch_ocsp_response_file =
+ make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_OCSP_UPDATE_INTERVAL:
+ return parse_duration(&config->tls.ocsp.update_interval, opt, optarg);
+ case SHRPX_OPTID_NO_OCSP:
+ config->tls.ocsp.disabled = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_HEADER_FIELD_BUFFER:
+ LOG(WARN) << opt
+ << ": deprecated. Use request-header-field-buffer instead.";
+ // fall through
+ case SHRPX_OPTID_REQUEST_HEADER_FIELD_BUFFER:
+ return parse_uint_with_unit(&config->http.request_header_field_buffer, opt,
+ optarg);
+ case SHRPX_OPTID_MAX_HEADER_FIELDS:
+ LOG(WARN) << opt << ": deprecated. Use max-request-header-fields instead.";
+ // fall through
+ case SHRPX_OPTID_MAX_REQUEST_HEADER_FIELDS:
+ return parse_uint(&config->http.max_request_header_fields, opt, optarg);
+ case SHRPX_OPTID_RESPONSE_HEADER_FIELD_BUFFER:
+ return parse_uint_with_unit(&config->http.response_header_field_buffer, opt,
+ optarg);
+ case SHRPX_OPTID_MAX_RESPONSE_HEADER_FIELDS:
+ return parse_uint(&config->http.max_response_header_fields, opt, optarg);
+ case SHRPX_OPTID_INCLUDE: {
+ if (included_set.count(optarg)) {
+ LOG(ERROR) << opt << ": " << optarg << " has already been included";
+ return -1;
+ }
+
+ included_set.insert(optarg);
+ auto rv =
+ load_config(config, optarg.c_str(), included_set, pattern_addr_indexer);
+ included_set.erase(optarg);
+
+ if (rv != 0) {
+ return -1;
+ }
+
+ return 0;
+ }
+ case SHRPX_OPTID_TLS_TICKET_KEY_CIPHER:
+ if (util::strieq_l("aes-128-cbc", optarg)) {
+ config->tls.ticket.cipher = EVP_aes_128_cbc();
+ } else if (util::strieq_l("aes-256-cbc", optarg)) {
+ config->tls.ticket.cipher = EVP_aes_256_cbc();
+ } else {
+ LOG(ERROR) << opt
+ << ": unsupported cipher for ticket encryption: " << optarg;
+ return -1;
+ }
+ config->tls.ticket.cipher_given = true;
+
+ return 0;
+ case SHRPX_OPTID_HOST_REWRITE:
+ config->http.no_host_rewrite = !util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED:
+ case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED: {
+ auto addr_end = std::find(std::begin(optarg), std::end(optarg), ';');
+ auto src_params = StringRef{addr_end, std::end(optarg)};
+
+ MemcachedConnectionParams params{};
+ if (parse_memcached_connection_params(params, src_params, StringRef{opt}) !=
+ 0) {
+ return -1;
+ }
+
+ if (split_host_port(host, sizeof(host), &port,
+ StringRef{std::begin(optarg), addr_end}, opt) == -1) {
+ return -1;
+ }
+
+ switch (optid) {
+ case SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED: {
+ auto &memcachedconf = config->tls.session_cache.memcached;
+ memcachedconf.host = make_string_ref(config->balloc, StringRef{host});
+ memcachedconf.port = port;
+ memcachedconf.tls = params.tls;
+ break;
+ }
+ case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED: {
+ auto &memcachedconf = config->tls.ticket.memcached;
+ memcachedconf.host = make_string_ref(config->balloc, StringRef{host});
+ memcachedconf.port = port;
+ memcachedconf.tls = params.tls;
+ break;
+ }
+ };
+
+ return 0;
+ }
+ case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_INTERVAL:
+ return parse_duration(&config->tls.ticket.memcached.interval, opt, optarg);
+ case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_MAX_RETRY: {
+ int n;
+ if (parse_uint(&n, opt, optarg) != 0) {
+ return -1;
+ }
+
+ if (n > 30) {
+ LOG(ERROR) << opt << ": must be smaller than or equal to 30";
+ return -1;
+ }
+
+ config->tls.ticket.memcached.max_retry = n;
+ return 0;
+ }
+ case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL:
+ return parse_uint(&config->tls.ticket.memcached.max_fail, opt, optarg);
+ case SHRPX_OPTID_TLS_DYN_REC_WARMUP_THRESHOLD: {
+ size_t n;
+ if (parse_uint_with_unit(&n, opt, optarg) != 0) {
+ return -1;
+ }
+
+ config->tls.dyn_rec.warmup_threshold = n;
+
+ return 0;
+ }
+
+ case SHRPX_OPTID_TLS_DYN_REC_IDLE_TIMEOUT:
+ return parse_duration(&config->tls.dyn_rec.idle_timeout, opt, optarg);
+
+ case SHRPX_OPTID_MRUBY_FILE:
+#ifdef HAVE_MRUBY
+ config->mruby_file = make_string_ref(config->balloc, optarg);
+#else // !HAVE_MRUBY
+ LOG(WARN) << opt
+ << ": ignored because mruby support is disabled at build time.";
+#endif // !HAVE_MRUBY
+ return 0;
+ case SHRPX_OPTID_ACCEPT_PROXY_PROTOCOL:
+ LOG(WARN) << opt << ": deprecated. Use proxyproto keyword in "
+ << SHRPX_OPT_FRONTEND << " instead.";
+ config->conn.upstream.accept_proxy_protocol = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_ADD_FORWARDED: {
+ auto &fwdconf = config->http.forwarded;
+ fwdconf.params = FORWARDED_NONE;
+ for (const auto &param : util::split_str(optarg, ',')) {
+ if (util::strieq_l("by", param)) {
+ fwdconf.params |= FORWARDED_BY;
+ continue;
+ }
+ if (util::strieq_l("for", param)) {
+ fwdconf.params |= FORWARDED_FOR;
+ continue;
+ }
+ if (util::strieq_l("host", param)) {
+ fwdconf.params |= FORWARDED_HOST;
+ continue;
+ }
+ if (util::strieq_l("proto", param)) {
+ fwdconf.params |= FORWARDED_PROTO;
+ continue;
+ }
+
+ LOG(ERROR) << opt << ": unknown parameter " << optarg;
+
+ return -1;
+ }
+
+ return 0;
+ }
+ case SHRPX_OPTID_STRIP_INCOMING_FORWARDED:
+ config->http.forwarded.strip_incoming = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_FORWARDED_BY:
+ case SHRPX_OPTID_FORWARDED_FOR: {
+ auto type = parse_forwarded_node_type(optarg);
+
+ if (type == static_cast<ForwardedNode>(-1) ||
+ (optid == SHRPX_OPTID_FORWARDED_FOR && optarg[0] == '_')) {
+ LOG(ERROR) << opt << ": unknown node type or illegal obfuscated string "
+ << optarg;
+ return -1;
+ }
+
+ auto &fwdconf = config->http.forwarded;
+
+ switch (optid) {
+ case SHRPX_OPTID_FORWARDED_BY:
+ fwdconf.by_node_type = type;
+ if (optarg[0] == '_') {
+ fwdconf.by_obfuscated = make_string_ref(config->balloc, optarg);
+ } else {
+ fwdconf.by_obfuscated = StringRef::from_lit("");
+ }
+ break;
+ case SHRPX_OPTID_FORWARDED_FOR:
+ fwdconf.for_node_type = type;
+ break;
+ }
+
+ return 0;
+ }
+ case SHRPX_OPTID_NO_HTTP2_CIPHER_BLACK_LIST:
+ LOG(WARN) << opt << ": deprecated. Use "
+ << SHRPX_OPT_NO_HTTP2_CIPHER_BLOCK_LIST << " instead.";
+ // fall through
+ case SHRPX_OPTID_NO_HTTP2_CIPHER_BLOCK_LIST:
+ config->tls.no_http2_cipher_block_list = util::strieq_l("yes", optarg);
+ return 0;
+ case SHRPX_OPTID_BACKEND_HTTP1_TLS:
+ case SHRPX_OPTID_BACKEND_TLS:
+ LOG(WARN) << opt << ": deprecated. Use tls keyword in "
+ << SHRPX_OPT_BACKEND << " instead.";
+ return 0;
+ case SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_TLS:
+ LOG(WARN) << opt << ": deprecated. Use tls keyword in "
+ << SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED;
+ return 0;
+ case SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_CERT_FILE:
+ config->tls.session_cache.memcached.cert_file =
+ make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_PRIVATE_KEY_FILE:
+ config->tls.session_cache.memcached.private_key_file =
+ make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_TLS:
+ LOG(WARN) << opt << ": deprecated. Use tls keyword in "
+ << SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED;
+ return 0;
+ case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_CERT_FILE:
+ config->tls.ticket.memcached.cert_file =
+ make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_PRIVATE_KEY_FILE:
+ config->tls.ticket.memcached.private_key_file =
+ make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_ADDRESS_FAMILY:
+ return parse_address_family(&config->tls.ticket.memcached.family, opt,
+ optarg);
+ case SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_ADDRESS_FAMILY:
+ return parse_address_family(&config->tls.session_cache.memcached.family,
+ opt, optarg);
+ case SHRPX_OPTID_BACKEND_ADDRESS_FAMILY:
+ return parse_address_family(&config->conn.downstream->family, opt, optarg);
+ case SHRPX_OPTID_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS:
+ return parse_uint(&config->http2.upstream.max_concurrent_streams, opt,
+ optarg);
+ case SHRPX_OPTID_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS:
+ return parse_uint(&config->http2.downstream.max_concurrent_streams, opt,
+ optarg);
+ case SHRPX_OPTID_ERROR_PAGE:
+ return parse_error_page(config->http.error_pages, opt, optarg);
+ case SHRPX_OPTID_NO_KQUEUE:
+ if ((ev_supported_backends() & EVBACKEND_KQUEUE) == 0) {
+ LOG(WARN) << opt << ": kqueue is not supported on this platform";
+ return 0;
+ }
+
+ config->ev_loop_flags = ev_recommended_backends() & ~EVBACKEND_KQUEUE;
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_HTTP2_SETTINGS_TIMEOUT:
+ return parse_duration(&config->http2.upstream.timeout.settings, opt,
+ optarg);
+ case SHRPX_OPTID_BACKEND_HTTP2_SETTINGS_TIMEOUT:
+ return parse_duration(&config->http2.downstream.timeout.settings, opt,
+ optarg);
+ case SHRPX_OPTID_API_MAX_REQUEST_BODY:
+ return parse_uint_with_unit(&config->api.max_request_body, opt, optarg);
+ case SHRPX_OPTID_BACKEND_MAX_BACKOFF:
+ return parse_duration(&config->conn.downstream->timeout.max_backoff, opt,
+ optarg);
+ case SHRPX_OPTID_SERVER_NAME:
+ config->http.server_name = make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_NO_SERVER_REWRITE:
+ config->http.no_server_rewrite = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_HTTP2_OPTIMIZE_WRITE_BUFFER_SIZE:
+ config->http2.upstream.optimize_write_buffer_size =
+ util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_HTTP2_OPTIMIZE_WINDOW_SIZE:
+ config->http2.upstream.optimize_window_size = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_SIZE:
+ if (parse_uint_with_unit(&config->http2.upstream.window_size, opt,
+ optarg) != 0) {
+ return -1;
+ }
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_SIZE:
+ if (parse_uint_with_unit(&config->http2.upstream.connection_window_size,
+ opt, optarg) != 0) {
+ return -1;
+ }
+
+ return 0;
+ case SHRPX_OPTID_BACKEND_HTTP2_WINDOW_SIZE:
+ if (parse_uint_with_unit(&config->http2.downstream.window_size, opt,
+ optarg) != 0) {
+ return -1;
+ }
+
+ return 0;
+ case SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE:
+ if (parse_uint_with_unit(&config->http2.downstream.connection_window_size,
+ opt, optarg) != 0) {
+ return -1;
+ }
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE:
+ if (parse_uint_with_unit(&config->http2.upstream.encoder_dynamic_table_size,
+ opt, optarg) != 0) {
+ return -1;
+ }
+
+ nghttp2_option_set_max_deflate_dynamic_table_size(
+ config->http2.upstream.option,
+ config->http2.upstream.encoder_dynamic_table_size);
+ nghttp2_option_set_max_deflate_dynamic_table_size(
+ config->http2.upstream.alt_mode_option,
+ config->http2.upstream.encoder_dynamic_table_size);
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE:
+ return parse_uint_with_unit(
+ &config->http2.upstream.decoder_dynamic_table_size, opt, optarg);
+ case SHRPX_OPTID_BACKEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE:
+ if (parse_uint_with_unit(
+ &config->http2.downstream.encoder_dynamic_table_size, opt,
+ optarg) != 0) {
+ return -1;
+ }
+
+ nghttp2_option_set_max_deflate_dynamic_table_size(
+ config->http2.downstream.option,
+ config->http2.downstream.encoder_dynamic_table_size);
+
+ return 0;
+ case SHRPX_OPTID_BACKEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE:
+ return parse_uint_with_unit(
+ &config->http2.downstream.decoder_dynamic_table_size, opt, optarg);
+ case SHRPX_OPTID_ECDH_CURVES:
+ config->tls.ecdh_curves = make_string_ref(config->balloc, optarg);
+ return 0;
+ case SHRPX_OPTID_TLS_SCT_DIR:
+#if defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_BORINGSSL)
+ return read_tls_sct_from_dir(config->tls.sct_data, opt, optarg);
+#else // !NGHTTP2_GENUINE_OPENSSL && !NGHTTP2_OPENSSL_IS_BORINGSSL
+ LOG(WARN)
+ << opt
+ << ": ignored because underlying TLS library does not support SCT";
+ return 0;
+#endif // !NGHTTP2_GENUINE_OPENSSL && !NGHTTP2_OPENSSL_IS_BORINGSSL
+ case SHRPX_OPTID_DNS_CACHE_TIMEOUT:
+ return parse_duration(&config->dns.timeout.cache, opt, optarg);
+ case SHRPX_OPTID_DNS_LOOKUP_TIMEOUT:
+ return parse_duration(&config->dns.timeout.lookup, opt, optarg);
+ case SHRPX_OPTID_DNS_MAX_TRY: {
+ int n;
+ if (parse_uint(&n, opt, optarg) != 0) {
+ return -1;
+ }
+
+ if (n > 5) {
+ LOG(ERROR) << opt << ": must be smaller than or equal to 5";
+ return -1;
+ }
+
+ config->dns.max_try = n;
+ return 0;
+ }
+ case SHRPX_OPTID_FRONTEND_KEEP_ALIVE_TIMEOUT:
+ return parse_duration(&config->conn.upstream.timeout.idle_read, opt,
+ optarg);
+ case SHRPX_OPTID_PSK_SECRETS:
+#ifndef OPENSSL_NO_PSK
+ return parse_psk_secrets(config, optarg);
+#else // OPENSSL_NO_PSK
+ LOG(WARN)
+ << opt
+ << ": ignored because underlying TLS library does not support PSK";
+ return 0;
+#endif // OPENSSL_NO_PSK
+ case SHRPX_OPTID_CLIENT_PSK_SECRETS:
+#ifndef OPENSSL_NO_PSK
+ return parse_client_psk_secrets(config, optarg);
+#else // OPENSSL_NO_PSK
+ LOG(WARN)
+ << opt
+ << ": ignored because underlying TLS library does not support PSK";
+ return 0;
+#endif // OPENSSL_NO_PSK
+ case SHRPX_OPTID_CLIENT_NO_HTTP2_CIPHER_BLACK_LIST:
+ LOG(WARN) << opt << ": deprecated. Use "
+ << SHRPX_OPT_CLIENT_NO_HTTP2_CIPHER_BLOCK_LIST << " instead.";
+ // fall through
+ case SHRPX_OPTID_CLIENT_NO_HTTP2_CIPHER_BLOCK_LIST:
+ config->tls.client.no_http2_cipher_block_list =
+ util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_CLIENT_CIPHERS:
+ config->tls.client.ciphers = make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_TLS13_CLIENT_CIPHERS:
+ config->tls.client.tls13_ciphers = make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_ACCESSLOG_WRITE_EARLY:
+ config->logging.access.write_early = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_TLS_MIN_PROTO_VERSION:
+ return parse_tls_proto_version(config->tls.min_proto_version, opt, optarg);
+ case SHRPX_OPTID_TLS_MAX_PROTO_VERSION:
+ return parse_tls_proto_version(config->tls.max_proto_version, opt, optarg);
+ case SHRPX_OPTID_REDIRECT_HTTPS_PORT: {
+ auto n = util::parse_uint(optarg);
+ if (n == -1 || n < 0 || n > 65535) {
+ LOG(ERROR) << opt
+ << ": bad value. Specify an integer in the range [0, "
+ "65535], inclusive";
+ return -1;
+ }
+ config->http.redirect_https_port = make_string_ref(config->balloc, optarg);
+ return 0;
+ }
+ case SHRPX_OPTID_FRONTEND_MAX_REQUESTS:
+ return parse_uint(&config->http.max_requests, opt, optarg);
+ case SHRPX_OPTID_SINGLE_THREAD:
+ config->single_thread = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_SINGLE_PROCESS:
+ config->single_process = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_NO_ADD_X_FORWARDED_PROTO:
+ config->http.xfp.add = !util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_NO_STRIP_INCOMING_X_FORWARDED_PROTO:
+ config->http.xfp.strip_incoming = !util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_OCSP_STARTUP:
+ config->tls.ocsp.startup = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_NO_VERIFY_OCSP:
+ config->tls.ocsp.no_verify = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_VERIFY_CLIENT_TOLERATE_EXPIRED:
+ config->tls.client_verify.tolerate_expired = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_IGNORE_PER_PATTERN_MRUBY_ERROR:
+ config->ignore_per_pattern_mruby_error = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_TLS_NO_POSTPONE_EARLY_DATA:
+ config->tls.no_postpone_early_data = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_TLS_MAX_EARLY_DATA: {
+ return parse_uint_with_unit(&config->tls.max_early_data, opt, optarg);
+ }
+ case SHRPX_OPTID_NO_STRIP_INCOMING_EARLY_DATA:
+ config->http.early_data.strip_incoming = !util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_QUIC_BPF_PROGRAM_FILE:
+#ifdef ENABLE_HTTP3
+ config->quic.bpf.prog_file = make_string_ref(config->balloc, optarg);
+#endif // ENABLE_HTTP3
+
+ return 0;
+ case SHRPX_OPTID_NO_QUIC_BPF:
+#ifdef ENABLE_HTTP3
+ config->quic.bpf.disabled = util::strieq_l("yes", optarg);
+#endif // ENABLE_HTTP3
+
+ return 0;
+ case SHRPX_OPTID_HTTP2_ALTSVC: {
+ AltSvc altsvc{};
+
+ if (parse_altsvc(altsvc, opt, optarg) != 0) {
+ return -1;
+ }
+
+ config->http.http2_altsvcs.push_back(std::move(altsvc));
+
+ return 0;
+ }
+ case SHRPX_OPTID_FRONTEND_HTTP3_READ_TIMEOUT:
+#ifdef ENABLE_HTTP3
+ return parse_duration(&config->conn.upstream.timeout.http3_read, opt,
+ optarg);
+#else // !ENABLE_HTTP3
+ return 0;
+#endif // !ENABLE_HTTP3
+ case SHRPX_OPTID_FRONTEND_QUIC_IDLE_TIMEOUT:
+#ifdef ENABLE_HTTP3
+ return parse_duration(&config->quic.upstream.timeout.idle, opt, optarg);
+#else // !ENABLE_HTTP3
+ return 0;
+#endif // !ENABLE_HTTP3
+ case SHRPX_OPTID_FRONTEND_QUIC_DEBUG_LOG:
+#ifdef ENABLE_HTTP3
+ config->quic.upstream.debug.log = util::strieq_l("yes", optarg);
+#endif // ENABLE_HTTP3
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_HTTP3_WINDOW_SIZE:
+#ifdef ENABLE_HTTP3
+ if (parse_uint_with_unit(&config->http3.upstream.window_size, opt,
+ optarg) != 0) {
+ return -1;
+ }
+#endif // ENABLE_HTTP3
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_HTTP3_CONNECTION_WINDOW_SIZE:
+#ifdef ENABLE_HTTP3
+ if (parse_uint_with_unit(&config->http3.upstream.connection_window_size,
+ opt, optarg) != 0) {
+ return -1;
+ }
+#endif // ENABLE_HTTP3
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_HTTP3_MAX_WINDOW_SIZE:
+#ifdef ENABLE_HTTP3
+ if (parse_uint_with_unit(&config->http3.upstream.max_window_size, opt,
+ optarg) != 0) {
+ return -1;
+ }
+#endif // ENABLE_HTTP3
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_HTTP3_MAX_CONNECTION_WINDOW_SIZE:
+#ifdef ENABLE_HTTP3
+ if (parse_uint_with_unit(&config->http3.upstream.max_connection_window_size,
+ opt, optarg) != 0) {
+ return -1;
+ }
+#endif // ENABLE_HTTP3
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_HTTP3_MAX_CONCURRENT_STREAMS:
+#ifdef ENABLE_HTTP3
+ return parse_uint(&config->http3.upstream.max_concurrent_streams, opt,
+ optarg);
+#else // !ENABLE_HTTP3
+ return 0;
+#endif // !ENABLE_HTTP3
+ case SHRPX_OPTID_FRONTEND_QUIC_EARLY_DATA:
+#ifdef ENABLE_HTTP3
+ config->quic.upstream.early_data = util::strieq_l("yes", optarg);
+#endif // ENABLE_HTTP3
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_QUIC_QLOG_DIR:
+#ifdef ENABLE_HTTP3
+ config->quic.upstream.qlog.dir = make_string_ref(config->balloc, optarg);
+#endif // ENABLE_HTTP3
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_QUIC_REQUIRE_TOKEN:
+#ifdef ENABLE_HTTP3
+ config->quic.upstream.require_token = util::strieq_l("yes", optarg);
+#endif // ENABLE_HTTP3
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_QUIC_CONGESTION_CONTROLLER:
+#ifdef ENABLE_HTTP3
+ if (util::strieq_l("cubic", optarg)) {
+ config->quic.upstream.congestion_controller = NGTCP2_CC_ALGO_CUBIC;
+ } else if (util::strieq_l("bbr", optarg)) {
+ config->quic.upstream.congestion_controller = NGTCP2_CC_ALGO_BBR;
+ } else {
+ LOG(ERROR) << opt << ": must be either cubic or bbr";
+ return -1;
+ }
+#endif // ENABLE_HTTP3
+
+ return 0;
+ case SHRPX_OPTID_QUIC_SERVER_ID:
+#ifdef ENABLE_HTTP3
+ if (optarg.size() != config->quic.server_id.size() * 2 ||
+ !util::is_hex_string(optarg)) {
+ LOG(ERROR) << opt << ": must be a hex-string";
+ return -1;
+ }
+ util::decode_hex(std::begin(config->quic.server_id), optarg);
+#endif // ENABLE_HTTP3
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_QUIC_SECRET_FILE:
+#ifdef ENABLE_HTTP3
+ config->quic.upstream.secret_file = make_string_ref(config->balloc, optarg);
+#endif // ENABLE_HTTP3
+
+ return 0;
+ case SHRPX_OPTID_RLIMIT_MEMLOCK: {
+ int n;
+
+ if (parse_uint(&n, opt, optarg) != 0) {
+ return -1;
+ }
+
+ if (n < 0) {
+ LOG(ERROR) << opt << ": specify the integer more than or equal to 0";
+
+ return -1;
+ }
+
+ config->rlimit_memlock = n;
+
+ return 0;
+ }
+ case SHRPX_OPTID_MAX_WORKER_PROCESSES:
+ return parse_uint(&config->max_worker_processes, opt, optarg);
+ case SHRPX_OPTID_WORKER_PROCESS_GRACE_SHUTDOWN_PERIOD:
+ return parse_duration(&config->worker_process_grace_shutdown_period, opt,
+ optarg);
+ case SHRPX_OPTID_FRONTEND_QUIC_INITIAL_RTT: {
+#ifdef ENABLE_HTTP3
+ return parse_duration(&config->quic.upstream.initial_rtt, opt, optarg);
+#endif // ENABLE_HTTP3
+
+ return 0;
+ }
+ case SHRPX_OPTID_REQUIRE_HTTP_SCHEME:
+ config->http.require_http_scheme = util::strieq_l("yes", optarg);
+ return 0;
+ case SHRPX_OPTID_TLS_KTLS:
+ config->tls.ktls = util::strieq_l("yes", optarg);
+ return 0;
+ case SHRPX_OPTID_NPN_LIST:
+ LOG(WARN) << opt << ": deprecated. Use alpn-list instead.";
+ // fall through
+ case SHRPX_OPTID_ALPN_LIST: {
+ auto list = util::split_str(optarg, ',');
+ config->tls.alpn_list.resize(list.size());
+ for (size_t i = 0; i < list.size(); ++i) {
+ config->tls.alpn_list[i] = make_string_ref(config->balloc, list[i]);
+ }
+
+ return 0;
+ }
+ case SHRPX_OPTID_CONF:
+ LOG(WARN) << "conf: ignored";
+
+ return 0;
+ }
+
+ LOG(ERROR) << "Unknown option: " << opt;
+
+ return -1;
+}
+
+int load_config(Config *config, const char *filename,
+ std::set<StringRef> &include_set,
+ std::map<StringRef, size_t> &pattern_addr_indexer) {
+ std::ifstream in(filename, std::ios::binary);
+ if (!in) {
+ LOG(ERROR) << "Could not open config file " << filename;
+ return -1;
+ }
+ std::string line;
+ int linenum = 0;
+ while (std::getline(in, line)) {
+ ++linenum;
+ if (line.empty() || line[0] == '#') {
+ continue;
+ }
+ auto eq = std::find(std::begin(line), std::end(line), '=');
+ if (eq == std::end(line)) {
+ LOG(ERROR) << "Bad configuration format in " << filename << " at line "
+ << linenum;
+ return -1;
+ }
+ *eq = '\0';
+
+ if (parse_config(config, StringRef{std::begin(line), eq},
+ StringRef{eq + 1, std::end(line)}, include_set,
+ pattern_addr_indexer) != 0) {
+ return -1;
+ }
+ }
+ return 0;
+}
+
+StringRef str_syslog_facility(int facility) {
+ switch (facility) {
+ case (LOG_AUTH):
+ return StringRef::from_lit("auth");
+#ifdef LOG_AUTHPRIV
+ case (LOG_AUTHPRIV):
+ return StringRef::from_lit("authpriv");
+#endif // LOG_AUTHPRIV
+ case (LOG_CRON):
+ return StringRef::from_lit("cron");
+ case (LOG_DAEMON):
+ return StringRef::from_lit("daemon");
+#ifdef LOG_FTP
+ case (LOG_FTP):
+ return StringRef::from_lit("ftp");
+#endif // LOG_FTP
+ case (LOG_KERN):
+ return StringRef::from_lit("kern");
+ case (LOG_LOCAL0):
+ return StringRef::from_lit("local0");
+ case (LOG_LOCAL1):
+ return StringRef::from_lit("local1");
+ case (LOG_LOCAL2):
+ return StringRef::from_lit("local2");
+ case (LOG_LOCAL3):
+ return StringRef::from_lit("local3");
+ case (LOG_LOCAL4):
+ return StringRef::from_lit("local4");
+ case (LOG_LOCAL5):
+ return StringRef::from_lit("local5");
+ case (LOG_LOCAL6):
+ return StringRef::from_lit("local6");
+ case (LOG_LOCAL7):
+ return StringRef::from_lit("local7");
+ case (LOG_LPR):
+ return StringRef::from_lit("lpr");
+ case (LOG_MAIL):
+ return StringRef::from_lit("mail");
+ case (LOG_SYSLOG):
+ return StringRef::from_lit("syslog");
+ case (LOG_USER):
+ return StringRef::from_lit("user");
+ case (LOG_UUCP):
+ return StringRef::from_lit("uucp");
+ default:
+ return StringRef::from_lit("(unknown)");
+ }
+}
+
+int int_syslog_facility(const StringRef &strfacility) {
+ if (util::strieq_l("auth", strfacility)) {
+ return LOG_AUTH;
+ }
+
+#ifdef LOG_AUTHPRIV
+ if (util::strieq_l("authpriv", strfacility)) {
+ return LOG_AUTHPRIV;
+ }
+#endif // LOG_AUTHPRIV
+
+ if (util::strieq_l("cron", strfacility)) {
+ return LOG_CRON;
+ }
+
+ if (util::strieq_l("daemon", strfacility)) {
+ return LOG_DAEMON;
+ }
+
+#ifdef LOG_FTP
+ if (util::strieq_l("ftp", strfacility)) {
+ return LOG_FTP;
+ }
+#endif // LOG_FTP
+
+ if (util::strieq_l("kern", strfacility)) {
+ return LOG_KERN;
+ }
+
+ if (util::strieq_l("local0", strfacility)) {
+ return LOG_LOCAL0;
+ }
+
+ if (util::strieq_l("local1", strfacility)) {
+ return LOG_LOCAL1;
+ }
+
+ if (util::strieq_l("local2", strfacility)) {
+ return LOG_LOCAL2;
+ }
+
+ if (util::strieq_l("local3", strfacility)) {
+ return LOG_LOCAL3;
+ }
+
+ if (util::strieq_l("local4", strfacility)) {
+ return LOG_LOCAL4;
+ }
+
+ if (util::strieq_l("local5", strfacility)) {
+ return LOG_LOCAL5;
+ }
+
+ if (util::strieq_l("local6", strfacility)) {
+ return LOG_LOCAL6;
+ }
+
+ if (util::strieq_l("local7", strfacility)) {
+ return LOG_LOCAL7;
+ }
+
+ if (util::strieq_l("lpr", strfacility)) {
+ return LOG_LPR;
+ }
+
+ if (util::strieq_l("mail", strfacility)) {
+ return LOG_MAIL;
+ }
+
+ if (util::strieq_l("news", strfacility)) {
+ return LOG_NEWS;
+ }
+
+ if (util::strieq_l("syslog", strfacility)) {
+ return LOG_SYSLOG;
+ }
+
+ if (util::strieq_l("user", strfacility)) {
+ return LOG_USER;
+ }
+
+ if (util::strieq_l("uucp", strfacility)) {
+ return LOG_UUCP;
+ }
+
+ return -1;
+}
+
+StringRef strproto(Proto proto) {
+ switch (proto) {
+ case Proto::NONE:
+ return StringRef::from_lit("none");
+ case Proto::HTTP1:
+ return StringRef::from_lit("http/1.1");
+ case Proto::HTTP2:
+ return StringRef::from_lit("h2");
+ case Proto::HTTP3:
+ return StringRef::from_lit("h3");
+ case Proto::MEMCACHED:
+ return StringRef::from_lit("memcached");
+ }
+
+ // gcc needs this.
+ assert(0);
+ abort();
+}
+
+namespace {
+// Consistent hashing method described in
+// https://github.com/RJ/ketama. Generate 160 32-bit hashes per |s|,
+// which is usually backend address. The each hash is associated to
+// index of backend address. When all hashes for every backend
+// address are calculated, sort it in ascending order of hash. To
+// choose the index, compute 32-bit hash based on client IP address,
+// and do lower bound search in the array. The returned index is the
+// backend to use.
+int compute_affinity_hash(std::vector<AffinityHash> &res, size_t idx,
+ const StringRef &s) {
+ int rv;
+ std::array<uint8_t, 32> buf;
+
+ for (auto i = 0; i < 20; ++i) {
+ auto t = s.str();
+ t += i;
+
+ rv = util::sha256(buf.data(), StringRef{t});
+ if (rv != 0) {
+ return -1;
+ }
+
+ for (int i = 0; i < 8; ++i) {
+ auto h = (static_cast<uint32_t>(buf[4 * i]) << 24) |
+ (static_cast<uint32_t>(buf[4 * i + 1]) << 16) |
+ (static_cast<uint32_t>(buf[4 * i + 2]) << 8) |
+ static_cast<uint32_t>(buf[4 * i + 3]);
+
+ res.emplace_back(idx, h);
+ }
+ }
+
+ return 0;
+}
+} // namespace
+
+// Configures the following member in |config|:
+// conn.downstream_router, conn.downstream.addr_groups,
+// conn.downstream.addr_group_catch_all.
+int configure_downstream_group(Config *config, bool http2_proxy,
+ bool numeric_addr_only,
+ const TLSConfig &tlsconf) {
+ int rv;
+
+ auto &downstreamconf = *config->conn.downstream;
+ auto &addr_groups = downstreamconf.addr_groups;
+ auto &routerconf = downstreamconf.router;
+ auto &router = routerconf.router;
+
+ if (addr_groups.empty()) {
+ DownstreamAddrConfig addr{};
+ addr.host = StringRef::from_lit(DEFAULT_DOWNSTREAM_HOST);
+ addr.port = DEFAULT_DOWNSTREAM_PORT;
+ addr.proto = Proto::HTTP1;
+ addr.weight = 1;
+ addr.group_weight = 1;
+
+ DownstreamAddrGroupConfig g(StringRef::from_lit("/"));
+ g.addrs.push_back(std::move(addr));
+ router.add_route(g.pattern, addr_groups.size());
+ addr_groups.push_back(std::move(g));
+ }
+
+ // backward compatibility: override all SNI fields with the option
+ // value --backend-tls-sni-field
+ if (!tlsconf.backend_sni_name.empty()) {
+ auto &sni = tlsconf.backend_sni_name;
+ for (auto &addr_group : addr_groups) {
+ for (auto &addr : addr_group.addrs) {
+ addr.sni = sni;
+ }
+ }
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Resolving backend address";
+ }
+
+ ssize_t catch_all_group = -1;
+ for (size_t i = 0; i < addr_groups.size(); ++i) {
+ auto &g = addr_groups[i];
+ if (g.pattern == StringRef::from_lit("/")) {
+ catch_all_group = i;
+ }
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Host-path pattern: group " << i << ": '" << g.pattern
+ << "'";
+ for (auto &addr : g.addrs) {
+ LOG(INFO) << "group " << i << " -> " << addr.host.c_str()
+ << (addr.host_unix ? "" : ":" + util::utos(addr.port))
+ << ", proto=" << strproto(addr.proto)
+ << (addr.tls ? ", tls" : "");
+ }
+ }
+#ifdef HAVE_MRUBY
+ // Try compile mruby script and catch compile error early.
+ if (!g.mruby_file.empty()) {
+ if (mruby::create_mruby_context(g.mruby_file) == nullptr) {
+ LOG(config->ignore_per_pattern_mruby_error ? ERROR : FATAL)
+ << "backend: Could not compile mruby file for pattern "
+ << g.pattern;
+ if (!config->ignore_per_pattern_mruby_error) {
+ return -1;
+ }
+ g.mruby_file = StringRef{};
+ }
+ }
+#endif // HAVE_MRUBY
+ }
+
+#ifdef HAVE_MRUBY
+ // Try compile mruby script (--mruby-file) here to catch compile
+ // error early.
+ if (!config->mruby_file.empty()) {
+ if (mruby::create_mruby_context(config->mruby_file) == nullptr) {
+ LOG(FATAL) << "mruby-file: Could not compile mruby file";
+ return -1;
+ }
+ }
+#endif // HAVE_MRUBY
+
+ if (catch_all_group == -1) {
+ LOG(FATAL) << "backend: No catch-all backend address is configured";
+ return -1;
+ }
+
+ downstreamconf.addr_group_catch_all = catch_all_group;
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Catch-all pattern is group " << catch_all_group;
+ }
+
+ auto resolve_flags = numeric_addr_only ? AI_NUMERICHOST | AI_NUMERICSERV : 0;
+
+ std::array<char, util::max_hostport> hostport_buf;
+
+ for (auto &g : addr_groups) {
+ std::unordered_map<StringRef, uint32_t> wgchk;
+ for (auto &addr : g.addrs) {
+ if (addr.group_weight) {
+ auto it = wgchk.find(addr.group);
+ if (it == std::end(wgchk)) {
+ wgchk.emplace(addr.group, addr.group_weight);
+ } else if ((*it).second != addr.group_weight) {
+ LOG(FATAL) << "backend: inconsistent group-weight for a single group";
+ return -1;
+ }
+ }
+
+ if (addr.host_unix) {
+ // for AF_UNIX socket, we use "localhost" as host for backend
+ // hostport. This is used as Host header field to backend and
+ // not going to be passed to any syscalls.
+ addr.hostport = StringRef::from_lit("localhost");
+
+ auto path = addr.host.c_str();
+ auto pathlen = addr.host.size();
+
+ if (pathlen + 1 > sizeof(addr.addr.su.un.sun_path)) {
+ LOG(FATAL) << "UNIX domain socket path " << path << " is too long > "
+ << sizeof(addr.addr.su.un.sun_path);
+ return -1;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Use UNIX domain socket path " << path
+ << " for backend connection";
+ }
+
+ addr.addr.su.un.sun_family = AF_UNIX;
+ // copy path including terminal NULL
+ std::copy_n(path, pathlen + 1, addr.addr.su.un.sun_path);
+ addr.addr.len = sizeof(addr.addr.su.un);
+
+ continue;
+ }
+
+ addr.hostport =
+ util::make_http_hostport(downstreamconf.balloc, addr.host, addr.port);
+
+ auto hostport =
+ util::make_hostport(std::begin(hostport_buf), addr.host, addr.port);
+
+ if (!addr.dns) {
+ if (resolve_hostname(&addr.addr, addr.host.c_str(), addr.port,
+ downstreamconf.family, resolve_flags) == -1) {
+ LOG(FATAL) << "Resolving backend address failed: " << hostport;
+ return -1;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Resolved backend address: " << hostport << " -> "
+ << util::to_numeric_addr(&addr.addr);
+ }
+ } else {
+ LOG(INFO) << "Resolving backend address " << hostport
+ << " takes place dynamically";
+ }
+ }
+
+ for (auto &addr : g.addrs) {
+ if (addr.group_weight == 0) {
+ auto it = wgchk.find(addr.group);
+ if (it == std::end(wgchk)) {
+ addr.group_weight = 1;
+ } else {
+ addr.group_weight = (*it).second;
+ }
+ }
+ }
+
+ if (g.affinity.type != SessionAffinity::NONE) {
+ size_t idx = 0;
+ for (auto &addr : g.addrs) {
+ StringRef key;
+ if (addr.dns) {
+ if (addr.host_unix) {
+ key = addr.host;
+ } else {
+ key = addr.hostport;
+ }
+ } else {
+ auto p = reinterpret_cast<uint8_t *>(&addr.addr.su);
+ key = StringRef{p, addr.addr.len};
+ }
+ rv = compute_affinity_hash(g.affinity_hash, idx, key);
+ if (rv != 0) {
+ return -1;
+ }
+
+ if (g.affinity.cookie.stickiness ==
+ SessionAffinityCookieStickiness::STRICT) {
+ addr.affinity_hash = util::hash32(key);
+ g.affinity_hash_map.emplace(addr.affinity_hash, idx);
+ }
+
+ ++idx;
+ }
+
+ std::sort(std::begin(g.affinity_hash), std::end(g.affinity_hash),
+ [](const AffinityHash &lhs, const AffinityHash &rhs) {
+ return lhs.hash < rhs.hash;
+ });
+ }
+
+ auto &timeout = g.timeout;
+ if (timeout.read < 1e-9) {
+ timeout.read = downstreamconf.timeout.read;
+ }
+ if (timeout.write < 1e-9) {
+ timeout.write = downstreamconf.timeout.write;
+ }
+ }
+
+ return 0;
+}
+
+int resolve_hostname(Address *addr, const char *hostname, uint16_t port,
+ int family, int additional_flags) {
+ int rv;
+
+ auto service = util::utos(port);
+
+ addrinfo hints{};
+ hints.ai_family = family;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags |= additional_flags;
+#ifdef AI_ADDRCONFIG
+ hints.ai_flags |= AI_ADDRCONFIG;
+#endif // AI_ADDRCONFIG
+ addrinfo *res;
+
+ rv = getaddrinfo(hostname, service.c_str(), &hints, &res);
+#ifdef AI_ADDRCONFIG
+ if (rv != 0) {
+ // Retry without AI_ADDRCONFIG
+ hints.ai_flags &= ~AI_ADDRCONFIG;
+ rv = getaddrinfo(hostname, service.c_str(), &hints, &res);
+ }
+#endif // AI_ADDRCONFIG
+ if (rv != 0) {
+ LOG(FATAL) << "Unable to resolve address for " << hostname << ": "
+ << gai_strerror(rv);
+ return -1;
+ }
+
+ auto res_d = defer(freeaddrinfo, res);
+
+ char host[NI_MAXHOST];
+ rv = getnameinfo(res->ai_addr, res->ai_addrlen, host, sizeof(host), nullptr,
+ 0, NI_NUMERICHOST);
+ if (rv != 0) {
+ LOG(FATAL) << "Address resolution for " << hostname
+ << " failed: " << gai_strerror(rv);
+
+ return -1;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Address resolution for " << hostname
+ << " succeeded: " << host;
+ }
+
+ memcpy(&addr->su, res->ai_addr, res->ai_addrlen);
+ addr->len = res->ai_addrlen;
+
+ return 0;
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_config.h b/src/shrpx_config.h
new file mode 100644
index 0000000..7f316eb
--- /dev/null
+++ b/src/shrpx_config.h
@@ -0,0 +1,1450 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_CONFIG_H
+#define SHRPX_CONFIG_H
+
+#include "shrpx.h"
+
+#include <sys/types.h>
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif // HAVE_SYS_SOCKET_H
+#include <sys/un.h>
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif // HAVE_NETINET_IN_H
+#ifdef HAVE_ARPA_INET_H
+# include <arpa/inet.h>
+#endif // HAVE_ARPA_INET_H
+#include <cinttypes>
+#include <cstdio>
+#include <vector>
+#include <memory>
+#include <set>
+#include <unordered_map>
+
+#include <openssl/ssl.h>
+
+#include <ev.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "shrpx_router.h"
+#if ENABLE_HTTP3
+# include "shrpx_quic.h"
+#endif // ENABLE_HTTP3
+#include "template.h"
+#include "http2.h"
+#include "network.h"
+#include "allocator.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+struct LogFragment;
+class ConnectBlocker;
+class Http2Session;
+
+namespace tls {
+
+class CertLookupTree;
+
+} // namespace tls
+
+constexpr auto SHRPX_OPT_PRIVATE_KEY_FILE =
+ StringRef::from_lit("private-key-file");
+constexpr auto SHRPX_OPT_PRIVATE_KEY_PASSWD_FILE =
+ StringRef::from_lit("private-key-passwd-file");
+constexpr auto SHRPX_OPT_CERTIFICATE_FILE =
+ StringRef::from_lit("certificate-file");
+constexpr auto SHRPX_OPT_DH_PARAM_FILE = StringRef::from_lit("dh-param-file");
+constexpr auto SHRPX_OPT_SUBCERT = StringRef::from_lit("subcert");
+constexpr auto SHRPX_OPT_BACKEND = StringRef::from_lit("backend");
+constexpr auto SHRPX_OPT_FRONTEND = StringRef::from_lit("frontend");
+constexpr auto SHRPX_OPT_WORKERS = StringRef::from_lit("workers");
+constexpr auto SHRPX_OPT_HTTP2_MAX_CONCURRENT_STREAMS =
+ StringRef::from_lit("http2-max-concurrent-streams");
+constexpr auto SHRPX_OPT_LOG_LEVEL = StringRef::from_lit("log-level");
+constexpr auto SHRPX_OPT_DAEMON = StringRef::from_lit("daemon");
+constexpr auto SHRPX_OPT_HTTP2_PROXY = StringRef::from_lit("http2-proxy");
+constexpr auto SHRPX_OPT_HTTP2_BRIDGE = StringRef::from_lit("http2-bridge");
+constexpr auto SHRPX_OPT_CLIENT_PROXY = StringRef::from_lit("client-proxy");
+constexpr auto SHRPX_OPT_ADD_X_FORWARDED_FOR =
+ StringRef::from_lit("add-x-forwarded-for");
+constexpr auto SHRPX_OPT_STRIP_INCOMING_X_FORWARDED_FOR =
+ StringRef::from_lit("strip-incoming-x-forwarded-for");
+constexpr auto SHRPX_OPT_NO_VIA = StringRef::from_lit("no-via");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP2_READ_TIMEOUT =
+ StringRef::from_lit("frontend-http2-read-timeout");
+constexpr auto SHRPX_OPT_FRONTEND_READ_TIMEOUT =
+ StringRef::from_lit("frontend-read-timeout");
+constexpr auto SHRPX_OPT_FRONTEND_WRITE_TIMEOUT =
+ StringRef::from_lit("frontend-write-timeout");
+constexpr auto SHRPX_OPT_BACKEND_READ_TIMEOUT =
+ StringRef::from_lit("backend-read-timeout");
+constexpr auto SHRPX_OPT_BACKEND_WRITE_TIMEOUT =
+ StringRef::from_lit("backend-write-timeout");
+constexpr auto SHRPX_OPT_STREAM_READ_TIMEOUT =
+ StringRef::from_lit("stream-read-timeout");
+constexpr auto SHRPX_OPT_STREAM_WRITE_TIMEOUT =
+ StringRef::from_lit("stream-write-timeout");
+constexpr auto SHRPX_OPT_ACCESSLOG_FILE = StringRef::from_lit("accesslog-file");
+constexpr auto SHRPX_OPT_ACCESSLOG_SYSLOG =
+ StringRef::from_lit("accesslog-syslog");
+constexpr auto SHRPX_OPT_ACCESSLOG_FORMAT =
+ StringRef::from_lit("accesslog-format");
+constexpr auto SHRPX_OPT_ERRORLOG_FILE = StringRef::from_lit("errorlog-file");
+constexpr auto SHRPX_OPT_ERRORLOG_SYSLOG =
+ StringRef::from_lit("errorlog-syslog");
+constexpr auto SHRPX_OPT_BACKEND_KEEP_ALIVE_TIMEOUT =
+ StringRef::from_lit("backend-keep-alive-timeout");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP2_WINDOW_BITS =
+ StringRef::from_lit("frontend-http2-window-bits");
+constexpr auto SHRPX_OPT_BACKEND_HTTP2_WINDOW_BITS =
+ StringRef::from_lit("backend-http2-window-bits");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS =
+ StringRef::from_lit("frontend-http2-connection-window-bits");
+constexpr auto SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_BITS =
+ StringRef::from_lit("backend-http2-connection-window-bits");
+constexpr auto SHRPX_OPT_FRONTEND_NO_TLS =
+ StringRef::from_lit("frontend-no-tls");
+constexpr auto SHRPX_OPT_BACKEND_NO_TLS = StringRef::from_lit("backend-no-tls");
+constexpr auto SHRPX_OPT_BACKEND_TLS_SNI_FIELD =
+ StringRef::from_lit("backend-tls-sni-field");
+constexpr auto SHRPX_OPT_PID_FILE = StringRef::from_lit("pid-file");
+constexpr auto SHRPX_OPT_USER = StringRef::from_lit("user");
+constexpr auto SHRPX_OPT_SYSLOG_FACILITY =
+ StringRef::from_lit("syslog-facility");
+constexpr auto SHRPX_OPT_BACKLOG = StringRef::from_lit("backlog");
+constexpr auto SHRPX_OPT_CIPHERS = StringRef::from_lit("ciphers");
+constexpr auto SHRPX_OPT_CLIENT = StringRef::from_lit("client");
+constexpr auto SHRPX_OPT_INSECURE = StringRef::from_lit("insecure");
+constexpr auto SHRPX_OPT_CACERT = StringRef::from_lit("cacert");
+constexpr auto SHRPX_OPT_BACKEND_IPV4 = StringRef::from_lit("backend-ipv4");
+constexpr auto SHRPX_OPT_BACKEND_IPV6 = StringRef::from_lit("backend-ipv6");
+constexpr auto SHRPX_OPT_BACKEND_HTTP_PROXY_URI =
+ StringRef::from_lit("backend-http-proxy-uri");
+constexpr auto SHRPX_OPT_READ_RATE = StringRef::from_lit("read-rate");
+constexpr auto SHRPX_OPT_READ_BURST = StringRef::from_lit("read-burst");
+constexpr auto SHRPX_OPT_WRITE_RATE = StringRef::from_lit("write-rate");
+constexpr auto SHRPX_OPT_WRITE_BURST = StringRef::from_lit("write-burst");
+constexpr auto SHRPX_OPT_WORKER_READ_RATE =
+ StringRef::from_lit("worker-read-rate");
+constexpr auto SHRPX_OPT_WORKER_READ_BURST =
+ StringRef::from_lit("worker-read-burst");
+constexpr auto SHRPX_OPT_WORKER_WRITE_RATE =
+ StringRef::from_lit("worker-write-rate");
+constexpr auto SHRPX_OPT_WORKER_WRITE_BURST =
+ StringRef::from_lit("worker-write-burst");
+constexpr auto SHRPX_OPT_NPN_LIST = StringRef::from_lit("npn-list");
+constexpr auto SHRPX_OPT_TLS_PROTO_LIST = StringRef::from_lit("tls-proto-list");
+constexpr auto SHRPX_OPT_VERIFY_CLIENT = StringRef::from_lit("verify-client");
+constexpr auto SHRPX_OPT_VERIFY_CLIENT_CACERT =
+ StringRef::from_lit("verify-client-cacert");
+constexpr auto SHRPX_OPT_CLIENT_PRIVATE_KEY_FILE =
+ StringRef::from_lit("client-private-key-file");
+constexpr auto SHRPX_OPT_CLIENT_CERT_FILE =
+ StringRef::from_lit("client-cert-file");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP2_DUMP_REQUEST_HEADER =
+ StringRef::from_lit("frontend-http2-dump-request-header");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER =
+ StringRef::from_lit("frontend-http2-dump-response-header");
+constexpr auto SHRPX_OPT_HTTP2_NO_COOKIE_CRUMBLING =
+ StringRef::from_lit("http2-no-cookie-crumbling");
+constexpr auto SHRPX_OPT_FRONTEND_FRAME_DEBUG =
+ StringRef::from_lit("frontend-frame-debug");
+constexpr auto SHRPX_OPT_PADDING = StringRef::from_lit("padding");
+constexpr auto SHRPX_OPT_ALTSVC = StringRef::from_lit("altsvc");
+constexpr auto SHRPX_OPT_ADD_REQUEST_HEADER =
+ StringRef::from_lit("add-request-header");
+constexpr auto SHRPX_OPT_ADD_RESPONSE_HEADER =
+ StringRef::from_lit("add-response-header");
+constexpr auto SHRPX_OPT_WORKER_FRONTEND_CONNECTIONS =
+ StringRef::from_lit("worker-frontend-connections");
+constexpr auto SHRPX_OPT_NO_LOCATION_REWRITE =
+ StringRef::from_lit("no-location-rewrite");
+constexpr auto SHRPX_OPT_NO_HOST_REWRITE =
+ StringRef::from_lit("no-host-rewrite");
+constexpr auto SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_HOST =
+ StringRef::from_lit("backend-http1-connections-per-host");
+constexpr auto SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND =
+ StringRef::from_lit("backend-http1-connections-per-frontend");
+constexpr auto SHRPX_OPT_LISTENER_DISABLE_TIMEOUT =
+ StringRef::from_lit("listener-disable-timeout");
+constexpr auto SHRPX_OPT_TLS_TICKET_KEY_FILE =
+ StringRef::from_lit("tls-ticket-key-file");
+constexpr auto SHRPX_OPT_RLIMIT_NOFILE = StringRef::from_lit("rlimit-nofile");
+constexpr auto SHRPX_OPT_BACKEND_REQUEST_BUFFER =
+ StringRef::from_lit("backend-request-buffer");
+constexpr auto SHRPX_OPT_BACKEND_RESPONSE_BUFFER =
+ StringRef::from_lit("backend-response-buffer");
+constexpr auto SHRPX_OPT_NO_SERVER_PUSH = StringRef::from_lit("no-server-push");
+constexpr auto SHRPX_OPT_BACKEND_HTTP2_CONNECTIONS_PER_WORKER =
+ StringRef::from_lit("backend-http2-connections-per-worker");
+constexpr auto SHRPX_OPT_FETCH_OCSP_RESPONSE_FILE =
+ StringRef::from_lit("fetch-ocsp-response-file");
+constexpr auto SHRPX_OPT_OCSP_UPDATE_INTERVAL =
+ StringRef::from_lit("ocsp-update-interval");
+constexpr auto SHRPX_OPT_NO_OCSP = StringRef::from_lit("no-ocsp");
+constexpr auto SHRPX_OPT_HEADER_FIELD_BUFFER =
+ StringRef::from_lit("header-field-buffer");
+constexpr auto SHRPX_OPT_MAX_HEADER_FIELDS =
+ StringRef::from_lit("max-header-fields");
+constexpr auto SHRPX_OPT_INCLUDE = StringRef::from_lit("include");
+constexpr auto SHRPX_OPT_TLS_TICKET_KEY_CIPHER =
+ StringRef::from_lit("tls-ticket-key-cipher");
+constexpr auto SHRPX_OPT_HOST_REWRITE = StringRef::from_lit("host-rewrite");
+constexpr auto SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED =
+ StringRef::from_lit("tls-session-cache-memcached");
+constexpr auto SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED =
+ StringRef::from_lit("tls-ticket-key-memcached");
+constexpr auto SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_INTERVAL =
+ StringRef::from_lit("tls-ticket-key-memcached-interval");
+constexpr auto SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_RETRY =
+ StringRef::from_lit("tls-ticket-key-memcached-max-retry");
+constexpr auto SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL =
+ StringRef::from_lit("tls-ticket-key-memcached-max-fail");
+constexpr auto SHRPX_OPT_MRUBY_FILE = StringRef::from_lit("mruby-file");
+constexpr auto SHRPX_OPT_ACCEPT_PROXY_PROTOCOL =
+ StringRef::from_lit("accept-proxy-protocol");
+constexpr auto SHRPX_OPT_FASTOPEN = StringRef::from_lit("fastopen");
+constexpr auto SHRPX_OPT_TLS_DYN_REC_WARMUP_THRESHOLD =
+ StringRef::from_lit("tls-dyn-rec-warmup-threshold");
+constexpr auto SHRPX_OPT_TLS_DYN_REC_IDLE_TIMEOUT =
+ StringRef::from_lit("tls-dyn-rec-idle-timeout");
+constexpr auto SHRPX_OPT_ADD_FORWARDED = StringRef::from_lit("add-forwarded");
+constexpr auto SHRPX_OPT_STRIP_INCOMING_FORWARDED =
+ StringRef::from_lit("strip-incoming-forwarded");
+constexpr auto SHRPX_OPT_FORWARDED_BY = StringRef::from_lit("forwarded-by");
+constexpr auto SHRPX_OPT_FORWARDED_FOR = StringRef::from_lit("forwarded-for");
+constexpr auto SHRPX_OPT_REQUEST_HEADER_FIELD_BUFFER =
+ StringRef::from_lit("request-header-field-buffer");
+constexpr auto SHRPX_OPT_MAX_REQUEST_HEADER_FIELDS =
+ StringRef::from_lit("max-request-header-fields");
+constexpr auto SHRPX_OPT_RESPONSE_HEADER_FIELD_BUFFER =
+ StringRef::from_lit("response-header-field-buffer");
+constexpr auto SHRPX_OPT_MAX_RESPONSE_HEADER_FIELDS =
+ StringRef::from_lit("max-response-header-fields");
+constexpr auto SHRPX_OPT_NO_HTTP2_CIPHER_BLOCK_LIST =
+ StringRef::from_lit("no-http2-cipher-block-list");
+constexpr auto SHRPX_OPT_NO_HTTP2_CIPHER_BLACK_LIST =
+ StringRef::from_lit("no-http2-cipher-black-list");
+constexpr auto SHRPX_OPT_BACKEND_HTTP1_TLS =
+ StringRef::from_lit("backend-http1-tls");
+constexpr auto SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_TLS =
+ StringRef::from_lit("tls-session-cache-memcached-tls");
+constexpr auto SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_CERT_FILE =
+ StringRef::from_lit("tls-session-cache-memcached-cert-file");
+constexpr auto SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_PRIVATE_KEY_FILE =
+ StringRef::from_lit("tls-session-cache-memcached-private-key-file");
+constexpr auto SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_ADDRESS_FAMILY =
+ StringRef::from_lit("tls-session-cache-memcached-address-family");
+constexpr auto SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_TLS =
+ StringRef::from_lit("tls-ticket-key-memcached-tls");
+constexpr auto SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_CERT_FILE =
+ StringRef::from_lit("tls-ticket-key-memcached-cert-file");
+constexpr auto SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_PRIVATE_KEY_FILE =
+ StringRef::from_lit("tls-ticket-key-memcached-private-key-file");
+constexpr auto SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_ADDRESS_FAMILY =
+ StringRef::from_lit("tls-ticket-key-memcached-address-family");
+constexpr auto SHRPX_OPT_BACKEND_ADDRESS_FAMILY =
+ StringRef::from_lit("backend-address-family");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS =
+ StringRef::from_lit("frontend-http2-max-concurrent-streams");
+constexpr auto SHRPX_OPT_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS =
+ StringRef::from_lit("backend-http2-max-concurrent-streams");
+constexpr auto SHRPX_OPT_BACKEND_CONNECTIONS_PER_FRONTEND =
+ StringRef::from_lit("backend-connections-per-frontend");
+constexpr auto SHRPX_OPT_BACKEND_TLS = StringRef::from_lit("backend-tls");
+constexpr auto SHRPX_OPT_BACKEND_CONNECTIONS_PER_HOST =
+ StringRef::from_lit("backend-connections-per-host");
+constexpr auto SHRPX_OPT_ERROR_PAGE = StringRef::from_lit("error-page");
+constexpr auto SHRPX_OPT_NO_KQUEUE = StringRef::from_lit("no-kqueue");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP2_SETTINGS_TIMEOUT =
+ StringRef::from_lit("frontend-http2-settings-timeout");
+constexpr auto SHRPX_OPT_BACKEND_HTTP2_SETTINGS_TIMEOUT =
+ StringRef::from_lit("backend-http2-settings-timeout");
+constexpr auto SHRPX_OPT_API_MAX_REQUEST_BODY =
+ StringRef::from_lit("api-max-request-body");
+constexpr auto SHRPX_OPT_BACKEND_MAX_BACKOFF =
+ StringRef::from_lit("backend-max-backoff");
+constexpr auto SHRPX_OPT_SERVER_NAME = StringRef::from_lit("server-name");
+constexpr auto SHRPX_OPT_NO_SERVER_REWRITE =
+ StringRef::from_lit("no-server-rewrite");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP2_OPTIMIZE_WRITE_BUFFER_SIZE =
+ StringRef::from_lit("frontend-http2-optimize-write-buffer-size");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP2_OPTIMIZE_WINDOW_SIZE =
+ StringRef::from_lit("frontend-http2-optimize-window-size");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP2_WINDOW_SIZE =
+ StringRef::from_lit("frontend-http2-window-size");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP2_CONNECTION_WINDOW_SIZE =
+ StringRef::from_lit("frontend-http2-connection-window-size");
+constexpr auto SHRPX_OPT_BACKEND_HTTP2_WINDOW_SIZE =
+ StringRef::from_lit("backend-http2-window-size");
+constexpr auto SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE =
+ StringRef::from_lit("backend-http2-connection-window-size");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE =
+ StringRef::from_lit("frontend-http2-encoder-dynamic-table-size");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE =
+ StringRef::from_lit("frontend-http2-decoder-dynamic-table-size");
+constexpr auto SHRPX_OPT_BACKEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE =
+ StringRef::from_lit("backend-http2-encoder-dynamic-table-size");
+constexpr auto SHRPX_OPT_BACKEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE =
+ StringRef::from_lit("backend-http2-decoder-dynamic-table-size");
+constexpr auto SHRPX_OPT_ECDH_CURVES = StringRef::from_lit("ecdh-curves");
+constexpr auto SHRPX_OPT_TLS_SCT_DIR = StringRef::from_lit("tls-sct-dir");
+constexpr auto SHRPX_OPT_BACKEND_CONNECT_TIMEOUT =
+ StringRef::from_lit("backend-connect-timeout");
+constexpr auto SHRPX_OPT_DNS_CACHE_TIMEOUT =
+ StringRef::from_lit("dns-cache-timeout");
+constexpr auto SHRPX_OPT_DNS_LOOKUP_TIMEOUT =
+ StringRef::from_lit("dns-lookup-timeout");
+constexpr auto SHRPX_OPT_DNS_MAX_TRY = StringRef::from_lit("dns-max-try");
+constexpr auto SHRPX_OPT_FRONTEND_KEEP_ALIVE_TIMEOUT =
+ StringRef::from_lit("frontend-keep-alive-timeout");
+constexpr auto SHRPX_OPT_PSK_SECRETS = StringRef::from_lit("psk-secrets");
+constexpr auto SHRPX_OPT_CLIENT_PSK_SECRETS =
+ StringRef::from_lit("client-psk-secrets");
+constexpr auto SHRPX_OPT_CLIENT_NO_HTTP2_CIPHER_BLOCK_LIST =
+ StringRef::from_lit("client-no-http2-cipher-block-list");
+constexpr auto SHRPX_OPT_CLIENT_NO_HTTP2_CIPHER_BLACK_LIST =
+ StringRef::from_lit("client-no-http2-cipher-black-list");
+constexpr auto SHRPX_OPT_CLIENT_CIPHERS = StringRef::from_lit("client-ciphers");
+constexpr auto SHRPX_OPT_ACCESSLOG_WRITE_EARLY =
+ StringRef::from_lit("accesslog-write-early");
+constexpr auto SHRPX_OPT_TLS_MIN_PROTO_VERSION =
+ StringRef::from_lit("tls-min-proto-version");
+constexpr auto SHRPX_OPT_TLS_MAX_PROTO_VERSION =
+ StringRef::from_lit("tls-max-proto-version");
+constexpr auto SHRPX_OPT_REDIRECT_HTTPS_PORT =
+ StringRef::from_lit("redirect-https-port");
+constexpr auto SHRPX_OPT_FRONTEND_MAX_REQUESTS =
+ StringRef::from_lit("frontend-max-requests");
+constexpr auto SHRPX_OPT_SINGLE_THREAD = StringRef::from_lit("single-thread");
+constexpr auto SHRPX_OPT_SINGLE_PROCESS = StringRef::from_lit("single-process");
+constexpr auto SHRPX_OPT_NO_ADD_X_FORWARDED_PROTO =
+ StringRef::from_lit("no-add-x-forwarded-proto");
+constexpr auto SHRPX_OPT_NO_STRIP_INCOMING_X_FORWARDED_PROTO =
+ StringRef::from_lit("no-strip-incoming-x-forwarded-proto");
+constexpr auto SHRPX_OPT_OCSP_STARTUP = StringRef::from_lit("ocsp-startup");
+constexpr auto SHRPX_OPT_NO_VERIFY_OCSP = StringRef::from_lit("no-verify-ocsp");
+constexpr auto SHRPX_OPT_VERIFY_CLIENT_TOLERATE_EXPIRED =
+ StringRef::from_lit("verify-client-tolerate-expired");
+constexpr auto SHRPX_OPT_IGNORE_PER_PATTERN_MRUBY_ERROR =
+ StringRef::from_lit("ignore-per-pattern-mruby-error");
+constexpr auto SHRPX_OPT_TLS_NO_POSTPONE_EARLY_DATA =
+ StringRef::from_lit("tls-no-postpone-early-data");
+constexpr auto SHRPX_OPT_TLS_MAX_EARLY_DATA =
+ StringRef::from_lit("tls-max-early-data");
+constexpr auto SHRPX_OPT_TLS13_CIPHERS = StringRef::from_lit("tls13-ciphers");
+constexpr auto SHRPX_OPT_TLS13_CLIENT_CIPHERS =
+ StringRef::from_lit("tls13-client-ciphers");
+constexpr auto SHRPX_OPT_NO_STRIP_INCOMING_EARLY_DATA =
+ StringRef::from_lit("no-strip-incoming-early-data");
+constexpr auto SHRPX_OPT_QUIC_BPF_PROGRAM_FILE =
+ StringRef::from_lit("quic-bpf-program-file");
+constexpr auto SHRPX_OPT_NO_QUIC_BPF = StringRef::from_lit("no-quic-bpf");
+constexpr auto SHRPX_OPT_HTTP2_ALTSVC = StringRef::from_lit("http2-altsvc");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP3_READ_TIMEOUT =
+ StringRef::from_lit("frontend-http3-read-timeout");
+constexpr auto SHRPX_OPT_FRONTEND_QUIC_IDLE_TIMEOUT =
+ StringRef::from_lit("frontend-quic-idle-timeout");
+constexpr auto SHRPX_OPT_FRONTEND_QUIC_DEBUG_LOG =
+ StringRef::from_lit("frontend-quic-debug-log");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP3_WINDOW_SIZE =
+ StringRef::from_lit("frontend-http3-window-size");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP3_CONNECTION_WINDOW_SIZE =
+ StringRef::from_lit("frontend-http3-connection-window-size");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP3_MAX_WINDOW_SIZE =
+ StringRef::from_lit("frontend-http3-max-window-size");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP3_MAX_CONNECTION_WINDOW_SIZE =
+ StringRef::from_lit("frontend-http3-max-connection-window-size");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP3_MAX_CONCURRENT_STREAMS =
+ StringRef::from_lit("frontend-http3-max-concurrent-streams");
+constexpr auto SHRPX_OPT_FRONTEND_QUIC_EARLY_DATA =
+ StringRef::from_lit("frontend-quic-early-data");
+constexpr auto SHRPX_OPT_FRONTEND_QUIC_QLOG_DIR =
+ StringRef::from_lit("frontend-quic-qlog-dir");
+constexpr auto SHRPX_OPT_FRONTEND_QUIC_REQUIRE_TOKEN =
+ StringRef::from_lit("frontend-quic-require-token");
+constexpr auto SHRPX_OPT_FRONTEND_QUIC_CONGESTION_CONTROLLER =
+ StringRef::from_lit("frontend-quic-congestion-controller");
+constexpr auto SHRPX_OPT_QUIC_SERVER_ID = StringRef::from_lit("quic-server-id");
+constexpr auto SHRPX_OPT_FRONTEND_QUIC_SECRET_FILE =
+ StringRef::from_lit("frontend-quic-secret-file");
+constexpr auto SHRPX_OPT_RLIMIT_MEMLOCK = StringRef::from_lit("rlimit-memlock");
+constexpr auto SHRPX_OPT_MAX_WORKER_PROCESSES =
+ StringRef::from_lit("max-worker-processes");
+constexpr auto SHRPX_OPT_WORKER_PROCESS_GRACE_SHUTDOWN_PERIOD =
+ StringRef::from_lit("worker-process-grace-shutdown-period");
+constexpr auto SHRPX_OPT_FRONTEND_QUIC_INITIAL_RTT =
+ StringRef::from_lit("frontend-quic-initial-rtt");
+constexpr auto SHRPX_OPT_REQUIRE_HTTP_SCHEME =
+ StringRef::from_lit("require-http-scheme");
+constexpr auto SHRPX_OPT_TLS_KTLS = StringRef::from_lit("tls-ktls");
+constexpr auto SHRPX_OPT_ALPN_LIST = StringRef::from_lit("alpn-list");
+
+constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8;
+
+constexpr char DEFAULT_DOWNSTREAM_HOST[] = "127.0.0.1";
+constexpr int16_t DEFAULT_DOWNSTREAM_PORT = 80;
+
+enum class Proto {
+ NONE,
+ HTTP1,
+ HTTP2,
+ HTTP3,
+ MEMCACHED,
+};
+
+enum class SessionAffinity {
+ // No session affinity
+ NONE,
+ // Client IP affinity
+ IP,
+ // Cookie based affinity
+ COOKIE,
+};
+
+enum class SessionAffinityCookieSecure {
+ // Secure attribute of session affinity cookie is determined by the
+ // request scheme.
+ AUTO,
+ // Secure attribute of session affinity cookie is always set.
+ YES,
+ // Secure attribute of session affinity cookie is always unset.
+ NO,
+};
+
+enum class SessionAffinityCookieStickiness {
+ // Backend server might be changed when an existing backend server
+ // is removed, or new backend server is added.
+ LOOSE,
+ // Backend server might be changed when a designated backend server
+ // is removed, but adding new backend server does not cause
+ // breakage.
+ STRICT,
+};
+
+struct AffinityConfig {
+ // Type of session affinity.
+ SessionAffinity type;
+ struct {
+ // Name of a cookie to use.
+ StringRef name;
+ // Path which a cookie is applied to.
+ StringRef path;
+ // Secure attribute
+ SessionAffinityCookieSecure secure;
+ // Affinity Stickiness
+ SessionAffinityCookieStickiness stickiness;
+ } cookie;
+};
+
+enum shrpx_forwarded_param {
+ FORWARDED_NONE = 0,
+ FORWARDED_BY = 0x1,
+ FORWARDED_FOR = 0x2,
+ FORWARDED_HOST = 0x4,
+ FORWARDED_PROTO = 0x8,
+};
+
+enum class ForwardedNode {
+ OBFUSCATED,
+ IP,
+};
+
+struct AltSvc {
+ StringRef protocol_id, host, origin, service, params;
+
+ uint16_t port;
+};
+
+enum class UpstreamAltMode {
+ // No alternative mode
+ NONE,
+ // API processing mode
+ API,
+ // Health monitor mode
+ HEALTHMON,
+};
+
+struct UpstreamAddr {
+ // The unique index of this address.
+ size_t index;
+ // The frontend address (e.g., FQDN, hostname, IP address). If
+ // |host_unix| is true, this is UNIX domain socket path. This must
+ // be NULL terminated string.
+ StringRef host;
+ // For TCP socket, this is <IP address>:<PORT>. For IPv6 address,
+ // address is surrounded by square brackets. If socket is UNIX
+ // domain socket, this is "localhost".
+ StringRef hostport;
+ // frontend port. 0 if |host_unix| is true.
+ uint16_t port;
+ // For TCP socket, this is either AF_INET or AF_INET6. For UNIX
+ // domain socket, this is 0.
+ int family;
+ // Alternate mode
+ UpstreamAltMode alt_mode;
+ // true if |host| contains UNIX domain socket path.
+ bool host_unix;
+ // true if TLS is enabled.
+ bool tls;
+ // true if SNI host should be used as a host when selecting backend
+ // server.
+ bool sni_fwd;
+ // true if client is supposed to send PROXY protocol v1 header.
+ bool accept_proxy_protocol;
+ bool quic;
+ int fd;
+};
+
+struct DownstreamAddrConfig {
+ // Resolved address if |dns| is false
+ Address addr;
+ // backend address. If |host_unix| is true, this is UNIX domain
+ // socket path. This must be NULL terminated string.
+ StringRef host;
+ // <HOST>:<PORT>. This does not treat 80 and 443 specially. If
+ // |host_unix| is true, this is "localhost".
+ StringRef hostport;
+ // hostname sent as SNI field
+ StringRef sni;
+ // name of group which this address belongs to.
+ StringRef group;
+ size_t fall;
+ size_t rise;
+ // weight of this address inside a weight group. Its range is [1,
+ // 256], inclusive.
+ uint32_t weight;
+ // weight of the weight group. Its range is [1, 256], inclusive.
+ uint32_t group_weight;
+ // affinity hash for this address. It is assigned when strict
+ // stickiness is enabled.
+ uint32_t affinity_hash;
+ // Application protocol used in this group
+ Proto proto;
+ // backend port. 0 if |host_unix| is true.
+ uint16_t port;
+ // true if |host| contains UNIX domain socket path.
+ bool host_unix;
+ bool tls;
+ // true if dynamic DNS is enabled
+ bool dns;
+ // true if :scheme pseudo header field should be upgraded to secure
+ // variant (e.g., "https") when forwarding request to a backend
+ // connected by TLS connection.
+ bool upgrade_scheme;
+ // true if a request should not be forwarded to a backend.
+ bool dnf;
+};
+
+// Mapping hash to idx which is an index into
+// DownstreamAddrGroupConfig::addrs.
+struct AffinityHash {
+ AffinityHash(size_t idx, uint32_t hash) : idx(idx), hash(hash) {}
+
+ size_t idx;
+ uint32_t hash;
+};
+
+struct DownstreamAddrGroupConfig {
+ DownstreamAddrGroupConfig(const StringRef &pattern)
+ : pattern(pattern),
+ affinity{SessionAffinity::NONE},
+ redirect_if_not_tls(false),
+ dnf{false},
+ timeout{} {}
+
+ StringRef pattern;
+ StringRef mruby_file;
+ std::vector<DownstreamAddrConfig> addrs;
+ // Bunch of session affinity hash. Only used if affinity ==
+ // SessionAffinity::IP.
+ std::vector<AffinityHash> affinity_hash;
+ // Maps affinity hash of each DownstreamAddrConfig to its index in
+ // addrs. It is only assigned when strict stickiness is enabled.
+ std::unordered_map<uint32_t, size_t> affinity_hash_map;
+ // Cookie based session affinity configuration.
+ AffinityConfig affinity;
+ // true if this group requires that client connection must be TLS,
+ // and the request must be redirected to https URI.
+ bool redirect_if_not_tls;
+ // true if a request should not be forwarded to a backend.
+ bool dnf;
+ // Timeouts for backend connection.
+ struct {
+ ev_tstamp read;
+ ev_tstamp write;
+ } timeout;
+};
+
+struct TicketKey {
+ const EVP_CIPHER *cipher;
+ const EVP_MD *hmac;
+ size_t hmac_keylen;
+ struct {
+ // name of this ticket configuration
+ std::array<uint8_t, 16> name;
+ // encryption key for |cipher|
+ std::array<uint8_t, 32> enc_key;
+ // hmac key for |hmac|
+ std::array<uint8_t, 32> hmac_key;
+ } data;
+};
+
+struct TicketKeys {
+ ~TicketKeys();
+ std::vector<TicketKey> keys;
+};
+
+struct TLSCertificate {
+ TLSCertificate(StringRef private_key_file, StringRef cert_file,
+ std::vector<uint8_t> sct_data)
+ : private_key_file(std::move(private_key_file)),
+ cert_file(std::move(cert_file)),
+ sct_data(std::move(sct_data)) {}
+
+ StringRef private_key_file;
+ StringRef cert_file;
+ std::vector<uint8_t> sct_data;
+};
+
+#ifdef ENABLE_HTTP3
+struct QUICKeyingMaterial {
+ std::array<uint8_t, SHRPX_QUIC_SECRET_RESERVEDLEN> reserved;
+ std::array<uint8_t, SHRPX_QUIC_SECRETLEN> secret;
+ std::array<uint8_t, SHRPX_QUIC_SALTLEN> salt;
+ std::array<uint8_t, SHRPX_QUIC_CID_ENCRYPTION_KEYLEN> cid_encryption_key;
+ // Identifier of this keying material. Only the first 2 bits are
+ // used.
+ uint8_t id;
+};
+
+struct QUICKeyingMaterials {
+ std::vector<QUICKeyingMaterial> keying_materials;
+};
+#endif // ENABLE_HTTP3
+
+struct HttpProxy {
+ Address addr;
+ // host in http proxy URI
+ StringRef host;
+ // userinfo in http proxy URI, not percent-encoded form
+ StringRef userinfo;
+ // port in http proxy URI
+ uint16_t port;
+};
+
+struct TLSConfig {
+ // RFC 5077 Session ticket related configurations
+ struct {
+ struct {
+ Address addr;
+ uint16_t port;
+ // Hostname of memcached server. This is also used as SNI field
+ // if TLS is enabled.
+ StringRef host;
+ // Client private key and certificate for authentication
+ StringRef private_key_file;
+ StringRef cert_file;
+ ev_tstamp interval;
+ // Maximum number of retries when getting TLS ticket key from
+ // mamcached, due to network error.
+ size_t max_retry;
+ // Maximum number of consecutive error from memcached, when this
+ // limit reached, TLS ticket is disabled.
+ size_t max_fail;
+ // Address family of memcached connection. One of either
+ // AF_INET, AF_INET6 or AF_UNSPEC.
+ int family;
+ bool tls;
+ } memcached;
+ std::vector<StringRef> files;
+ const EVP_CIPHER *cipher;
+ // true if --tls-ticket-key-cipher is used
+ bool cipher_given;
+ } ticket;
+
+ // Session cache related configurations
+ struct {
+ struct {
+ Address addr;
+ uint16_t port;
+ // Hostname of memcached server. This is also used as SNI field
+ // if TLS is enabled.
+ StringRef host;
+ // Client private key and certificate for authentication
+ StringRef private_key_file;
+ StringRef cert_file;
+ // Address family of memcached connection. One of either
+ // AF_INET, AF_INET6 or AF_UNSPEC.
+ int family;
+ bool tls;
+ } memcached;
+ } session_cache;
+
+ // Dynamic record sizing configurations
+ struct {
+ size_t warmup_threshold;
+ ev_tstamp idle_timeout;
+ } dyn_rec;
+
+ // OCSP related configurations
+ struct {
+ ev_tstamp update_interval;
+ StringRef fetch_ocsp_response_file;
+ bool disabled;
+ bool startup;
+ bool no_verify;
+ } ocsp;
+
+ // Client verification configurations
+ struct {
+ // Path to file containing CA certificate solely used for client
+ // certificate validation
+ StringRef cacert;
+ bool enabled;
+ // true if we accept an expired client certificate.
+ bool tolerate_expired;
+ } client_verify;
+
+ // Client (backend connection) TLS configuration.
+ struct {
+ // Client PSK configuration
+ struct {
+ // identity must be NULL terminated string.
+ StringRef identity;
+ StringRef secret;
+ } psk;
+ StringRef private_key_file;
+ StringRef cert_file;
+ StringRef ciphers;
+ StringRef tls13_ciphers;
+ bool no_http2_cipher_block_list;
+ } client;
+
+ // PSK secrets. The key is identity, and the associated value is
+ // its secret.
+ std::map<StringRef, StringRef> psk_secrets;
+ // The list of additional TLS certificate pair
+ std::vector<TLSCertificate> subcerts;
+ std::vector<unsigned char> alpn_prefs;
+ // list of supported ALPN protocol strings in the order of
+ // preference.
+ std::vector<StringRef> alpn_list;
+ // list of supported SSL/TLS protocol strings.
+ std::vector<StringRef> tls_proto_list;
+ std::vector<uint8_t> sct_data;
+ BIO_METHOD *bio_method;
+ // Bit mask to disable SSL/TLS protocol versions. This will be
+ // passed to SSL_CTX_set_options().
+ long int tls_proto_mask;
+ StringRef backend_sni_name;
+ std::chrono::seconds session_timeout;
+ StringRef private_key_file;
+ StringRef private_key_passwd;
+ StringRef cert_file;
+ StringRef dh_param_file;
+ StringRef ciphers;
+ StringRef tls13_ciphers;
+ StringRef ecdh_curves;
+ StringRef cacert;
+ // The maximum amount of 0-RTT data that server accepts.
+ uint32_t max_early_data;
+ // The minimum and maximum TLS version. These values are defined in
+ // OpenSSL header file.
+ int min_proto_version;
+ int max_proto_version;
+ bool insecure;
+ bool no_http2_cipher_block_list;
+ // true if forwarding requests included in TLS early data should not
+ // be postponed until TLS handshake finishes.
+ bool no_postpone_early_data;
+ bool ktls;
+};
+
+#ifdef ENABLE_HTTP3
+struct QUICConfig {
+ struct {
+ struct {
+ ev_tstamp idle;
+ } timeout;
+ struct {
+ bool log;
+ } debug;
+ struct {
+ StringRef dir;
+ } qlog;
+ ngtcp2_cc_algo congestion_controller;
+ bool early_data;
+ bool require_token;
+ StringRef secret_file;
+ ev_tstamp initial_rtt;
+ } upstream;
+ struct {
+ StringRef prog_file;
+ bool disabled;
+ } bpf;
+ std::array<uint8_t, SHRPX_QUIC_SERVER_IDLEN> server_id;
+};
+
+struct Http3Config {
+ struct {
+ size_t max_concurrent_streams;
+ int32_t window_size;
+ int32_t connection_window_size;
+ int32_t max_window_size;
+ int32_t max_connection_window_size;
+ } upstream;
+};
+#endif // ENABLE_HTTP3
+
+// custom error page
+struct ErrorPage {
+ // not NULL-terminated
+ std::vector<uint8_t> content;
+ // 0 is special value, and it matches all HTTP status code.
+ unsigned int http_status;
+};
+
+struct HttpConfig {
+ struct {
+ // obfuscated value used in "by" parameter of Forwarded header
+ // field. This is only used when user defined static obfuscated
+ // string is provided.
+ StringRef by_obfuscated;
+ // bitwise-OR of one or more of shrpx_forwarded_param values.
+ uint32_t params;
+ // type of value recorded in "by" parameter of Forwarded header
+ // field.
+ ForwardedNode by_node_type;
+ // type of value recorded in "for" parameter of Forwarded header
+ // field.
+ ForwardedNode for_node_type;
+ bool strip_incoming;
+ } forwarded;
+ struct {
+ bool add;
+ bool strip_incoming;
+ } xff;
+ struct {
+ bool add;
+ bool strip_incoming;
+ } xfp;
+ struct {
+ bool strip_incoming;
+ } early_data;
+ std::vector<AltSvc> altsvcs;
+ // altsvcs serialized in a wire format.
+ StringRef altsvc_header_value;
+ std::vector<AltSvc> http2_altsvcs;
+ // http2_altsvcs serialized in a wire format.
+ StringRef http2_altsvc_header_value;
+ std::vector<ErrorPage> error_pages;
+ HeaderRefs add_request_headers;
+ HeaderRefs add_response_headers;
+ StringRef server_name;
+ // Port number which appears in Location header field when https
+ // redirect is made.
+ StringRef redirect_https_port;
+ size_t request_header_field_buffer;
+ size_t max_request_header_fields;
+ size_t response_header_field_buffer;
+ size_t max_response_header_fields;
+ size_t max_requests;
+ bool no_via;
+ bool no_location_rewrite;
+ bool no_host_rewrite;
+ bool no_server_rewrite;
+ bool require_http_scheme;
+};
+
+struct Http2Config {
+ struct {
+ struct {
+ struct {
+ StringRef request_header_file;
+ StringRef response_header_file;
+ FILE *request_header;
+ FILE *response_header;
+ } dump;
+ bool frame_debug;
+ } debug;
+ struct {
+ ev_tstamp settings;
+ } timeout;
+ nghttp2_option *option;
+ nghttp2_option *alt_mode_option;
+ nghttp2_session_callbacks *callbacks;
+ size_t max_concurrent_streams;
+ size_t encoder_dynamic_table_size;
+ size_t decoder_dynamic_table_size;
+ int32_t window_size;
+ int32_t connection_window_size;
+ bool optimize_write_buffer_size;
+ bool optimize_window_size;
+ } upstream;
+ struct {
+ struct {
+ ev_tstamp settings;
+ } timeout;
+ nghttp2_option *option;
+ nghttp2_session_callbacks *callbacks;
+ size_t encoder_dynamic_table_size;
+ size_t decoder_dynamic_table_size;
+ int32_t window_size;
+ int32_t connection_window_size;
+ size_t max_concurrent_streams;
+ } downstream;
+ struct {
+ ev_tstamp stream_read;
+ ev_tstamp stream_write;
+ } timeout;
+ bool no_cookie_crumbling;
+ bool no_server_push;
+};
+
+struct LoggingConfig {
+ struct {
+ std::vector<LogFragment> format;
+ StringRef file;
+ // Send accesslog to syslog, ignoring accesslog_file.
+ bool syslog;
+ // Write accesslog when response headers are received from
+ // backend, rather than response body is received and sent.
+ bool write_early;
+ } access;
+ struct {
+ StringRef file;
+ // Send errorlog to syslog, ignoring errorlog_file.
+ bool syslog;
+ } error;
+ int syslog_facility;
+ int severity;
+};
+
+struct RateLimitConfig {
+ size_t rate;
+ size_t burst;
+};
+
+// Wildcard host pattern routing. We strips left most '*' from host
+// field. router includes all path patterns sharing the same wildcard
+// host.
+struct WildcardPattern {
+ WildcardPattern(const StringRef &host) : host(host) {}
+
+ // This might not be NULL terminated. Currently it is only used for
+ // comparison.
+ StringRef host;
+ Router router;
+};
+
+// Configuration to select backend to forward request
+struct RouterConfig {
+ Router router;
+ // Router for reversed wildcard hosts. Since this router has
+ // wildcard hosts reversed without '*', one should call match()
+ // function with reversed host stripping last character. This is
+ // because we require at least one character must match for '*'.
+ // The index stored in this router is index of wildcard_patterns.
+ Router rev_wildcard_router;
+ std::vector<WildcardPattern> wildcard_patterns;
+};
+
+struct DownstreamConfig {
+ DownstreamConfig()
+ : balloc(1024, 1024),
+ timeout{},
+ addr_group_catch_all{0},
+ connections_per_host{0},
+ connections_per_frontend{0},
+ request_buffer_size{0},
+ response_buffer_size{0},
+ family{0} {}
+
+ DownstreamConfig(const DownstreamConfig &) = delete;
+ DownstreamConfig(DownstreamConfig &&) = delete;
+ DownstreamConfig &operator=(const DownstreamConfig &) = delete;
+ DownstreamConfig &operator=(DownstreamConfig &&) = delete;
+
+ // Allocator to allocate memory for Downstream configuration. Since
+ // we may swap around DownstreamConfig in arbitrary times with API
+ // calls, we should use their own allocator instead of per Config
+ // allocator.
+ BlockAllocator balloc;
+ struct {
+ ev_tstamp read;
+ ev_tstamp write;
+ ev_tstamp idle_read;
+ ev_tstamp connect;
+ // The maximum backoff while checking health check for offline
+ // backend or while detaching failed backend from load balancing
+ // group temporarily.
+ ev_tstamp max_backoff;
+ } timeout;
+ RouterConfig router;
+ std::vector<DownstreamAddrGroupConfig> addr_groups;
+ // The index of catch-all group in downstream_addr_groups.
+ size_t addr_group_catch_all;
+ size_t connections_per_host;
+ size_t connections_per_frontend;
+ size_t request_buffer_size;
+ size_t response_buffer_size;
+ // Address family of backend connection. One of either AF_INET,
+ // AF_INET6 or AF_UNSPEC. This is ignored if backend connection
+ // is made via Unix domain socket.
+ int family;
+};
+
+struct ConnectionConfig {
+ struct {
+ struct {
+ ev_tstamp sleep;
+ } timeout;
+ // address of frontend acceptors
+ std::vector<UpstreamAddr> addrs;
+ int backlog;
+ // TCP fastopen. If this is positive, it is passed to
+ // setsockopt() along with TCP_FASTOPEN.
+ int fastopen;
+ } listener;
+
+#ifdef ENABLE_HTTP3
+ struct {
+ std::vector<UpstreamAddr> addrs;
+ } quic_listener;
+#endif // ENABLE_HTTP3
+
+ struct {
+ struct {
+ ev_tstamp http2_read;
+ ev_tstamp http3_read;
+ ev_tstamp read;
+ ev_tstamp write;
+ ev_tstamp idle_read;
+ } timeout;
+ struct {
+ RateLimitConfig read;
+ RateLimitConfig write;
+ } ratelimit;
+ size_t worker_connections;
+ // Deprecated. See UpstreamAddr.accept_proxy_protocol.
+ bool accept_proxy_protocol;
+ } upstream;
+
+ std::shared_ptr<DownstreamConfig> downstream;
+};
+
+struct APIConfig {
+ // Maximum request body size for one API request
+ size_t max_request_body;
+ // true if at least one of UpstreamAddr has api enabled
+ bool enabled;
+};
+
+struct DNSConfig {
+ struct {
+ ev_tstamp cache;
+ ev_tstamp lookup;
+ } timeout;
+ // The number of tries name resolver makes before abandoning
+ // request.
+ size_t max_try;
+};
+
+struct Config {
+ Config()
+ : balloc(4096, 4096),
+ downstream_http_proxy{},
+ http{},
+ http2{},
+ tls{},
+#ifdef ENABLE_HTTP3
+ quic{},
+#endif // ENABLE_HTTP3
+ logging{},
+ conn{},
+ api{},
+ dns{},
+ config_revision{0},
+ num_worker{0},
+ padding{0},
+ rlimit_nofile{0},
+ rlimit_memlock{0},
+ uid{0},
+ gid{0},
+ pid{0},
+ verbose{false},
+ daemon{false},
+ http2_proxy{false},
+ single_process{false},
+ single_thread{false},
+ ignore_per_pattern_mruby_error{false},
+ ev_loop_flags{0},
+ max_worker_processes{0},
+ worker_process_grace_shutdown_period{0.} {
+ }
+ ~Config();
+
+ Config(Config &&) = delete;
+ Config(const Config &&) = delete;
+ Config &operator=(Config &&) = delete;
+ Config &operator=(const Config &&) = delete;
+
+ // Allocator to allocate memory for this object except for
+ // DownstreamConfig. Currently, it is used to allocate memory for
+ // strings.
+ BlockAllocator balloc;
+ HttpProxy downstream_http_proxy;
+ HttpConfig http;
+ Http2Config http2;
+ TLSConfig tls;
+#ifdef ENABLE_HTTP3
+ QUICConfig quic;
+ Http3Config http3;
+#endif // ENABLE_HTTP3
+ LoggingConfig logging;
+ ConnectionConfig conn;
+ APIConfig api;
+ DNSConfig dns;
+ StringRef pid_file;
+ StringRef conf_path;
+ StringRef user;
+ StringRef mruby_file;
+ // The revision of configuration which is opaque string, and changes
+ // on each configuration reloading. This does not change on
+ // backendconfig API call. This value is returned in health check
+ // as "nghttpx-conf-rev" response header field. The external
+ // program can check this value to know whether reloading has
+ // completed or not.
+ uint64_t config_revision;
+ size_t num_worker;
+ size_t padding;
+ size_t rlimit_nofile;
+ size_t rlimit_memlock;
+ uid_t uid;
+ gid_t gid;
+ pid_t pid;
+ bool verbose;
+ bool daemon;
+ bool http2_proxy;
+ // Run nghttpx in single process mode. With this mode, signal
+ // handling is omitted.
+ bool single_process;
+ bool single_thread;
+ // Ignore mruby compile error for per-pattern mruby script.
+ bool ignore_per_pattern_mruby_error;
+ // flags passed to ev_default_loop() and ev_loop_new()
+ int ev_loop_flags;
+ size_t max_worker_processes;
+ ev_tstamp worker_process_grace_shutdown_period;
+};
+
+const Config *get_config();
+Config *mod_config();
+// Replaces the current config with given |new_config|. The old config is
+// returned.
+std::unique_ptr<Config> replace_config(std::unique_ptr<Config> new_config);
+void create_config();
+
+// generated by gennghttpxfun.py
+enum {
+ SHRPX_OPTID_ACCEPT_PROXY_PROTOCOL,
+ SHRPX_OPTID_ACCESSLOG_FILE,
+ SHRPX_OPTID_ACCESSLOG_FORMAT,
+ SHRPX_OPTID_ACCESSLOG_SYSLOG,
+ SHRPX_OPTID_ACCESSLOG_WRITE_EARLY,
+ SHRPX_OPTID_ADD_FORWARDED,
+ SHRPX_OPTID_ADD_REQUEST_HEADER,
+ SHRPX_OPTID_ADD_RESPONSE_HEADER,
+ SHRPX_OPTID_ADD_X_FORWARDED_FOR,
+ SHRPX_OPTID_ALPN_LIST,
+ SHRPX_OPTID_ALTSVC,
+ SHRPX_OPTID_API_MAX_REQUEST_BODY,
+ SHRPX_OPTID_BACKEND,
+ SHRPX_OPTID_BACKEND_ADDRESS_FAMILY,
+ SHRPX_OPTID_BACKEND_CONNECT_TIMEOUT,
+ SHRPX_OPTID_BACKEND_CONNECTIONS_PER_FRONTEND,
+ SHRPX_OPTID_BACKEND_CONNECTIONS_PER_HOST,
+ SHRPX_OPTID_BACKEND_HTTP_PROXY_URI,
+ SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND,
+ SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_HOST,
+ SHRPX_OPTID_BACKEND_HTTP1_TLS,
+ SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_BITS,
+ SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE,
+ SHRPX_OPTID_BACKEND_HTTP2_CONNECTIONS_PER_WORKER,
+ SHRPX_OPTID_BACKEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE,
+ SHRPX_OPTID_BACKEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE,
+ SHRPX_OPTID_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS,
+ SHRPX_OPTID_BACKEND_HTTP2_SETTINGS_TIMEOUT,
+ SHRPX_OPTID_BACKEND_HTTP2_WINDOW_BITS,
+ SHRPX_OPTID_BACKEND_HTTP2_WINDOW_SIZE,
+ SHRPX_OPTID_BACKEND_IPV4,
+ SHRPX_OPTID_BACKEND_IPV6,
+ SHRPX_OPTID_BACKEND_KEEP_ALIVE_TIMEOUT,
+ SHRPX_OPTID_BACKEND_MAX_BACKOFF,
+ SHRPX_OPTID_BACKEND_NO_TLS,
+ SHRPX_OPTID_BACKEND_READ_TIMEOUT,
+ SHRPX_OPTID_BACKEND_REQUEST_BUFFER,
+ SHRPX_OPTID_BACKEND_RESPONSE_BUFFER,
+ SHRPX_OPTID_BACKEND_TLS,
+ SHRPX_OPTID_BACKEND_TLS_SNI_FIELD,
+ SHRPX_OPTID_BACKEND_WRITE_TIMEOUT,
+ SHRPX_OPTID_BACKLOG,
+ SHRPX_OPTID_CACERT,
+ SHRPX_OPTID_CERTIFICATE_FILE,
+ SHRPX_OPTID_CIPHERS,
+ SHRPX_OPTID_CLIENT,
+ SHRPX_OPTID_CLIENT_CERT_FILE,
+ SHRPX_OPTID_CLIENT_CIPHERS,
+ SHRPX_OPTID_CLIENT_NO_HTTP2_CIPHER_BLACK_LIST,
+ SHRPX_OPTID_CLIENT_NO_HTTP2_CIPHER_BLOCK_LIST,
+ SHRPX_OPTID_CLIENT_PRIVATE_KEY_FILE,
+ SHRPX_OPTID_CLIENT_PROXY,
+ SHRPX_OPTID_CLIENT_PSK_SECRETS,
+ SHRPX_OPTID_CONF,
+ SHRPX_OPTID_DAEMON,
+ SHRPX_OPTID_DH_PARAM_FILE,
+ SHRPX_OPTID_DNS_CACHE_TIMEOUT,
+ SHRPX_OPTID_DNS_LOOKUP_TIMEOUT,
+ SHRPX_OPTID_DNS_MAX_TRY,
+ SHRPX_OPTID_ECDH_CURVES,
+ SHRPX_OPTID_ERROR_PAGE,
+ SHRPX_OPTID_ERRORLOG_FILE,
+ SHRPX_OPTID_ERRORLOG_SYSLOG,
+ SHRPX_OPTID_FASTOPEN,
+ SHRPX_OPTID_FETCH_OCSP_RESPONSE_FILE,
+ SHRPX_OPTID_FORWARDED_BY,
+ SHRPX_OPTID_FORWARDED_FOR,
+ SHRPX_OPTID_FRONTEND,
+ SHRPX_OPTID_FRONTEND_FRAME_DEBUG,
+ SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS,
+ SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_SIZE,
+ SHRPX_OPTID_FRONTEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE,
+ SHRPX_OPTID_FRONTEND_HTTP2_DUMP_REQUEST_HEADER,
+ SHRPX_OPTID_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER,
+ SHRPX_OPTID_FRONTEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE,
+ SHRPX_OPTID_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS,
+ SHRPX_OPTID_FRONTEND_HTTP2_OPTIMIZE_WINDOW_SIZE,
+ SHRPX_OPTID_FRONTEND_HTTP2_OPTIMIZE_WRITE_BUFFER_SIZE,
+ SHRPX_OPTID_FRONTEND_HTTP2_READ_TIMEOUT,
+ SHRPX_OPTID_FRONTEND_HTTP2_SETTINGS_TIMEOUT,
+ SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_BITS,
+ SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_SIZE,
+ SHRPX_OPTID_FRONTEND_HTTP3_CONNECTION_WINDOW_SIZE,
+ SHRPX_OPTID_FRONTEND_HTTP3_MAX_CONCURRENT_STREAMS,
+ SHRPX_OPTID_FRONTEND_HTTP3_MAX_CONNECTION_WINDOW_SIZE,
+ SHRPX_OPTID_FRONTEND_HTTP3_MAX_WINDOW_SIZE,
+ SHRPX_OPTID_FRONTEND_HTTP3_READ_TIMEOUT,
+ SHRPX_OPTID_FRONTEND_HTTP3_WINDOW_SIZE,
+ SHRPX_OPTID_FRONTEND_KEEP_ALIVE_TIMEOUT,
+ SHRPX_OPTID_FRONTEND_MAX_REQUESTS,
+ SHRPX_OPTID_FRONTEND_NO_TLS,
+ SHRPX_OPTID_FRONTEND_QUIC_CONGESTION_CONTROLLER,
+ SHRPX_OPTID_FRONTEND_QUIC_DEBUG_LOG,
+ SHRPX_OPTID_FRONTEND_QUIC_EARLY_DATA,
+ SHRPX_OPTID_FRONTEND_QUIC_IDLE_TIMEOUT,
+ SHRPX_OPTID_FRONTEND_QUIC_INITIAL_RTT,
+ SHRPX_OPTID_FRONTEND_QUIC_QLOG_DIR,
+ SHRPX_OPTID_FRONTEND_QUIC_REQUIRE_TOKEN,
+ SHRPX_OPTID_FRONTEND_QUIC_SECRET_FILE,
+ SHRPX_OPTID_FRONTEND_READ_TIMEOUT,
+ SHRPX_OPTID_FRONTEND_WRITE_TIMEOUT,
+ SHRPX_OPTID_HEADER_FIELD_BUFFER,
+ SHRPX_OPTID_HOST_REWRITE,
+ SHRPX_OPTID_HTTP2_ALTSVC,
+ SHRPX_OPTID_HTTP2_BRIDGE,
+ SHRPX_OPTID_HTTP2_MAX_CONCURRENT_STREAMS,
+ SHRPX_OPTID_HTTP2_NO_COOKIE_CRUMBLING,
+ SHRPX_OPTID_HTTP2_PROXY,
+ SHRPX_OPTID_IGNORE_PER_PATTERN_MRUBY_ERROR,
+ SHRPX_OPTID_INCLUDE,
+ SHRPX_OPTID_INSECURE,
+ SHRPX_OPTID_LISTENER_DISABLE_TIMEOUT,
+ SHRPX_OPTID_LOG_LEVEL,
+ SHRPX_OPTID_MAX_HEADER_FIELDS,
+ SHRPX_OPTID_MAX_REQUEST_HEADER_FIELDS,
+ SHRPX_OPTID_MAX_RESPONSE_HEADER_FIELDS,
+ SHRPX_OPTID_MAX_WORKER_PROCESSES,
+ SHRPX_OPTID_MRUBY_FILE,
+ SHRPX_OPTID_NO_ADD_X_FORWARDED_PROTO,
+ SHRPX_OPTID_NO_HOST_REWRITE,
+ SHRPX_OPTID_NO_HTTP2_CIPHER_BLACK_LIST,
+ SHRPX_OPTID_NO_HTTP2_CIPHER_BLOCK_LIST,
+ SHRPX_OPTID_NO_KQUEUE,
+ SHRPX_OPTID_NO_LOCATION_REWRITE,
+ SHRPX_OPTID_NO_OCSP,
+ SHRPX_OPTID_NO_QUIC_BPF,
+ SHRPX_OPTID_NO_SERVER_PUSH,
+ SHRPX_OPTID_NO_SERVER_REWRITE,
+ SHRPX_OPTID_NO_STRIP_INCOMING_EARLY_DATA,
+ SHRPX_OPTID_NO_STRIP_INCOMING_X_FORWARDED_PROTO,
+ SHRPX_OPTID_NO_VERIFY_OCSP,
+ SHRPX_OPTID_NO_VIA,
+ SHRPX_OPTID_NPN_LIST,
+ SHRPX_OPTID_OCSP_STARTUP,
+ SHRPX_OPTID_OCSP_UPDATE_INTERVAL,
+ SHRPX_OPTID_PADDING,
+ SHRPX_OPTID_PID_FILE,
+ SHRPX_OPTID_PRIVATE_KEY_FILE,
+ SHRPX_OPTID_PRIVATE_KEY_PASSWD_FILE,
+ SHRPX_OPTID_PSK_SECRETS,
+ SHRPX_OPTID_QUIC_BPF_PROGRAM_FILE,
+ SHRPX_OPTID_QUIC_SERVER_ID,
+ SHRPX_OPTID_READ_BURST,
+ SHRPX_OPTID_READ_RATE,
+ SHRPX_OPTID_REDIRECT_HTTPS_PORT,
+ SHRPX_OPTID_REQUEST_HEADER_FIELD_BUFFER,
+ SHRPX_OPTID_REQUIRE_HTTP_SCHEME,
+ SHRPX_OPTID_RESPONSE_HEADER_FIELD_BUFFER,
+ SHRPX_OPTID_RLIMIT_MEMLOCK,
+ SHRPX_OPTID_RLIMIT_NOFILE,
+ SHRPX_OPTID_SERVER_NAME,
+ SHRPX_OPTID_SINGLE_PROCESS,
+ SHRPX_OPTID_SINGLE_THREAD,
+ SHRPX_OPTID_STREAM_READ_TIMEOUT,
+ SHRPX_OPTID_STREAM_WRITE_TIMEOUT,
+ SHRPX_OPTID_STRIP_INCOMING_FORWARDED,
+ SHRPX_OPTID_STRIP_INCOMING_X_FORWARDED_FOR,
+ SHRPX_OPTID_SUBCERT,
+ SHRPX_OPTID_SYSLOG_FACILITY,
+ SHRPX_OPTID_TLS_DYN_REC_IDLE_TIMEOUT,
+ SHRPX_OPTID_TLS_DYN_REC_WARMUP_THRESHOLD,
+ SHRPX_OPTID_TLS_KTLS,
+ SHRPX_OPTID_TLS_MAX_EARLY_DATA,
+ SHRPX_OPTID_TLS_MAX_PROTO_VERSION,
+ SHRPX_OPTID_TLS_MIN_PROTO_VERSION,
+ SHRPX_OPTID_TLS_NO_POSTPONE_EARLY_DATA,
+ SHRPX_OPTID_TLS_PROTO_LIST,
+ SHRPX_OPTID_TLS_SCT_DIR,
+ SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED,
+ SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_ADDRESS_FAMILY,
+ SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_CERT_FILE,
+ SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_PRIVATE_KEY_FILE,
+ SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_TLS,
+ SHRPX_OPTID_TLS_TICKET_KEY_CIPHER,
+ SHRPX_OPTID_TLS_TICKET_KEY_FILE,
+ SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED,
+ SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_ADDRESS_FAMILY,
+ SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_CERT_FILE,
+ SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_INTERVAL,
+ SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL,
+ SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_MAX_RETRY,
+ SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_PRIVATE_KEY_FILE,
+ SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_TLS,
+ SHRPX_OPTID_TLS13_CIPHERS,
+ SHRPX_OPTID_TLS13_CLIENT_CIPHERS,
+ SHRPX_OPTID_USER,
+ SHRPX_OPTID_VERIFY_CLIENT,
+ SHRPX_OPTID_VERIFY_CLIENT_CACERT,
+ SHRPX_OPTID_VERIFY_CLIENT_TOLERATE_EXPIRED,
+ SHRPX_OPTID_WORKER_FRONTEND_CONNECTIONS,
+ SHRPX_OPTID_WORKER_PROCESS_GRACE_SHUTDOWN_PERIOD,
+ SHRPX_OPTID_WORKER_READ_BURST,
+ SHRPX_OPTID_WORKER_READ_RATE,
+ SHRPX_OPTID_WORKER_WRITE_BURST,
+ SHRPX_OPTID_WORKER_WRITE_RATE,
+ SHRPX_OPTID_WORKERS,
+ SHRPX_OPTID_WRITE_BURST,
+ SHRPX_OPTID_WRITE_RATE,
+ SHRPX_OPTID_MAXIDX,
+};
+
+// Looks up token for given option name |name| of length |namelen|.
+int option_lookup_token(const char *name, size_t namelen);
+
+// Parses option name |opt| and value |optarg|. The results are
+// stored into the object pointed by |config|. This function returns 0
+// if it succeeds, or -1. The |included_set| contains the all paths
+// already included while processing this configuration, to avoid loop
+// in --include option. The |pattern_addr_indexer| contains a pair of
+// pattern of backend, and its index in DownstreamConfig::addr_groups.
+// It is introduced to speed up loading configuration file with lots
+// of backends.
+int parse_config(Config *config, const StringRef &opt, const StringRef &optarg,
+ std::set<StringRef> &included_set,
+ std::map<StringRef, size_t> &pattern_addr_indexer);
+
+// Similar to parse_config() above, but additional |optid| which
+// should be the return value of option_lookup_token(opt).
+int parse_config(Config *config, int optid, const StringRef &opt,
+ const StringRef &optarg, std::set<StringRef> &included_set,
+ std::map<StringRef, size_t> &pattern_addr_indexer);
+
+// Loads configurations from |filename| and stores them in |config|.
+// This function returns 0 if it succeeds, or -1. See parse_config()
+// for |include_set|.
+int load_config(Config *config, const char *filename,
+ std::set<StringRef> &include_set,
+ std::map<StringRef, size_t> &pattern_addr_indexer);
+
+// Parses header field in |optarg|. We expect header field is formed
+// like "NAME: VALUE". We require that NAME is non empty string. ":"
+// is allowed at the start of the NAME, but NAME == ":" is not
+// allowed. This function returns pair of NAME and VALUE.
+HeaderRefs::value_type parse_header(BlockAllocator &balloc,
+ const StringRef &optarg);
+
+std::vector<LogFragment> parse_log_format(BlockAllocator &balloc,
+ const StringRef &optarg);
+
+// Returns string for syslog |facility|.
+StringRef str_syslog_facility(int facility);
+
+// Returns integer value of syslog |facility| string.
+int int_syslog_facility(const StringRef &strfacility);
+
+FILE *open_file_for_write(const char *filename);
+
+// Reads TLS ticket key file in |files| and returns TicketKey which
+// 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<StringRef> &files,
+ const EVP_CIPHER *cipher, const EVP_MD *hmac);
+
+#ifdef ENABLE_HTTP3
+std::shared_ptr<QUICKeyingMaterials>
+read_quic_secret_file(const StringRef &path);
+#endif // ENABLE_HTTP3
+
+// Returns string representation of |proto|.
+StringRef strproto(Proto proto);
+
+int configure_downstream_group(Config *config, bool http2_proxy,
+ bool numeric_addr_only,
+ const TLSConfig &tlsconf);
+
+int resolve_hostname(Address *addr, const char *hostname, uint16_t port,
+ int family, int additional_flags = 0);
+
+} // namespace shrpx
+
+#endif // SHRPX_CONFIG_H
diff --git a/src/shrpx_config_test.cc b/src/shrpx_config_test.cc
new file mode 100644
index 0000000..a8f0962
--- /dev/null
+++ b/src/shrpx_config_test.cc
@@ -0,0 +1,249 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_config_test.h"
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+
+#include <cstdlib>
+
+#include <CUnit/CUnit.h>
+
+#include "shrpx_config.h"
+#include "shrpx_log.h"
+
+namespace shrpx {
+
+void test_shrpx_config_parse_header(void) {
+ BlockAllocator balloc(4096, 4096);
+
+ auto p = parse_header(balloc, StringRef::from_lit("a: b"));
+ CU_ASSERT("a" == p.name);
+ CU_ASSERT("b" == p.value);
+
+ p = parse_header(balloc, StringRef::from_lit("a: b"));
+ CU_ASSERT("a" == p.name);
+ CU_ASSERT("b" == p.value);
+
+ p = parse_header(balloc, StringRef::from_lit(":a: b"));
+ CU_ASSERT(p.name.empty());
+
+ p = parse_header(balloc, StringRef::from_lit("a: :b"));
+ CU_ASSERT("a" == p.name);
+ CU_ASSERT(":b" == p.value);
+
+ p = parse_header(balloc, StringRef::from_lit(": b"));
+ CU_ASSERT(p.name.empty());
+
+ p = parse_header(balloc, StringRef::from_lit("alpha: bravo charlie"));
+ CU_ASSERT("alpha" == p.name);
+ CU_ASSERT("bravo charlie" == p.value);
+
+ p = parse_header(balloc, StringRef::from_lit("a,: b"));
+ CU_ASSERT(p.name.empty());
+
+ p = parse_header(balloc, StringRef::from_lit("a: b\x0a"));
+ CU_ASSERT(p.name.empty());
+}
+
+void test_shrpx_config_parse_log_format(void) {
+ BlockAllocator balloc(4096, 4096);
+
+ auto res = parse_log_format(
+ balloc, StringRef::from_lit(
+ R"($remote_addr - $remote_user [$time_local] )"
+ R"("$request" $status $body_bytes_sent )"
+ R"("${http_referer}" $http_host "$http_user_agent")"));
+ CU_ASSERT(16 == res.size());
+
+ CU_ASSERT(LogFragmentType::REMOTE_ADDR == res[0].type);
+
+ CU_ASSERT(LogFragmentType::LITERAL == res[1].type);
+ CU_ASSERT(" - $remote_user [" == res[1].value);
+
+ CU_ASSERT(LogFragmentType::TIME_LOCAL == res[2].type);
+
+ CU_ASSERT(LogFragmentType::LITERAL == res[3].type);
+ CU_ASSERT("] \"" == res[3].value);
+
+ CU_ASSERT(LogFragmentType::REQUEST == res[4].type);
+
+ CU_ASSERT(LogFragmentType::LITERAL == res[5].type);
+ CU_ASSERT("\" " == res[5].value);
+
+ CU_ASSERT(LogFragmentType::STATUS == res[6].type);
+
+ CU_ASSERT(LogFragmentType::LITERAL == res[7].type);
+ CU_ASSERT(" " == res[7].value);
+
+ CU_ASSERT(LogFragmentType::BODY_BYTES_SENT == res[8].type);
+
+ CU_ASSERT(LogFragmentType::LITERAL == res[9].type);
+ CU_ASSERT(" \"" == res[9].value);
+
+ CU_ASSERT(LogFragmentType::HTTP == res[10].type);
+ CU_ASSERT("referer" == res[10].value);
+
+ CU_ASSERT(LogFragmentType::LITERAL == res[11].type);
+ CU_ASSERT("\" " == res[11].value);
+
+ CU_ASSERT(LogFragmentType::AUTHORITY == res[12].type);
+
+ CU_ASSERT(LogFragmentType::LITERAL == res[13].type);
+ CU_ASSERT(" \"" == res[13].value);
+
+ CU_ASSERT(LogFragmentType::HTTP == res[14].type);
+ CU_ASSERT("user-agent" == res[14].value);
+
+ CU_ASSERT(LogFragmentType::LITERAL == res[15].type);
+ CU_ASSERT("\"" == res[15].value);
+
+ res = parse_log_format(balloc, StringRef::from_lit("$"));
+
+ CU_ASSERT(1 == res.size());
+
+ CU_ASSERT(LogFragmentType::LITERAL == res[0].type);
+ CU_ASSERT("$" == res[0].value);
+
+ res = parse_log_format(balloc, StringRef::from_lit("${"));
+
+ CU_ASSERT(1 == res.size());
+
+ CU_ASSERT(LogFragmentType::LITERAL == res[0].type);
+ CU_ASSERT("${" == res[0].value);
+
+ res = parse_log_format(balloc, StringRef::from_lit("${a"));
+
+ CU_ASSERT(1 == res.size());
+
+ CU_ASSERT(LogFragmentType::LITERAL == res[0].type);
+ CU_ASSERT("${a" == res[0].value);
+
+ res = parse_log_format(balloc, StringRef::from_lit("${a "));
+
+ CU_ASSERT(1 == res.size());
+
+ CU_ASSERT(LogFragmentType::LITERAL == res[0].type);
+ CU_ASSERT("${a " == res[0].value);
+
+ res = parse_log_format(balloc, StringRef::from_lit("$$remote_addr"));
+
+ CU_ASSERT(2 == res.size());
+
+ CU_ASSERT(LogFragmentType::LITERAL == res[0].type);
+ CU_ASSERT("$" == res[0].value);
+
+ CU_ASSERT(LogFragmentType::REMOTE_ADDR == res[1].type);
+ CU_ASSERT("" == res[1].value);
+}
+
+void test_shrpx_config_read_tls_ticket_key_file(void) {
+ char file1[] = "/tmp/nghttpx-unittest.XXXXXX";
+ auto fd1 = mkstemp(file1);
+ CU_ASSERT(fd1 != -1);
+ CU_ASSERT(48 ==
+ write(fd1, "0..............12..............34..............5", 48));
+ char file2[] = "/tmp/nghttpx-unittest.XXXXXX";
+ auto fd2 = mkstemp(file2);
+ CU_ASSERT(fd2 != -1);
+ CU_ASSERT(48 ==
+ write(fd2, "6..............78..............9a..............b", 48));
+
+ close(fd1);
+ close(fd2);
+ auto ticket_keys = read_tls_ticket_key_file(
+ {StringRef{file1}, StringRef{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(std::equal(std::begin(key->data.name), std::end(key->data.name),
+ "0..............1"));
+ CU_ASSERT(std::equal(std::begin(key->data.enc_key),
+ std::begin(key->data.enc_key) + 16, "2..............3"));
+ CU_ASSERT(std::equal(std::begin(key->data.hmac_key),
+ std::begin(key->data.hmac_key) + 16,
+ "4..............5"));
+ CU_ASSERT(16 == key->hmac_keylen);
+
+ key = &ticket_keys->keys[1];
+ CU_ASSERT(std::equal(std::begin(key->data.name), std::end(key->data.name),
+ "6..............7"));
+ CU_ASSERT(std::equal(std::begin(key->data.enc_key),
+ std::begin(key->data.enc_key) + 16, "8..............9"));
+ CU_ASSERT(std::equal(std::begin(key->data.hmac_key),
+ std::begin(key->data.hmac_key) + 16,
+ "a..............b"));
+ CU_ASSERT(16 == key->hmac_keylen);
+}
+
+void test_shrpx_config_read_tls_ticket_key_file_aes_256(void) {
+ char file1[] = "/tmp/nghttpx-unittest.XXXXXX";
+ auto fd1 = mkstemp(file1);
+ CU_ASSERT(fd1 != -1);
+ CU_ASSERT(80 == write(fd1,
+ "0..............12..............................34..."
+ "...........................5",
+ 80));
+ char file2[] = "/tmp/nghttpx-unittest.XXXXXX";
+ auto fd2 = mkstemp(file2);
+ CU_ASSERT(fd2 != -1);
+ CU_ASSERT(80 == write(fd2,
+ "6..............78..............................9a..."
+ "...........................b",
+ 80));
+
+ close(fd1);
+ close(fd2);
+ auto ticket_keys = read_tls_ticket_key_file(
+ {StringRef{file1}, StringRef{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(std::equal(std::begin(key->data.name), std::end(key->data.name),
+ "0..............1"));
+ CU_ASSERT(std::equal(std::begin(key->data.enc_key),
+ std::end(key->data.enc_key),
+ "2..............................3"));
+ CU_ASSERT(std::equal(std::begin(key->data.hmac_key),
+ std::end(key->data.hmac_key),
+ "4..............................5"));
+
+ key = &ticket_keys->keys[1];
+ CU_ASSERT(std::equal(std::begin(key->data.name), std::end(key->data.name),
+ "6..............7"));
+ CU_ASSERT(std::equal(std::begin(key->data.enc_key),
+ std::end(key->data.enc_key),
+ "8..............................9"));
+ CU_ASSERT(std::equal(std::begin(key->data.hmac_key),
+ std::end(key->data.hmac_key),
+ "a..............................b"));
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_config_test.h b/src/shrpx_config_test.h
new file mode 100644
index 0000000..a30de41
--- /dev/null
+++ b/src/shrpx_config_test.h
@@ -0,0 +1,42 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_CONFIG_TEST_H
+#define SHRPX_CONFIG_TEST_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+namespace shrpx {
+
+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
+
+#endif // SHRPX_CONFIG_TEST_H
diff --git a/src/shrpx_connect_blocker.cc b/src/shrpx_connect_blocker.cc
new file mode 100644
index 0000000..ff767b0
--- /dev/null
+++ b/src/shrpx_connect_blocker.cc
@@ -0,0 +1,143 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_connect_blocker.h"
+#include "shrpx_config.h"
+#include "shrpx_log.h"
+
+namespace shrpx {
+
+namespace {
+void connect_blocker_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto connect_blocker = static_cast<ConnectBlocker *>(w->data);
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Unblock";
+ }
+
+ connect_blocker->call_unblock_func();
+}
+} // namespace
+
+ConnectBlocker::ConnectBlocker(std::mt19937 &gen, struct ev_loop *loop,
+ std::function<void()> block_func,
+ std::function<void()> unblock_func)
+ : gen_(gen),
+ block_func_(std::move(block_func)),
+ unblock_func_(std::move(unblock_func)),
+ loop_(loop),
+ fail_count_(0),
+ offline_(false) {
+ ev_timer_init(&timer_, connect_blocker_cb, 0., 0.);
+ timer_.data = this;
+}
+
+ConnectBlocker::~ConnectBlocker() { ev_timer_stop(loop_, &timer_); }
+
+bool ConnectBlocker::blocked() const { return ev_is_active(&timer_); }
+
+void ConnectBlocker::on_success() {
+ if (ev_is_active(&timer_)) {
+ return;
+ }
+
+ fail_count_ = 0;
+}
+
+// Use the similar backoff algorithm described in
+// https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md
+namespace {
+constexpr size_t MAX_BACKOFF_EXP = 10;
+constexpr auto MULTIPLIER = 1.6;
+constexpr auto JITTER = 0.2;
+} // namespace
+
+void ConnectBlocker::on_failure() {
+ if (ev_is_active(&timer_)) {
+ return;
+ }
+
+ call_block_func();
+
+ ++fail_count_;
+
+ auto base_backoff =
+ util::int_pow(MULTIPLIER, std::min(MAX_BACKOFF_EXP, fail_count_));
+ auto dist = std::uniform_real_distribution<>(-JITTER * base_backoff,
+ JITTER * base_backoff);
+
+ auto &downstreamconf = *get_config()->conn.downstream;
+
+ auto backoff =
+ std::min(downstreamconf.timeout.max_backoff, base_backoff + dist(gen_));
+
+ LOG(WARN) << "Could not connect " << fail_count_
+ << " times in a row; sleep for " << backoff << " seconds";
+
+ ev_timer_set(&timer_, backoff, 0.);
+ ev_timer_start(loop_, &timer_);
+}
+
+size_t ConnectBlocker::get_fail_count() const { return fail_count_; }
+
+void ConnectBlocker::offline() {
+ if (offline_) {
+ return;
+ }
+
+ if (!ev_is_active(&timer_)) {
+ call_block_func();
+ }
+
+ offline_ = true;
+
+ ev_timer_stop(loop_, &timer_);
+ ev_timer_set(&timer_, std::numeric_limits<double>::max(), 0.);
+ ev_timer_start(loop_, &timer_);
+}
+
+void ConnectBlocker::online() {
+ ev_timer_stop(loop_, &timer_);
+
+ call_unblock_func();
+
+ fail_count_ = 0;
+
+ offline_ = false;
+}
+
+bool ConnectBlocker::in_offline() const { return offline_; }
+
+void ConnectBlocker::call_block_func() {
+ if (block_func_) {
+ block_func_();
+ }
+}
+
+void ConnectBlocker::call_unblock_func() {
+ if (unblock_func_) {
+ unblock_func_();
+ }
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_connect_blocker.h b/src/shrpx_connect_blocker.h
new file mode 100644
index 0000000..1ebe789
--- /dev/null
+++ b/src/shrpx_connect_blocker.h
@@ -0,0 +1,86 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_CONNECT_BLOCKER_H
+#define SHRPX_CONNECT_BLOCKER_H
+
+#include "shrpx.h"
+
+#include <random>
+#include <functional>
+
+#include <ev.h>
+
+namespace shrpx {
+
+class ConnectBlocker {
+public:
+ ConnectBlocker(std::mt19937 &gen, struct ev_loop *loop,
+ std::function<void()> block_func,
+ std::function<void()> unblock_func);
+ ~ConnectBlocker();
+
+ // Returns true if making connection is not allowed.
+ bool blocked() const;
+ // Call this function if connect operation succeeded. This will
+ // reset sleep_ to minimum value.
+ void on_success();
+ // Call this function if connect operations failed. This will start
+ // timer and blocks connection establishment with exponential
+ // backoff.
+ void on_failure();
+
+ size_t get_fail_count() const;
+
+ // Peer is now considered offline. This effectively means that the
+ // connection is blocked until online() is called.
+ void offline();
+
+ // Peer is now considered online
+ void online();
+
+ // Returns true if peer is considered offline.
+ bool in_offline() const;
+
+ void call_block_func();
+ void call_unblock_func();
+
+private:
+ std::mt19937 &gen_;
+ // Called when blocking is started
+ std::function<void()> block_func_;
+ // Called when unblocked
+ std::function<void()> unblock_func_;
+ ev_timer timer_;
+ struct ev_loop *loop_;
+ // The number of consecutive connection failure. Reset to 0 on
+ // success.
+ size_t fail_count_;
+ // true if peer is considered offline.
+ bool offline_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_CONNECT_BLOCKER_H
diff --git a/src/shrpx_connection.cc b/src/shrpx_connection.cc
new file mode 100644
index 0000000..a5ab390
--- /dev/null
+++ b/src/shrpx_connection.cc
@@ -0,0 +1,1275 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_connection.h"
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+#include <netinet/tcp.h>
+
+#include <limits>
+
+#include <openssl/err.h>
+
+#include "shrpx_tls.h"
+#include "shrpx_memcached_request.h"
+#include "shrpx_log.h"
+#include "memchunk.h"
+#include "util.h"
+#include "ssl_compat.h"
+
+using namespace nghttp2;
+using namespace std::chrono_literals;
+
+namespace shrpx {
+
+Connection::Connection(struct ev_loop *loop, int fd, SSL *ssl,
+ MemchunkPool *mcpool, ev_tstamp write_timeout,
+ ev_tstamp read_timeout,
+ const RateLimitConfig &write_limit,
+ const RateLimitConfig &read_limit, IOCb writecb,
+ IOCb readcb, TimerCb timeoutcb, void *data,
+ size_t tls_dyn_rec_warmup_threshold,
+ ev_tstamp tls_dyn_rec_idle_timeout, Proto proto)
+ :
+#ifdef ENABLE_HTTP3
+ conn_ref{nullptr, this},
+#endif // ENABLE_HTTP3
+ tls{DefaultMemchunks(mcpool), DefaultPeekMemchunks(mcpool),
+ DefaultMemchunks(mcpool)},
+ wlimit(loop, &wev, write_limit.rate, write_limit.burst),
+ rlimit(loop, &rev, read_limit.rate, read_limit.burst, this),
+ loop(loop),
+ data(data),
+ fd(fd),
+ tls_dyn_rec_warmup_threshold(tls_dyn_rec_warmup_threshold),
+ tls_dyn_rec_idle_timeout(util::duration_from(tls_dyn_rec_idle_timeout)),
+ proto(proto),
+ read_timeout(read_timeout) {
+
+ ev_io_init(&wev, writecb, fd, EV_WRITE);
+ ev_io_init(&rev, readcb, proto == Proto::HTTP3 ? 0 : fd, EV_READ);
+
+ wev.data = this;
+ rev.data = this;
+
+ ev_timer_init(&wt, timeoutcb, 0., write_timeout);
+ ev_timer_init(&rt, timeoutcb, 0., read_timeout);
+
+ wt.data = this;
+ rt.data = this;
+
+ if (ssl) {
+ set_ssl(ssl);
+ }
+}
+
+Connection::~Connection() { disconnect(); }
+
+void Connection::disconnect() {
+ if (tls.ssl) {
+ if (proto != Proto::HTTP3) {
+ SSL_set_shutdown(tls.ssl,
+ SSL_get_shutdown(tls.ssl) | SSL_RECEIVED_SHUTDOWN);
+ ERR_clear_error();
+
+ if (tls.cached_session) {
+ SSL_SESSION_free(tls.cached_session);
+ tls.cached_session = nullptr;
+ }
+
+ if (tls.cached_session_lookup_req) {
+ tls.cached_session_lookup_req->canceled = true;
+ tls.cached_session_lookup_req = nullptr;
+ }
+
+ SSL_shutdown(tls.ssl);
+ }
+
+ SSL_free(tls.ssl);
+ tls.ssl = nullptr;
+
+ tls.wbuf.reset();
+ tls.rbuf.reset();
+ tls.last_write_idle = {};
+ tls.warmup_writelen = 0;
+ tls.last_writelen = 0;
+ tls.last_readlen = 0;
+ tls.handshake_state = TLSHandshakeState::NORMAL;
+ tls.initial_handshake_done = false;
+ tls.reneg_started = false;
+ tls.sct_requested = false;
+ tls.early_data_finish = false;
+ }
+
+ if (proto != Proto::HTTP3 && fd != -1) {
+ shutdown(fd, SHUT_WR);
+ close(fd);
+ fd = -1;
+ }
+
+ // Stop watchers here because they could be activated in
+ // SSL_shutdown().
+ ev_timer_stop(loop, &rt);
+ ev_timer_stop(loop, &wt);
+
+ rlimit.stopw();
+ wlimit.stopw();
+}
+
+void Connection::prepare_client_handshake() {
+ SSL_set_connect_state(tls.ssl);
+ // This prevents SSL_read_early_data from being called.
+ tls.early_data_finish = true;
+}
+
+void Connection::prepare_server_handshake() {
+ auto &tlsconf = get_config()->tls;
+ if (proto != Proto::HTTP3 && !tlsconf.session_cache.memcached.host.empty()) {
+ auto bio = BIO_new(tlsconf.bio_method);
+ BIO_set_data(bio, this);
+ SSL_set_bio(tls.ssl, bio, bio);
+ }
+
+ SSL_set_accept_state(tls.ssl);
+ tls.server_handshake = true;
+}
+
+// BIO implementation is inspired by openldap implementation:
+// http://www.openldap.org/devel/cvsweb.cgi/~checkout~/libraries/libldap/tls_o.c
+namespace {
+int shrpx_bio_write(BIO *b, const char *buf, int len) {
+ if (buf == nullptr || len <= 0) {
+ return 0;
+ }
+
+ auto conn = static_cast<Connection *>(BIO_get_data(b));
+ auto &wbuf = conn->tls.wbuf;
+
+ BIO_clear_retry_flags(b);
+
+ if (conn->tls.initial_handshake_done) {
+ // After handshake finished, send |buf| of length |len| to the
+ // socket directly.
+
+ // Only when TLS session was prematurely ended before server sent
+ // all handshake message, this condition is true. This could be
+ // alert from SSL_shutdown(). Since connection is already down,
+ // just return error.
+ if (wbuf.rleft()) {
+ return -1;
+ }
+ auto nwrite = conn->write_clear(buf, len);
+ if (nwrite < 0) {
+ return -1;
+ }
+
+ if (nwrite == 0) {
+ BIO_set_retry_write(b);
+ return -1;
+ }
+
+ return nwrite;
+ }
+
+ wbuf.append(buf, len);
+
+ return len;
+}
+} // namespace
+
+namespace {
+int shrpx_bio_read(BIO *b, char *buf, int len) {
+ if (buf == nullptr || len <= 0) {
+ return 0;
+ }
+
+ auto conn = static_cast<Connection *>(BIO_get_data(b));
+ auto &rbuf = conn->tls.rbuf;
+
+ BIO_clear_retry_flags(b);
+
+ if (conn->tls.initial_handshake_done && rbuf.rleft() == 0) {
+ auto nread = conn->read_clear(buf, len);
+ if (nread < 0) {
+ return -1;
+ }
+ if (nread == 0) {
+ BIO_set_retry_read(b);
+ return -1;
+ }
+ return nread;
+ }
+
+ if (rbuf.rleft() == 0) {
+ BIO_set_retry_read(b);
+ return -1;
+ }
+
+ return rbuf.remove(buf, len);
+}
+} // namespace
+
+namespace {
+int shrpx_bio_puts(BIO *b, const char *str) {
+ return shrpx_bio_write(b, str, strlen(str));
+}
+} // namespace
+
+namespace {
+int shrpx_bio_gets(BIO *b, char *buf, int len) { return -1; }
+} // namespace
+
+namespace {
+long shrpx_bio_ctrl(BIO *b, int cmd, long num, void *ptr) {
+ switch (cmd) {
+ case BIO_CTRL_FLUSH:
+ return 1;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int shrpx_bio_create(BIO *b) {
+ BIO_set_init(b, 1);
+
+ return 1;
+}
+} // namespace
+
+namespace {
+int shrpx_bio_destroy(BIO *b) {
+ if (b == nullptr) {
+ return 0;
+ }
+
+ return 1;
+}
+} // namespace
+
+BIO_METHOD *create_bio_method() {
+ auto meth = BIO_meth_new(BIO_TYPE_FD, "nghttpx-bio");
+ BIO_meth_set_write(meth, shrpx_bio_write);
+ BIO_meth_set_read(meth, shrpx_bio_read);
+ BIO_meth_set_puts(meth, shrpx_bio_puts);
+ BIO_meth_set_gets(meth, shrpx_bio_gets);
+ BIO_meth_set_ctrl(meth, shrpx_bio_ctrl);
+ BIO_meth_set_create(meth, shrpx_bio_create);
+ BIO_meth_set_destroy(meth, shrpx_bio_destroy);
+
+ return meth;
+}
+
+void Connection::set_ssl(SSL *ssl) {
+ tls.ssl = ssl;
+
+ SSL_set_app_data(tls.ssl, this);
+}
+
+namespace {
+// We should buffer at least full encrypted TLS record here.
+// Theoretically, peer can send client hello in several TLS records,
+// which could exceed this limit, but it is not portable, and we don't
+// have to handle such exotic behaviour.
+bool read_buffer_full(DefaultPeekMemchunks &rbuf) {
+ return rbuf.rleft_buffered() >= 20_k;
+}
+} // namespace
+
+int Connection::tls_handshake() {
+ wlimit.stopw();
+ ev_timer_stop(loop, &wt);
+
+ auto &tlsconf = get_config()->tls;
+
+ if (!tls.server_handshake || tlsconf.session_cache.memcached.host.empty()) {
+ return tls_handshake_simple();
+ }
+
+ std::array<uint8_t, 16_k> buf;
+
+ if (ev_is_active(&rev)) {
+ auto nread = read_clear(buf.data(), buf.size());
+ if (nread < 0) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: handshake read error";
+ }
+ return -1;
+ }
+ tls.rbuf.append(buf.data(), nread);
+ if (read_buffer_full(tls.rbuf)) {
+ rlimit.stopw();
+ }
+ }
+
+ if (tls.initial_handshake_done) {
+ return write_tls_pending_handshake();
+ }
+
+ switch (tls.handshake_state) {
+ case TLSHandshakeState::WAIT_FOR_SESSION_CACHE:
+ return SHRPX_ERR_INPROGRESS;
+ case TLSHandshakeState::GOT_SESSION_CACHE: {
+ // Use the same trick invented by @kazuho in h2o project.
+
+ // Discard all outgoing data.
+ tls.wbuf.reset();
+ // Rewind buffered incoming data to replay client hello.
+ tls.rbuf.disable_peek(false);
+
+ auto ssl_ctx = SSL_get_SSL_CTX(tls.ssl);
+ auto ssl_opts = SSL_get_options(tls.ssl);
+ SSL_free(tls.ssl);
+
+ auto ssl = tls::create_ssl(ssl_ctx);
+ if (!ssl) {
+ return -1;
+ }
+ if (ssl_opts & SSL_OP_NO_TICKET) {
+ SSL_set_options(ssl, SSL_OP_NO_TICKET);
+ }
+
+ set_ssl(ssl);
+
+ prepare_server_handshake();
+
+ tls.handshake_state = TLSHandshakeState::NORMAL;
+ break;
+ }
+ case TLSHandshakeState::CANCEL_SESSION_CACHE:
+ tls.handshake_state = TLSHandshakeState::NORMAL;
+ break;
+ default:
+ break;
+ }
+
+ int rv;
+
+ ERR_clear_error();
+
+#ifdef NGHTTP2_GENUINE_OPENSSL
+ if (!tls.server_handshake || tls.early_data_finish) {
+ rv = SSL_do_handshake(tls.ssl);
+ } else {
+ for (;;) {
+ size_t nread;
+
+ rv = SSL_read_early_data(tls.ssl, buf.data(), buf.size(), &nread);
+ if (rv == SSL_READ_EARLY_DATA_ERROR) {
+ // If we have early data, and server sends ServerHello, assume
+ // that handshake is completed in server side, and start
+ // processing request. If we don't exit handshake code here,
+ // server waits for EndOfEarlyData and Finished message from
+ // client, which voids the purpose of 0-RTT data. The left
+ // over of handshake is done through write_tls or read_tls.
+ if (tlsconf.no_postpone_early_data &&
+ (tls.handshake_state == TLSHandshakeState::WRITE_STARTED ||
+ tls.wbuf.rleft()) &&
+ tls.earlybuf.rleft()) {
+ rv = 1;
+ }
+
+ break;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: read early data " << nread << " bytes";
+ }
+
+ tls.earlybuf.append(buf.data(), nread);
+
+ if (rv == SSL_READ_EARLY_DATA_FINISH) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: read all early data; total "
+ << tls.earlybuf.rleft() << " bytes";
+ }
+ tls.early_data_finish = true;
+ // The same reason stated above.
+ if (tlsconf.no_postpone_early_data &&
+ (tls.handshake_state == TLSHandshakeState::WRITE_STARTED ||
+ tls.wbuf.rleft()) &&
+ tls.earlybuf.rleft()) {
+ rv = 1;
+ } else {
+ ERR_clear_error();
+ rv = SSL_do_handshake(tls.ssl);
+ }
+ break;
+ }
+ }
+ }
+#else // !NGHTTP2_GENUINE_OPENSSL
+ rv = SSL_do_handshake(tls.ssl);
+#endif // !NGHTTP2_GENUINE_OPENSSL
+
+ if (rv <= 0) {
+ auto err = SSL_get_error(tls.ssl, rv);
+ switch (err) {
+ case SSL_ERROR_WANT_READ:
+ if (read_buffer_full(tls.rbuf)) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: handshake message is too large";
+ }
+ return -1;
+ }
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ break;
+ case SSL_ERROR_SSL: {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: handshake libssl error: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ }
+
+ struct iovec iov[1];
+ auto iovcnt = tls.wbuf.riovec(iov, 1);
+ auto nwrite = writev_clear(iov, iovcnt);
+ if (nwrite > 0) {
+ tls.wbuf.drain(nwrite);
+ }
+
+ return SHRPX_ERR_NETWORK;
+ }
+ default:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: handshake libssl error " << err;
+ }
+ return SHRPX_ERR_NETWORK;
+ }
+ }
+
+ if (tls.handshake_state == TLSHandshakeState::WAIT_FOR_SESSION_CACHE) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: handshake is still in progress";
+ }
+ return SHRPX_ERR_INPROGRESS;
+ }
+
+ // Don't send handshake data if handshake was completed in OpenSSL
+ // routine. We have to check HTTP/2 requirement if HTTP/2 was
+ // negotiated before sending finished message to the peer.
+ if ((rv != 1
+#ifdef NGHTTP2_OPENSSL_IS_BORINGSSL
+ || SSL_in_init(tls.ssl)
+#endif // NGHTTP2_OPENSSL_IS_BORINGSSL
+ ) &&
+ tls.wbuf.rleft()) {
+ // First write indicates that resumption stuff has done.
+ if (tls.handshake_state != TLSHandshakeState::WRITE_STARTED) {
+ tls.handshake_state = TLSHandshakeState::WRITE_STARTED;
+ // If peek has already disabled, this is noop.
+ tls.rbuf.disable_peek(true);
+ }
+ std::array<struct iovec, 4> iov;
+ auto iovcnt = tls.wbuf.riovec(iov.data(), iov.size());
+ auto nwrite = writev_clear(iov.data(), iovcnt);
+ if (nwrite < 0) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: handshake write error";
+ }
+ return -1;
+ }
+ tls.wbuf.drain(nwrite);
+
+ if (tls.wbuf.rleft()) {
+ wlimit.startw();
+ ev_timer_again(loop, &wt);
+ }
+ }
+
+ if (!read_buffer_full(tls.rbuf)) {
+ // We may have stopped reading
+ rlimit.startw();
+ }
+
+ if (rv != 1) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: handshake is still in progress";
+ }
+ return SHRPX_ERR_INPROGRESS;
+ }
+
+#ifdef NGHTTP2_OPENSSL_IS_BORINGSSL
+ if (!tlsconf.no_postpone_early_data && SSL_in_early_data(tls.ssl) &&
+ SSL_in_init(tls.ssl)) {
+ auto nread = SSL_read(tls.ssl, buf.data(), buf.size());
+ if (nread <= 0) {
+ auto err = SSL_get_error(tls.ssl, nread);
+ switch (err) {
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_WRITE:
+ break;
+ case SSL_ERROR_ZERO_RETURN:
+ return SHRPX_ERR_EOF;
+ case SSL_ERROR_SSL:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "SSL_read: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ }
+ return SHRPX_ERR_NETWORK;
+ default:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "SSL_read: SSL_get_error returned " << err;
+ }
+ return SHRPX_ERR_NETWORK;
+ }
+ } else {
+ tls.earlybuf.append(buf.data(), nread);
+ }
+
+ if (SSL_in_init(tls.ssl)) {
+ return SHRPX_ERR_INPROGRESS;
+ }
+ }
+#endif // NGHTTP2_OPENSSL_IS_BORINGSSL
+
+ // Handshake was done
+
+ rv = check_http2_requirement();
+ if (rv != 0) {
+ return -1;
+ }
+
+ // Just in case
+ tls.rbuf.disable_peek(true);
+
+ tls.initial_handshake_done = true;
+
+ return write_tls_pending_handshake();
+}
+
+int Connection::tls_handshake_simple() {
+ wlimit.stopw();
+ ev_timer_stop(loop, &wt);
+
+ if (tls.initial_handshake_done) {
+ return write_tls_pending_handshake();
+ }
+
+ if (SSL_get_fd(tls.ssl) == -1) {
+ SSL_set_fd(tls.ssl, fd);
+ }
+
+ int rv;
+#if defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_BORINGSSL)
+ auto &tlsconf = get_config()->tls;
+ std::array<uint8_t, 16_k> buf;
+#endif // NGHTTP2_GENUINE_OPENSSL || NGHTTP2_OPENSSL_IS_BORINGSSL
+
+ ERR_clear_error();
+
+#ifdef NGHTTP2_GENUINE_OPENSSL
+ if (!tls.server_handshake || tls.early_data_finish) {
+ rv = SSL_do_handshake(tls.ssl);
+ } else {
+ for (;;) {
+ size_t nread;
+
+ rv = SSL_read_early_data(tls.ssl, buf.data(), buf.size(), &nread);
+ if (rv == SSL_READ_EARLY_DATA_ERROR) {
+ // If we have early data, and server sends ServerHello, assume
+ // that handshake is completed in server side, and start
+ // processing request. If we don't exit handshake code here,
+ // server waits for EndOfEarlyData and Finished message from
+ // client, which voids the purpose of 0-RTT data. The left
+ // over of handshake is done through write_tls or read_tls.
+ if (tlsconf.no_postpone_early_data && tls.earlybuf.rleft()) {
+ rv = 1;
+ }
+
+ break;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: read early data " << nread << " bytes";
+ }
+
+ tls.earlybuf.append(buf.data(), nread);
+
+ if (rv == SSL_READ_EARLY_DATA_FINISH) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: read all early data; total "
+ << tls.earlybuf.rleft() << " bytes";
+ }
+ tls.early_data_finish = true;
+ // The same reason stated above.
+ if (tlsconf.no_postpone_early_data && tls.earlybuf.rleft()) {
+ rv = 1;
+ } else {
+ ERR_clear_error();
+ rv = SSL_do_handshake(tls.ssl);
+ }
+ break;
+ }
+ }
+ }
+#else // !NGHTTP2_GENUINE_OPENSSL
+ rv = SSL_do_handshake(tls.ssl);
+#endif // !NGHTTP2_GENUINE_OPENSSL
+
+ if (rv <= 0) {
+ auto err = SSL_get_error(tls.ssl, rv);
+ switch (err) {
+ case SSL_ERROR_WANT_READ:
+ if (read_buffer_full(tls.rbuf)) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: handshake message is too large";
+ }
+ return -1;
+ }
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ wlimit.startw();
+ ev_timer_again(loop, &wt);
+ break;
+ case SSL_ERROR_SSL: {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: handshake libssl error: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ }
+ return SHRPX_ERR_NETWORK;
+ }
+ default:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: handshake libssl error " << err;
+ }
+ return SHRPX_ERR_NETWORK;
+ }
+ }
+
+ if (rv != 1) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: handshake is still in progress";
+ }
+ return SHRPX_ERR_INPROGRESS;
+ }
+
+#ifdef NGHTTP2_OPENSSL_IS_BORINGSSL
+ if (!tlsconf.no_postpone_early_data && SSL_in_early_data(tls.ssl) &&
+ SSL_in_init(tls.ssl)) {
+ auto nread = SSL_read(tls.ssl, buf.data(), buf.size());
+ if (nread <= 0) {
+ auto err = SSL_get_error(tls.ssl, nread);
+ switch (err) {
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_WRITE:
+ break;
+ case SSL_ERROR_ZERO_RETURN:
+ return SHRPX_ERR_EOF;
+ case SSL_ERROR_SSL:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "SSL_read: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ }
+ return SHRPX_ERR_NETWORK;
+ default:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "SSL_read: SSL_get_error returned " << err;
+ }
+ return SHRPX_ERR_NETWORK;
+ }
+ } else {
+ tls.earlybuf.append(buf.data(), nread);
+ }
+
+ if (SSL_in_init(tls.ssl)) {
+ return SHRPX_ERR_INPROGRESS;
+ }
+ }
+#endif // NGHTTP2_OPENSSL_IS_BORINGSSL
+
+ // Handshake was done
+
+ rv = check_http2_requirement();
+ if (rv != 0) {
+ return -1;
+ }
+
+ tls.initial_handshake_done = true;
+
+ return write_tls_pending_handshake();
+}
+
+int Connection::write_tls_pending_handshake() {
+ // Send handshake data left in the buffer
+ while (tls.wbuf.rleft()) {
+ std::array<struct iovec, 4> iov;
+ auto iovcnt = tls.wbuf.riovec(iov.data(), iov.size());
+ auto nwrite = writev_clear(iov.data(), iovcnt);
+ if (nwrite < 0) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: handshake write error";
+ }
+ return -1;
+ }
+ if (nwrite == 0) {
+ wlimit.startw();
+ ev_timer_again(loop, &wt);
+
+ return SHRPX_ERR_INPROGRESS;
+ }
+ tls.wbuf.drain(nwrite);
+ }
+
+#ifdef NGHTTP2_OPENSSL_IS_BORINGSSL
+ if (!SSL_in_init(tls.ssl)) {
+ // This will send a session ticket.
+ auto nwrite = SSL_write(tls.ssl, "", 0);
+ if (nwrite < 0) {
+ auto err = SSL_get_error(tls.ssl, nwrite);
+ switch (err) {
+ case SSL_ERROR_WANT_READ:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Close connection due to TLS renegotiation";
+ }
+ return SHRPX_ERR_NETWORK;
+ case SSL_ERROR_WANT_WRITE:
+ break;
+ case SSL_ERROR_SSL:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "SSL_write: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ }
+ return SHRPX_ERR_NETWORK;
+ default:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "SSL_write: SSL_get_error returned " << err;
+ }
+ return SHRPX_ERR_NETWORK;
+ }
+ }
+ }
+#endif // NGHTTP2_OPENSSL_IS_BORINGSSL
+
+ // We have to start read watcher, since later stage of code expects
+ // this.
+ rlimit.startw();
+
+ // We may have whole request in tls.rbuf. This means that we don't
+ // get notified further read event. This is especially true for
+ // HTTP/1.1.
+ handle_tls_pending_read();
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "SSL/TLS handshake completed";
+ nghttp2::tls::TLSSessionInfo tls_info{};
+ if (nghttp2::tls::get_tls_session_info(&tls_info, tls.ssl)) {
+ LOG(INFO) << "cipher=" << tls_info.cipher
+ << " protocol=" << tls_info.protocol
+ << " resumption=" << (tls_info.session_reused ? "yes" : "no")
+ << " session_id="
+ << util::format_hex(tls_info.session_id,
+ tls_info.session_id_length);
+ }
+ }
+
+ return 0;
+}
+
+int Connection::check_http2_requirement() {
+ const unsigned char *next_proto = nullptr;
+ unsigned int next_proto_len;
+
+ SSL_get0_alpn_selected(tls.ssl, &next_proto, &next_proto_len);
+ if (next_proto == nullptr ||
+ !util::check_h2_is_selected(StringRef{next_proto, next_proto_len})) {
+ return 0;
+ }
+ if (!nghttp2::tls::check_http2_tls_version(tls.ssl)) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "TLSv1.2 was not negotiated. HTTP/2 must not be used.";
+ }
+ return -1;
+ }
+
+ auto check_block_list = false;
+ if (tls.server_handshake) {
+ check_block_list = !get_config()->tls.no_http2_cipher_block_list;
+ } else {
+ check_block_list = !get_config()->tls.client.no_http2_cipher_block_list;
+ }
+
+ if (check_block_list &&
+ nghttp2::tls::check_http2_cipher_block_list(tls.ssl)) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "The negotiated cipher suite is in HTTP/2 cipher suite "
+ "block list. HTTP/2 must not be used.";
+ }
+ return -1;
+ }
+
+ return 0;
+}
+
+namespace {
+constexpr size_t SHRPX_SMALL_WRITE_LIMIT = 1300;
+} // namespace
+
+size_t Connection::get_tls_write_limit() {
+
+ if (tls_dyn_rec_warmup_threshold == 0) {
+ return std::numeric_limits<ssize_t>::max();
+ }
+
+ auto t = std::chrono::steady_clock::now();
+
+ if (tls.last_write_idle.time_since_epoch().count() >= 0 &&
+ t - tls.last_write_idle > tls_dyn_rec_idle_timeout) {
+ // Time out, use small record size
+ tls.warmup_writelen = 0;
+ return SHRPX_SMALL_WRITE_LIMIT;
+ }
+
+ if (tls.warmup_writelen >= tls_dyn_rec_warmup_threshold) {
+ return std::numeric_limits<ssize_t>::max();
+ }
+
+ return SHRPX_SMALL_WRITE_LIMIT;
+}
+
+void Connection::update_tls_warmup_writelen(size_t n) {
+ if (tls.warmup_writelen < tls_dyn_rec_warmup_threshold) {
+ tls.warmup_writelen += n;
+ }
+}
+
+void Connection::start_tls_write_idle() {
+ if (tls.last_write_idle.time_since_epoch().count() < 0) {
+ tls.last_write_idle = std::chrono::steady_clock::now();
+ }
+}
+
+ssize_t Connection::write_tls(const void *data, size_t len) {
+ // SSL_write requires the same arguments (buf pointer and its
+ // length) on SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE.
+ // get_write_limit() may return smaller length than previously
+ // passed to SSL_write, which violates OpenSSL assumption. To avoid
+ // this, we keep last length passed to SSL_write to
+ // tls.last_writelen if SSL_write indicated I/O blocking.
+ if (tls.last_writelen == 0) {
+ len = std::min(len, wlimit.avail());
+ len = std::min(len, get_tls_write_limit());
+ if (len == 0) {
+ return 0;
+ }
+ } else {
+ len = tls.last_writelen;
+ tls.last_writelen = 0;
+ }
+
+ tls.last_write_idle = std::chrono::steady_clock::time_point(-1s);
+
+ auto &tlsconf = get_config()->tls;
+ auto via_bio =
+ tls.server_handshake && !tlsconf.session_cache.memcached.host.empty();
+
+ ERR_clear_error();
+
+#ifdef NGHTTP2_GENUINE_OPENSSL
+ int rv;
+ if (SSL_is_init_finished(tls.ssl)) {
+ rv = SSL_write(tls.ssl, data, len);
+ } else {
+ size_t nwrite;
+ rv = SSL_write_early_data(tls.ssl, data, len, &nwrite);
+ // Use the same semantics with SSL_write.
+ if (rv == 1) {
+ rv = nwrite;
+ }
+ }
+#else // !NGHTTP2_GENUINE_OPENSSL
+ auto rv = SSL_write(tls.ssl, data, len);
+#endif // !NGHTTP2_GENUINE_OPENSSL
+
+ if (rv <= 0) {
+ auto err = SSL_get_error(tls.ssl, rv);
+ switch (err) {
+ case SSL_ERROR_WANT_READ:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Close connection due to TLS renegotiation";
+ }
+ return SHRPX_ERR_NETWORK;
+ case SSL_ERROR_WANT_WRITE:
+ tls.last_writelen = len;
+ // starting write watcher and timer is done in write_clear via
+ // bio otherwise.
+ if (!via_bio) {
+ wlimit.startw();
+ ev_timer_again(loop, &wt);
+ }
+
+ return 0;
+ case SSL_ERROR_SSL:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "SSL_write: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ }
+ return SHRPX_ERR_NETWORK;
+ default:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "SSL_write: SSL_get_error returned " << err;
+ }
+ return SHRPX_ERR_NETWORK;
+ }
+ }
+
+ if (!via_bio) {
+ wlimit.drain(rv);
+
+ if (ev_is_active(&wt)) {
+ ev_timer_again(loop, &wt);
+ }
+ }
+
+ update_tls_warmup_writelen(rv);
+
+ return rv;
+}
+
+ssize_t Connection::read_tls(void *data, size_t len) {
+ ERR_clear_error();
+
+#if defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_BORINGSSL)
+ if (tls.earlybuf.rleft()) {
+ return tls.earlybuf.remove(data, len);
+ }
+#endif // NGHTTP2_GENUINE_OPENSSL || NGHTTP2_OPENSSL_IS_BORINGSSL
+
+ // SSL_read requires the same arguments (buf pointer and its
+ // length) on SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE.
+ // rlimit_.avail() or rlimit_.avail() may return different length
+ // than the length previously passed to SSL_read, which violates
+ // OpenSSL assumption. To avoid this, we keep last length passed
+ // to SSL_read to tls_last_readlen_ if SSL_read indicated I/O
+ // blocking.
+ if (tls.last_readlen == 0) {
+ len = std::min(len, rlimit.avail());
+ if (len == 0) {
+ return 0;
+ }
+ } else {
+ len = tls.last_readlen;
+ tls.last_readlen = 0;
+ }
+
+ auto &tlsconf = get_config()->tls;
+ auto via_bio =
+ tls.server_handshake && !tlsconf.session_cache.memcached.host.empty();
+
+#ifdef NGHTTP2_GENUINE_OPENSSL
+ if (!tls.early_data_finish) {
+ // TLSv1.3 handshake is still going on.
+ size_t nread;
+ auto rv = SSL_read_early_data(tls.ssl, data, len, &nread);
+ if (rv == SSL_READ_EARLY_DATA_ERROR) {
+ auto err = SSL_get_error(tls.ssl, rv);
+ switch (err) {
+ case SSL_ERROR_WANT_READ:
+ tls.last_readlen = len;
+ return 0;
+ case SSL_ERROR_SSL:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "SSL_read: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ }
+ return SHRPX_ERR_NETWORK;
+ default:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "SSL_read: SSL_get_error returned " << err;
+ }
+ return SHRPX_ERR_NETWORK;
+ }
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: read early data " << nread << " bytes";
+ }
+
+ if (rv == SSL_READ_EARLY_DATA_FINISH) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: read all early data";
+ }
+ tls.early_data_finish = true;
+ // We may have stopped write watcher in write_tls.
+ wlimit.startw();
+ }
+
+ if (!via_bio) {
+ rlimit.drain(nread);
+ }
+
+ return nread;
+ }
+#endif // NGHTTP2_GENUINE_OPENSSL
+
+ auto rv = SSL_read(tls.ssl, data, len);
+
+ if (rv <= 0) {
+ auto err = SSL_get_error(tls.ssl, rv);
+ switch (err) {
+ case SSL_ERROR_WANT_READ:
+ tls.last_readlen = len;
+ return 0;
+ case SSL_ERROR_WANT_WRITE:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Close connection due to TLS renegotiation";
+ }
+ return SHRPX_ERR_NETWORK;
+ case SSL_ERROR_ZERO_RETURN:
+ return SHRPX_ERR_EOF;
+ case SSL_ERROR_SSL:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "SSL_read: " << ERR_error_string(ERR_get_error(), nullptr);
+ }
+ return SHRPX_ERR_NETWORK;
+ default:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "SSL_read: SSL_get_error returned " << err;
+ }
+ return SHRPX_ERR_NETWORK;
+ }
+ }
+
+ if (!via_bio) {
+ rlimit.drain(rv);
+ }
+
+ return rv;
+}
+
+ssize_t Connection::write_clear(const void *data, size_t len) {
+ len = std::min(len, wlimit.avail());
+ if (len == 0) {
+ return 0;
+ }
+
+ ssize_t nwrite;
+ while ((nwrite = write(fd, data, len)) == -1 && errno == EINTR)
+ ;
+ if (nwrite == -1) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ wlimit.startw();
+ ev_timer_again(loop, &wt);
+ return 0;
+ }
+ return SHRPX_ERR_NETWORK;
+ }
+
+ wlimit.drain(nwrite);
+
+ if (ev_is_active(&wt)) {
+ ev_timer_again(loop, &wt);
+ }
+
+ return nwrite;
+}
+
+ssize_t Connection::writev_clear(struct iovec *iov, int iovcnt) {
+ iovcnt = limit_iovec(iov, iovcnt, wlimit.avail());
+ if (iovcnt == 0) {
+ return 0;
+ }
+
+ ssize_t nwrite;
+ while ((nwrite = writev(fd, iov, iovcnt)) == -1 && errno == EINTR)
+ ;
+ if (nwrite == -1) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ wlimit.startw();
+ ev_timer_again(loop, &wt);
+ return 0;
+ }
+ return SHRPX_ERR_NETWORK;
+ }
+
+ wlimit.drain(nwrite);
+
+ if (ev_is_active(&wt)) {
+ ev_timer_again(loop, &wt);
+ }
+
+ return nwrite;
+}
+
+ssize_t Connection::read_clear(void *data, size_t len) {
+ len = std::min(len, rlimit.avail());
+ if (len == 0) {
+ return 0;
+ }
+
+ ssize_t nread;
+ while ((nread = read(fd, data, len)) == -1 && errno == EINTR)
+ ;
+ if (nread == -1) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ return 0;
+ }
+ return SHRPX_ERR_NETWORK;
+ }
+
+ if (nread == 0) {
+ return SHRPX_ERR_EOF;
+ }
+
+ rlimit.drain(nread);
+
+ return nread;
+}
+
+ssize_t Connection::read_nolim_clear(void *data, size_t len) {
+ ssize_t nread;
+ while ((nread = read(fd, data, len)) == -1 && errno == EINTR)
+ ;
+ if (nread == -1) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ return 0;
+ }
+ return SHRPX_ERR_NETWORK;
+ }
+
+ if (nread == 0) {
+ return SHRPX_ERR_EOF;
+ }
+
+ return nread;
+}
+
+ssize_t Connection::peek_clear(void *data, size_t len) {
+ ssize_t nread;
+ while ((nread = recv(fd, data, len, MSG_PEEK)) == -1 && errno == EINTR)
+ ;
+ if (nread == -1) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ return 0;
+ }
+ return SHRPX_ERR_NETWORK;
+ }
+
+ if (nread == 0) {
+ return SHRPX_ERR_EOF;
+ }
+
+ return nread;
+}
+
+void Connection::handle_tls_pending_read() {
+ if (!ev_is_active(&rev)) {
+ return;
+ }
+ rlimit.handle_tls_pending_read();
+}
+
+int Connection::get_tcp_hint(TCPHint *hint) const {
+#if defined(TCP_INFO) && defined(TCP_NOTSENT_LOWAT)
+ struct tcp_info tcp_info;
+ socklen_t tcp_info_len = sizeof(tcp_info);
+ int rv;
+
+ rv = getsockopt(fd, IPPROTO_TCP, TCP_INFO, &tcp_info, &tcp_info_len);
+
+ if (rv != 0) {
+ return -1;
+ }
+
+ auto avail_packets = tcp_info.tcpi_snd_cwnd > tcp_info.tcpi_unacked
+ ? tcp_info.tcpi_snd_cwnd - tcp_info.tcpi_unacked
+ : 0;
+
+ // http://www.slideshare.net/kazuho/programming-tcp-for-responsiveness
+
+ // TODO 29 (5 (header) + 8 (explicit nonce) + 16 (tag)) is TLS
+ // overhead for AES-GCM. For CHACHA20_POLY1305, it is 21 since it
+ // does not need 8 bytes explicit nonce.
+ //
+ // For TLSv1.3, AES-GCM and CHACHA20_POLY1305 overhead are now 22
+ // bytes (5 (header) + 1 (ContentType) + 16 (tag)).
+ size_t tls_overhead;
+# ifdef TLS1_3_VERSION
+ if (SSL_version(tls.ssl) == TLS1_3_VERSION) {
+ tls_overhead = 22;
+ } else
+# endif // TLS1_3_VERSION
+ {
+ tls_overhead = 29;
+ }
+
+ auto writable_size =
+ (avail_packets + 2) * (tcp_info.tcpi_snd_mss - tls_overhead);
+ if (writable_size > 16_k) {
+ writable_size = writable_size & ~(16_k - 1);
+ } else {
+ if (writable_size < 536) {
+ LOG(INFO) << "writable_size is too small: " << writable_size;
+ }
+ // TODO is this required?
+ writable_size = std::max(writable_size, static_cast<size_t>(536 * 2));
+ }
+
+ // if (LOG_ENABLED(INFO)) {
+ // LOG(INFO) << "snd_cwnd=" << tcp_info.tcpi_snd_cwnd
+ // << ", unacked=" << tcp_info.tcpi_unacked
+ // << ", snd_mss=" << tcp_info.tcpi_snd_mss
+ // << ", rtt=" << tcp_info.tcpi_rtt << "us"
+ // << ", rcv_space=" << tcp_info.tcpi_rcv_space
+ // << ", writable=" << writable_size;
+ // }
+
+ hint->write_buffer_size = writable_size;
+ // TODO tcpi_rcv_space is considered as rwin, is that correct?
+ hint->rwin = tcp_info.tcpi_rcv_space;
+
+ return 0;
+#else // !defined(TCP_INFO) || !defined(TCP_NOTSENT_LOWAT)
+ return -1;
+#endif // !defined(TCP_INFO) || !defined(TCP_NOTSENT_LOWAT)
+}
+
+void Connection::again_rt(ev_tstamp t) {
+ read_timeout = t;
+ rt.repeat = t;
+ ev_timer_again(loop, &rt);
+ last_read = std::chrono::steady_clock::now();
+}
+
+void Connection::again_rt() {
+ rt.repeat = read_timeout;
+ ev_timer_again(loop, &rt);
+ last_read = std::chrono::steady_clock::now();
+}
+
+bool Connection::expired_rt() {
+ auto delta = read_timeout - util::ev_tstamp_from(
+ std::chrono::steady_clock::now() - last_read);
+ if (delta < 1e-9) {
+ return true;
+ }
+ rt.repeat = delta;
+ ev_timer_again(loop, &rt);
+ return false;
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_connection.h b/src/shrpx_connection.h
new file mode 100644
index 0000000..10526f7
--- /dev/null
+++ b/src/shrpx_connection.h
@@ -0,0 +1,203 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_CONNECTION_H
+#define SHRPX_CONNECTION_H
+
+#include "shrpx_config.h"
+
+#include <sys/uio.h>
+
+#include <ev.h>
+
+#include <openssl/ssl.h>
+
+#ifdef ENABLE_HTTP3
+# include <ngtcp2/ngtcp2_crypto.h>
+#endif // ENABLE_HTTP3
+
+#include "shrpx_rate_limit.h"
+#include "shrpx_error.h"
+#include "memchunk.h"
+
+namespace shrpx {
+
+struct MemcachedRequest;
+
+namespace tls {
+struct TLSSessionCache;
+} // namespace tls
+
+enum class TLSHandshakeState {
+ NORMAL,
+ WAIT_FOR_SESSION_CACHE,
+ GOT_SESSION_CACHE,
+ CANCEL_SESSION_CACHE,
+ WRITE_STARTED,
+};
+
+struct TLSConnection {
+ DefaultMemchunks wbuf;
+ DefaultPeekMemchunks rbuf;
+ // Stores TLSv1.3 early data.
+ DefaultMemchunks earlybuf;
+ SSL *ssl;
+ SSL_SESSION *cached_session;
+ MemcachedRequest *cached_session_lookup_req;
+ tls::TLSSessionCache *client_session_cache;
+ std::chrono::steady_clock::time_point last_write_idle;
+ size_t warmup_writelen;
+ // length passed to SSL_write and SSL_read last time. This is
+ // required since these functions require the exact same parameters
+ // on non-blocking I/O.
+ size_t last_writelen, last_readlen;
+ TLSHandshakeState handshake_state;
+ bool initial_handshake_done;
+ bool reneg_started;
+ // true if ssl is prepared to do handshake as server.
+ bool server_handshake;
+ // true if ssl is initialized as server, and client requested
+ // signed_certificate_timestamp extension.
+ bool sct_requested;
+ // true if TLSv1.3 early data has been completely received. Since
+ // SSL_read_early_data acts like SSL_do_handshake, this field may be
+ // true even if the negotiated TLS version is TLSv1.2 or earlier.
+ // This value is also true if this is client side connection for
+ // convenience.
+ bool early_data_finish;
+};
+
+struct TCPHint {
+ size_t write_buffer_size;
+ uint32_t rwin;
+};
+
+template <typename T> using EVCb = void (*)(struct ev_loop *, T *, int);
+
+using IOCb = EVCb<ev_io>;
+using TimerCb = EVCb<ev_timer>;
+
+struct Connection {
+ Connection(struct ev_loop *loop, int fd, SSL *ssl, MemchunkPool *mcpool,
+ ev_tstamp write_timeout, ev_tstamp read_timeout,
+ const RateLimitConfig &write_limit,
+ const RateLimitConfig &read_limit, IOCb writecb, IOCb readcb,
+ TimerCb timeoutcb, void *data, size_t tls_dyn_rec_warmup_threshold,
+ ev_tstamp tls_dyn_rec_idle_timeout, Proto proto);
+ ~Connection();
+
+ void disconnect();
+
+ void prepare_client_handshake();
+ void prepare_server_handshake();
+
+ int tls_handshake();
+ int tls_handshake_simple();
+ int write_tls_pending_handshake();
+
+ int check_http2_requirement();
+
+ // All write_* and writev_clear functions return number of bytes
+ // written. If nothing cannot be written (e.g., there is no
+ // allowance in RateLimit or underlying connection blocks), return
+ // 0. SHRPX_ERR_NETWORK is returned in case of error.
+ //
+ // All read_* functions return number of bytes read. If nothing
+ // cannot be read (e.g., there is no allowance in Ratelimit or
+ // underlying connection blocks), return 0. SHRPX_ERR_EOF is
+ // returned in case of EOF and no data was read. Otherwise
+ // SHRPX_ERR_NETWORK is return in case of error.
+ ssize_t write_tls(const void *data, size_t len);
+ ssize_t read_tls(void *data, size_t len);
+
+ size_t get_tls_write_limit();
+ // Updates the number of bytes written in warm up period.
+ void update_tls_warmup_writelen(size_t n);
+ // Tells there is no immediate write now. This triggers timer to
+ // determine fallback to short record size mode.
+ void start_tls_write_idle();
+
+ ssize_t write_clear(const void *data, size_t len);
+ ssize_t writev_clear(struct iovec *iov, int iovcnt);
+ ssize_t read_clear(void *data, size_t len);
+ // Read at most |len| bytes of data from socket without rate limit.
+ ssize_t read_nolim_clear(void *data, size_t len);
+ // Peek at most |len| bytes of data from socket without rate limit.
+ ssize_t peek_clear(void *data, size_t len);
+
+ void handle_tls_pending_read();
+
+ void set_ssl(SSL *ssl);
+
+ int get_tcp_hint(TCPHint *hint) const;
+
+ // These functions are provided for read timer which is frequently
+ // restarted. We do a trick to make a bit more efficient than just
+ // calling ev_timer_again().
+
+ // Restarts read timer with timeout value |t|.
+ void again_rt(ev_tstamp t);
+ // Restarts read timer without changing timeout.
+ void again_rt();
+ // Returns true if read timer expired.
+ bool expired_rt();
+
+#ifdef ENABLE_HTTP3
+ // This must be the first member of Connection.
+ ngtcp2_crypto_conn_ref conn_ref;
+#endif // ENABLE_HTTP3
+ TLSConnection tls;
+ ev_io wev;
+ ev_io rev;
+ ev_timer wt;
+ ev_timer rt;
+ RateLimit wlimit;
+ RateLimit rlimit;
+ struct ev_loop *loop;
+ void *data;
+ int fd;
+ size_t tls_dyn_rec_warmup_threshold;
+ std::chrono::steady_clock::duration tls_dyn_rec_idle_timeout;
+ // Application protocol used over the connection. This field is not
+ // used in this object at the moment. The rest of the program may
+ // use this value when it is useful.
+ Proto proto;
+ // The point of time when last read is observed. Note: since we use
+ // |rt| as idle timer, the activity is not limited to read.
+ std::chrono::steady_clock::time_point last_read;
+ // Timeout for read timer |rt|.
+ ev_tstamp read_timeout;
+};
+
+#ifdef ENABLE_HTTP3
+static_assert(std::is_standard_layout<Connection>::value,
+ "Connection is not standard layout");
+#endif // ENABLE_HTTP3
+
+// Creates BIO_method shared by all SSL objects.
+BIO_METHOD *create_bio_method();
+
+} // namespace shrpx
+
+#endif // SHRPX_CONNECTION_H
diff --git a/src/shrpx_connection_handler.cc b/src/shrpx_connection_handler.cc
new file mode 100644
index 0000000..330e832
--- /dev/null
+++ b/src/shrpx_connection_handler.cc
@@ -0,0 +1,1319 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_connection_handler.h"
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <cerrno>
+#include <thread>
+#include <random>
+
+#include "shrpx_client_handler.h"
+#include "shrpx_tls.h"
+#include "shrpx_worker.h"
+#include "shrpx_config.h"
+#include "shrpx_http2_session.h"
+#include "shrpx_connect_blocker.h"
+#include "shrpx_downstream_connection.h"
+#include "shrpx_accept_handler.h"
+#include "shrpx_memcached_dispatcher.h"
+#include "shrpx_signal.h"
+#include "shrpx_log.h"
+#include "xsi_strerror.h"
+#include "util.h"
+#include "template.h"
+#include "ssl_compat.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+namespace {
+void acceptor_disable_cb(struct ev_loop *loop, ev_timer *w, int revent) {
+ auto h = static_cast<ConnectionHandler *>(w->data);
+
+ // If we are in graceful shutdown period, we must not enable
+ // acceptors again.
+ if (h->get_graceful_shutdown()) {
+ return;
+ }
+
+ h->enable_acceptor();
+}
+} // namespace
+
+namespace {
+void ocsp_cb(struct ev_loop *loop, ev_timer *w, int revent) {
+ auto h = static_cast<ConnectionHandler *>(w->data);
+
+ // If we are in graceful shutdown period, we won't do ocsp query.
+ if (h->get_graceful_shutdown()) {
+ return;
+ }
+
+ LOG(NOTICE) << "Start ocsp update";
+
+ h->proceed_next_cert_ocsp();
+}
+} // namespace
+
+namespace {
+void ocsp_read_cb(struct ev_loop *loop, ev_io *w, int revent) {
+ auto h = static_cast<ConnectionHandler *>(w->data);
+
+ h->read_ocsp_chunk();
+}
+} // namespace
+
+namespace {
+void ocsp_chld_cb(struct ev_loop *loop, ev_child *w, int revent) {
+ auto h = static_cast<ConnectionHandler *>(w->data);
+
+ h->handle_ocsp_complete();
+}
+} // namespace
+
+namespace {
+void thread_join_async_cb(struct ev_loop *loop, ev_async *w, int revent) {
+ ev_break(loop);
+}
+} // namespace
+
+namespace {
+void serial_event_async_cb(struct ev_loop *loop, ev_async *w, int revent) {
+ auto h = static_cast<ConnectionHandler *>(w->data);
+
+ h->handle_serial_event();
+}
+} // namespace
+
+ConnectionHandler::ConnectionHandler(struct ev_loop *loop, std::mt19937 &gen)
+ :
+#ifdef ENABLE_HTTP3
+ quic_ipc_fd_(-1),
+#endif // ENABLE_HTTP3
+ gen_(gen),
+ single_worker_(nullptr),
+ loop_(loop),
+#ifdef HAVE_NEVERBLEED
+ nb_(nullptr),
+#endif // HAVE_NEVERBLEED
+ tls_ticket_key_memcached_get_retry_count_(0),
+ tls_ticket_key_memcached_fail_count_(0),
+ worker_round_robin_cnt_(get_config()->api.enabled ? 1 : 0),
+ graceful_shutdown_(false),
+ enable_acceptor_on_ocsp_completion_(false) {
+ ev_timer_init(&disable_acceptor_timer_, acceptor_disable_cb, 0., 0.);
+ disable_acceptor_timer_.data = this;
+
+ ev_timer_init(&ocsp_timer_, ocsp_cb, 0., 0.);
+ ocsp_timer_.data = this;
+
+ ev_io_init(&ocsp_.rev, ocsp_read_cb, -1, EV_READ);
+ ocsp_.rev.data = this;
+
+ ev_async_init(&thread_join_asyncev_, thread_join_async_cb);
+
+ ev_async_init(&serial_event_asyncev_, serial_event_async_cb);
+ serial_event_asyncev_.data = this;
+
+ ev_async_start(loop_, &serial_event_asyncev_);
+
+ ev_child_init(&ocsp_.chldev, ocsp_chld_cb, 0, 0);
+ ocsp_.chldev.data = this;
+
+ ocsp_.next = 0;
+ ocsp_.proc.rfd = -1;
+
+ reset_ocsp();
+}
+
+ConnectionHandler::~ConnectionHandler() {
+ ev_child_stop(loop_, &ocsp_.chldev);
+ ev_async_stop(loop_, &serial_event_asyncev_);
+ ev_async_stop(loop_, &thread_join_asyncev_);
+ ev_io_stop(loop_, &ocsp_.rev);
+ ev_timer_stop(loop_, &ocsp_timer_);
+ ev_timer_stop(loop_, &disable_acceptor_timer_);
+
+#ifdef ENABLE_HTTP3
+ for (auto ssl_ctx : quic_all_ssl_ctx_) {
+ if (ssl_ctx == nullptr) {
+ continue;
+ }
+
+ auto tls_ctx_data =
+ static_cast<tls::TLSContextData *>(SSL_CTX_get_app_data(ssl_ctx));
+ delete tls_ctx_data;
+ SSL_CTX_free(ssl_ctx);
+ }
+#endif // ENABLE_HTTP3
+
+ for (auto ssl_ctx : all_ssl_ctx_) {
+ auto tls_ctx_data =
+ static_cast<tls::TLSContextData *>(SSL_CTX_get_app_data(ssl_ctx));
+ delete tls_ctx_data;
+ SSL_CTX_free(ssl_ctx);
+ }
+
+ // Free workers before destroying ev_loop
+ workers_.clear();
+
+ for (auto loop : worker_loops_) {
+ ev_loop_destroy(loop);
+ }
+}
+
+void ConnectionHandler::set_ticket_keys_to_worker(
+ const std::shared_ptr<TicketKeys> &ticket_keys) {
+ for (auto &worker : workers_) {
+ worker->set_ticket_keys(ticket_keys);
+ }
+}
+
+void ConnectionHandler::worker_reopen_log_files() {
+ for (auto &worker : workers_) {
+ WorkerEvent wev{};
+
+ wev.type = WorkerEventType::REOPEN_LOG;
+
+ worker->send(std::move(wev));
+ }
+}
+
+void ConnectionHandler::worker_replace_downstream(
+ std::shared_ptr<DownstreamConfig> downstreamconf) {
+ for (auto &worker : workers_) {
+ WorkerEvent wev{};
+
+ wev.type = WorkerEventType::REPLACE_DOWNSTREAM;
+ wev.downstreamconf = downstreamconf;
+
+ worker->send(std::move(wev));
+ }
+}
+
+int ConnectionHandler::create_single_worker() {
+ cert_tree_ = tls::create_cert_lookup_tree();
+ auto sv_ssl_ctx = tls::setup_server_ssl_context(
+ all_ssl_ctx_, indexed_ssl_ctx_, cert_tree_.get()
+#ifdef HAVE_NEVERBLEED
+ ,
+ nb_
+#endif // HAVE_NEVERBLEED
+ );
+
+#ifdef ENABLE_HTTP3
+ quic_cert_tree_ = tls::create_cert_lookup_tree();
+ auto quic_sv_ssl_ctx = tls::setup_quic_server_ssl_context(
+ quic_all_ssl_ctx_, quic_indexed_ssl_ctx_, quic_cert_tree_.get()
+# ifdef HAVE_NEVERBLEED
+ ,
+ nb_
+# endif // HAVE_NEVERBLEED
+ );
+#endif // ENABLE_HTTP3
+
+ auto cl_ssl_ctx = tls::setup_downstream_client_ssl_context(
+#ifdef HAVE_NEVERBLEED
+ nb_
+#endif // HAVE_NEVERBLEED
+ );
+
+ if (cl_ssl_ctx) {
+ all_ssl_ctx_.push_back(cl_ssl_ctx);
+#ifdef ENABLE_HTTP3
+ quic_all_ssl_ctx_.push_back(nullptr);
+#endif // ENABLE_HTTP3
+ }
+
+ auto config = get_config();
+ auto &tlsconf = config->tls;
+
+ SSL_CTX *session_cache_ssl_ctx = nullptr;
+ {
+ auto &memcachedconf = config->tls.session_cache.memcached;
+ if (memcachedconf.tls) {
+ session_cache_ssl_ctx = tls::create_ssl_client_context(
+#ifdef HAVE_NEVERBLEED
+ nb_,
+#endif // HAVE_NEVERBLEED
+ tlsconf.cacert, memcachedconf.cert_file,
+ memcachedconf.private_key_file);
+ all_ssl_ctx_.push_back(session_cache_ssl_ctx);
+#ifdef ENABLE_HTTP3
+ quic_all_ssl_ctx_.push_back(nullptr);
+#endif // ENABLE_HTTP3
+ }
+ }
+
+#if defined(ENABLE_HTTP3) && defined(HAVE_LIBBPF)
+ quic_bpf_refs_.resize(config->conn.quic_listener.addrs.size());
+#endif // ENABLE_HTTP3 && HAVE_LIBBPF
+
+#ifdef ENABLE_HTTP3
+ assert(cid_prefixes_.size() == 1);
+ const auto &cid_prefix = cid_prefixes_[0];
+#endif // ENABLE_HTTP3
+
+ single_worker_ = std::make_unique<Worker>(
+ loop_, sv_ssl_ctx, cl_ssl_ctx, session_cache_ssl_ctx, cert_tree_.get(),
+#ifdef ENABLE_HTTP3
+ quic_sv_ssl_ctx, quic_cert_tree_.get(), cid_prefix.data(),
+ cid_prefix.size(),
+# ifdef HAVE_LIBBPF
+ /* index = */ 0,
+# endif // HAVE_LIBBPF
+#endif // ENABLE_HTTP3
+ ticket_keys_, this, config->conn.downstream);
+#ifdef HAVE_MRUBY
+ if (single_worker_->create_mruby_context() != 0) {
+ return -1;
+ }
+#endif // HAVE_MRUBY
+
+#ifdef ENABLE_HTTP3
+ if (single_worker_->setup_quic_server_socket() != 0) {
+ return -1;
+ }
+#endif // ENABLE_HTTP3
+
+ return 0;
+}
+
+int ConnectionHandler::create_worker_thread(size_t num) {
+#ifndef NOTHREADS
+ assert(workers_.size() == 0);
+
+ cert_tree_ = tls::create_cert_lookup_tree();
+ auto sv_ssl_ctx = tls::setup_server_ssl_context(
+ all_ssl_ctx_, indexed_ssl_ctx_, cert_tree_.get()
+# ifdef HAVE_NEVERBLEED
+ ,
+ nb_
+# endif // HAVE_NEVERBLEED
+ );
+
+# ifdef ENABLE_HTTP3
+ quic_cert_tree_ = tls::create_cert_lookup_tree();
+ auto quic_sv_ssl_ctx = tls::setup_quic_server_ssl_context(
+ quic_all_ssl_ctx_, quic_indexed_ssl_ctx_, quic_cert_tree_.get()
+# ifdef HAVE_NEVERBLEED
+ ,
+ nb_
+# endif // HAVE_NEVERBLEED
+ );
+# endif // ENABLE_HTTP3
+
+ auto cl_ssl_ctx = tls::setup_downstream_client_ssl_context(
+# ifdef HAVE_NEVERBLEED
+ nb_
+# endif // HAVE_NEVERBLEED
+ );
+
+ if (cl_ssl_ctx) {
+ all_ssl_ctx_.push_back(cl_ssl_ctx);
+# ifdef ENABLE_HTTP3
+ quic_all_ssl_ctx_.push_back(nullptr);
+# endif // ENABLE_HTTP3
+ }
+
+ auto config = get_config();
+ auto &tlsconf = config->tls;
+ auto &apiconf = config->api;
+
+# if defined(ENABLE_HTTP3) && defined(HAVE_LIBBPF)
+ quic_bpf_refs_.resize(config->conn.quic_listener.addrs.size());
+# endif // ENABLE_HTTP3 && HAVE_LIBBPF
+
+ // We have dedicated worker for API request processing.
+ if (apiconf.enabled) {
+ ++num;
+ }
+
+ SSL_CTX *session_cache_ssl_ctx = nullptr;
+ {
+ auto &memcachedconf = config->tls.session_cache.memcached;
+
+ if (memcachedconf.tls) {
+ session_cache_ssl_ctx = tls::create_ssl_client_context(
+# ifdef HAVE_NEVERBLEED
+ nb_,
+# endif // HAVE_NEVERBLEED
+ tlsconf.cacert, memcachedconf.cert_file,
+ memcachedconf.private_key_file);
+ all_ssl_ctx_.push_back(session_cache_ssl_ctx);
+# ifdef ENABLE_HTTP3
+ quic_all_ssl_ctx_.push_back(nullptr);
+# endif // ENABLE_HTTP3
+ }
+ }
+
+# ifdef ENABLE_HTTP3
+ assert(cid_prefixes_.size() == num);
+# endif // ENABLE_HTTP3
+
+ for (size_t i = 0; i < num; ++i) {
+ auto loop = ev_loop_new(config->ev_loop_flags);
+
+# ifdef ENABLE_HTTP3
+ const auto &cid_prefix = cid_prefixes_[i];
+# endif // ENABLE_HTTP3
+
+ auto worker = std::make_unique<Worker>(
+ loop, sv_ssl_ctx, cl_ssl_ctx, session_cache_ssl_ctx, cert_tree_.get(),
+# ifdef ENABLE_HTTP3
+ quic_sv_ssl_ctx, quic_cert_tree_.get(), cid_prefix.data(),
+ cid_prefix.size(),
+# ifdef HAVE_LIBBPF
+ i,
+# endif // HAVE_LIBBPF
+# endif // ENABLE_HTTP3
+ ticket_keys_, this, config->conn.downstream);
+# ifdef HAVE_MRUBY
+ if (worker->create_mruby_context() != 0) {
+ return -1;
+ }
+# endif // HAVE_MRUBY
+
+# ifdef ENABLE_HTTP3
+ if ((!apiconf.enabled || i != 0) &&
+ worker->setup_quic_server_socket() != 0) {
+ return -1;
+ }
+# endif // ENABLE_HTTP3
+
+ workers_.push_back(std::move(worker));
+ worker_loops_.push_back(loop);
+
+ LLOG(NOTICE, this) << "Created worker thread #" << workers_.size() - 1;
+ }
+
+ for (auto &worker : workers_) {
+ worker->run_async();
+ }
+
+#endif // NOTHREADS
+
+ return 0;
+}
+
+void ConnectionHandler::join_worker() {
+#ifndef NOTHREADS
+ int n = 0;
+
+ if (LOG_ENABLED(INFO)) {
+ LLOG(INFO, this) << "Waiting for worker thread to join: n="
+ << workers_.size();
+ }
+
+ for (auto &worker : workers_) {
+ worker->wait();
+ if (LOG_ENABLED(INFO)) {
+ LLOG(INFO, this) << "Thread #" << n << " joined";
+ }
+ ++n;
+ }
+#endif // NOTHREADS
+}
+
+void ConnectionHandler::graceful_shutdown_worker() {
+ if (single_worker_) {
+ return;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LLOG(INFO, this) << "Sending graceful shutdown signal to worker";
+ }
+
+ for (auto &worker : workers_) {
+ WorkerEvent wev{};
+ wev.type = WorkerEventType::GRACEFUL_SHUTDOWN;
+
+ worker->send(std::move(wev));
+ }
+
+#ifndef NOTHREADS
+ ev_async_start(loop_, &thread_join_asyncev_);
+
+ thread_join_fut_ = std::async(std::launch::async, [this]() {
+ (void)reopen_log_files(get_config()->logging);
+ join_worker();
+ ev_async_send(get_loop(), &thread_join_asyncev_);
+ delete_log_config();
+ });
+#endif // NOTHREADS
+}
+
+int ConnectionHandler::handle_connection(int fd, sockaddr *addr, int addrlen,
+ const UpstreamAddr *faddr) {
+ if (LOG_ENABLED(INFO)) {
+ LLOG(INFO, this) << "Accepted connection from "
+ << util::numeric_name(addr, addrlen) << ", fd=" << fd;
+ }
+
+ auto config = get_config();
+
+ if (single_worker_) {
+ auto &upstreamconf = config->conn.upstream;
+ if (single_worker_->get_worker_stat()->num_connections >=
+ upstreamconf.worker_connections) {
+
+ if (LOG_ENABLED(INFO)) {
+ LLOG(INFO, this) << "Too many connections >="
+ << upstreamconf.worker_connections;
+ }
+
+ close(fd);
+ return -1;
+ }
+
+ auto client =
+ tls::accept_connection(single_worker_.get(), fd, addr, addrlen, faddr);
+ if (!client) {
+ LLOG(ERROR, this) << "ClientHandler creation failed";
+
+ close(fd);
+ return -1;
+ }
+
+ return 0;
+ }
+
+ Worker *worker;
+
+ if (faddr->alt_mode == UpstreamAltMode::API) {
+ worker = workers_[0].get();
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Dispatch connection to API worker #0";
+ }
+ } else {
+ worker = workers_[worker_round_robin_cnt_].get();
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Dispatch connection to worker #" << worker_round_robin_cnt_;
+ }
+
+ if (++worker_round_robin_cnt_ == workers_.size()) {
+ auto &apiconf = config->api;
+
+ if (apiconf.enabled) {
+ worker_round_robin_cnt_ = 1;
+ } else {
+ worker_round_robin_cnt_ = 0;
+ }
+ }
+ }
+
+ WorkerEvent wev{};
+ wev.type = WorkerEventType::NEW_CONNECTION;
+ wev.client_fd = fd;
+ memcpy(&wev.client_addr, addr, addrlen);
+ wev.client_addrlen = addrlen;
+ wev.faddr = faddr;
+
+ worker->send(std::move(wev));
+
+ return 0;
+}
+
+struct ev_loop *ConnectionHandler::get_loop() const { return loop_; }
+
+Worker *ConnectionHandler::get_single_worker() const {
+ return single_worker_.get();
+}
+
+void ConnectionHandler::add_acceptor(std::unique_ptr<AcceptHandler> h) {
+ acceptors_.push_back(std::move(h));
+}
+
+void ConnectionHandler::delete_acceptor() { acceptors_.clear(); }
+
+void ConnectionHandler::enable_acceptor() {
+ for (auto &a : acceptors_) {
+ a->enable();
+ }
+}
+
+void ConnectionHandler::disable_acceptor() {
+ for (auto &a : acceptors_) {
+ a->disable();
+ }
+}
+
+void ConnectionHandler::sleep_acceptor(ev_tstamp t) {
+ if (t == 0. || ev_is_active(&disable_acceptor_timer_)) {
+ return;
+ }
+
+ disable_acceptor();
+
+ ev_timer_set(&disable_acceptor_timer_, t, 0.);
+ ev_timer_start(loop_, &disable_acceptor_timer_);
+}
+
+void ConnectionHandler::accept_pending_connection() {
+ for (auto &a : acceptors_) {
+ a->accept_connection();
+ }
+}
+
+void ConnectionHandler::set_ticket_keys(
+ std::shared_ptr<TicketKeys> ticket_keys) {
+ ticket_keys_ = std::move(ticket_keys);
+ if (single_worker_) {
+ single_worker_->set_ticket_keys(ticket_keys_);
+ }
+}
+
+const std::shared_ptr<TicketKeys> &ConnectionHandler::get_ticket_keys() const {
+ return ticket_keys_;
+}
+
+void ConnectionHandler::set_graceful_shutdown(bool f) {
+ graceful_shutdown_ = f;
+ if (single_worker_) {
+ single_worker_->set_graceful_shutdown(f);
+ }
+}
+
+bool ConnectionHandler::get_graceful_shutdown() const {
+ return graceful_shutdown_;
+}
+
+void ConnectionHandler::cancel_ocsp_update() {
+ enable_acceptor_on_ocsp_completion_ = false;
+ ev_timer_stop(loop_, &ocsp_timer_);
+
+ if (ocsp_.proc.pid == 0) {
+ return;
+ }
+
+ int rv;
+
+ rv = kill(ocsp_.proc.pid, SIGTERM);
+ if (rv != 0) {
+ auto error = errno;
+ LOG(ERROR) << "Could not send signal to OCSP query process: errno="
+ << error;
+ }
+
+ while ((rv = waitpid(ocsp_.proc.pid, nullptr, 0)) == -1 && errno == EINTR)
+ ;
+ if (rv == -1) {
+ auto error = errno;
+ LOG(ERROR) << "Error occurred while we were waiting for the completion of "
+ "OCSP query process: errno="
+ << error;
+ }
+}
+
+// inspired by h2o_read_command function from h2o project:
+// https://github.com/h2o/h2o
+int ConnectionHandler::start_ocsp_update(const char *cert_file) {
+ int rv;
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Start ocsp update for " << cert_file;
+ }
+
+ assert(!ev_is_active(&ocsp_.rev));
+ assert(!ev_is_active(&ocsp_.chldev));
+
+ char *const argv[] = {
+ const_cast<char *>(
+ get_config()->tls.ocsp.fetch_ocsp_response_file.c_str()),
+ const_cast<char *>(cert_file), nullptr};
+
+ Process proc;
+ rv = exec_read_command(proc, argv);
+ if (rv != 0) {
+ return -1;
+ }
+
+ ocsp_.proc = proc;
+
+ ev_io_set(&ocsp_.rev, ocsp_.proc.rfd, EV_READ);
+ ev_io_start(loop_, &ocsp_.rev);
+
+ ev_child_set(&ocsp_.chldev, ocsp_.proc.pid, 0);
+ ev_child_start(loop_, &ocsp_.chldev);
+
+ return 0;
+}
+
+void ConnectionHandler::read_ocsp_chunk() {
+ std::array<uint8_t, 4_k> buf;
+ for (;;) {
+ ssize_t n;
+ while ((n = read(ocsp_.proc.rfd, buf.data(), buf.size())) == -1 &&
+ errno == EINTR)
+ ;
+
+ if (n == -1) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ return;
+ }
+ auto error = errno;
+ LOG(WARN) << "Reading from ocsp query command failed: errno=" << error;
+ ocsp_.error = error;
+
+ break;
+ }
+
+ if (n == 0) {
+ break;
+ }
+
+ std::copy_n(std::begin(buf), n, std::back_inserter(ocsp_.resp));
+ }
+
+ ev_io_stop(loop_, &ocsp_.rev);
+}
+
+void ConnectionHandler::handle_ocsp_complete() {
+ ev_io_stop(loop_, &ocsp_.rev);
+ ev_child_stop(loop_, &ocsp_.chldev);
+
+ assert(ocsp_.next < all_ssl_ctx_.size());
+#ifdef ENABLE_HTTP3
+ assert(all_ssl_ctx_.size() == quic_all_ssl_ctx_.size());
+#endif // ENABLE_HTTP3
+
+ auto ssl_ctx = all_ssl_ctx_[ocsp_.next];
+ auto tls_ctx_data =
+ static_cast<tls::TLSContextData *>(SSL_CTX_get_app_data(ssl_ctx));
+
+ auto rstatus = ocsp_.chldev.rstatus;
+ auto status = WEXITSTATUS(rstatus);
+ if (ocsp_.error || !WIFEXITED(rstatus) || status != 0) {
+ LOG(WARN) << "ocsp query command for " << tls_ctx_data->cert_file
+ << " failed: error=" << ocsp_.error << ", rstatus=" << log::hex
+ << rstatus << log::dec << ", status=" << status;
+ ++ocsp_.next;
+ proceed_next_cert_ocsp();
+ return;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "ocsp update for " << tls_ctx_data->cert_file
+ << " finished successfully";
+ }
+
+ auto config = get_config();
+ auto &tlsconf = config->tls;
+
+ if (tlsconf.ocsp.no_verify ||
+ tls::verify_ocsp_response(ssl_ctx, ocsp_.resp.data(),
+ ocsp_.resp.size()) == 0) {
+#ifdef ENABLE_HTTP3
+ // We have list of SSL_CTX with the same certificate in
+ // quic_all_ssl_ctx_ as well. Some SSL_CTXs are missing there in
+ // that case we get nullptr.
+ auto quic_ssl_ctx = quic_all_ssl_ctx_[ocsp_.next];
+ if (quic_ssl_ctx) {
+# ifndef NGHTTP2_OPENSSL_IS_BORINGSSL
+ auto quic_tls_ctx_data = static_cast<tls::TLSContextData *>(
+ SSL_CTX_get_app_data(quic_ssl_ctx));
+# ifdef HAVE_ATOMIC_STD_SHARED_PTR
+ std::atomic_store_explicit(
+ &quic_tls_ctx_data->ocsp_data,
+ std::make_shared<std::vector<uint8_t>>(ocsp_.resp),
+ std::memory_order_release);
+# else // !HAVE_ATOMIC_STD_SHARED_PTR
+ std::lock_guard<std::mutex> g(quic_tls_ctx_data->mu);
+ quic_tls_ctx_data->ocsp_data =
+ std::make_shared<std::vector<uint8_t>>(ocsp_.resp);
+# endif // !HAVE_ATOMIC_STD_SHARED_PTR
+# else // NGHTTP2_OPENSSL_IS_BORINGSSL
+ SSL_CTX_set_ocsp_response(quic_ssl_ctx, ocsp_.resp.data(),
+ ocsp_.resp.size());
+# endif // NGHTTP2_OPENSSL_IS_BORINGSSL
+ }
+#endif // ENABLE_HTTP3
+
+#ifndef NGHTTP2_OPENSSL_IS_BORINGSSL
+# ifdef HAVE_ATOMIC_STD_SHARED_PTR
+ std::atomic_store_explicit(
+ &tls_ctx_data->ocsp_data,
+ std::make_shared<std::vector<uint8_t>>(std::move(ocsp_.resp)),
+ std::memory_order_release);
+# else // !HAVE_ATOMIC_STD_SHARED_PTR
+ std::lock_guard<std::mutex> g(tls_ctx_data->mu);
+ tls_ctx_data->ocsp_data =
+ std::make_shared<std::vector<uint8_t>>(std::move(ocsp_.resp));
+# endif // !HAVE_ATOMIC_STD_SHARED_PTR
+#else // NGHTTP2_OPENSSL_IS_BORINGSSL
+ SSL_CTX_set_ocsp_response(ssl_ctx, ocsp_.resp.data(), ocsp_.resp.size());
+#endif // NGHTTP2_OPENSSL_IS_BORINGSSL
+ }
+
+ ++ocsp_.next;
+ proceed_next_cert_ocsp();
+}
+
+void ConnectionHandler::reset_ocsp() {
+ if (ocsp_.proc.rfd != -1) {
+ close(ocsp_.proc.rfd);
+ }
+
+ ocsp_.proc.rfd = -1;
+ ocsp_.proc.pid = 0;
+ ocsp_.error = 0;
+ ocsp_.resp = std::vector<uint8_t>();
+}
+
+void ConnectionHandler::proceed_next_cert_ocsp() {
+ for (;;) {
+ reset_ocsp();
+ if (ocsp_.next == all_ssl_ctx_.size()) {
+ ocsp_.next = 0;
+ // We have updated all ocsp response, and schedule next update.
+ ev_timer_set(&ocsp_timer_, get_config()->tls.ocsp.update_interval, 0.);
+ ev_timer_start(loop_, &ocsp_timer_);
+
+ if (enable_acceptor_on_ocsp_completion_) {
+ enable_acceptor_on_ocsp_completion_ = false;
+ enable_acceptor();
+ }
+
+ return;
+ }
+
+ auto ssl_ctx = all_ssl_ctx_[ocsp_.next];
+ auto tls_ctx_data =
+ static_cast<tls::TLSContextData *>(SSL_CTX_get_app_data(ssl_ctx));
+
+ // client SSL_CTX is also included in all_ssl_ctx_, but has no
+ // tls_ctx_data.
+ if (!tls_ctx_data) {
+ ++ocsp_.next;
+ continue;
+ }
+
+ auto cert_file = tls_ctx_data->cert_file;
+
+ if (start_ocsp_update(cert_file) != 0) {
+ ++ocsp_.next;
+ continue;
+ }
+
+ break;
+ }
+}
+
+void ConnectionHandler::set_tls_ticket_key_memcached_dispatcher(
+ std::unique_ptr<MemcachedDispatcher> dispatcher) {
+ tls_ticket_key_memcached_dispatcher_ = std::move(dispatcher);
+}
+
+MemcachedDispatcher *
+ConnectionHandler::get_tls_ticket_key_memcached_dispatcher() const {
+ return tls_ticket_key_memcached_dispatcher_.get();
+}
+
+// Use the similar backoff algorithm described in
+// https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md
+namespace {
+constexpr size_t MAX_BACKOFF_EXP = 10;
+constexpr auto MULTIPLIER = 3.2;
+constexpr auto JITTER = 0.2;
+} // namespace
+
+void ConnectionHandler::on_tls_ticket_key_network_error(ev_timer *w) {
+ if (++tls_ticket_key_memcached_get_retry_count_ >=
+ get_config()->tls.ticket.memcached.max_retry) {
+ LOG(WARN) << "Memcached: tls ticket get retry all failed "
+ << tls_ticket_key_memcached_get_retry_count_ << " times.";
+
+ on_tls_ticket_key_not_found(w);
+ return;
+ }
+
+ auto base_backoff = util::int_pow(
+ MULTIPLIER,
+ std::min(MAX_BACKOFF_EXP, tls_ticket_key_memcached_get_retry_count_));
+ auto dist = std::uniform_real_distribution<>(-JITTER * base_backoff,
+ JITTER * base_backoff);
+
+ auto backoff = base_backoff + dist(gen_);
+
+ LOG(WARN)
+ << "Memcached: tls ticket get failed due to network error, retrying in "
+ << backoff << " seconds";
+
+ ev_timer_set(w, backoff, 0.);
+ ev_timer_start(loop_, w);
+}
+
+void ConnectionHandler::on_tls_ticket_key_not_found(ev_timer *w) {
+ tls_ticket_key_memcached_get_retry_count_ = 0;
+
+ if (++tls_ticket_key_memcached_fail_count_ >=
+ get_config()->tls.ticket.memcached.max_fail) {
+ LOG(WARN) << "Memcached: could not get tls ticket; disable tls ticket";
+
+ tls_ticket_key_memcached_fail_count_ = 0;
+
+ set_ticket_keys(nullptr);
+ set_ticket_keys_to_worker(nullptr);
+ }
+
+ LOG(WARN) << "Memcached: tls ticket get failed, schedule next";
+ schedule_next_tls_ticket_key_memcached_get(w);
+}
+
+void ConnectionHandler::on_tls_ticket_key_get_success(
+ const std::shared_ptr<TicketKeys> &ticket_keys, ev_timer *w) {
+ LOG(NOTICE) << "Memcached: tls ticket get success";
+
+ tls_ticket_key_memcached_get_retry_count_ = 0;
+ tls_ticket_key_memcached_fail_count_ = 0;
+
+ schedule_next_tls_ticket_key_memcached_get(w);
+
+ if (!ticket_keys || ticket_keys->keys.empty()) {
+ LOG(WARN) << "Memcached: tls ticket keys are empty; tls ticket disabled";
+ set_ticket_keys(nullptr);
+ set_ticket_keys_to_worker(nullptr);
+ return;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "ticket keys get done";
+ 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);
+ }
+ }
+
+ set_ticket_keys(ticket_keys);
+ set_ticket_keys_to_worker(ticket_keys);
+}
+
+void ConnectionHandler::schedule_next_tls_ticket_key_memcached_get(
+ ev_timer *w) {
+ ev_timer_set(w, get_config()->tls.ticket.memcached.interval, 0.);
+ ev_timer_start(loop_, w);
+}
+
+SSL_CTX *ConnectionHandler::create_tls_ticket_key_memcached_ssl_ctx() {
+ auto config = get_config();
+ auto &tlsconf = config->tls;
+ auto &memcachedconf = config->tls.ticket.memcached;
+
+ auto ssl_ctx = tls::create_ssl_client_context(
+#ifdef HAVE_NEVERBLEED
+ nb_,
+#endif // HAVE_NEVERBLEED
+ tlsconf.cacert, memcachedconf.cert_file, memcachedconf.private_key_file);
+
+ all_ssl_ctx_.push_back(ssl_ctx);
+#ifdef ENABLE_HTTP3
+ quic_all_ssl_ctx_.push_back(nullptr);
+#endif // ENABLE_HTTP3
+
+ return ssl_ctx;
+}
+
+#ifdef HAVE_NEVERBLEED
+void ConnectionHandler::set_neverbleed(neverbleed_t *nb) { nb_ = nb; }
+#endif // HAVE_NEVERBLEED
+
+void ConnectionHandler::handle_serial_event() {
+ std::vector<SerialEvent> q;
+ {
+ std::lock_guard<std::mutex> g(serial_event_mu_);
+ q.swap(serial_events_);
+ }
+
+ for (auto &sev : q) {
+ switch (sev.type) {
+ case SerialEventType::REPLACE_DOWNSTREAM:
+ // Mmake sure that none of worker uses
+ // get_config()->conn.downstream
+ mod_config()->conn.downstream = sev.downstreamconf;
+
+ if (single_worker_) {
+ single_worker_->replace_downstream_config(sev.downstreamconf);
+
+ break;
+ }
+
+ worker_replace_downstream(sev.downstreamconf);
+
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void ConnectionHandler::send_replace_downstream(
+ const std::shared_ptr<DownstreamConfig> &downstreamconf) {
+ send_serial_event(
+ SerialEvent(SerialEventType::REPLACE_DOWNSTREAM, downstreamconf));
+}
+
+void ConnectionHandler::send_serial_event(SerialEvent ev) {
+ {
+ std::lock_guard<std::mutex> g(serial_event_mu_);
+
+ serial_events_.push_back(std::move(ev));
+ }
+
+ ev_async_send(loop_, &serial_event_asyncev_);
+}
+
+SSL_CTX *ConnectionHandler::get_ssl_ctx(size_t idx) const {
+ return all_ssl_ctx_[idx];
+}
+
+const std::vector<SSL_CTX *> &
+ConnectionHandler::get_indexed_ssl_ctx(size_t idx) const {
+ return indexed_ssl_ctx_[idx];
+}
+
+#ifdef ENABLE_HTTP3
+const std::vector<SSL_CTX *> &
+ConnectionHandler::get_quic_indexed_ssl_ctx(size_t idx) const {
+ return quic_indexed_ssl_ctx_[idx];
+}
+#endif // ENABLE_HTTP3
+
+void ConnectionHandler::set_enable_acceptor_on_ocsp_completion(bool f) {
+ enable_acceptor_on_ocsp_completion_ = f;
+}
+
+#ifdef ENABLE_HTTP3
+int ConnectionHandler::forward_quic_packet(
+ const UpstreamAddr *faddr, const Address &remote_addr,
+ const Address &local_addr, const ngtcp2_pkt_info &pi,
+ const uint8_t *cid_prefix, const uint8_t *data, size_t datalen) {
+ assert(!get_config()->single_thread);
+
+ for (auto &worker : workers_) {
+ if (!std::equal(cid_prefix, cid_prefix + SHRPX_QUIC_CID_PREFIXLEN,
+ worker->get_cid_prefix())) {
+ continue;
+ }
+
+ WorkerEvent wev{};
+ wev.type = WorkerEventType::QUIC_PKT_FORWARD;
+ wev.quic_pkt = std::make_unique<QUICPacket>(faddr->index, remote_addr,
+ local_addr, pi, data, datalen);
+
+ worker->send(std::move(wev));
+
+ return 0;
+ }
+
+ return -1;
+}
+
+void ConnectionHandler::set_quic_keying_materials(
+ std::shared_ptr<QUICKeyingMaterials> qkms) {
+ quic_keying_materials_ = std::move(qkms);
+}
+
+const std::shared_ptr<QUICKeyingMaterials> &
+ConnectionHandler::get_quic_keying_materials() const {
+ return quic_keying_materials_;
+}
+
+void ConnectionHandler::set_cid_prefixes(
+ const std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>>
+ &cid_prefixes) {
+ cid_prefixes_ = cid_prefixes;
+}
+
+QUICLingeringWorkerProcess *
+ConnectionHandler::match_quic_lingering_worker_process_cid_prefix(
+ const uint8_t *dcid, size_t dcidlen) {
+ assert(dcidlen >= SHRPX_QUIC_CID_PREFIXLEN);
+
+ for (auto &lwps : quic_lingering_worker_processes_) {
+ for (auto &cid_prefix : lwps.cid_prefixes) {
+ if (std::equal(std::begin(cid_prefix), std::end(cid_prefix), dcid)) {
+ return &lwps;
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+# ifdef HAVE_LIBBPF
+std::vector<BPFRef> &ConnectionHandler::get_quic_bpf_refs() {
+ return quic_bpf_refs_;
+}
+
+void ConnectionHandler::unload_bpf_objects() {
+ LOG(NOTICE) << "Unloading BPF objects";
+
+ for (auto &ref : quic_bpf_refs_) {
+ if (ref.obj == nullptr) {
+ continue;
+ }
+
+ bpf_object__close(ref.obj);
+
+ ref.obj = nullptr;
+ }
+}
+# endif // HAVE_LIBBPF
+
+void ConnectionHandler::set_quic_ipc_fd(int fd) { quic_ipc_fd_ = fd; }
+
+void ConnectionHandler::set_quic_lingering_worker_processes(
+ const std::vector<QUICLingeringWorkerProcess> &quic_lwps) {
+ quic_lingering_worker_processes_ = quic_lwps;
+}
+
+int ConnectionHandler::forward_quic_packet_to_lingering_worker_process(
+ QUICLingeringWorkerProcess *quic_lwp, const Address &remote_addr,
+ const Address &local_addr, const ngtcp2_pkt_info &pi, const uint8_t *data,
+ size_t datalen) {
+ std::array<uint8_t, 512> header;
+
+ assert(header.size() >= 1 + 1 + 1 + 1 + sizeof(sockaddr_storage) * 2);
+ assert(remote_addr.len > 0);
+ assert(local_addr.len > 0);
+
+ auto p = header.data();
+
+ *p++ = static_cast<uint8_t>(QUICIPCType::DGRAM_FORWARD);
+ *p++ = static_cast<uint8_t>(remote_addr.len - 1);
+ p = std::copy_n(reinterpret_cast<const uint8_t *>(&remote_addr.su),
+ remote_addr.len, p);
+ *p++ = static_cast<uint8_t>(local_addr.len - 1);
+ p = std::copy_n(reinterpret_cast<const uint8_t *>(&local_addr.su),
+ local_addr.len, p);
+ *p++ = pi.ecn;
+
+ iovec msg_iov[] = {
+ {
+ .iov_base = header.data(),
+ .iov_len = static_cast<size_t>(p - header.data()),
+ },
+ {
+ .iov_base = const_cast<uint8_t *>(data),
+ .iov_len = datalen,
+ },
+ };
+
+ msghdr msg{};
+ msg.msg_iov = msg_iov;
+ msg.msg_iovlen = array_size(msg_iov);
+
+ ssize_t nwrite;
+
+ while ((nwrite = sendmsg(quic_lwp->quic_ipc_fd, &msg, 0)) == -1 &&
+ errno == EINTR)
+ ;
+
+ if (nwrite == -1) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+
+ auto error = errno;
+ LOG(ERROR) << "Failed to send QUIC IPC message: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+
+ return -1;
+ }
+
+ return 0;
+}
+
+int ConnectionHandler::quic_ipc_read() {
+ std::array<uint8_t, 65536> buf;
+
+ ssize_t nread;
+
+ while ((nread = recv(quic_ipc_fd_, buf.data(), buf.size(), 0)) == -1 &&
+ errno == EINTR)
+ ;
+
+ if (nread == -1) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+
+ auto error = errno;
+ LOG(ERROR) << "Failed to read data from QUIC IPC channel: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+
+ return -1;
+ }
+
+ if (nread == 0) {
+ return 0;
+ }
+
+ size_t len = 1 + 1 + 1 + 1;
+
+ // Wire format:
+ // TYPE(1) REMOTE_ADDRLEN(1) REMOTE_ADDR(N) LOCAL_ADDRLEN(1) LOCAL_ADDR(N)
+ // ECN(1) DGRAM_PAYLOAD(N)
+ //
+ // When encoding, REMOTE_ADDRLEN and LOCAL_ADDRLEN are decremented
+ // by 1.
+ if (static_cast<size_t>(nread) < len) {
+ return 0;
+ }
+
+ auto p = buf.data();
+ if (*p != static_cast<uint8_t>(QUICIPCType::DGRAM_FORWARD)) {
+ LOG(ERROR) << "Unknown QUICIPCType: " << static_cast<uint32_t>(*p);
+
+ return -1;
+ }
+
+ ++p;
+
+ auto pkt = std::make_unique<QUICPacket>();
+
+ auto remote_addrlen = static_cast<size_t>(*p++) + 1;
+ if (remote_addrlen > sizeof(sockaddr_storage)) {
+ LOG(ERROR) << "The length of remote address is too large: "
+ << remote_addrlen;
+
+ return -1;
+ }
+
+ len += remote_addrlen;
+
+ if (static_cast<size_t>(nread) < len) {
+ LOG(ERROR) << "Insufficient QUIC IPC message length";
+
+ return -1;
+ }
+
+ pkt->remote_addr.len = remote_addrlen;
+ memcpy(&pkt->remote_addr.su, p, remote_addrlen);
+
+ p += remote_addrlen;
+
+ auto local_addrlen = static_cast<size_t>(*p++) + 1;
+ if (local_addrlen > sizeof(sockaddr_storage)) {
+ LOG(ERROR) << "The length of local address is too large: " << local_addrlen;
+
+ return -1;
+ }
+
+ len += local_addrlen;
+
+ if (static_cast<size_t>(nread) < len) {
+ LOG(ERROR) << "Insufficient QUIC IPC message length";
+
+ return -1;
+ }
+
+ pkt->local_addr.len = local_addrlen;
+ memcpy(&pkt->local_addr.su, p, local_addrlen);
+
+ p += local_addrlen;
+
+ pkt->pi.ecn = *p++;
+
+ auto datalen = nread - (p - buf.data());
+
+ pkt->data.assign(p, p + datalen);
+
+ // At the moment, UpstreamAddr index is unknown.
+ pkt->upstream_addr_index = static_cast<size_t>(-1);
+
+ ngtcp2_version_cid vc;
+
+ auto rv = ngtcp2_pkt_decode_version_cid(&vc, p, datalen, SHRPX_QUIC_SCIDLEN);
+ if (rv < 0) {
+ LOG(ERROR) << "ngtcp2_pkt_decode_version_cid: " << ngtcp2_strerror(rv);
+
+ return -1;
+ }
+
+ if (vc.dcidlen != SHRPX_QUIC_SCIDLEN) {
+ LOG(ERROR) << "DCID length is invalid";
+ return -1;
+ }
+
+ if (single_worker_) {
+ auto faddr = single_worker_->find_quic_upstream_addr(pkt->local_addr);
+ if (faddr == nullptr) {
+ LOG(ERROR) << "No suitable upstream address found";
+
+ return 0;
+ }
+
+ auto quic_conn_handler = single_worker_->get_quic_connection_handler();
+
+ // Ignore return value
+ quic_conn_handler->handle_packet(faddr, pkt->remote_addr, pkt->local_addr,
+ pkt->pi, pkt->data.data(),
+ pkt->data.size());
+
+ return 0;
+ }
+
+ auto &qkm = quic_keying_materials_->keying_materials.front();
+
+ std::array<uint8_t, SHRPX_QUIC_DECRYPTED_DCIDLEN> decrypted_dcid;
+
+ if (decrypt_quic_connection_id(decrypted_dcid.data(),
+ vc.dcid + SHRPX_QUIC_CID_PREFIX_OFFSET,
+ qkm.cid_encryption_key.data()) != 0) {
+ return -1;
+ }
+
+ for (auto &worker : workers_) {
+ if (!std::equal(std::begin(decrypted_dcid),
+ std::begin(decrypted_dcid) + SHRPX_QUIC_CID_PREFIXLEN,
+ worker->get_cid_prefix())) {
+ continue;
+ }
+
+ WorkerEvent wev{
+ .type = WorkerEventType::QUIC_PKT_FORWARD,
+ .quic_pkt = std::move(pkt),
+ };
+ worker->send(std::move(wev));
+
+ return 0;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "No worker to match CID prefix";
+ }
+
+ return 0;
+}
+#endif // ENABLE_HTTP3
+
+} // namespace shrpx
diff --git a/src/shrpx_connection_handler.h b/src/shrpx_connection_handler.h
new file mode 100644
index 0000000..f3748ab
--- /dev/null
+++ b/src/shrpx_connection_handler.h
@@ -0,0 +1,322 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_CONNECTION_HANDLER_H
+#define SHRPX_CONNECTION_HANDLER_H
+
+#include "shrpx.h"
+
+#include <sys/types.h>
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif // HAVE_SYS_SOCKET_H
+
+#include <mutex>
+#include <memory>
+#include <vector>
+#include <random>
+#ifndef NOTHREADS
+# include <future>
+#endif // NOTHREADS
+
+#ifdef HAVE_LIBBPF
+# include <bpf/libbpf.h>
+#endif // HAVE_LIBBPF
+
+#include <openssl/ssl.h>
+
+#include <ev.h>
+
+#ifdef HAVE_NEVERBLEED
+# include <neverbleed.h>
+#endif // HAVE_NEVERBLEED
+
+#include "shrpx_downstream_connection_pool.h"
+#include "shrpx_config.h"
+#include "shrpx_exec.h"
+
+namespace shrpx {
+
+class Http2Session;
+class ConnectBlocker;
+class AcceptHandler;
+class Worker;
+struct WorkerStat;
+struct TicketKeys;
+class MemcachedDispatcher;
+struct UpstreamAddr;
+
+namespace tls {
+
+class CertLookupTree;
+
+} // namespace tls
+
+struct OCSPUpdateContext {
+ // ocsp response buffer
+ std::vector<uint8_t> resp;
+ // Process running fetch-ocsp-response script
+ Process proc;
+ // index to ConnectionHandler::all_ssl_ctx_, which points to next
+ // SSL_CTX to update ocsp response cache.
+ size_t next;
+ ev_child chldev;
+ ev_io rev;
+ // errno encountered while processing response
+ int error;
+};
+
+// SerialEvent is an event sent from Worker thread.
+enum class SerialEventType {
+ NONE,
+ REPLACE_DOWNSTREAM,
+};
+
+struct SerialEvent {
+ // ctor for event uses DownstreamConfig
+ SerialEvent(SerialEventType type,
+ const std::shared_ptr<DownstreamConfig> &downstreamconf)
+ : type(type), downstreamconf(downstreamconf) {}
+
+ SerialEventType type;
+ std::shared_ptr<DownstreamConfig> downstreamconf;
+};
+
+#ifdef ENABLE_HTTP3
+# ifdef HAVE_LIBBPF
+struct BPFRef {
+ bpf_object *obj;
+ bpf_map *reuseport_array;
+ bpf_map *cid_prefix_map;
+};
+# endif // HAVE_LIBBPF
+
+// QUIC IPC message type.
+enum class QUICIPCType {
+ NONE,
+ // Send forwarded QUIC UDP datagram and its metadata.
+ DGRAM_FORWARD,
+};
+
+// WorkerProcesses which are in graceful shutdown period.
+struct QUICLingeringWorkerProcess {
+ QUICLingeringWorkerProcess(
+ std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>> cid_prefixes,
+ int quic_ipc_fd)
+ : cid_prefixes{std::move(cid_prefixes)}, quic_ipc_fd{quic_ipc_fd} {}
+
+ std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>> cid_prefixes;
+ // Socket to send QUIC IPC message to this worker process.
+ int quic_ipc_fd;
+};
+#endif // ENABLE_HTTP3
+
+class ConnectionHandler {
+public:
+ ConnectionHandler(struct ev_loop *loop, std::mt19937 &gen);
+ ~ConnectionHandler();
+ int handle_connection(int fd, sockaddr *addr, int addrlen,
+ const UpstreamAddr *faddr);
+ // Creates Worker object for single threaded configuration.
+ int create_single_worker();
+ // Creates |num| Worker objects for multi threaded configuration.
+ // The |num| must be strictly more than 1.
+ int 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 set_ticket_keys(std::shared_ptr<TicketKeys> ticket_keys);
+ const std::shared_ptr<TicketKeys> &get_ticket_keys() const;
+ struct ev_loop *get_loop() const;
+ Worker *get_single_worker() const;
+ void add_acceptor(std::unique_ptr<AcceptHandler> h);
+ void delete_acceptor();
+ void enable_acceptor();
+ void disable_acceptor();
+ void sleep_acceptor(ev_tstamp t);
+ void accept_pending_connection();
+ void graceful_shutdown_worker();
+ void set_graceful_shutdown(bool f);
+ bool get_graceful_shutdown() const;
+ void join_worker();
+
+ // Cancels ocsp update process
+ void cancel_ocsp_update();
+ // Starts ocsp update for certificate |cert_file|.
+ int start_ocsp_update(const char *cert_file);
+ // Reads incoming data from ocsp update process
+ void read_ocsp_chunk();
+ // Handles the completion of one ocsp update
+ void handle_ocsp_complete();
+ // Resets ocsp_;
+ void reset_ocsp();
+ // Proceeds to the next certificate's ocsp update. If all
+ // certificates' ocsp update has been done, schedule next ocsp
+ // update.
+ void proceed_next_cert_ocsp();
+
+ void set_tls_ticket_key_memcached_dispatcher(
+ std::unique_ptr<MemcachedDispatcher> dispatcher);
+
+ MemcachedDispatcher *get_tls_ticket_key_memcached_dispatcher() const;
+ void on_tls_ticket_key_network_error(ev_timer *w);
+ void on_tls_ticket_key_not_found(ev_timer *w);
+ void
+ on_tls_ticket_key_get_success(const std::shared_ptr<TicketKeys> &ticket_keys,
+ ev_timer *w);
+ void schedule_next_tls_ticket_key_memcached_get(ev_timer *w);
+ SSL_CTX *create_tls_ticket_key_memcached_ssl_ctx();
+ // Returns the SSL_CTX at all_ssl_ctx_[idx]. This does not perform
+ // array bound checking.
+ SSL_CTX *get_ssl_ctx(size_t idx) const;
+
+ const std::vector<SSL_CTX *> &get_indexed_ssl_ctx(size_t idx) const;
+#ifdef ENABLE_HTTP3
+ const std::vector<SSL_CTX *> &get_quic_indexed_ssl_ctx(size_t idx) const;
+
+ int forward_quic_packet(const UpstreamAddr *faddr, const Address &remote_addr,
+ const Address &local_addr, const ngtcp2_pkt_info &pi,
+ const uint8_t *cid_prefix, const uint8_t *data,
+ size_t datalen);
+
+ void set_quic_keying_materials(std::shared_ptr<QUICKeyingMaterials> qkms);
+ const std::shared_ptr<QUICKeyingMaterials> &get_quic_keying_materials() const;
+
+ void set_cid_prefixes(
+ const std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>>
+ &cid_prefixes);
+
+ void set_quic_lingering_worker_processes(
+ const std::vector<QUICLingeringWorkerProcess> &quic_lwps);
+
+ // Return matching QUICLingeringWorkerProcess which has a CID prefix
+ // such that |dcid| starts with it. If no such
+ // QUICLingeringWorkerProcess, it returns nullptr.
+ QUICLingeringWorkerProcess *
+ match_quic_lingering_worker_process_cid_prefix(const uint8_t *dcid,
+ size_t dcidlen);
+
+ int forward_quic_packet_to_lingering_worker_process(
+ QUICLingeringWorkerProcess *quic_lwp, const Address &remote_addr,
+ const Address &local_addr, const ngtcp2_pkt_info &pi, const uint8_t *data,
+ size_t datalen);
+
+ void set_quic_ipc_fd(int fd);
+
+ int quic_ipc_read();
+
+# ifdef HAVE_LIBBPF
+ std::vector<BPFRef> &get_quic_bpf_refs();
+ void unload_bpf_objects();
+# endif // HAVE_LIBBPF
+#endif // ENABLE_HTTP3
+
+#ifdef HAVE_NEVERBLEED
+ void set_neverbleed(neverbleed_t *nb);
+#endif // HAVE_NEVERBLEED
+
+ // Send SerialEvent SerialEventType::REPLACE_DOWNSTREAM to this
+ // object.
+ void send_replace_downstream(
+ const std::shared_ptr<DownstreamConfig> &downstreamconf);
+ // Internal function to send |ev| to this object.
+ void send_serial_event(SerialEvent ev);
+ // Handles SerialEvents received.
+ void handle_serial_event();
+ // Sends WorkerEvent to make them replace downstream.
+ void
+ worker_replace_downstream(std::shared_ptr<DownstreamConfig> downstreamconf);
+
+ void set_enable_acceptor_on_ocsp_completion(bool f);
+
+private:
+ // Stores all SSL_CTX objects.
+ std::vector<SSL_CTX *> all_ssl_ctx_;
+ // Stores all SSL_CTX objects in a way that its index is stored in
+ // cert_tree. The SSL_CTXs stored in the same index share the same
+ // hostname, but could have different signature algorithm. The
+ // selection among them are performed by hostname presented by SNI,
+ // and signature algorithm presented by client.
+ std::vector<std::vector<SSL_CTX *>> indexed_ssl_ctx_;
+#ifdef ENABLE_HTTP3
+ std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>> cid_prefixes_;
+ std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>>
+ lingering_cid_prefixes_;
+ int quic_ipc_fd_;
+ std::vector<QUICLingeringWorkerProcess> quic_lingering_worker_processes_;
+# ifdef HAVE_LIBBPF
+ std::vector<BPFRef> quic_bpf_refs_;
+# endif // HAVE_LIBBPF
+ std::shared_ptr<QUICKeyingMaterials> quic_keying_materials_;
+ std::vector<SSL_CTX *> quic_all_ssl_ctx_;
+ std::vector<std::vector<SSL_CTX *>> quic_indexed_ssl_ctx_;
+#endif // ENABLE_HTTP3
+ OCSPUpdateContext ocsp_;
+ std::mt19937 &gen_;
+ // ev_loop for each worker
+ std::vector<struct ev_loop *> worker_loops_;
+ // Worker instances when multi threaded mode (-nN, N >= 2) is used.
+ // If at least one frontend enables API request, we allocate 1
+ // additional worker dedicated to API request .
+ std::vector<std::unique_ptr<Worker>> workers_;
+ // mutex for serial event resive buffer handling
+ std::mutex serial_event_mu_;
+ // SerialEvent receive buffer
+ std::vector<SerialEvent> serial_events_;
+ // Worker instance used when single threaded mode (-n1) is used.
+ // Otherwise, nullptr and workers_ has instances of Worker instead.
+ std::unique_ptr<Worker> single_worker_;
+ std::unique_ptr<tls::CertLookupTree> cert_tree_;
+#ifdef ENABLE_HTTP3
+ std::unique_ptr<tls::CertLookupTree> quic_cert_tree_;
+#endif // ENABLE_HTTP3
+ std::unique_ptr<MemcachedDispatcher> tls_ticket_key_memcached_dispatcher_;
+ // Current TLS session ticket keys. Note that TLS connection does
+ // not refer to this field directly. They use TicketKeys object in
+ // Worker object.
+ std::shared_ptr<TicketKeys> ticket_keys_;
+ struct ev_loop *loop_;
+ std::vector<std::unique_ptr<AcceptHandler>> acceptors_;
+#ifdef HAVE_NEVERBLEED
+ neverbleed_t *nb_;
+#endif // HAVE_NEVERBLEED
+ ev_timer disable_acceptor_timer_;
+ ev_timer ocsp_timer_;
+ ev_async thread_join_asyncev_;
+ ev_async serial_event_asyncev_;
+#ifndef NOTHREADS
+ std::future<void> thread_join_fut_;
+#endif // NOTHREADS
+ size_t tls_ticket_key_memcached_get_retry_count_;
+ size_t tls_ticket_key_memcached_fail_count_;
+ unsigned int worker_round_robin_cnt_;
+ bool graceful_shutdown_;
+ // true if acceptors should be enabled after the initial ocsp update
+ // has finished.
+ bool enable_acceptor_on_ocsp_completion_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_CONNECTION_HANDLER_H
diff --git a/src/shrpx_dns_resolver.cc b/src/shrpx_dns_resolver.cc
new file mode 100644
index 0000000..f83ecb7
--- /dev/null
+++ b/src/shrpx_dns_resolver.cc
@@ -0,0 +1,353 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_dns_resolver.h"
+
+#include <cstring>
+#include <sys/time.h>
+
+#include "shrpx_log.h"
+#include "shrpx_connection.h"
+#include "shrpx_config.h"
+
+namespace shrpx {
+
+namespace {
+void sock_state_cb(void *data, int s, int read, int write) {
+ auto resolv = static_cast<DNSResolver *>(data);
+
+ if (resolv->get_status(nullptr) != DNSResolverStatus::RUNNING) {
+ return;
+ }
+
+ if (read) {
+ resolv->start_rev(s);
+ } else {
+ resolv->stop_rev(s);
+ }
+ if (write) {
+ resolv->start_wev(s);
+ } else {
+ resolv->stop_wev(s);
+ }
+}
+} // namespace
+
+namespace {
+void host_cb(void *arg, int status, int timeouts, hostent *hostent) {
+ auto resolv = static_cast<DNSResolver *>(arg);
+ resolv->on_result(status, hostent);
+}
+} // namespace
+
+namespace {
+void process_result(DNSResolver *resolv) {
+ auto cb = resolv->get_complete_cb();
+ if (!cb) {
+ return;
+ }
+ Address result;
+ auto status = resolv->get_status(&result);
+ switch (status) {
+ case DNSResolverStatus::OK:
+ case DNSResolverStatus::ERROR:
+ cb(status, &result);
+ break;
+ default:
+ break;
+ }
+ // resolv may be deleted here.
+}
+} // namespace
+
+namespace {
+void readcb(struct ev_loop *loop, ev_io *w, int revents) {
+ auto resolv = static_cast<DNSResolver *>(w->data);
+ resolv->on_read(w->fd);
+ process_result(resolv);
+}
+} // namespace
+
+namespace {
+void writecb(struct ev_loop *loop, ev_io *w, int revents) {
+ auto resolv = static_cast<DNSResolver *>(w->data);
+ resolv->on_write(w->fd);
+ process_result(resolv);
+}
+} // namespace
+
+namespace {
+void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto resolv = static_cast<DNSResolver *>(w->data);
+ resolv->on_timeout();
+ process_result(resolv);
+}
+} // namespace
+
+namespace {
+void stop_ev(struct ev_loop *loop,
+ const std::vector<std::unique_ptr<ev_io>> &evs) {
+ for (auto &w : evs) {
+ ev_io_stop(loop, w.get());
+ }
+}
+} // namespace
+
+DNSResolver::DNSResolver(struct ev_loop *loop)
+ : result_{},
+ loop_(loop),
+ channel_(nullptr),
+ family_(AF_UNSPEC),
+ status_(DNSResolverStatus::IDLE) {
+ ev_timer_init(&timer_, timeoutcb, 0., 0.);
+ timer_.data = this;
+}
+
+DNSResolver::~DNSResolver() {
+ if (channel_) {
+ ares_destroy(channel_);
+ }
+
+ stop_ev(loop_, revs_);
+ stop_ev(loop_, wevs_);
+
+ ev_timer_stop(loop_, &timer_);
+}
+
+int DNSResolver::resolve(const StringRef &name, int family) {
+ if (status_ != DNSResolverStatus::IDLE) {
+ return -1;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Start resolving host " << name << " in IPv"
+ << (family == AF_INET ? "4" : "6");
+ }
+
+ name_ = name;
+ family_ = family;
+
+ int rv;
+
+ auto &dnsconf = get_config()->dns;
+
+ ares_options opts{};
+ opts.sock_state_cb = sock_state_cb;
+ opts.sock_state_cb_data = this;
+ opts.timeout = static_cast<int>(dnsconf.timeout.lookup * 1000);
+ opts.tries = dnsconf.max_try;
+
+ auto optmask = ARES_OPT_SOCK_STATE_CB | ARES_OPT_TIMEOUTMS | ARES_OPT_TRIES;
+
+ ares_channel chan;
+ rv = ares_init_options(&chan, &opts, optmask);
+ if (rv != ARES_SUCCESS) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "ares_init_options failed: " << ares_strerror(rv);
+ }
+ status_ = DNSResolverStatus::ERROR;
+ return -1;
+ }
+
+ channel_ = chan;
+ status_ = DNSResolverStatus::RUNNING;
+
+ ares_gethostbyname(channel_, name_.c_str(), family_, host_cb, this);
+ reset_timeout();
+
+ return 0;
+}
+
+int DNSResolver::on_read(int fd) { return handle_event(fd, ARES_SOCKET_BAD); }
+
+int DNSResolver::on_write(int fd) { return handle_event(ARES_SOCKET_BAD, fd); }
+
+int DNSResolver::on_timeout() {
+ return handle_event(ARES_SOCKET_BAD, ARES_SOCKET_BAD);
+}
+
+int DNSResolver::handle_event(int rfd, int wfd) {
+ if (status_ == DNSResolverStatus::IDLE) {
+ return -1;
+ }
+
+ ares_process_fd(channel_, rfd, wfd);
+
+ switch (status_) {
+ case DNSResolverStatus::RUNNING:
+ reset_timeout();
+ return 0;
+ case DNSResolverStatus::OK:
+ return 0;
+ case DNSResolverStatus::ERROR:
+ return -1;
+ default:
+ // Unreachable
+ assert(0);
+ abort();
+ }
+}
+
+void DNSResolver::reset_timeout() {
+ if (status_ != DNSResolverStatus::RUNNING) {
+ return;
+ }
+ timeval tvout;
+ auto tv = ares_timeout(channel_, nullptr, &tvout);
+ if (tv == nullptr) {
+ return;
+ }
+ // To avoid that timer_.repeat becomes 0, which makes ev_timer_again
+ // useless, add tiny fraction of time.
+ timer_.repeat = tv->tv_sec + tv->tv_usec / 1000000. + 1e-9;
+ ev_timer_again(loop_, &timer_);
+}
+
+DNSResolverStatus DNSResolver::get_status(Address *result) const {
+ if (status_ != DNSResolverStatus::OK) {
+ return status_;
+ }
+
+ if (result) {
+ memcpy(result, &result_, sizeof(result_));
+ }
+
+ return status_;
+}
+
+namespace {
+void start_ev(std::vector<std::unique_ptr<ev_io>> &evs, struct ev_loop *loop,
+ int fd, int event, IOCb cb, void *data) {
+ for (auto &w : evs) {
+ if (w->fd == fd) {
+ return;
+ }
+ }
+ for (auto &w : evs) {
+ if (w->fd == -1) {
+ ev_io_set(w.get(), fd, event);
+ ev_io_start(loop, w.get());
+ return;
+ }
+ }
+
+ auto w = std::make_unique<ev_io>();
+ ev_io_init(w.get(), cb, fd, event);
+ w->data = data;
+ ev_io_start(loop, w.get());
+ evs.emplace_back(std::move(w));
+}
+} // namespace
+
+namespace {
+void stop_ev(std::vector<std::unique_ptr<ev_io>> &evs, struct ev_loop *loop,
+ int fd, int event) {
+ for (auto &w : evs) {
+ if (w->fd == fd) {
+ ev_io_stop(loop, w.get());
+ ev_io_set(w.get(), -1, event);
+ return;
+ }
+ }
+}
+} // namespace
+
+void DNSResolver::start_rev(int fd) {
+ start_ev(revs_, loop_, fd, EV_READ, readcb, this);
+}
+
+void DNSResolver::stop_rev(int fd) { stop_ev(revs_, loop_, fd, EV_READ); }
+
+void DNSResolver::start_wev(int fd) {
+ start_ev(wevs_, loop_, fd, EV_WRITE, writecb, this);
+}
+
+void DNSResolver::stop_wev(int fd) { stop_ev(wevs_, loop_, fd, EV_WRITE); }
+
+void DNSResolver::on_result(int status, hostent *hostent) {
+ stop_ev(loop_, revs_);
+ stop_ev(loop_, wevs_);
+ ev_timer_stop(loop_, &timer_);
+
+ if (status != ARES_SUCCESS) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Name lookup for " << name_
+ << " failed: " << ares_strerror(status);
+ }
+ status_ = DNSResolverStatus::ERROR;
+ return;
+ }
+
+ auto ap = *hostent->h_addr_list;
+ if (!ap) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Name lookup for " << name_ << "failed: no address returned";
+ }
+ status_ = DNSResolverStatus::ERROR;
+ return;
+ }
+
+ switch (hostent->h_addrtype) {
+ case AF_INET:
+ status_ = DNSResolverStatus::OK;
+ result_.len = sizeof(result_.su.in);
+ result_.su.in = {};
+ result_.su.in.sin_family = AF_INET;
+#ifdef HAVE_SOCKADDR_IN_SIN_LEN
+ result_.su.in.sin_len = sizeof(result_.su.in);
+#endif // HAVE_SOCKADDR_IN_SIN_LEN
+ memcpy(&result_.su.in.sin_addr, ap, sizeof(result_.su.in.sin_addr));
+ break;
+ case AF_INET6:
+ status_ = DNSResolverStatus::OK;
+ result_.len = sizeof(result_.su.in6);
+ result_.su.in6 = {};
+ result_.su.in6.sin6_family = AF_INET6;
+#ifdef HAVE_SOCKADDR_IN6_SIN6_LEN
+ result_.su.in6.sin6_len = sizeof(result_.su.in6);
+#endif // HAVE_SOCKADDR_IN6_SIN6_LEN
+ memcpy(&result_.su.in6.sin6_addr, ap, sizeof(result_.su.in6.sin6_addr));
+ break;
+ default:
+ assert(0);
+ }
+
+ if (status_ == DNSResolverStatus::OK) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Name lookup succeeded: " << name_ << " -> "
+ << util::numeric_name(&result_.su.sa, result_.len);
+ }
+ return;
+ }
+
+ status_ = DNSResolverStatus::ERROR;
+}
+
+void DNSResolver::set_complete_cb(CompleteCb cb) {
+ completeCb_ = std::move(cb);
+}
+
+CompleteCb DNSResolver::get_complete_cb() const { return completeCb_; }
+
+} // namespace shrpx
diff --git a/src/shrpx_dns_resolver.h b/src/shrpx_dns_resolver.h
new file mode 100644
index 0000000..e622f99
--- /dev/null
+++ b/src/shrpx_dns_resolver.h
@@ -0,0 +1,118 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_DNS_RESOLVER_H
+#define SHRPX_DNS_RESOLVER_H
+
+#include "shrpx.h"
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include <vector>
+
+#include <ev.h>
+#include <ares.h>
+
+#include "template.h"
+#include "network.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+enum class DNSResolverStatus {
+ // Resolver is in initial status
+ IDLE,
+ // Resolver is currently resolving host name
+ RUNNING,
+ // Resolver successfully resolved host name
+ OK,
+ // Resolver failed to resolve host name
+ ERROR,
+};
+
+// Callback function called when host name lookup is finished.
+// |status| is either DNSResolverStatus::OK, or
+// DNSResolverStatus::ERROR. If |status| is DNSResolverStatus::OK,
+// |result| points to the resolved address. Note that port portion of
+// |result| is undefined, and must be initialized by application.
+// This callback function is not called if name lookup finishes in
+// DNSResolver::resolve() completely. In this case, application
+// should call DNSResolver::get_status() to get current status and
+// result. In other words, callback is called if get_status() returns
+// DNSResolverStatus::RUNNING.
+using CompleteCb =
+ std::function<void(DNSResolverStatus status, const Address *result)>;
+
+// DNSResolver is asynchronous name resolver, backed by c-ares
+// library.
+class DNSResolver {
+public:
+ DNSResolver(struct ev_loop *loop);
+ ~DNSResolver();
+
+ // Starts resolving hostname |name|.
+ int resolve(const StringRef &name, int family);
+ // Returns status. If status_ is DNSResolverStatus::SUCCESS &&
+ // |result| is not nullptr, |*result| is filled.
+ DNSResolverStatus get_status(Address *result) const;
+ // Sets callback function when name lookup finishes. The callback
+ // function is called in a way that it can destroy this DNSResolver.
+ void set_complete_cb(CompleteCb cb);
+ CompleteCb get_complete_cb() const;
+
+ // Calls these functions when read/write event occurred respectively.
+ int on_read(int fd);
+ int on_write(int fd);
+ int on_timeout();
+ // Calls this function when DNS query finished.
+ void on_result(int status, hostent *hostent);
+ void reset_timeout();
+
+ void start_rev(int fd);
+ void stop_rev(int fd);
+ void start_wev(int fd);
+ void stop_wev(int fd);
+
+private:
+ int handle_event(int rfd, int wfd);
+
+ std::vector<std::unique_ptr<ev_io>> revs_, wevs_;
+ Address result_;
+ CompleteCb completeCb_;
+ ev_timer timer_;
+ StringRef name_;
+ struct ev_loop *loop_;
+ // ares_channel is pointer type
+ ares_channel channel_;
+ // AF_INET or AF_INET6. AF_INET for A record lookup, and AF_INET6
+ // for AAAA record lookup.
+ int family_;
+ DNSResolverStatus status_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_DNS_RESOLVER_H
diff --git a/src/shrpx_dns_tracker.cc b/src/shrpx_dns_tracker.cc
new file mode 100644
index 0000000..57387ce
--- /dev/null
+++ b/src/shrpx_dns_tracker.cc
@@ -0,0 +1,328 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_dns_tracker.h"
+#include "shrpx_config.h"
+#include "shrpx_log.h"
+#include "util.h"
+
+namespace shrpx {
+
+namespace {
+void gccb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto dns_tracker = static_cast<DNSTracker *>(w->data);
+ dns_tracker->gc();
+}
+} // namespace
+
+DNSTracker::DNSTracker(struct ev_loop *loop, int family)
+ : loop_(loop), family_(family) {
+ ev_timer_init(&gc_timer_, gccb, 0., 12_h);
+ gc_timer_.data = this;
+}
+
+DNSTracker::~DNSTracker() {
+ ev_timer_stop(loop_, &gc_timer_);
+
+ for (auto &p : ents_) {
+ auto &qlist = p.second.qlist;
+ while (!qlist.empty()) {
+ auto head = qlist.head;
+ qlist.remove(head);
+ head->status = DNSResolverStatus::ERROR;
+ head->in_qlist = false;
+ // TODO Not sure we should call callback here, or it is even be
+ // safe to do that.
+ }
+ }
+}
+
+ResolverEntry DNSTracker::make_entry(std::unique_ptr<DualDNSResolver> resolv,
+ ImmutableString host,
+ DNSResolverStatus status,
+ const Address *result) {
+ auto &dnsconf = get_config()->dns;
+
+ auto ent = ResolverEntry{};
+ ent.resolv = std::move(resolv);
+ ent.host = std::move(host);
+ ent.status = status;
+ switch (status) {
+ case DNSResolverStatus::ERROR:
+ case DNSResolverStatus::OK:
+ ent.expiry = std::chrono::steady_clock::now() +
+ util::duration_from(dnsconf.timeout.cache);
+ break;
+ default:
+ break;
+ }
+ if (result) {
+ ent.result = *result;
+ }
+ return ent;
+}
+
+void DNSTracker::update_entry(ResolverEntry &ent,
+ std::unique_ptr<DualDNSResolver> resolv,
+ DNSResolverStatus status, const Address *result) {
+ auto &dnsconf = get_config()->dns;
+
+ ent.resolv = std::move(resolv);
+ ent.status = status;
+ switch (status) {
+ case DNSResolverStatus::ERROR:
+ case DNSResolverStatus::OK:
+ ent.expiry = std::chrono::steady_clock::now() +
+ util::duration_from(dnsconf.timeout.cache);
+ break;
+ default:
+ break;
+ }
+ if (result) {
+ ent.result = *result;
+ }
+}
+
+DNSResolverStatus DNSTracker::resolve(Address *result, DNSQuery *dnsq) {
+ int rv;
+
+ auto it = ents_.find(dnsq->host);
+
+ if (it == std::end(ents_)) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "DNS entry not found for " << dnsq->host;
+ }
+
+ auto resolv = std::make_unique<DualDNSResolver>(loop_, family_);
+ auto host_copy =
+ ImmutableString{std::begin(dnsq->host), std::end(dnsq->host)};
+ auto host = StringRef{host_copy};
+
+ rv = resolv->resolve(host);
+ if (rv != 0) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Name lookup failed for " << host;
+ }
+
+ ents_.emplace(host, make_entry(nullptr, std::move(host_copy),
+ DNSResolverStatus::ERROR, nullptr));
+
+ start_gc_timer();
+
+ return DNSResolverStatus::ERROR;
+ }
+
+ switch (resolv->get_status(result)) {
+ case DNSResolverStatus::ERROR:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Name lookup failed for " << host;
+ }
+
+ ents_.emplace(host, make_entry(nullptr, std::move(host_copy),
+ DNSResolverStatus::ERROR, nullptr));
+
+ start_gc_timer();
+
+ return DNSResolverStatus::ERROR;
+ case DNSResolverStatus::OK:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Name lookup succeeded: " << host << " -> "
+ << util::numeric_name(&result->su.sa, result->len);
+ }
+
+ ents_.emplace(host, make_entry(nullptr, std::move(host_copy),
+ DNSResolverStatus::OK, result));
+
+ start_gc_timer();
+
+ return DNSResolverStatus::OK;
+ case DNSResolverStatus::RUNNING: {
+ auto p = ents_.emplace(host,
+ make_entry(std::move(resolv), std::move(host_copy),
+ DNSResolverStatus::RUNNING, nullptr));
+
+ start_gc_timer();
+
+ auto &ent = (*p.first).second;
+
+ add_to_qlist(ent, dnsq);
+
+ return DNSResolverStatus::RUNNING;
+ }
+ default:
+ assert(0);
+ }
+ }
+
+ auto &ent = (*it).second;
+
+ if (ent.status != DNSResolverStatus::RUNNING &&
+ ent.expiry < std::chrono::steady_clock::now()) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "DNS entry found for " << dnsq->host
+ << ", but it has been expired";
+ }
+
+ auto resolv = std::make_unique<DualDNSResolver>(loop_, family_);
+ auto host = StringRef{ent.host};
+
+ rv = resolv->resolve(host);
+ if (rv != 0) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Name lookup failed for " << host;
+ }
+
+ update_entry(ent, nullptr, DNSResolverStatus::ERROR, nullptr);
+
+ return DNSResolverStatus::ERROR;
+ }
+
+ switch (resolv->get_status(result)) {
+ case DNSResolverStatus::ERROR:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Name lookup failed for " << host;
+ }
+
+ update_entry(ent, nullptr, DNSResolverStatus::ERROR, nullptr);
+
+ return DNSResolverStatus::ERROR;
+ case DNSResolverStatus::OK:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Name lookup succeeded: " << host << " -> "
+ << util::numeric_name(&result->su.sa, result->len);
+ }
+
+ update_entry(ent, nullptr, DNSResolverStatus::OK, result);
+
+ return DNSResolverStatus::OK;
+ case DNSResolverStatus::RUNNING:
+ update_entry(ent, std::move(resolv), DNSResolverStatus::RUNNING, nullptr);
+ add_to_qlist(ent, dnsq);
+
+ return DNSResolverStatus::RUNNING;
+ default:
+ assert(0);
+ }
+ }
+
+ switch (ent.status) {
+ case DNSResolverStatus::RUNNING:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Waiting for name lookup complete for " << dnsq->host;
+ }
+ ent.qlist.append(dnsq);
+ dnsq->in_qlist = true;
+ return DNSResolverStatus::RUNNING;
+ case DNSResolverStatus::ERROR:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Name lookup failed for " << dnsq->host << " (cached)";
+ }
+ return DNSResolverStatus::ERROR;
+ case DNSResolverStatus::OK:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Name lookup succeeded (cached): " << dnsq->host << " -> "
+ << util::numeric_name(&ent.result.su.sa, ent.result.len);
+ }
+ if (result) {
+ memcpy(result, &ent.result, sizeof(*result));
+ }
+ return DNSResolverStatus::OK;
+ default:
+ assert(0);
+ abort();
+ }
+}
+
+void DNSTracker::add_to_qlist(ResolverEntry &ent, DNSQuery *dnsq) {
+ ent.resolv->set_complete_cb(
+ [&ent](DNSResolverStatus status, const Address *result) {
+ auto &qlist = ent.qlist;
+ while (!qlist.empty()) {
+ auto head = qlist.head;
+ qlist.remove(head);
+ head->status = status;
+ head->in_qlist = false;
+ auto cb = head->cb;
+ cb(status, result);
+ }
+
+ auto &dnsconf = get_config()->dns;
+
+ ent.resolv.reset();
+ ent.status = status;
+ ent.expiry = std::chrono::steady_clock::now() +
+ util::duration_from(dnsconf.timeout.cache);
+ if (ent.status == DNSResolverStatus::OK) {
+ ent.result = *result;
+ }
+ });
+ ent.qlist.append(dnsq);
+ dnsq->in_qlist = true;
+}
+
+void DNSTracker::cancel(DNSQuery *dnsq) {
+ if (!dnsq->in_qlist) {
+ return;
+ }
+
+ auto it = ents_.find(dnsq->host);
+ if (it == std::end(ents_)) {
+ return;
+ }
+
+ auto &ent = (*it).second;
+ ent.qlist.remove(dnsq);
+ dnsq->in_qlist = false;
+}
+
+void DNSTracker::start_gc_timer() {
+ if (ev_is_active(&gc_timer_)) {
+ return;
+ }
+
+ ev_timer_again(loop_, &gc_timer_);
+}
+
+void DNSTracker::gc() {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Starting removing expired DNS cache entries";
+ }
+
+ auto now = std::chrono::steady_clock::now();
+ for (auto it = std::begin(ents_); it != std::end(ents_);) {
+ auto &ent = (*it).second;
+ if (ent.expiry >= now) {
+ ++it;
+ continue;
+ }
+
+ it = ents_.erase(it);
+ }
+
+ if (ents_.empty()) {
+ ev_timer_stop(loop_, &gc_timer_);
+ }
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_dns_tracker.h b/src/shrpx_dns_tracker.h
new file mode 100644
index 0000000..c7caac0
--- /dev/null
+++ b/src/shrpx_dns_tracker.h
@@ -0,0 +1,121 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_DNS_TRACKER_H
+#define SHRPX_DNS_TRACKER_H
+
+#include "shrpx.h"
+
+#include <map>
+#include <chrono>
+
+#include "shrpx_dual_dns_resolver.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+struct DNSQuery {
+ DNSQuery(StringRef host, CompleteCb cb)
+ : host(std::move(host)),
+ cb(std::move(cb)),
+ dlnext(nullptr),
+ dlprev(nullptr),
+ status(DNSResolverStatus::IDLE),
+ in_qlist(false) {}
+
+ // Host name we lookup for.
+ StringRef host;
+ // Callback function called when name lookup finished. This
+ // callback is not called if name lookup finishes within
+ // DNSTracker::resolve().
+ CompleteCb cb;
+ DNSQuery *dlnext, *dlprev;
+ DNSResolverStatus status;
+ // true if this object is in linked list ResolverEntry::qlist.
+ bool in_qlist;
+};
+
+struct ResolverEntry {
+ // Host name this entry lookups for.
+ ImmutableString host;
+ // DNS resolver. Only non-nullptr if status is
+ // DNSResolverStatus::RUNNING.
+ std::unique_ptr<DualDNSResolver> resolv;
+ // DNSQuery interested in this name lookup result. The result is
+ // notified to them all.
+ DList<DNSQuery> qlist;
+ // Use the same enum with DNSResolverStatus
+ DNSResolverStatus status;
+ // result and its expiry time
+ Address result;
+ // time point when cached result expires.
+ std::chrono::steady_clock::time_point expiry;
+};
+
+class DNSTracker {
+public:
+ DNSTracker(struct ev_loop *loop, int family);
+ ~DNSTracker();
+
+ // Lookups host name described in |dnsq|. If name lookup finishes
+ // within this function (either it came from /etc/hosts, host name
+ // is numeric, lookup result is cached, etc), it returns
+ // DNSResolverStatus::OK or DNSResolverStatus::ERROR. If lookup is
+ // successful, DNSResolverStatus::OK is returned, and |result| is
+ // filled. If lookup failed, DNSResolverStatus::ERROR is returned.
+ // If name lookup is being done background, it returns
+ // DNSResolverStatus::RUNNING. Its completion is notified by
+ // calling dnsq->cb.
+ DNSResolverStatus resolve(Address *result, DNSQuery *dnsq);
+ // Cancels name lookup requested by |dnsq|.
+ void cancel(DNSQuery *dnsq);
+ // Removes expired entries from ents_.
+ void gc();
+ // Starts GC timer.
+ void start_gc_timer();
+
+private:
+ ResolverEntry make_entry(std::unique_ptr<DualDNSResolver> resolv,
+ ImmutableString host, DNSResolverStatus status,
+ const Address *result);
+
+ void update_entry(ResolverEntry &ent, std::unique_ptr<DualDNSResolver> resolv,
+ DNSResolverStatus status, const Address *result);
+
+ void add_to_qlist(ResolverEntry &ent, DNSQuery *dnsq);
+
+ std::map<StringRef, ResolverEntry> ents_;
+ // Periodically iterates ents_, and removes expired entries to avoid
+ // excessive use of memory. Since only backend API can potentially
+ // increase memory consumption, interval could be very long.
+ ev_timer gc_timer_;
+ struct ev_loop *loop_;
+ // IP version preference.
+ int family_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_DNS_TRACKER_H
diff --git a/src/shrpx_downstream.cc b/src/shrpx_downstream.cc
new file mode 100644
index 0000000..9ea52b4
--- /dev/null
+++ b/src/shrpx_downstream.cc
@@ -0,0 +1,1189 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_downstream.h"
+
+#include <cassert>
+
+#include "url-parser/url_parser.h"
+
+#include "shrpx_upstream.h"
+#include "shrpx_client_handler.h"
+#include "shrpx_config.h"
+#include "shrpx_error.h"
+#include "shrpx_downstream_connection.h"
+#include "shrpx_downstream_queue.h"
+#include "shrpx_worker.h"
+#include "shrpx_http2_session.h"
+#include "shrpx_log.h"
+#ifdef HAVE_MRUBY
+# include "shrpx_mruby.h"
+#endif // HAVE_MRUBY
+#include "util.h"
+#include "http2.h"
+
+namespace shrpx {
+
+namespace {
+void upstream_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto downstream = static_cast<Downstream *>(w->data);
+ auto upstream = downstream->get_upstream();
+
+ auto which = revents == EV_READ ? "read" : "write";
+
+ if (LOG_ENABLED(INFO)) {
+ DLOG(INFO, downstream) << "upstream timeout stream_id="
+ << downstream->get_stream_id() << " event=" << which;
+ }
+
+ downstream->disable_upstream_rtimer();
+ downstream->disable_upstream_wtimer();
+
+ upstream->on_timeout(downstream);
+}
+} // namespace
+
+namespace {
+void upstream_rtimeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+ upstream_timeoutcb(loop, w, EV_READ);
+}
+} // namespace
+
+namespace {
+void upstream_wtimeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+ upstream_timeoutcb(loop, w, EV_WRITE);
+}
+} // namespace
+
+namespace {
+void downstream_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto downstream = static_cast<Downstream *>(w->data);
+
+ auto which = revents == EV_READ ? "read" : "write";
+
+ if (LOG_ENABLED(INFO)) {
+ DLOG(INFO, downstream) << "downstream timeout stream_id="
+ << downstream->get_downstream_stream_id()
+ << " event=" << which;
+ }
+
+ downstream->disable_downstream_rtimer();
+ downstream->disable_downstream_wtimer();
+
+ auto dconn = downstream->get_downstream_connection();
+
+ if (dconn) {
+ dconn->on_timeout();
+ }
+}
+} // namespace
+
+namespace {
+void downstream_rtimeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+ downstream_timeoutcb(loop, w, EV_READ);
+}
+} // namespace
+
+namespace {
+void downstream_wtimeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+ downstream_timeoutcb(loop, w, EV_WRITE);
+}
+} // namespace
+
+// upstream could be nullptr for unittests
+Downstream::Downstream(Upstream *upstream, MemchunkPool *mcpool,
+ int64_t stream_id)
+ : dlnext(nullptr),
+ dlprev(nullptr),
+ response_sent_body_length(0),
+ balloc_(1024, 1024),
+ req_(balloc_),
+ resp_(balloc_),
+ request_start_time_(std::chrono::high_resolution_clock::now()),
+ blocked_request_buf_(mcpool),
+ request_buf_(mcpool),
+ response_buf_(mcpool),
+ upstream_(upstream),
+ blocked_link_(nullptr),
+ addr_(nullptr),
+ num_retry_(0),
+ stream_id_(stream_id),
+ assoc_stream_id_(-1),
+ downstream_stream_id_(-1),
+ response_rst_stream_error_code_(NGHTTP2_NO_ERROR),
+ affinity_cookie_(0),
+ request_state_(DownstreamState::INITIAL),
+ response_state_(DownstreamState::INITIAL),
+ dispatch_state_(DispatchState::NONE),
+ upgraded_(false),
+ chunked_request_(false),
+ chunked_response_(false),
+ expect_final_response_(false),
+ request_pending_(false),
+ request_header_sent_(false),
+ accesslog_written_(false),
+ new_affinity_cookie_(false),
+ blocked_request_data_eof_(false),
+ expect_100_continue_(false),
+ stop_reading_(false) {
+
+ auto &timeoutconf = get_config()->http2.timeout;
+
+ ev_timer_init(&upstream_rtimer_, &upstream_rtimeoutcb, 0.,
+ timeoutconf.stream_read);
+ ev_timer_init(&upstream_wtimer_, &upstream_wtimeoutcb, 0.,
+ timeoutconf.stream_write);
+ ev_timer_init(&downstream_rtimer_, &downstream_rtimeoutcb, 0.,
+ timeoutconf.stream_read);
+ ev_timer_init(&downstream_wtimer_, &downstream_wtimeoutcb, 0.,
+ timeoutconf.stream_write);
+
+ upstream_rtimer_.data = this;
+ upstream_wtimer_.data = this;
+ downstream_rtimer_.data = this;
+ downstream_wtimer_.data = this;
+
+ rcbufs_.reserve(32);
+#ifdef ENABLE_HTTP3
+ rcbufs3_.reserve(32);
+#endif // ENABLE_HTTP3
+}
+
+Downstream::~Downstream() {
+ if (LOG_ENABLED(INFO)) {
+ DLOG(INFO, this) << "Deleting";
+ }
+
+ // check nullptr for unittest
+ if (upstream_) {
+ auto loop = upstream_->get_client_handler()->get_loop();
+
+ ev_timer_stop(loop, &upstream_rtimer_);
+ ev_timer_stop(loop, &upstream_wtimer_);
+ ev_timer_stop(loop, &downstream_rtimer_);
+ ev_timer_stop(loop, &downstream_wtimer_);
+
+#ifdef HAVE_MRUBY
+ auto handler = upstream_->get_client_handler();
+ auto worker = handler->get_worker();
+ auto mruby_ctx = worker->get_mruby_context();
+
+ mruby_ctx->delete_downstream(this);
+#endif // HAVE_MRUBY
+ }
+
+#ifdef HAVE_MRUBY
+ if (dconn_) {
+ const auto &group = dconn_->get_downstream_addr_group();
+ if (group) {
+ const auto &mruby_ctx = group->shared_addr->mruby_ctx;
+ mruby_ctx->delete_downstream(this);
+ }
+ }
+#endif // HAVE_MRUBY
+
+ // DownstreamConnection may refer to this object. Delete it now
+ // explicitly.
+ dconn_.reset();
+
+#ifdef ENABLE_HTTP3
+ for (auto rcbuf : rcbufs3_) {
+ nghttp3_rcbuf_decref(rcbuf);
+ }
+#endif // ENABLE_HTTP3
+
+ for (auto rcbuf : rcbufs_) {
+ nghttp2_rcbuf_decref(rcbuf);
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ DLOG(INFO, this) << "Deleted";
+ }
+}
+
+int Downstream::attach_downstream_connection(
+ std::unique_ptr<DownstreamConnection> dconn) {
+ if (dconn->attach_downstream(this) != 0) {
+ return -1;
+ }
+
+ dconn_ = std::move(dconn);
+
+ return 0;
+}
+
+void Downstream::detach_downstream_connection() {
+ if (!dconn_) {
+ return;
+ }
+
+#ifdef HAVE_MRUBY
+ const auto &group = dconn_->get_downstream_addr_group();
+ if (group) {
+ const auto &mruby_ctx = group->shared_addr->mruby_ctx;
+ mruby_ctx->delete_downstream(this);
+ }
+#endif // HAVE_MRUBY
+
+ dconn_->detach_downstream(this);
+
+ auto handler = dconn_->get_client_handler();
+
+ handler->pool_downstream_connection(
+ std::unique_ptr<DownstreamConnection>(dconn_.release()));
+}
+
+DownstreamConnection *Downstream::get_downstream_connection() {
+ return dconn_.get();
+}
+
+std::unique_ptr<DownstreamConnection> Downstream::pop_downstream_connection() {
+#ifdef HAVE_MRUBY
+ if (!dconn_) {
+ return nullptr;
+ }
+
+ const auto &group = dconn_->get_downstream_addr_group();
+ if (group) {
+ const auto &mruby_ctx = group->shared_addr->mruby_ctx;
+ mruby_ctx->delete_downstream(this);
+ }
+#endif // HAVE_MRUBY
+
+ return std::unique_ptr<DownstreamConnection>(dconn_.release());
+}
+
+void Downstream::pause_read(IOCtrlReason reason) {
+ if (dconn_) {
+ dconn_->pause_read(reason);
+ }
+}
+
+int Downstream::resume_read(IOCtrlReason reason, size_t consumed) {
+ if (dconn_) {
+ return dconn_->resume_read(reason, consumed);
+ }
+
+ return 0;
+}
+
+void Downstream::force_resume_read() {
+ if (dconn_) {
+ dconn_->force_resume_read();
+ }
+}
+
+namespace {
+const HeaderRefs::value_type *
+search_header_linear_backwards(const HeaderRefs &headers,
+ const StringRef &name) {
+ for (auto it = headers.rbegin(); it != headers.rend(); ++it) {
+ auto &kv = *it;
+ if (kv.name == name) {
+ return &kv;
+ }
+ }
+ return nullptr;
+}
+} // namespace
+
+StringRef Downstream::assemble_request_cookie() {
+ size_t len = 0;
+
+ for (auto &kv : req_.fs.headers()) {
+ if (kv.token != http2::HD_COOKIE || kv.value.empty()) {
+ continue;
+ }
+
+ len += kv.value.size() + str_size("; ");
+ }
+
+ auto iov = make_byte_ref(balloc_, len + 1);
+ auto p = iov.base;
+
+ for (auto &kv : req_.fs.headers()) {
+ if (kv.token != http2::HD_COOKIE || kv.value.empty()) {
+ continue;
+ }
+
+ auto end = std::end(kv.value);
+ for (auto it = std::begin(kv.value) + kv.value.size();
+ it != std::begin(kv.value); --it) {
+ auto c = *(it - 1);
+ if (c == ' ' || c == ';') {
+ continue;
+ }
+ end = it;
+ break;
+ }
+
+ p = std::copy(std::begin(kv.value), end, p);
+ p = util::copy_lit(p, "; ");
+ }
+
+ // cut trailing "; "
+ if (p - iov.base >= 2) {
+ p -= 2;
+ }
+
+ return StringRef{iov.base, p};
+}
+
+uint32_t Downstream::find_affinity_cookie(const StringRef &name) {
+ for (auto &kv : req_.fs.headers()) {
+ if (kv.token != http2::HD_COOKIE) {
+ continue;
+ }
+
+ for (auto it = std::begin(kv.value); it != std::end(kv.value);) {
+ if (*it == '\t' || *it == ' ' || *it == ';') {
+ ++it;
+ continue;
+ }
+
+ auto end = std::find(it, std::end(kv.value), '=');
+ if (end == std::end(kv.value)) {
+ return 0;
+ }
+
+ if (!util::streq(name, StringRef{it, end})) {
+ it = std::find(it, std::end(kv.value), ';');
+ continue;
+ }
+
+ it = std::find(end + 1, std::end(kv.value), ';');
+ auto val = StringRef{end + 1, it};
+ if (val.size() != 8) {
+ return 0;
+ }
+ uint32_t h = 0;
+ for (auto c : val) {
+ auto n = util::hex_to_uint(c);
+ if (n == 256) {
+ return 0;
+ }
+ h <<= 4;
+ h += n;
+ }
+ affinity_cookie_ = h;
+ return h;
+ }
+ }
+ return 0;
+}
+
+size_t Downstream::count_crumble_request_cookie() {
+ size_t n = 0;
+ for (auto &kv : req_.fs.headers()) {
+ if (kv.token != http2::HD_COOKIE) {
+ continue;
+ }
+
+ for (auto it = std::begin(kv.value); it != std::end(kv.value);) {
+ if (*it == '\t' || *it == ' ' || *it == ';') {
+ ++it;
+ continue;
+ }
+
+ it = std::find(it, std::end(kv.value), ';');
+
+ ++n;
+ }
+ }
+ return n;
+}
+
+void Downstream::crumble_request_cookie(std::vector<nghttp2_nv> &nva) {
+ for (auto &kv : req_.fs.headers()) {
+ if (kv.token != http2::HD_COOKIE) {
+ continue;
+ }
+
+ for (auto it = std::begin(kv.value); it != std::end(kv.value);) {
+ if (*it == '\t' || *it == ' ' || *it == ';') {
+ ++it;
+ continue;
+ }
+
+ auto first = it;
+
+ it = std::find(it, std::end(kv.value), ';');
+
+ nva.push_back({(uint8_t *)"cookie", (uint8_t *)first, str_size("cookie"),
+ (size_t)(it - first),
+ (uint8_t)(NGHTTP2_NV_FLAG_NO_COPY_NAME |
+ NGHTTP2_NV_FLAG_NO_COPY_VALUE |
+ (kv.no_index ? NGHTTP2_NV_FLAG_NO_INDEX : 0))});
+ }
+ }
+}
+
+namespace {
+void add_header(size_t &sum, HeaderRefs &headers, const StringRef &name,
+ const StringRef &value, bool no_index, int32_t token) {
+ sum += name.size() + value.size();
+ headers.emplace_back(name, value, no_index, token);
+}
+} // namespace
+
+namespace {
+StringRef alloc_header_name(BlockAllocator &balloc, const StringRef &name) {
+ auto iov = make_byte_ref(balloc, name.size() + 1);
+ auto p = iov.base;
+ p = std::copy(std::begin(name), std::end(name), p);
+ util::inp_strlower(iov.base, p);
+ *p = '\0';
+
+ return StringRef{iov.base, p};
+}
+} // namespace
+
+namespace {
+void append_last_header_key(BlockAllocator &balloc, bool &key_prev, size_t &sum,
+ HeaderRefs &headers, const char *data, size_t len) {
+ assert(key_prev);
+ sum += len;
+ auto &item = headers.back();
+ auto name =
+ realloc_concat_string_ref(balloc, item.name, StringRef{data, len});
+
+ auto p = const_cast<uint8_t *>(name.byte());
+ util::inp_strlower(p + name.size() - len, p + name.size());
+
+ item.name = name;
+ item.token = http2::lookup_token(item.name);
+}
+} // namespace
+
+namespace {
+void append_last_header_value(BlockAllocator &balloc, bool &key_prev,
+ size_t &sum, HeaderRefs &headers,
+ const char *data, size_t len) {
+ key_prev = false;
+ sum += len;
+ auto &item = headers.back();
+ item.value =
+ realloc_concat_string_ref(balloc, item.value, StringRef{data, len});
+}
+} // namespace
+
+int FieldStore::parse_content_length() {
+ content_length = -1;
+
+ for (auto &kv : headers_) {
+ if (kv.token != http2::HD_CONTENT_LENGTH) {
+ continue;
+ }
+
+ auto len = util::parse_uint(kv.value);
+ if (len == -1) {
+ return -1;
+ }
+ if (content_length != -1) {
+ return -1;
+ }
+ content_length = len;
+ }
+ return 0;
+}
+
+const HeaderRefs::value_type *FieldStore::header(int32_t token) const {
+ for (auto it = headers_.rbegin(); it != headers_.rend(); ++it) {
+ auto &kv = *it;
+ if (kv.token == token) {
+ return &kv;
+ }
+ }
+ return nullptr;
+}
+
+HeaderRefs::value_type *FieldStore::header(int32_t token) {
+ for (auto it = headers_.rbegin(); it != headers_.rend(); ++it) {
+ auto &kv = *it;
+ if (kv.token == token) {
+ return &kv;
+ }
+ }
+ return nullptr;
+}
+
+const HeaderRefs::value_type *FieldStore::header(const StringRef &name) const {
+ return search_header_linear_backwards(headers_, name);
+}
+
+void FieldStore::add_header_token(const StringRef &name, const StringRef &value,
+ bool no_index, int32_t token) {
+ shrpx::add_header(buffer_size_, headers_, name, value, no_index, token);
+}
+
+void FieldStore::alloc_add_header_name(const StringRef &name) {
+ auto name_ref = alloc_header_name(balloc_, name);
+ auto token = http2::lookup_token(name_ref);
+ add_header_token(name_ref, StringRef{}, false, token);
+ header_key_prev_ = true;
+}
+
+void FieldStore::append_last_header_key(const char *data, size_t len) {
+ shrpx::append_last_header_key(balloc_, header_key_prev_, buffer_size_,
+ headers_, data, len);
+}
+
+void FieldStore::append_last_header_value(const char *data, size_t len) {
+ shrpx::append_last_header_value(balloc_, header_key_prev_, buffer_size_,
+ headers_, data, len);
+}
+
+void FieldStore::clear_headers() {
+ headers_.clear();
+ header_key_prev_ = false;
+}
+
+void FieldStore::add_trailer_token(const StringRef &name,
+ const StringRef &value, bool no_index,
+ int32_t token) {
+ // Header size limit should be applied to all header and trailer
+ // fields combined.
+ shrpx::add_header(buffer_size_, trailers_, name, value, no_index, token);
+}
+
+void FieldStore::alloc_add_trailer_name(const StringRef &name) {
+ auto name_ref = alloc_header_name(balloc_, name);
+ auto token = http2::lookup_token(name_ref);
+ add_trailer_token(name_ref, StringRef{}, false, token);
+ trailer_key_prev_ = true;
+}
+
+void FieldStore::append_last_trailer_key(const char *data, size_t len) {
+ shrpx::append_last_header_key(balloc_, trailer_key_prev_, buffer_size_,
+ trailers_, data, len);
+}
+
+void FieldStore::append_last_trailer_value(const char *data, size_t len) {
+ shrpx::append_last_header_value(balloc_, trailer_key_prev_, buffer_size_,
+ trailers_, data, len);
+}
+
+void FieldStore::erase_content_length_and_transfer_encoding() {
+ for (auto &kv : headers_) {
+ switch (kv.token) {
+ case http2::HD_CONTENT_LENGTH:
+ case http2::HD_TRANSFER_ENCODING:
+ kv.name = StringRef{};
+ kv.token = -1;
+ break;
+ }
+ }
+}
+
+void Downstream::set_request_start_time(
+ std::chrono::high_resolution_clock::time_point time) {
+ request_start_time_ = std::move(time);
+}
+
+const std::chrono::high_resolution_clock::time_point &
+Downstream::get_request_start_time() const {
+ return request_start_time_;
+}
+
+void Downstream::reset_upstream(Upstream *upstream) {
+ upstream_ = upstream;
+ if (dconn_) {
+ dconn_->on_upstream_change(upstream);
+ }
+}
+
+Upstream *Downstream::get_upstream() const { return upstream_; }
+
+void Downstream::set_stream_id(int64_t stream_id) { stream_id_ = stream_id; }
+
+int64_t Downstream::get_stream_id() const { return stream_id_; }
+
+void Downstream::set_request_state(DownstreamState state) {
+ request_state_ = state;
+}
+
+DownstreamState Downstream::get_request_state() const { return request_state_; }
+
+bool Downstream::get_chunked_request() const { return chunked_request_; }
+
+void Downstream::set_chunked_request(bool f) { chunked_request_ = f; }
+
+bool Downstream::request_buf_full() {
+ auto handler = upstream_->get_client_handler();
+ auto faddr = handler->get_upstream_addr();
+ auto worker = handler->get_worker();
+
+ // We don't check buffer size here for API endpoint.
+ if (faddr->alt_mode == UpstreamAltMode::API) {
+ return false;
+ }
+
+ if (dconn_) {
+ auto &downstreamconf = *worker->get_downstream_config();
+ return blocked_request_buf_.rleft() + request_buf_.rleft() >=
+ downstreamconf.request_buffer_size;
+ }
+
+ return false;
+}
+
+DefaultMemchunks *Downstream::get_request_buf() { return &request_buf_; }
+
+// Call this function after this object is attached to
+// Downstream. Otherwise, the program will crash.
+int Downstream::push_request_headers() {
+ if (!dconn_) {
+ DLOG(INFO, this) << "dconn_ is NULL";
+ return -1;
+ }
+ return dconn_->push_request_headers();
+}
+
+int Downstream::push_upload_data_chunk(const uint8_t *data, size_t datalen) {
+ req_.recv_body_length += datalen;
+
+ if (!dconn_ && !request_header_sent_) {
+ blocked_request_buf_.append(data, datalen);
+ req_.unconsumed_body_length += datalen;
+ return 0;
+ }
+
+ // Assumes that request headers have already been pushed to output
+ // buffer using push_request_headers().
+ if (!dconn_) {
+ DLOG(INFO, this) << "dconn_ is NULL";
+ return -1;
+ }
+ if (dconn_->push_upload_data_chunk(data, datalen) != 0) {
+ return -1;
+ }
+
+ req_.unconsumed_body_length += datalen;
+
+ return 0;
+}
+
+int Downstream::end_upload_data() {
+ if (!dconn_ && !request_header_sent_) {
+ blocked_request_data_eof_ = true;
+ return 0;
+ }
+ if (!dconn_) {
+ DLOG(INFO, this) << "dconn_ is NULL";
+ return -1;
+ }
+ return dconn_->end_upload_data();
+}
+
+void Downstream::rewrite_location_response_header(
+ const StringRef &upstream_scheme) {
+ auto hd = resp_.fs.header(http2::HD_LOCATION);
+ if (!hd) {
+ return;
+ }
+
+ if (request_downstream_host_.empty() || req_.authority.empty()) {
+ return;
+ }
+
+ http_parser_url u{};
+ auto rv = http_parser_parse_url(hd->value.c_str(), hd->value.size(), 0, &u);
+ if (rv != 0) {
+ return;
+ }
+
+ auto new_uri = http2::rewrite_location_uri(balloc_, hd->value, u,
+ request_downstream_host_,
+ req_.authority, upstream_scheme);
+
+ if (new_uri.empty()) {
+ return;
+ }
+
+ hd->value = new_uri;
+}
+
+bool Downstream::get_chunked_response() const { return chunked_response_; }
+
+void Downstream::set_chunked_response(bool f) { chunked_response_ = f; }
+
+int Downstream::on_read() {
+ if (!dconn_) {
+ DLOG(INFO, this) << "dconn_ is NULL";
+ return -1;
+ }
+ return dconn_->on_read();
+}
+
+void Downstream::set_response_state(DownstreamState state) {
+ response_state_ = state;
+}
+
+DownstreamState Downstream::get_response_state() const {
+ return response_state_;
+}
+
+DefaultMemchunks *Downstream::get_response_buf() { return &response_buf_; }
+
+bool Downstream::response_buf_full() {
+ if (dconn_) {
+ auto handler = upstream_->get_client_handler();
+ auto worker = handler->get_worker();
+ auto &downstreamconf = *worker->get_downstream_config();
+
+ return response_buf_.rleft() >= downstreamconf.response_buffer_size;
+ }
+
+ return false;
+}
+
+bool Downstream::validate_request_recv_body_length() const {
+ if (req_.fs.content_length == -1) {
+ return true;
+ }
+
+ if (req_.fs.content_length != req_.recv_body_length) {
+ if (LOG_ENABLED(INFO)) {
+ DLOG(INFO, this) << "request invalid bodylen: content-length="
+ << req_.fs.content_length
+ << ", received=" << req_.recv_body_length;
+ }
+ return false;
+ }
+
+ return true;
+}
+
+bool Downstream::validate_response_recv_body_length() const {
+ if (!expect_response_body() || resp_.fs.content_length == -1) {
+ return true;
+ }
+
+ if (resp_.fs.content_length != resp_.recv_body_length) {
+ if (LOG_ENABLED(INFO)) {
+ DLOG(INFO, this) << "response invalid bodylen: content-length="
+ << resp_.fs.content_length
+ << ", received=" << resp_.recv_body_length;
+ }
+ return false;
+ }
+
+ return true;
+}
+
+void Downstream::check_upgrade_fulfilled_http2() {
+ // This handles nonzero req_.connect_proto and h1 frontend requests
+ // WebSocket upgrade.
+ upgraded_ = (req_.method == HTTP_CONNECT ||
+ req_.connect_proto == ConnectProto::WEBSOCKET) &&
+ resp_.http_status / 100 == 2;
+}
+
+void Downstream::check_upgrade_fulfilled_http1() {
+ if (req_.method == HTTP_CONNECT) {
+ if (req_.connect_proto == ConnectProto::WEBSOCKET) {
+ if (resp_.http_status != 101) {
+ return;
+ }
+
+ // This is done for HTTP/2 frontend only.
+ auto accept = resp_.fs.header(http2::HD_SEC_WEBSOCKET_ACCEPT);
+ if (!accept) {
+ return;
+ }
+
+ std::array<uint8_t, base64::encode_length(20)> accept_buf;
+ auto expected =
+ http2::make_websocket_accept_token(accept_buf.data(), ws_key_);
+
+ upgraded_ = expected != "" && expected == accept->value;
+ } else {
+ upgraded_ = resp_.http_status / 100 == 2;
+ }
+
+ return;
+ }
+
+ if (resp_.http_status == 101) {
+ // TODO Do more strict checking for upgrade headers
+ upgraded_ = req_.upgrade_request;
+
+ return;
+ }
+}
+
+void Downstream::inspect_http2_request() {
+ if (req_.method == HTTP_CONNECT) {
+ req_.upgrade_request = true;
+ }
+}
+
+void Downstream::inspect_http1_request() {
+ if (req_.method == HTTP_CONNECT) {
+ req_.upgrade_request = true;
+ } else if (req_.http_minor > 0) {
+ auto upgrade = req_.fs.header(http2::HD_UPGRADE);
+ if (upgrade) {
+ const auto &val = upgrade->value;
+ // TODO Perform more strict checking for upgrade headers
+ if (util::streq_l(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, val.c_str(),
+ val.size())) {
+ req_.http2_upgrade_seen = true;
+ } else {
+ req_.upgrade_request = true;
+
+ // TODO Should we check Sec-WebSocket-Key, and
+ // Sec-WebSocket-Version as well?
+ if (util::strieq_l("websocket", val)) {
+ req_.connect_proto = ConnectProto::WEBSOCKET;
+ }
+ }
+ }
+ }
+ auto transfer_encoding = req_.fs.header(http2::HD_TRANSFER_ENCODING);
+ if (transfer_encoding) {
+ req_.fs.content_length = -1;
+ }
+
+ auto expect = req_.fs.header(http2::HD_EXPECT);
+ expect_100_continue_ =
+ expect &&
+ util::strieq(expect->value, StringRef::from_lit("100-continue"));
+}
+
+void Downstream::inspect_http1_response() {
+ auto transfer_encoding = resp_.fs.header(http2::HD_TRANSFER_ENCODING);
+ if (transfer_encoding) {
+ resp_.fs.content_length = -1;
+ }
+}
+
+void Downstream::reset_response() {
+ resp_.http_status = 0;
+ resp_.http_major = 1;
+ resp_.http_minor = 1;
+}
+
+bool Downstream::get_non_final_response() const {
+ return !upgraded_ && resp_.http_status / 100 == 1;
+}
+
+bool Downstream::supports_non_final_response() const {
+ return req_.http_major == 3 || req_.http_major == 2 ||
+ (req_.http_major == 1 && req_.http_minor == 1);
+}
+
+bool Downstream::get_upgraded() const { return upgraded_; }
+
+bool Downstream::get_http2_upgrade_request() const {
+ return req_.http2_upgrade_seen && req_.fs.header(http2::HD_HTTP2_SETTINGS) &&
+ response_state_ == DownstreamState::INITIAL;
+}
+
+StringRef Downstream::get_http2_settings() const {
+ auto http2_settings = req_.fs.header(http2::HD_HTTP2_SETTINGS);
+ if (!http2_settings) {
+ return StringRef{};
+ }
+ return http2_settings->value;
+}
+
+void Downstream::set_downstream_stream_id(int64_t stream_id) {
+ downstream_stream_id_ = stream_id;
+}
+
+int64_t Downstream::get_downstream_stream_id() const {
+ return downstream_stream_id_;
+}
+
+uint32_t Downstream::get_response_rst_stream_error_code() const {
+ return response_rst_stream_error_code_;
+}
+
+void Downstream::set_response_rst_stream_error_code(uint32_t error_code) {
+ response_rst_stream_error_code_ = error_code;
+}
+
+void Downstream::set_expect_final_response(bool f) {
+ expect_final_response_ = f;
+}
+
+bool Downstream::get_expect_final_response() const {
+ return expect_final_response_;
+}
+
+bool Downstream::expect_response_body() const {
+ return !resp_.headers_only &&
+ http2::expect_response_body(req_.method, resp_.http_status);
+}
+
+bool Downstream::expect_response_trailer() const {
+ // In HTTP/2, if final response HEADERS does not bear END_STREAM it
+ // is possible trailer fields might come, regardless of request
+ // method or status code.
+ return !resp_.headers_only &&
+ (resp_.http_major == 3 || resp_.http_major == 2);
+}
+
+namespace {
+void reset_timer(struct ev_loop *loop, ev_timer *w) { ev_timer_again(loop, w); }
+} // namespace
+
+namespace {
+void try_reset_timer(struct ev_loop *loop, ev_timer *w) {
+ if (!ev_is_active(w)) {
+ return;
+ }
+ ev_timer_again(loop, w);
+}
+} // namespace
+
+namespace {
+void ensure_timer(struct ev_loop *loop, ev_timer *w) {
+ if (ev_is_active(w)) {
+ return;
+ }
+ ev_timer_again(loop, w);
+}
+} // namespace
+
+namespace {
+void disable_timer(struct ev_loop *loop, ev_timer *w) {
+ ev_timer_stop(loop, w);
+}
+} // namespace
+
+void Downstream::reset_upstream_rtimer() {
+ if (get_config()->http2.timeout.stream_read == 0.) {
+ return;
+ }
+ auto loop = upstream_->get_client_handler()->get_loop();
+ reset_timer(loop, &upstream_rtimer_);
+}
+
+void Downstream::reset_upstream_wtimer() {
+ auto loop = upstream_->get_client_handler()->get_loop();
+ auto &timeoutconf = get_config()->http2.timeout;
+
+ if (timeoutconf.stream_write != 0.) {
+ reset_timer(loop, &upstream_wtimer_);
+ }
+ if (timeoutconf.stream_read != 0.) {
+ try_reset_timer(loop, &upstream_rtimer_);
+ }
+}
+
+void Downstream::ensure_upstream_wtimer() {
+ if (get_config()->http2.timeout.stream_write == 0.) {
+ return;
+ }
+ auto loop = upstream_->get_client_handler()->get_loop();
+ ensure_timer(loop, &upstream_wtimer_);
+}
+
+void Downstream::disable_upstream_rtimer() {
+ if (get_config()->http2.timeout.stream_read == 0.) {
+ return;
+ }
+ auto loop = upstream_->get_client_handler()->get_loop();
+ disable_timer(loop, &upstream_rtimer_);
+}
+
+void Downstream::disable_upstream_wtimer() {
+ if (get_config()->http2.timeout.stream_write == 0.) {
+ return;
+ }
+ auto loop = upstream_->get_client_handler()->get_loop();
+ disable_timer(loop, &upstream_wtimer_);
+}
+
+void Downstream::reset_downstream_rtimer() {
+ if (get_config()->http2.timeout.stream_read == 0.) {
+ return;
+ }
+ auto loop = upstream_->get_client_handler()->get_loop();
+ reset_timer(loop, &downstream_rtimer_);
+}
+
+void Downstream::reset_downstream_wtimer() {
+ auto loop = upstream_->get_client_handler()->get_loop();
+ auto &timeoutconf = get_config()->http2.timeout;
+
+ if (timeoutconf.stream_write != 0.) {
+ reset_timer(loop, &downstream_wtimer_);
+ }
+ if (timeoutconf.stream_read != 0.) {
+ try_reset_timer(loop, &downstream_rtimer_);
+ }
+}
+
+void Downstream::ensure_downstream_wtimer() {
+ if (get_config()->http2.timeout.stream_write == 0.) {
+ return;
+ }
+ auto loop = upstream_->get_client_handler()->get_loop();
+ ensure_timer(loop, &downstream_wtimer_);
+}
+
+void Downstream::disable_downstream_rtimer() {
+ if (get_config()->http2.timeout.stream_read == 0.) {
+ return;
+ }
+ auto loop = upstream_->get_client_handler()->get_loop();
+ disable_timer(loop, &downstream_rtimer_);
+}
+
+void Downstream::disable_downstream_wtimer() {
+ if (get_config()->http2.timeout.stream_write == 0.) {
+ return;
+ }
+ auto loop = upstream_->get_client_handler()->get_loop();
+ disable_timer(loop, &downstream_wtimer_);
+}
+
+bool Downstream::accesslog_ready() const {
+ return !accesslog_written_ && resp_.http_status > 0;
+}
+
+void Downstream::add_retry() { ++num_retry_; }
+
+bool Downstream::no_more_retry() const { return num_retry_ > 50; }
+
+void Downstream::set_request_downstream_host(const StringRef &host) {
+ request_downstream_host_ = host;
+}
+
+void Downstream::set_request_pending(bool f) { request_pending_ = f; }
+
+bool Downstream::get_request_pending() const { return request_pending_; }
+
+void Downstream::set_request_header_sent(bool f) { request_header_sent_ = f; }
+
+bool Downstream::get_request_header_sent() const {
+ return request_header_sent_;
+}
+
+bool Downstream::request_submission_ready() const {
+ return (request_state_ == DownstreamState::HEADER_COMPLETE ||
+ request_state_ == DownstreamState::MSG_COMPLETE) &&
+ (request_pending_ || !request_header_sent_) &&
+ response_state_ == DownstreamState::INITIAL;
+}
+
+DispatchState Downstream::get_dispatch_state() const { return dispatch_state_; }
+
+void Downstream::set_dispatch_state(DispatchState s) { dispatch_state_ = s; }
+
+void Downstream::attach_blocked_link(BlockedLink *l) {
+ assert(!blocked_link_);
+
+ l->downstream = this;
+ blocked_link_ = l;
+}
+
+BlockedLink *Downstream::detach_blocked_link() {
+ auto link = blocked_link_;
+ blocked_link_ = nullptr;
+ return link;
+}
+
+bool Downstream::can_detach_downstream_connection() const {
+ // We should check request and response buffer. If request buffer
+ // is not empty, then we might leave downstream connection in weird
+ // state, especially for HTTP/1.1
+ return dconn_ && response_state_ == DownstreamState::MSG_COMPLETE &&
+ request_state_ == DownstreamState::MSG_COMPLETE && !upgraded_ &&
+ !resp_.connection_close && request_buf_.rleft() == 0;
+}
+
+DefaultMemchunks Downstream::pop_response_buf() {
+ return std::move(response_buf_);
+}
+
+void Downstream::set_assoc_stream_id(int64_t stream_id) {
+ assoc_stream_id_ = stream_id;
+}
+
+int64_t Downstream::get_assoc_stream_id() const { return assoc_stream_id_; }
+
+BlockAllocator &Downstream::get_block_allocator() { return balloc_; }
+
+void Downstream::add_rcbuf(nghttp2_rcbuf *rcbuf) {
+ nghttp2_rcbuf_incref(rcbuf);
+ rcbufs_.push_back(rcbuf);
+}
+
+#ifdef ENABLE_HTTP3
+void Downstream::add_rcbuf(nghttp3_rcbuf *rcbuf) {
+ nghttp3_rcbuf_incref(rcbuf);
+ rcbufs3_.push_back(rcbuf);
+}
+#endif // ENABLE_HTTP3
+
+void Downstream::set_downstream_addr_group(
+ const std::shared_ptr<DownstreamAddrGroup> &group) {
+ group_ = group;
+}
+
+void Downstream::set_addr(const DownstreamAddr *addr) { addr_ = addr; }
+
+const DownstreamAddr *Downstream::get_addr() const { return addr_; }
+
+void Downstream::set_accesslog_written(bool f) { accesslog_written_ = f; }
+
+void Downstream::renew_affinity_cookie(uint32_t h) {
+ affinity_cookie_ = h;
+ new_affinity_cookie_ = true;
+}
+
+uint32_t Downstream::get_affinity_cookie_to_send() const {
+ if (new_affinity_cookie_) {
+ return affinity_cookie_;
+ }
+ return 0;
+}
+
+DefaultMemchunks *Downstream::get_blocked_request_buf() {
+ return &blocked_request_buf_;
+}
+
+bool Downstream::get_blocked_request_data_eof() const {
+ return blocked_request_data_eof_;
+}
+
+void Downstream::set_blocked_request_data_eof(bool f) {
+ blocked_request_data_eof_ = f;
+}
+
+void Downstream::set_ws_key(const StringRef &key) { ws_key_ = key; }
+
+bool Downstream::get_expect_100_continue() const {
+ return expect_100_continue_;
+}
+
+bool Downstream::get_stop_reading() const { return stop_reading_; }
+
+void Downstream::set_stop_reading(bool f) { stop_reading_ = f; }
+
+} // namespace shrpx
diff --git a/src/shrpx_downstream.h b/src/shrpx_downstream.h
new file mode 100644
index 0000000..146cae5
--- /dev/null
+++ b/src/shrpx_downstream.h
@@ -0,0 +1,628 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_DOWNSTREAM_H
+#define SHRPX_DOWNSTREAM_H
+
+#include "shrpx.h"
+
+#include <cinttypes>
+#include <vector>
+#include <string>
+#include <memory>
+#include <chrono>
+#include <algorithm>
+
+#include <ev.h>
+
+#include <nghttp2/nghttp2.h>
+
+#ifdef ENABLE_HTTP3
+# include <nghttp3/nghttp3.h>
+#endif // ENABLE_HTTP3
+
+#include "llhttp.h"
+
+#include "shrpx_io_control.h"
+#include "shrpx_log_config.h"
+#include "http2.h"
+#include "memchunk.h"
+#include "allocator.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+class Upstream;
+class DownstreamConnection;
+struct BlockedLink;
+struct DownstreamAddrGroup;
+struct DownstreamAddr;
+
+class FieldStore {
+public:
+ FieldStore(BlockAllocator &balloc, size_t headers_initial_capacity)
+ : content_length(-1),
+ balloc_(balloc),
+ buffer_size_(0),
+ header_key_prev_(false),
+ trailer_key_prev_(false) {
+ headers_.reserve(headers_initial_capacity);
+ }
+
+ const HeaderRefs &headers() const { return headers_; }
+ const HeaderRefs &trailers() const { return trailers_; }
+
+ HeaderRefs &headers() { return headers_; }
+ HeaderRefs &trailers() { return trailers_; }
+
+ const void add_extra_buffer_size(size_t n) { buffer_size_ += n; }
+ size_t buffer_size() const { return buffer_size_; }
+
+ size_t num_fields() const { return headers_.size() + trailers_.size(); }
+
+ // Returns pointer to the header field with the name |name|. If
+ // multiple header have |name| as name, return last occurrence from
+ // the beginning. If no such header is found, returns nullptr.
+ const HeaderRefs::value_type *header(int32_t token) const;
+ HeaderRefs::value_type *header(int32_t token);
+ // Returns pointer to the header field with the name |name|. If no
+ // such header is found, returns nullptr.
+ const HeaderRefs::value_type *header(const StringRef &name) const;
+
+ void add_header_token(const StringRef &name, const StringRef &value,
+ bool no_index, int32_t token);
+
+ // Adds header field name |name|. First, the copy of header field
+ // name pointed by name.c_str() of length name.size() is made, and
+ // stored.
+ void alloc_add_header_name(const StringRef &name);
+
+ void append_last_header_key(const char *data, size_t len);
+ void append_last_header_value(const char *data, size_t len);
+
+ bool header_key_prev() const { return header_key_prev_; }
+
+ // Parses content-length, and records it in the field. If there are
+ // multiple Content-Length, returns -1.
+ int parse_content_length();
+
+ // Empties headers.
+ void clear_headers();
+
+ void add_trailer_token(const StringRef &name, const StringRef &value,
+ bool no_index, int32_t token);
+
+ // Adds trailer field name |name|. First, the copy of trailer field
+ // name pointed by name.c_str() of length name.size() is made, and
+ // stored.
+ void alloc_add_trailer_name(const StringRef &name);
+
+ void append_last_trailer_key(const char *data, size_t len);
+ void append_last_trailer_value(const char *data, size_t len);
+
+ bool trailer_key_prev() const { return trailer_key_prev_; }
+
+ // erase_content_length_and_transfer_encoding erases content-length
+ // and transfer-encoding header fields.
+ void erase_content_length_and_transfer_encoding();
+
+ // content-length, -1 if it is unknown.
+ int64_t content_length;
+
+private:
+ BlockAllocator &balloc_;
+ HeaderRefs headers_;
+ // trailer fields. For HTTP/1.1, trailer fields are only included
+ // with chunked encoding. For HTTP/2, there is no such limit.
+ HeaderRefs trailers_;
+ // Sum of the length of name and value in headers_ and trailers_.
+ // This could also be increased by add_extra_buffer_size() to take
+ // into account for request URI in case of HTTP/1.x request.
+ size_t buffer_size_;
+ bool header_key_prev_;
+ bool trailer_key_prev_;
+};
+
+// Protocols allowed in HTTP/2 :protocol header field.
+enum class ConnectProto {
+ NONE,
+ WEBSOCKET,
+};
+
+struct Request {
+ Request(BlockAllocator &balloc)
+ : fs(balloc, 16),
+ recv_body_length(0),
+ unconsumed_body_length(0),
+ method(-1),
+ http_major(1),
+ http_minor(1),
+ connect_proto(ConnectProto::NONE),
+ upgrade_request(false),
+ http2_upgrade_seen(false),
+ connection_close(false),
+ http2_expect_body(false),
+ no_authority(false),
+ forwarded_once(false) {}
+
+ void consume(size_t len) {
+ assert(unconsumed_body_length >= len);
+ unconsumed_body_length -= len;
+ }
+
+ bool regular_connect_method() const {
+ return method == HTTP_CONNECT && connect_proto == ConnectProto::NONE;
+ }
+
+ bool extended_connect_method() const {
+ return connect_proto != ConnectProto::NONE;
+ }
+
+ FieldStore fs;
+ // Timestamp when all request header fields are received.
+ std::shared_ptr<Timestamp> tstamp;
+ // Request scheme. For HTTP/2, this is :scheme header field value.
+ // For HTTP/1.1, this is deduced from URI or connection.
+ StringRef scheme;
+ // Request authority. This is HTTP/2 :authority header field value
+ // or host header field value. We may deduce it from absolute-form
+ // HTTP/1 request. We also store authority-form HTTP/1 request.
+ // This could be empty if request comes from HTTP/1.0 without Host
+ // header field and origin-form.
+ StringRef authority;
+ // Request path, including query component. For HTTP/1.1, this is
+ // request-target. For HTTP/2, this is :path header field value.
+ // For CONNECT request, this is empty.
+ StringRef path;
+ // This is original authority which cannot be changed by per-pattern
+ // mruby script.
+ StringRef orig_authority;
+ // This is original path which cannot be changed by per-pattern
+ // mruby script.
+ StringRef orig_path;
+ // the length of request body received so far
+ int64_t recv_body_length;
+ // The number of bytes not consumed by the application yet.
+ size_t unconsumed_body_length;
+ int method;
+ // HTTP major and minor version
+ int http_major, http_minor;
+ // connect_proto specified in HTTP/2 :protocol pseudo header field
+ // which enables extended CONNECT method. This field is also set if
+ // WebSocket upgrade is requested in h1 frontend for convenience.
+ ConnectProto connect_proto;
+ // Returns true if the request is HTTP upgrade (HTTP Upgrade or
+ // CONNECT method). Upgrade to HTTP/2 is excluded. For HTTP/2
+ // Upgrade, check get_http2_upgrade_request().
+ bool upgrade_request;
+ // true if h2c is seen in Upgrade header field.
+ bool http2_upgrade_seen;
+ bool connection_close;
+ // true if this is HTTP/2, and request body is expected. Note that
+ // we don't take into account HTTP method here.
+ bool http2_expect_body;
+ // true if request does not have any information about authority.
+ // This happens when: For HTTP/2 request, :authority is missing.
+ // For HTTP/1 request, origin or asterisk form is used.
+ bool no_authority;
+ // true if backend selection is done for request once.
+ // orig_authority and orig_path have the authority and path which
+ // are used for the first backend selection.
+ bool forwarded_once;
+};
+
+struct Response {
+ Response(BlockAllocator &balloc)
+ : fs(balloc, 32),
+ recv_body_length(0),
+ unconsumed_body_length(0),
+ http_status(0),
+ http_major(1),
+ http_minor(1),
+ connection_close(false),
+ headers_only(false) {}
+
+ void consume(size_t len) {
+ assert(unconsumed_body_length >= len);
+ unconsumed_body_length -= len;
+ }
+
+ // returns true if a resource denoted by scheme, authority, and path
+ // has already been pushed.
+ bool is_resource_pushed(const StringRef &scheme, const StringRef &authority,
+ const StringRef &path) const {
+ if (!pushed_resources) {
+ return false;
+ }
+ return std::find(std::begin(*pushed_resources), std::end(*pushed_resources),
+ std::make_tuple(scheme, authority, path)) !=
+ std::end(*pushed_resources);
+ }
+
+ // remember that a resource denoted by scheme, authority, and path
+ // is pushed.
+ void resource_pushed(const StringRef &scheme, const StringRef &authority,
+ const StringRef &path) {
+ if (!pushed_resources) {
+ pushed_resources = std::make_unique<
+ std::vector<std::tuple<StringRef, StringRef, StringRef>>>();
+ }
+ pushed_resources->emplace_back(scheme, authority, path);
+ }
+
+ FieldStore fs;
+ // array of the tuple of scheme, authority, and path of pushed
+ // resource. This is required because RFC 8297 says that server
+ // typically includes header fields appeared in non-final response
+ // header fields in final response header fields. Without checking
+ // that a particular resource has already been pushed, or not, we
+ // end up pushing the same resource at least twice. It is unknown
+ // that we should use more complex data structure (e.g., std::set)
+ // to find the resources faster.
+ std::unique_ptr<std::vector<std::tuple<StringRef, StringRef, StringRef>>>
+ pushed_resources;
+ // the length of response body received so far
+ int64_t recv_body_length;
+ // The number of bytes not consumed by the application yet. This is
+ // mainly for HTTP/2 backend.
+ size_t unconsumed_body_length;
+ // HTTP status code
+ unsigned int http_status;
+ int http_major, http_minor;
+ bool connection_close;
+ // true if response only consists of HEADERS, and it bears
+ // END_STREAM. This is used to tell Http2Upstream that it can send
+ // response with single HEADERS with END_STREAM flag only.
+ bool headers_only;
+};
+
+enum class DownstreamState {
+ INITIAL,
+ HEADER_COMPLETE,
+ MSG_COMPLETE,
+ STREAM_CLOSED,
+ CONNECT_FAIL,
+ MSG_RESET,
+ // header contains invalid header field. We can safely send error
+ // response (502) to a client.
+ MSG_BAD_HEADER,
+ // header fields in HTTP/1 request exceed the configuration limit.
+ // This state is only transitioned from INITIAL state, and solely
+ // used to signal 431 status code to the client.
+ HTTP1_REQUEST_HEADER_TOO_LARGE,
+};
+
+enum class DispatchState {
+ NONE,
+ PENDING,
+ BLOCKED,
+ ACTIVE,
+ FAILURE,
+};
+
+class Downstream {
+public:
+ Downstream(Upstream *upstream, MemchunkPool *mcpool, int64_t stream_id);
+ ~Downstream();
+ void reset_upstream(Upstream *upstream);
+ Upstream *get_upstream() const;
+ void set_stream_id(int64_t stream_id);
+ int64_t get_stream_id() const;
+ void set_assoc_stream_id(int64_t stream_id);
+ int64_t get_assoc_stream_id() const;
+ void pause_read(IOCtrlReason reason);
+ int resume_read(IOCtrlReason reason, size_t consumed);
+ void force_resume_read();
+ // Set stream ID for downstream HTTP2 connection.
+ void set_downstream_stream_id(int64_t stream_id);
+ int64_t get_downstream_stream_id() const;
+
+ int attach_downstream_connection(std::unique_ptr<DownstreamConnection> dconn);
+ void detach_downstream_connection();
+ DownstreamConnection *get_downstream_connection();
+ // Returns dconn_ and nullifies dconn_.
+ std::unique_ptr<DownstreamConnection> pop_downstream_connection();
+
+ // Returns true if output buffer is full. If underlying dconn_ is
+ // NULL, this function always returns false.
+ bool request_buf_full();
+ // Returns true if upgrade (HTTP Upgrade or CONNECT) is succeeded in
+ // h1 backend. This should not depend on inspect_http1_response().
+ void check_upgrade_fulfilled_http1();
+ // Returns true if upgrade (HTTP Upgrade or CONNECT) is succeeded in
+ // h2 backend.
+ void check_upgrade_fulfilled_http2();
+ // Returns true if the upgrade is succeeded as a result of the call
+ // check_upgrade_fulfilled_http*(). HTTP/2 Upgrade is excluded.
+ bool get_upgraded() const;
+ // Inspects HTTP/2 request.
+ void inspect_http2_request();
+ // Inspects HTTP/1 request. This checks whether the request is
+ // upgrade request and tranfer-encoding etc.
+ void inspect_http1_request();
+ // Returns true if the request is HTTP Upgrade for HTTP/2
+ bool get_http2_upgrade_request() const;
+ // Returns the value of HTTP2-Settings request header field.
+ StringRef get_http2_settings() const;
+
+ // downstream request API
+ const Request &request() const { return req_; }
+ Request &request() { return req_; }
+
+ // Count number of crumbled cookies
+ size_t count_crumble_request_cookie();
+ // Crumbles (split cookie by ";") in request_headers_ and adds them
+ // in |nva|. Headers::no_index is inherited.
+ void crumble_request_cookie(std::vector<nghttp2_nv> &nva);
+ // Assembles request cookies. The opposite operation against
+ // crumble_request_cookie().
+ StringRef assemble_request_cookie();
+
+ void
+ set_request_start_time(std::chrono::high_resolution_clock::time_point time);
+ const std::chrono::high_resolution_clock::time_point &
+ get_request_start_time() const;
+ int push_request_headers();
+ bool get_chunked_request() const;
+ void set_chunked_request(bool f);
+ int push_upload_data_chunk(const uint8_t *data, size_t datalen);
+ int end_upload_data();
+ // Validates that received request body length and content-length
+ // matches.
+ bool validate_request_recv_body_length() const;
+ void set_request_downstream_host(const StringRef &host);
+ bool expect_response_body() const;
+ bool expect_response_trailer() const;
+ void set_request_state(DownstreamState state);
+ DownstreamState get_request_state() const;
+ DefaultMemchunks *get_request_buf();
+ void set_request_pending(bool f);
+ bool get_request_pending() const;
+ void set_request_header_sent(bool f);
+ bool get_request_header_sent() const;
+ // Returns true if request is ready to be submitted to downstream.
+ // When sending pending request, get_request_pending() should be
+ // checked too because this function may return true when
+ // get_request_pending() returns false.
+ bool request_submission_ready() const;
+
+ DefaultMemchunks *get_blocked_request_buf();
+ bool get_blocked_request_data_eof() const;
+ void set_blocked_request_data_eof(bool f);
+
+ // downstream response API
+ const Response &response() const { return resp_; }
+ Response &response() { return resp_; }
+
+ // Rewrites the location response header field.
+ void rewrite_location_response_header(const StringRef &upstream_scheme);
+
+ bool get_chunked_response() const;
+ void set_chunked_response(bool f);
+
+ void set_response_state(DownstreamState state);
+ DownstreamState get_response_state() const;
+ DefaultMemchunks *get_response_buf();
+ bool response_buf_full();
+ // Validates that received response body length and content-length
+ // matches.
+ bool validate_response_recv_body_length() const;
+ uint32_t get_response_rst_stream_error_code() const;
+ void set_response_rst_stream_error_code(uint32_t error_code);
+ // Inspects HTTP/1 response. This checks tranfer-encoding etc.
+ void inspect_http1_response();
+ // Clears some of member variables for response.
+ void reset_response();
+ // True if the response is non-final (1xx status code). Note that
+ // if connection was upgraded, 101 status code is treated as final.
+ bool get_non_final_response() const;
+ // True if protocol version used by client supports non final
+ // response. Only HTTP/1.1 and HTTP/2 clients support it.
+ bool supports_non_final_response() const;
+ void set_expect_final_response(bool f);
+ bool get_expect_final_response() const;
+
+ // Call this method when there is incoming data in downstream
+ // connection.
+ int on_read();
+
+ // Resets upstream read timer. If it is active, timeout value is
+ // reset. If it is not active, timer will be started.
+ void reset_upstream_rtimer();
+ // Resets upstream write timer. If it is active, timeout value is
+ // reset. If it is not active, timer will be started. This
+ // function also resets read timer if it has been started.
+ void reset_upstream_wtimer();
+ // Makes sure that upstream write timer is started. If it has been
+ // started, do nothing. Otherwise, write timer will be started.
+ void ensure_upstream_wtimer();
+ // Disables upstream read timer.
+ void disable_upstream_rtimer();
+ // Disables upstream write timer.
+ void disable_upstream_wtimer();
+
+ // Downstream timer functions. They works in a similar way just
+ // like the upstream timer function.
+ void reset_downstream_rtimer();
+ void reset_downstream_wtimer();
+ void ensure_downstream_wtimer();
+ void disable_downstream_rtimer();
+ void disable_downstream_wtimer();
+
+ // Returns true if accesslog can be written for this downstream.
+ bool accesslog_ready() const;
+
+ // Increment retry count
+ void add_retry();
+ // true if retry attempt should not be done.
+ bool no_more_retry() const;
+
+ DispatchState get_dispatch_state() const;
+ void set_dispatch_state(DispatchState s);
+
+ 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;
+
+ DefaultMemchunks pop_response_buf();
+
+ BlockAllocator &get_block_allocator();
+
+ void add_rcbuf(nghttp2_rcbuf *rcbuf);
+#ifdef ENABLE_HTTP3
+ void add_rcbuf(nghttp3_rcbuf *rcbuf);
+#endif // ENABLE_HTTP3
+
+ void
+ set_downstream_addr_group(const std::shared_ptr<DownstreamAddrGroup> &group);
+ void set_addr(const DownstreamAddr *addr);
+
+ const DownstreamAddr *get_addr() const;
+
+ void set_accesslog_written(bool f);
+
+ // Finds affinity cookie from request header fields. The name of
+ // cookie is given in |name|. If an affinity cookie is found, it is
+ // assigned to a member function, and is returned. If it is not
+ // found, or is malformed, returns 0.
+ uint32_t find_affinity_cookie(const StringRef &name);
+ // Set |h| as affinity cookie.
+ void renew_affinity_cookie(uint32_t h);
+ // Returns affinity cookie to send. If it does not need to be sent,
+ // for example, because the value is retrieved from a request header
+ // field, returns 0.
+ uint32_t get_affinity_cookie_to_send() const;
+
+ void set_ws_key(const StringRef &key);
+
+ bool get_expect_100_continue() const;
+
+ bool get_stop_reading() const;
+ void set_stop_reading(bool f);
+
+ enum {
+ EVENT_ERROR = 0x1,
+ EVENT_TIMEOUT = 0x2,
+ };
+
+ Downstream *dlnext, *dlprev;
+
+ // the length of response body sent to upstream client
+ int64_t response_sent_body_length;
+
+private:
+ BlockAllocator balloc_;
+
+ std::vector<nghttp2_rcbuf *> rcbufs_;
+#ifdef ENABLE_HTTP3
+ std::vector<nghttp3_rcbuf *> rcbufs3_;
+#endif // ENABLE_HTTP3
+
+ Request req_;
+ Response resp_;
+
+ std::chrono::high_resolution_clock::time_point request_start_time_;
+
+ // host we requested to downstream. This is used to rewrite
+ // location header field to decide the location should be rewritten
+ // or not.
+ StringRef request_downstream_host_;
+
+ // Data arrived in frontend before sending header fields to backend
+ // are stored in this buffer.
+ DefaultMemchunks blocked_request_buf_;
+ DefaultMemchunks request_buf_;
+ DefaultMemchunks response_buf_;
+
+ // The Sec-WebSocket-Key field sent to the peer. This field is used
+ // if frontend uses RFC 8441 WebSocket bootstrapping via HTTP/2.
+ StringRef ws_key_;
+
+ ev_timer upstream_rtimer_;
+ ev_timer upstream_wtimer_;
+
+ ev_timer downstream_rtimer_;
+ ev_timer downstream_wtimer_;
+
+ Upstream *upstream_;
+ std::unique_ptr<DownstreamConnection> dconn_;
+
+ // only used by HTTP/2 upstream
+ BlockedLink *blocked_link_;
+ // The backend address used to fulfill this request. These are for
+ // logging purpose.
+ std::shared_ptr<DownstreamAddrGroup> group_;
+ const DownstreamAddr *addr_;
+ // How many times we tried in backend connection
+ size_t num_retry_;
+ // The stream ID in frontend connection
+ int64_t stream_id_;
+ // The associated stream ID in frontend connection if this is pushed
+ // stream.
+ int64_t assoc_stream_id_;
+ // stream ID in backend connection
+ int64_t downstream_stream_id_;
+ // RST_STREAM error_code from downstream HTTP2 connection
+ uint32_t response_rst_stream_error_code_;
+ // An affinity cookie value.
+ uint32_t affinity_cookie_;
+ // request state
+ DownstreamState request_state_;
+ // response state
+ DownstreamState response_state_;
+ // only used by HTTP/2 upstream
+ DispatchState dispatch_state_;
+ // true if the connection is upgraded (HTTP Upgrade or CONNECT),
+ // excluding upgrade to HTTP/2.
+ bool upgraded_;
+ // true if backend request uses chunked transfer-encoding
+ bool chunked_request_;
+ // true if response to client uses chunked transfer-encoding
+ bool chunked_response_;
+ // true if we have not got final response code
+ bool expect_final_response_;
+ // true if downstream request is pending because backend connection
+ // has not been established or should be checked before use;
+ // currently used only with HTTP/2 connection.
+ bool request_pending_;
+ // true if downstream request header is considered to be sent.
+ bool request_header_sent_;
+ // true if access.log has been written.
+ bool accesslog_written_;
+ // true if affinity cookie is generated for this request.
+ bool new_affinity_cookie_;
+ // true if eof is received from client before sending header fields
+ // to backend.
+ bool blocked_request_data_eof_;
+ // true if request contains "expect: 100-continue" header field.
+ bool expect_100_continue_;
+ bool stop_reading_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_DOWNSTREAM_H
diff --git a/src/shrpx_downstream_connection.cc b/src/shrpx_downstream_connection.cc
new file mode 100644
index 0000000..16a2d6f
--- /dev/null
+++ b/src/shrpx_downstream_connection.cc
@@ -0,0 +1,48 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_downstream_connection.h"
+
+#include "shrpx_client_handler.h"
+#include "shrpx_downstream.h"
+#include "shrpx_log.h"
+
+namespace shrpx {
+
+DownstreamConnection::DownstreamConnection()
+ : client_handler_(nullptr), downstream_(nullptr) {}
+
+DownstreamConnection::~DownstreamConnection() {}
+
+void DownstreamConnection::set_client_handler(ClientHandler *handler) {
+ client_handler_ = handler;
+}
+
+ClientHandler *DownstreamConnection::get_client_handler() {
+ return client_handler_;
+}
+
+Downstream *DownstreamConnection::get_downstream() { return downstream_; }
+
+} // namespace shrpx
diff --git a/src/shrpx_downstream_connection.h b/src/shrpx_downstream_connection.h
new file mode 100644
index 0000000..8efdcbe
--- /dev/null
+++ b/src/shrpx_downstream_connection.h
@@ -0,0 +1,81 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_DOWNSTREAM_CONNECTION_H
+#define SHRPX_DOWNSTREAM_CONNECTION_H
+
+#include "shrpx.h"
+
+#include <memory>
+
+#include "shrpx_io_control.h"
+
+namespace shrpx {
+
+class ClientHandler;
+class Upstream;
+class Downstream;
+struct DownstreamAddrGroup;
+struct DownstreamAddr;
+
+class DownstreamConnection {
+public:
+ DownstreamConnection();
+ virtual ~DownstreamConnection();
+ virtual int attach_downstream(Downstream *downstream) = 0;
+ virtual void detach_downstream(Downstream *downstream) = 0;
+
+ virtual int push_request_headers() = 0;
+ virtual int push_upload_data_chunk(const uint8_t *data, size_t datalen) = 0;
+ virtual int end_upload_data() = 0;
+
+ virtual void pause_read(IOCtrlReason reason) = 0;
+ virtual int resume_read(IOCtrlReason reason, size_t consumed) = 0;
+ virtual void force_resume_read() = 0;
+
+ virtual int on_read() = 0;
+ virtual int on_write() = 0;
+ virtual int on_timeout() { return 0; }
+
+ virtual void on_upstream_change(Upstream *upstream) = 0;
+
+ // true if this object is poolable.
+ virtual bool poolable() const = 0;
+
+ virtual const std::shared_ptr<DownstreamAddrGroup> &
+ get_downstream_addr_group() const = 0;
+ virtual DownstreamAddr *get_addr() const = 0;
+
+ void set_client_handler(ClientHandler *client_handler);
+ ClientHandler *get_client_handler();
+ Downstream *get_downstream();
+
+protected:
+ ClientHandler *client_handler_;
+ Downstream *downstream_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_DOWNSTREAM_CONNECTION_H
diff --git a/src/shrpx_downstream_connection_pool.cc b/src/shrpx_downstream_connection_pool.cc
new file mode 100644
index 0000000..0ee66b6
--- /dev/null
+++ b/src/shrpx_downstream_connection_pool.cc
@@ -0,0 +1,66 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_downstream_connection_pool.h"
+#include "shrpx_downstream_connection.h"
+
+namespace shrpx {
+
+DownstreamConnectionPool::DownstreamConnectionPool() {}
+
+DownstreamConnectionPool::~DownstreamConnectionPool() { remove_all(); }
+
+void DownstreamConnectionPool::remove_all() {
+ for (auto dconn : pool_) {
+ delete dconn;
+ }
+
+ pool_.clear();
+}
+
+void DownstreamConnectionPool::add_downstream_connection(
+ std::unique_ptr<DownstreamConnection> dconn) {
+ pool_.insert(dconn.release());
+}
+
+std::unique_ptr<DownstreamConnection>
+DownstreamConnectionPool::pop_downstream_connection() {
+ if (pool_.empty()) {
+ return nullptr;
+ }
+
+ auto it = std::begin(pool_);
+ auto dconn = std::unique_ptr<DownstreamConnection>(*it);
+ pool_.erase(it);
+
+ return dconn;
+}
+
+void DownstreamConnectionPool::remove_downstream_connection(
+ DownstreamConnection *dconn) {
+ pool_.erase(dconn);
+ delete dconn;
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_downstream_connection_pool.h b/src/shrpx_downstream_connection_pool.h
new file mode 100644
index 0000000..34dc30d
--- /dev/null
+++ b/src/shrpx_downstream_connection_pool.h
@@ -0,0 +1,53 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_DOWNSTREAM_CONNECTION_POOL_H
+#define SHRPX_DOWNSTREAM_CONNECTION_POOL_H
+
+#include "shrpx.h"
+
+#include <memory>
+#include <set>
+
+namespace shrpx {
+
+class DownstreamConnection;
+
+class DownstreamConnectionPool {
+public:
+ DownstreamConnectionPool();
+ ~DownstreamConnectionPool();
+
+ void add_downstream_connection(std::unique_ptr<DownstreamConnection> dconn);
+ std::unique_ptr<DownstreamConnection> pop_downstream_connection();
+ void remove_downstream_connection(DownstreamConnection *dconn);
+ void remove_all();
+
+private:
+ std::set<DownstreamConnection *> pool_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_DOWNSTREAM_CONNECTION_POOL_H
diff --git a/src/shrpx_downstream_queue.cc b/src/shrpx_downstream_queue.cc
new file mode 100644
index 0000000..f8906e8
--- /dev/null
+++ b/src/shrpx_downstream_queue.cc
@@ -0,0 +1,175 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_downstream_queue.h"
+
+#include <cassert>
+#include <limits>
+
+#include "shrpx_downstream.h"
+
+namespace shrpx {
+
+DownstreamQueue::HostEntry::HostEntry(ImmutableString &&key)
+ : key(std::move(key)), num_active(0) {}
+
+DownstreamQueue::DownstreamQueue(size_t conn_max_per_host, bool unified_host)
+ : conn_max_per_host_(conn_max_per_host == 0
+ ? std::numeric_limits<size_t>::max()
+ : conn_max_per_host),
+ unified_host_(unified_host) {}
+
+DownstreamQueue::~DownstreamQueue() {
+ dlist_delete_all(downstreams_);
+ for (auto &p : host_entries_) {
+ auto &ent = p.second;
+ dlist_delete_all(ent.blocked);
+ }
+}
+
+void DownstreamQueue::add_pending(std::unique_ptr<Downstream> downstream) {
+ downstream->set_dispatch_state(DispatchState::PENDING);
+ downstreams_.append(downstream.release());
+}
+
+void DownstreamQueue::mark_failure(Downstream *downstream) {
+ downstream->set_dispatch_state(DispatchState::FAILURE);
+}
+
+DownstreamQueue::HostEntry &
+DownstreamQueue::find_host_entry(const StringRef &host) {
+ auto itr = host_entries_.find(host);
+ if (itr == std::end(host_entries_)) {
+ auto key = ImmutableString{std::begin(host), std::end(host)};
+ auto key_ref = StringRef{key};
+#ifdef HAVE_STD_MAP_EMPLACE
+ std::tie(itr, std::ignore) =
+ host_entries_.emplace(key_ref, HostEntry(std::move(key)));
+#else // !HAVE_STD_MAP_EMPLACE
+ // for g++-4.7
+ std::tie(itr, std::ignore) = host_entries_.insert(
+ std::make_pair(key_ref, HostEntry(std::move(key))));
+#endif // !HAVE_STD_MAP_EMPLACE
+ }
+ return (*itr).second;
+}
+
+StringRef DownstreamQueue::make_host_key(const StringRef &host) const {
+ return unified_host_ ? StringRef{} : host;
+}
+
+StringRef DownstreamQueue::make_host_key(Downstream *downstream) const {
+ return make_host_key(downstream->request().authority);
+}
+
+void DownstreamQueue::mark_active(Downstream *downstream) {
+ auto &ent = find_host_entry(make_host_key(downstream));
+ ++ent.num_active;
+
+ downstream->set_dispatch_state(DispatchState::ACTIVE);
+}
+
+void DownstreamQueue::mark_blocked(Downstream *downstream) {
+ auto &ent = find_host_entry(make_host_key(downstream));
+
+ downstream->set_dispatch_state(DispatchState::BLOCKED);
+
+ auto link = new BlockedLink{};
+ downstream->attach_blocked_link(link);
+ ent.blocked.append(link);
+}
+
+bool DownstreamQueue::can_activate(const StringRef &host) const {
+ auto itr = host_entries_.find(make_host_key(host));
+ if (itr == std::end(host_entries_)) {
+ return true;
+ }
+ auto &ent = (*itr).second;
+ return ent.num_active < conn_max_per_host_;
+}
+
+namespace {
+bool remove_host_entry_if_empty(const DownstreamQueue::HostEntry &ent,
+ DownstreamQueue::HostEntryMap &host_entries,
+ const StringRef &host) {
+ if (ent.blocked.empty() && ent.num_active == 0) {
+ host_entries.erase(host);
+ return true;
+ }
+ return false;
+}
+} // namespace
+
+Downstream *DownstreamQueue::remove_and_get_blocked(Downstream *downstream,
+ bool next_blocked) {
+ // Delete downstream when this function returns.
+ auto delptr = std::unique_ptr<Downstream>(downstream);
+
+ downstreams_.remove(downstream);
+
+ auto host = make_host_key(downstream);
+ auto &ent = find_host_entry(host);
+
+ if (downstream->get_dispatch_state() == DispatchState::ACTIVE) {
+ --ent.num_active;
+ } else {
+ // For those downstreams deleted while in blocked state
+ auto link = downstream->detach_blocked_link();
+ if (link) {
+ ent.blocked.remove(link);
+ delete link;
+ }
+ }
+
+ if (remove_host_entry_if_empty(ent, host_entries_, host)) {
+ return nullptr;
+ }
+
+ if (!next_blocked || ent.num_active >= conn_max_per_host_) {
+ return nullptr;
+ }
+
+ auto link = ent.blocked.head;
+
+ if (!link) {
+ return nullptr;
+ }
+
+ auto next_downstream = link->downstream;
+ auto link2 = next_downstream->detach_blocked_link();
+ // This is required with --disable-assert.
+ (void)link2;
+ assert(link2 == link);
+ ent.blocked.remove(link);
+ delete link;
+ remove_host_entry_if_empty(ent, host_entries_, host);
+
+ return next_downstream;
+}
+
+Downstream *DownstreamQueue::get_downstreams() const {
+ return downstreams_.head;
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_downstream_queue.h b/src/shrpx_downstream_queue.h
new file mode 100644
index 0000000..a5b980f
--- /dev/null
+++ b/src/shrpx_downstream_queue.h
@@ -0,0 +1,116 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_DOWNSTREAM_QUEUE_H
+#define SHRPX_DOWNSTREAM_QUEUE_H
+
+#include "shrpx.h"
+
+#include <cinttypes>
+#include <map>
+#include <set>
+#include <memory>
+
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+class Downstream;
+
+// Link entry in HostEntry.blocked and downstream because downstream
+// could be deleted in anytime and we'd like to find Downstream in
+// O(1). Downstream has field to link back to this object.
+struct BlockedLink {
+ Downstream *downstream;
+ BlockedLink *dlnext, *dlprev;
+};
+
+class DownstreamQueue {
+public:
+ struct HostEntry {
+ HostEntry(ImmutableString &&key);
+
+ HostEntry(HostEntry &&) = default;
+ HostEntry &operator=(HostEntry &&) = default;
+
+ HostEntry(const HostEntry &) = delete;
+ HostEntry &operator=(const HostEntry &) = delete;
+
+ // Key that associates this object
+ ImmutableString key;
+ // Set of stream ID that blocked by conn_max_per_host_.
+ DList<BlockedLink> blocked;
+ // The number of connections currently made to this host.
+ size_t num_active;
+ };
+
+ using HostEntryMap = std::map<StringRef, HostEntry>;
+
+ // conn_max_per_host == 0 means no limit for downstream connection.
+ DownstreamQueue(size_t conn_max_per_host = 0, bool unified_host = true);
+ ~DownstreamQueue();
+ // Add |downstream| to this queue. This is entry point for
+ // Downstream object.
+ void add_pending(std::unique_ptr<Downstream> downstream);
+ // Set |downstream| to failure state, which means that downstream
+ // failed to connect to backend.
+ void mark_failure(Downstream *downstream);
+ // Set |downstream| to active state, which means that downstream
+ // connection has started.
+ void mark_active(Downstream *downstream);
+ // Set |downstream| to blocked state, which means that download
+ // connection was blocked because conn_max_per_host_ limit.
+ void mark_blocked(Downstream *downstream);
+ // Returns true if we can make downstream connection to given
+ // |host|.
+ bool can_activate(const StringRef &host) const;
+ // Removes and frees |downstream| object. If |downstream| is in
+ // DispatchState::ACTIVE, and |next_blocked| is true, this function
+ // may return Downstream object with the same target host in
+ // DispatchState::BLOCKED if its connection is now not blocked by
+ // conn_max_per_host_ limit.
+ Downstream *remove_and_get_blocked(Downstream *downstream,
+ bool next_blocked = true);
+ Downstream *get_downstreams() const;
+ HostEntry &find_host_entry(const StringRef &host);
+ StringRef make_host_key(const StringRef &host) const;
+ StringRef make_host_key(Downstream *downstream) const;
+
+private:
+ // Per target host structure to keep track of the number of
+ // connections to the same host.
+ HostEntryMap host_entries_;
+ DList<Downstream> downstreams_;
+ // Maximum number of concurrent connections to the same host.
+ size_t conn_max_per_host_;
+ // true if downstream host is treated as the same. Used for reverse
+ // proxying.
+ bool unified_host_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_DOWNSTREAM_QUEUE_H
diff --git a/src/shrpx_downstream_test.cc b/src/shrpx_downstream_test.cc
new file mode 100644
index 0000000..6100b18
--- /dev/null
+++ b/src/shrpx_downstream_test.cc
@@ -0,0 +1,231 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_downstream_test.h"
+
+#include <iostream>
+
+#include <CUnit/CUnit.h>
+
+#include "shrpx_downstream.h"
+
+namespace shrpx {
+
+void test_downstream_field_store_append_last_header(void) {
+ BlockAllocator balloc(16, 16);
+ FieldStore fs(balloc, 0);
+ fs.alloc_add_header_name(StringRef::from_lit("alpha"));
+ auto bravo = StringRef::from_lit("BRAVO");
+ fs.append_last_header_key(bravo.c_str(), bravo.size());
+ // Add more characters so that relloc occurs
+ auto golf = StringRef::from_lit("golF0123456789");
+ fs.append_last_header_key(golf.c_str(), golf.size());
+
+ auto charlie = StringRef::from_lit("Charlie");
+ fs.append_last_header_value(charlie.c_str(), charlie.size());
+ auto delta = StringRef::from_lit("deltA");
+ fs.append_last_header_value(delta.c_str(), delta.size());
+ // Add more characters so that relloc occurs
+ auto echo = StringRef::from_lit("echo0123456789");
+ fs.append_last_header_value(echo.c_str(), echo.size());
+
+ fs.add_header_token(StringRef::from_lit("echo"),
+ StringRef::from_lit("foxtrot"), false, -1);
+
+ auto ans =
+ HeaderRefs{{StringRef::from_lit("alphabravogolf0123456789"),
+ StringRef::from_lit("CharliedeltAecho0123456789")},
+ {StringRef::from_lit("echo"), StringRef::from_lit("foxtrot")}};
+ CU_ASSERT(ans == fs.headers());
+}
+
+void test_downstream_field_store_header(void) {
+ BlockAllocator balloc(16, 16);
+ FieldStore fs(balloc, 0);
+ fs.add_header_token(StringRef::from_lit("alpha"), StringRef::from_lit("0"),
+ false, -1);
+ fs.add_header_token(StringRef::from_lit(":authority"),
+ StringRef::from_lit("1"), false, http2::HD__AUTHORITY);
+ fs.add_header_token(StringRef::from_lit("content-length"),
+ StringRef::from_lit("2"), false,
+ http2::HD_CONTENT_LENGTH);
+
+ // By token
+ CU_ASSERT(HeaderRef(StringRef{":authority"}, StringRef{"1"}) ==
+ *fs.header(http2::HD__AUTHORITY));
+ CU_ASSERT(nullptr == fs.header(http2::HD__METHOD));
+
+ // By name
+ CU_ASSERT(HeaderRef(StringRef{"alpha"}, StringRef{"0"}) ==
+ *fs.header(StringRef::from_lit("alpha")));
+ CU_ASSERT(nullptr == fs.header(StringRef::from_lit("bravo")));
+}
+
+void test_downstream_crumble_request_cookie(void) {
+ Downstream d(nullptr, nullptr, 0);
+ auto &req = d.request();
+ req.fs.add_header_token(StringRef::from_lit(":method"),
+ StringRef::from_lit("get"), false, -1);
+ req.fs.add_header_token(StringRef::from_lit(":path"),
+ StringRef::from_lit("/"), false, -1);
+ req.fs.add_header_token(StringRef::from_lit("cookie"),
+ StringRef::from_lit("alpha; bravo; ; ;; charlie;;"),
+ true, http2::HD_COOKIE);
+ req.fs.add_header_token(StringRef::from_lit("cookie"),
+ StringRef::from_lit(";delta"), false,
+ http2::HD_COOKIE);
+ req.fs.add_header_token(StringRef::from_lit("cookie"),
+ StringRef::from_lit("echo"), false, http2::HD_COOKIE);
+
+ std::vector<nghttp2_nv> nva;
+ d.crumble_request_cookie(nva);
+
+ auto num_cookies = d.count_crumble_request_cookie();
+
+ CU_ASSERT(5 == nva.size());
+ CU_ASSERT(5 == num_cookies);
+
+ HeaderRefs cookies;
+ std::transform(std::begin(nva), std::end(nva), std::back_inserter(cookies),
+ [](const nghttp2_nv &nv) {
+ return HeaderRef(StringRef{nv.name, nv.namelen},
+ StringRef{nv.value, nv.valuelen},
+ nv.flags & NGHTTP2_NV_FLAG_NO_INDEX);
+ });
+
+ HeaderRefs ans = {
+ {StringRef::from_lit("cookie"), StringRef::from_lit("alpha")},
+ {StringRef::from_lit("cookie"), StringRef::from_lit("bravo")},
+ {StringRef::from_lit("cookie"), StringRef::from_lit("charlie")},
+ {StringRef::from_lit("cookie"), StringRef::from_lit("delta")},
+ {StringRef::from_lit("cookie"), StringRef::from_lit("echo")}};
+
+ CU_ASSERT(ans == cookies);
+ CU_ASSERT(cookies[0].no_index);
+ CU_ASSERT(cookies[1].no_index);
+ CU_ASSERT(cookies[2].no_index);
+}
+
+void test_downstream_assemble_request_cookie(void) {
+ Downstream d(nullptr, nullptr, 0);
+ auto &req = d.request();
+
+ req.fs.add_header_token(StringRef::from_lit(":method"),
+ StringRef::from_lit("get"), false, -1);
+ req.fs.add_header_token(StringRef::from_lit(":path"),
+ StringRef::from_lit("/"), false, -1);
+ req.fs.add_header_token(StringRef::from_lit("cookie"),
+ StringRef::from_lit("alpha"), false,
+ http2::HD_COOKIE);
+ req.fs.add_header_token(StringRef::from_lit("cookie"),
+ StringRef::from_lit("bravo;"), false,
+ http2::HD_COOKIE);
+ req.fs.add_header_token(StringRef::from_lit("cookie"),
+ StringRef::from_lit("charlie; "), false,
+ http2::HD_COOKIE);
+ req.fs.add_header_token(StringRef::from_lit("cookie"),
+ StringRef::from_lit("delta;;"), false,
+ http2::HD_COOKIE);
+ CU_ASSERT("alpha; bravo; charlie; delta" == d.assemble_request_cookie());
+}
+
+void test_downstream_rewrite_location_response_header(void) {
+ Downstream d(nullptr, nullptr, 0);
+ auto &req = d.request();
+ auto &resp = d.response();
+ d.set_request_downstream_host(StringRef::from_lit("localhost2"));
+ req.authority = StringRef::from_lit("localhost:8443");
+ resp.fs.add_header_token(StringRef::from_lit("location"),
+ StringRef::from_lit("http://localhost2:3000/"),
+ false, http2::HD_LOCATION);
+ d.rewrite_location_response_header(StringRef::from_lit("https"));
+ auto location = resp.fs.header(http2::HD_LOCATION);
+ CU_ASSERT("https://localhost:8443/" == (*location).value);
+}
+
+void test_downstream_supports_non_final_response(void) {
+ Downstream d(nullptr, nullptr, 0);
+ auto &req = d.request();
+
+ req.http_major = 3;
+ req.http_minor = 0;
+
+ CU_ASSERT(d.supports_non_final_response());
+
+ req.http_major = 2;
+ req.http_minor = 0;
+
+ CU_ASSERT(d.supports_non_final_response());
+
+ req.http_major = 1;
+ req.http_minor = 1;
+
+ CU_ASSERT(d.supports_non_final_response());
+
+ req.http_major = 1;
+ req.http_minor = 0;
+
+ CU_ASSERT(!d.supports_non_final_response());
+
+ req.http_major = 0;
+ req.http_minor = 9;
+
+ CU_ASSERT(!d.supports_non_final_response());
+}
+
+void test_downstream_find_affinity_cookie(void) {
+ Downstream d(nullptr, nullptr, 0);
+
+ auto &req = d.request();
+ req.fs.add_header_token(StringRef::from_lit("cookie"), StringRef{}, false,
+ http2::HD_COOKIE);
+ req.fs.add_header_token(StringRef::from_lit("cookie"),
+ StringRef::from_lit("a=b;;c=d"), false,
+ http2::HD_COOKIE);
+ req.fs.add_header_token(StringRef::from_lit("content-length"),
+ StringRef::from_lit("599"), false,
+ http2::HD_CONTENT_LENGTH);
+ req.fs.add_header_token(StringRef::from_lit("cookie"),
+ StringRef::from_lit("lb=deadbeef;LB=f1f2f3f4"), false,
+ http2::HD_COOKIE);
+ req.fs.add_header_token(StringRef::from_lit("cookie"),
+ StringRef::from_lit("short=e1e2e3e"), false,
+ http2::HD_COOKIE);
+
+ uint32_t aff;
+
+ aff = d.find_affinity_cookie(StringRef::from_lit("lb"));
+
+ CU_ASSERT(0xdeadbeef == aff);
+
+ aff = d.find_affinity_cookie(StringRef::from_lit("LB"));
+
+ CU_ASSERT(0xf1f2f3f4 == aff);
+
+ aff = d.find_affinity_cookie(StringRef::from_lit("short"));
+
+ CU_ASSERT(0 == aff);
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_downstream_test.h b/src/shrpx_downstream_test.h
new file mode 100644
index 0000000..ef06ea3
--- /dev/null
+++ b/src/shrpx_downstream_test.h
@@ -0,0 +1,44 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_DOWNSTREAM_TEST_H
+#define SHRPX_DOWNSTREAM_TEST_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+namespace shrpx {
+
+void test_downstream_field_store_append_last_header(void);
+void test_downstream_field_store_header(void);
+void test_downstream_crumble_request_cookie(void);
+void test_downstream_assemble_request_cookie(void);
+void test_downstream_rewrite_location_response_header(void);
+void test_downstream_supports_non_final_response(void);
+void test_downstream_find_affinity_cookie(void);
+
+} // namespace shrpx
+
+#endif // SHRPX_DOWNSTREAM_TEST_H
diff --git a/src/shrpx_dual_dns_resolver.cc b/src/shrpx_dual_dns_resolver.cc
new file mode 100644
index 0000000..8c6c5c9
--- /dev/null
+++ b/src/shrpx_dual_dns_resolver.cc
@@ -0,0 +1,93 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_dual_dns_resolver.h"
+
+namespace shrpx {
+
+DualDNSResolver::DualDNSResolver(struct ev_loop *loop, int family)
+ : family_(family), resolv4_(loop), resolv6_(loop) {
+ auto cb = [this](DNSResolverStatus, const Address *) {
+ Address result;
+
+ auto status = this->get_status(&result);
+ switch (status) {
+ case DNSResolverStatus::ERROR:
+ case DNSResolverStatus::OK:
+ break;
+ default:
+ return;
+ }
+
+ auto cb = this->get_complete_cb();
+ cb(status, &result);
+ };
+
+ if (family_ == AF_UNSPEC || family_ == AF_INET) {
+ resolv4_.set_complete_cb(cb);
+ }
+ if (family_ == AF_UNSPEC || family_ == AF_INET6) {
+ resolv6_.set_complete_cb(cb);
+ }
+}
+
+int DualDNSResolver::resolve(const StringRef &host) {
+ int rv4 = 0, rv6 = 0;
+ if (family_ == AF_UNSPEC || family_ == AF_INET) {
+ rv4 = resolv4_.resolve(host, AF_INET);
+ }
+ if (family_ == AF_UNSPEC || family_ == AF_INET6) {
+ rv6 = resolv6_.resolve(host, AF_INET6);
+ }
+
+ if (rv4 != 0 && rv6 != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+CompleteCb DualDNSResolver::get_complete_cb() const { return complete_cb_; }
+
+void DualDNSResolver::set_complete_cb(CompleteCb cb) { complete_cb_ = cb; }
+
+DNSResolverStatus DualDNSResolver::get_status(Address *result) const {
+ auto rv6 = resolv6_.get_status(result);
+ if (rv6 == DNSResolverStatus::OK) {
+ return DNSResolverStatus::OK;
+ }
+ auto rv4 = resolv4_.get_status(result);
+ if (rv4 == DNSResolverStatus::OK) {
+ return DNSResolverStatus::OK;
+ }
+ if (rv4 == DNSResolverStatus::RUNNING || rv6 == DNSResolverStatus::RUNNING) {
+ return DNSResolverStatus::RUNNING;
+ }
+ if (rv4 == DNSResolverStatus::ERROR || rv6 == DNSResolverStatus::ERROR) {
+ return DNSResolverStatus::ERROR;
+ }
+ return DNSResolverStatus::IDLE;
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_dual_dns_resolver.h b/src/shrpx_dual_dns_resolver.h
new file mode 100644
index 0000000..98065da
--- /dev/null
+++ b/src/shrpx_dual_dns_resolver.h
@@ -0,0 +1,69 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_DUAL_DNS_RESOLVER_H
+#define SHRPX_DUAL_DNS_RESOLVER_H
+
+#include "shrpx.h"
+
+#include <ev.h>
+
+#include "shrpx_dns_resolver.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+// DualDNSResolver performs name resolution for both A and AAAA
+// records at the same time. The first successful return (or if we
+// have both successful results, prefer to AAAA) is chosen. This is
+// wrapper around 2 DNSResolver inside. resolve(), get_status(), and
+// how CompleteCb is called have the same semantics with DNSResolver.
+class DualDNSResolver {
+public:
+ // |family| controls IP version preference. If |family| ==
+ // AF_UNSPEC, bot A and AAAA lookups are performed. If |family| ==
+ // AF_INET, only A lookup is performed. If |family| == AF_INET6,
+ // only AAAA lookup is performed.
+ DualDNSResolver(struct ev_loop *loop, int family);
+
+ // Resolves |host|. |host| must be NULL-terminated string.
+ int resolve(const StringRef &host);
+ CompleteCb get_complete_cb() const;
+ void set_complete_cb(CompleteCb cb);
+ DNSResolverStatus get_status(Address *result) const;
+
+private:
+ // IP version preference.
+ int family_;
+ // For A record
+ DNSResolver resolv4_;
+ // For AAAA record
+ DNSResolver resolv6_;
+ CompleteCb complete_cb_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_DUAL_DNS_RESOLVER_H
diff --git a/src/shrpx_error.h b/src/shrpx_error.h
new file mode 100644
index 0000000..c89611f
--- /dev/null
+++ b/src/shrpx_error.h
@@ -0,0 +1,47 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_ERROR_H
+#define SHRPX_ERROR_H
+
+#include "shrpx.h"
+
+namespace shrpx {
+
+// Deprecated, do not use.
+enum ErrorCode {
+ SHRPX_ERR_SUCCESS = 0,
+ SHRPX_ERR_ERROR = -1,
+ SHRPX_ERR_NETWORK = -100,
+ SHRPX_ERR_EOF = -101,
+ SHRPX_ERR_INPROGRESS = -102,
+ SHRPX_ERR_DCONN_CANCELED = -103,
+ SHRPX_ERR_RETRY = -104,
+ SHRPX_ERR_TLS_REQUIRED = -105,
+ SHRPX_ERR_SEND_BLOCKED = -106,
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_ERROR_H
diff --git a/src/shrpx_exec.cc b/src/shrpx_exec.cc
new file mode 100644
index 0000000..d5cd091
--- /dev/null
+++ b/src/shrpx_exec.cc
@@ -0,0 +1,138 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_exec.h"
+
+#include <cerrno>
+
+#include "shrpx_signal.h"
+#include "shrpx_log.h"
+#include "util.h"
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+// inspired by h2o_read_command function from h2o project:
+// https://github.com/h2o/h2o
+int exec_read_command(Process &proc, char *const argv[]) {
+ int rv;
+ int pfd[2];
+
+#ifdef O_CLOEXEC
+ if (pipe2(pfd, O_CLOEXEC) == -1) {
+ return -1;
+ }
+#else // !O_CLOEXEC
+ if (pipe(pfd) == -1) {
+ return -1;
+ }
+ util::make_socket_closeonexec(pfd[0]);
+ util::make_socket_closeonexec(pfd[1]);
+#endif // !O_CLOEXEC
+
+ auto closer = defer([&pfd]() {
+ if (pfd[0] != -1) {
+ close(pfd[0]);
+ }
+
+ if (pfd[1] != -1) {
+ close(pfd[1]);
+ }
+ });
+
+ sigset_t oldset;
+
+ rv = shrpx_signal_block_all(&oldset);
+ if (rv != 0) {
+ auto error = errno;
+ LOG(ERROR) << "Blocking all signals failed: errno=" << error;
+
+ return -1;
+ }
+
+ auto pid = fork();
+
+ if (pid == 0) {
+ // This is multithreaded program, and we are allowed to use only
+ // async-signal-safe functions here.
+
+ // child process
+ shrpx_signal_unset_worker_proc_ign_handler();
+
+ rv = shrpx_signal_unblock_all();
+ if (rv != 0) {
+ static constexpr char msg[] = "Unblocking all signals failed\n";
+ while (write(STDERR_FILENO, msg, str_size(msg)) == -1 && errno == EINTR)
+ ;
+ nghttp2_Exit(EXIT_FAILURE);
+ }
+
+ dup2(pfd[1], 1);
+ close(pfd[0]);
+
+ rv = execv(argv[0], argv);
+ if (rv == -1) {
+ static constexpr char msg[] = "Could not execute command\n";
+ while (write(STDERR_FILENO, msg, str_size(msg)) == -1 && errno == EINTR)
+ ;
+ nghttp2_Exit(EXIT_FAILURE);
+ }
+ // unreachable
+ }
+
+ // parent process
+ if (pid == -1) {
+ auto error = errno;
+ LOG(ERROR) << "Could not execute command: " << argv[0]
+ << ", fork() failed, errno=" << error;
+ }
+
+ rv = shrpx_signal_set(&oldset);
+ if (rv != 0) {
+ auto error = errno;
+ LOG(FATAL) << "Restoring all signals failed: errno=" << error;
+
+ nghttp2_Exit(EXIT_FAILURE);
+ }
+
+ if (pid == -1) {
+ return -1;
+ }
+
+ close(pfd[1]);
+ pfd[1] = -1;
+
+ util::make_socket_nonblocking(pfd[0]);
+
+ proc.pid = pid;
+ proc.rfd = pfd[0];
+
+ pfd[0] = -1;
+
+ return 0;
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_exec.h b/src/shrpx_exec.h
new file mode 100644
index 0000000..2d8a770
--- /dev/null
+++ b/src/shrpx_exec.h
@@ -0,0 +1,47 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_EXEC_H
+#define SHRPX_EXEC_H
+
+#include "unistd.h"
+
+namespace shrpx {
+
+struct Process {
+ pid_t pid;
+ // fd to read from process
+ int rfd;
+};
+
+// Executes command |argv| after forking current process. The command
+// should not expect to read from stdin. Parent process can read the
+// stdout from command using proc.rfd. On success, this function
+// returns 0, and process information is stored in |proc|. Otherwise,
+// returns -1.
+int exec_read_command(Process &proc, char *const argv[]);
+
+} // namespace shrpx
+
+#endif // SHRPX_EXEC_H
diff --git a/src/shrpx_health_monitor_downstream_connection.cc b/src/shrpx_health_monitor_downstream_connection.cc
new file mode 100644
index 0000000..89e5396
--- /dev/null
+++ b/src/shrpx_health_monitor_downstream_connection.cc
@@ -0,0 +1,116 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_health_monitor_downstream_connection.h"
+
+#include "shrpx_client_handler.h"
+#include "shrpx_upstream.h"
+#include "shrpx_downstream.h"
+#include "shrpx_log.h"
+
+namespace shrpx {
+
+HealthMonitorDownstreamConnection::HealthMonitorDownstreamConnection() {}
+
+HealthMonitorDownstreamConnection::~HealthMonitorDownstreamConnection() {}
+
+int HealthMonitorDownstreamConnection::attach_downstream(
+ Downstream *downstream) {
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "Attaching to DOWNSTREAM:" << downstream;
+ }
+
+ downstream_ = downstream;
+
+ return 0;
+}
+
+void HealthMonitorDownstreamConnection::detach_downstream(
+ Downstream *downstream) {
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "Detaching from DOWNSTREAM:" << downstream;
+ }
+ downstream_ = nullptr;
+}
+
+int HealthMonitorDownstreamConnection::push_request_headers() {
+ downstream_->set_request_header_sent(true);
+ auto src = downstream_->get_blocked_request_buf();
+ auto dest = downstream_->get_request_buf();
+ src->remove(*dest);
+
+ return 0;
+}
+
+int HealthMonitorDownstreamConnection::push_upload_data_chunk(
+ const uint8_t *data, size_t datalen) {
+ return 0;
+}
+
+int HealthMonitorDownstreamConnection::end_upload_data() {
+ auto upstream = downstream_->get_upstream();
+ auto &resp = downstream_->response();
+
+ resp.http_status = 200;
+
+ resp.fs.add_header_token(StringRef::from_lit("content-length"),
+ StringRef::from_lit("0"), false,
+ http2::HD_CONTENT_LENGTH);
+
+ if (upstream->send_reply(downstream_, nullptr, 0) != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+void HealthMonitorDownstreamConnection::pause_read(IOCtrlReason reason) {}
+
+int HealthMonitorDownstreamConnection::resume_read(IOCtrlReason reason,
+ size_t consumed) {
+ return 0;
+}
+
+void HealthMonitorDownstreamConnection::force_resume_read() {}
+
+int HealthMonitorDownstreamConnection::on_read() { return 0; }
+
+int HealthMonitorDownstreamConnection::on_write() { return 0; }
+
+void HealthMonitorDownstreamConnection::on_upstream_change(Upstream *upstream) {
+}
+
+bool HealthMonitorDownstreamConnection::poolable() const { return false; }
+
+const std::shared_ptr<DownstreamAddrGroup> &
+HealthMonitorDownstreamConnection::get_downstream_addr_group() const {
+ static std::shared_ptr<DownstreamAddrGroup> s;
+ return s;
+}
+
+DownstreamAddr *HealthMonitorDownstreamConnection::get_addr() const {
+ return nullptr;
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_health_monitor_downstream_connection.h b/src/shrpx_health_monitor_downstream_connection.h
new file mode 100644
index 0000000..c0eb633
--- /dev/null
+++ b/src/shrpx_health_monitor_downstream_connection.h
@@ -0,0 +1,64 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_HEALTH_MONITOR_DOWNSTREAM_CONNECTION_H
+#define SHRPX_HEALTH_MONITOR_DOWNSTREAM_CONNECTION_H
+
+#include "shrpx_downstream_connection.h"
+
+namespace shrpx {
+
+class Worker;
+
+class HealthMonitorDownstreamConnection : public DownstreamConnection {
+public:
+ HealthMonitorDownstreamConnection();
+ virtual ~HealthMonitorDownstreamConnection();
+ virtual int attach_downstream(Downstream *downstream);
+ virtual void detach_downstream(Downstream *downstream);
+
+ virtual int push_request_headers();
+ virtual int push_upload_data_chunk(const uint8_t *data, size_t datalen);
+ virtual int end_upload_data();
+
+ virtual void pause_read(IOCtrlReason reason);
+ virtual int resume_read(IOCtrlReason reason, size_t consumed);
+ virtual void force_resume_read();
+
+ virtual int on_read();
+ virtual int on_write();
+
+ virtual void on_upstream_change(Upstream *upstream);
+
+ // true if this object is poolable.
+ virtual bool poolable() const;
+
+ virtual const std::shared_ptr<DownstreamAddrGroup> &
+ get_downstream_addr_group() const;
+ virtual DownstreamAddr *get_addr() const;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_HEALTH_MONITOR_DOWNSTREAM_CONNECTION_H
diff --git a/src/shrpx_http.cc b/src/shrpx_http.cc
new file mode 100644
index 0000000..ad32dc9
--- /dev/null
+++ b/src/shrpx_http.cc
@@ -0,0 +1,280 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_http.h"
+
+#include "shrpx_config.h"
+#include "shrpx_log.h"
+#include "http2.h"
+#include "util.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+namespace http {
+
+StringRef create_error_html(BlockAllocator &balloc, unsigned int http_status) {
+ auto &httpconf = get_config()->http;
+
+ const auto &error_pages = httpconf.error_pages;
+ for (const auto &page : error_pages) {
+ if (page.http_status == 0 || page.http_status == http_status) {
+ return StringRef{std::begin(page.content), std::end(page.content)};
+ }
+ }
+
+ auto status_string = http2::stringify_status(balloc, http_status);
+ auto reason_phrase = http2::get_reason_phrase(http_status);
+
+ return concat_string_ref(
+ balloc, StringRef::from_lit(R"(<!DOCTYPE html><html lang="en"><title>)"),
+ status_string, StringRef::from_lit(" "), reason_phrase,
+ StringRef::from_lit("</title><body><h1>"), status_string,
+ StringRef::from_lit(" "), reason_phrase,
+ StringRef::from_lit("</h1><footer>"), httpconf.server_name,
+ StringRef::from_lit("</footer></body></html>"));
+}
+
+StringRef create_forwarded(BlockAllocator &balloc, int params,
+ const StringRef &node_by, const StringRef &node_for,
+ const StringRef &host, const StringRef &proto) {
+ size_t len = 0;
+ if ((params & FORWARDED_BY) && !node_by.empty()) {
+ len += str_size("by=\"") + node_by.size() + str_size("\";");
+ }
+ if ((params & FORWARDED_FOR) && !node_for.empty()) {
+ len += str_size("for=\"") + node_for.size() + str_size("\";");
+ }
+ if ((params & FORWARDED_HOST) && !host.empty()) {
+ len += str_size("host=\"") + host.size() + str_size("\";");
+ }
+ if ((params & FORWARDED_PROTO) && !proto.empty()) {
+ len += str_size("proto=") + proto.size() + str_size(";");
+ }
+
+ auto iov = make_byte_ref(balloc, len + 1);
+ auto p = iov.base;
+
+ if ((params & FORWARDED_BY) && !node_by.empty()) {
+ // This must be quoted-string unless it is obfuscated version
+ // (which starts with "_") or some special value (e.g.,
+ // "localhost" for UNIX domain socket), since ':' is not allowed
+ // in token. ':' is used to separate host and port.
+ if (node_by[0] == '_' || node_by[0] == 'l') {
+ p = util::copy_lit(p, "by=");
+ p = std::copy(std::begin(node_by), std::end(node_by), p);
+ p = util::copy_lit(p, ";");
+ } else {
+ p = util::copy_lit(p, "by=\"");
+ p = std::copy(std::begin(node_by), std::end(node_by), p);
+ p = util::copy_lit(p, "\";");
+ }
+ }
+ if ((params & FORWARDED_FOR) && !node_for.empty()) {
+ // We only quote IPv6 literal address only, which starts with '['.
+ if (node_for[0] == '[') {
+ p = util::copy_lit(p, "for=\"");
+ p = std::copy(std::begin(node_for), std::end(node_for), p);
+ p = util::copy_lit(p, "\";");
+ } else {
+ p = util::copy_lit(p, "for=");
+ p = std::copy(std::begin(node_for), std::end(node_for), p);
+ p = util::copy_lit(p, ";");
+ }
+ }
+ if ((params & FORWARDED_HOST) && !host.empty()) {
+ // Just be quoted to skip checking characters.
+ p = util::copy_lit(p, "host=\"");
+ p = std::copy(std::begin(host), std::end(host), p);
+ p = util::copy_lit(p, "\";");
+ }
+ if ((params & FORWARDED_PROTO) && !proto.empty()) {
+ // Scheme production rule only allow characters which are all in
+ // token.
+ p = util::copy_lit(p, "proto=");
+ p = std::copy(std::begin(proto), std::end(proto), p);
+ *p++ = ';';
+ }
+
+ if (iov.base == p) {
+ return StringRef{};
+ }
+
+ --p;
+ *p = '\0';
+
+ return StringRef{iov.base, p};
+}
+
+std::string colorizeHeaders(const char *hdrs) {
+ std::string nhdrs;
+ const char *p = strchr(hdrs, '\n');
+ if (!p) {
+ // Not valid HTTP header
+ return hdrs;
+ }
+ nhdrs.append(hdrs, p + 1);
+ ++p;
+ while (1) {
+ const char *np = strchr(p, ':');
+ if (!np) {
+ nhdrs.append(p);
+ break;
+ }
+ nhdrs += TTY_HTTP_HD;
+ nhdrs.append(p, np);
+ nhdrs += TTY_RST;
+ auto redact = util::strieq_l("authorization", StringRef{p, np});
+ p = np;
+ np = strchr(p, '\n');
+ if (!np) {
+ if (redact) {
+ nhdrs.append(": <redacted>");
+ } else {
+ nhdrs.append(p);
+ }
+ break;
+ }
+ if (redact) {
+ nhdrs.append(": <redacted>\n");
+ } else {
+ nhdrs.append(p, np + 1);
+ }
+ p = np + 1;
+ }
+ return nhdrs;
+}
+
+ssize_t select_padding_callback(nghttp2_session *session,
+ const nghttp2_frame *frame, size_t max_payload,
+ void *user_data) {
+ return std::min(max_payload, frame->hd.length + get_config()->padding);
+}
+
+StringRef create_affinity_cookie(BlockAllocator &balloc, const StringRef &name,
+ uint32_t affinity_cookie,
+ const StringRef &path, bool secure) {
+ static constexpr auto PATH_PREFIX = StringRef::from_lit("; Path=");
+ static constexpr auto SECURE = StringRef::from_lit("; Secure");
+ // <name>=<value>[; Path=<path>][; Secure]
+ size_t len = name.size() + 1 + 8;
+
+ if (!path.empty()) {
+ len += PATH_PREFIX.size() + path.size();
+ }
+ if (secure) {
+ len += SECURE.size();
+ }
+
+ auto iov = make_byte_ref(balloc, len + 1);
+ auto p = iov.base;
+ p = std::copy(std::begin(name), std::end(name), p);
+ *p++ = '=';
+ affinity_cookie = htonl(affinity_cookie);
+ p = util::format_hex(p,
+ StringRef{reinterpret_cast<uint8_t *>(&affinity_cookie),
+ reinterpret_cast<uint8_t *>(&affinity_cookie) +
+ sizeof(affinity_cookie)});
+ if (!path.empty()) {
+ p = std::copy(std::begin(PATH_PREFIX), std::end(PATH_PREFIX), p);
+ p = std::copy(std::begin(path), std::end(path), p);
+ }
+ if (secure) {
+ p = std::copy(std::begin(SECURE), std::end(SECURE), p);
+ }
+ *p = '\0';
+ return StringRef{iov.base, p};
+}
+
+bool require_cookie_secure_attribute(SessionAffinityCookieSecure secure,
+ const StringRef &scheme) {
+ switch (secure) {
+ case SessionAffinityCookieSecure::AUTO:
+ return scheme == "https";
+ case SessionAffinityCookieSecure::YES:
+ return true;
+ default:
+ return false;
+ }
+}
+
+StringRef create_altsvc_header_value(BlockAllocator &balloc,
+ const std::vector<AltSvc> &altsvcs) {
+ // <PROTOID>="<HOST>:<SERVICE>"; <PARAMS>
+ size_t len = 0;
+
+ if (altsvcs.empty()) {
+ return StringRef{};
+ }
+
+ for (auto &altsvc : altsvcs) {
+ len += util::percent_encode_tokenlen(altsvc.protocol_id);
+ len += str_size("=\"");
+ len += util::quote_stringlen(altsvc.host);
+ len += str_size(":");
+ len += altsvc.service.size();
+ len += str_size("\"");
+ if (!altsvc.params.empty()) {
+ len += str_size("; ");
+ len += altsvc.params.size();
+ }
+ }
+
+ // ", " between items.
+ len += (altsvcs.size() - 1) * 2;
+
+ // We will write additional ", " at the end, and cut it later.
+ auto iov = make_byte_ref(balloc, len + 2);
+ auto p = iov.base;
+
+ for (auto &altsvc : altsvcs) {
+ p = util::percent_encode_token(p, altsvc.protocol_id);
+ p = util::copy_lit(p, "=\"");
+ p = util::quote_string(p, altsvc.host);
+ *p++ = ':';
+ p = std::copy(std::begin(altsvc.service), std::end(altsvc.service), p);
+ *p++ = '"';
+ if (!altsvc.params.empty()) {
+ p = util::copy_lit(p, "; ");
+ p = std::copy(std::begin(altsvc.params), std::end(altsvc.params), p);
+ }
+ p = util::copy_lit(p, ", ");
+ }
+
+ p -= 2;
+ *p = '\0';
+
+ assert(static_cast<size_t>(p - iov.base) == len);
+
+ return StringRef{iov.base, p};
+}
+
+bool check_http_scheme(const StringRef &scheme, bool encrypted) {
+ return encrypted ? scheme == "https" : scheme == "http";
+}
+
+} // namespace http
+
+} // namespace shrpx
diff --git a/src/shrpx_http.h b/src/shrpx_http.h
new file mode 100644
index 0000000..18935d8
--- /dev/null
+++ b/src/shrpx_http.h
@@ -0,0 +1,96 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_HTTP_H
+#define SHRPX_HTTP_H
+
+#include "shrpx.h"
+
+#include <string>
+
+#include <nghttp2/nghttp2.h>
+
+#include "shrpx_config.h"
+#include "util.h"
+#include "allocator.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+namespace http {
+
+StringRef create_error_html(BlockAllocator &balloc, unsigned int status_code);
+
+template <typename OutputIt>
+OutputIt create_via_header_value(OutputIt dst, int major, int minor) {
+ *dst++ = static_cast<char>(major + '0');
+ if (major < 2) {
+ *dst++ = '.';
+ *dst++ = static_cast<char>(minor + '0');
+ }
+ return util::copy_lit(dst, " nghttpx");
+}
+
+// Returns generated RFC 7239 Forwarded header field value. The
+// |params| is bitwise-OR of zero or more of shrpx_forwarded_param
+// defined in shrpx_config.h.
+StringRef create_forwarded(BlockAllocator &balloc, int params,
+ const StringRef &node_by, const StringRef &node_for,
+ const StringRef &host, const StringRef &proto);
+
+// Adds ANSI color codes to HTTP headers |hdrs|.
+std::string colorizeHeaders(const char *hdrs);
+
+ssize_t select_padding_callback(nghttp2_session *session,
+ const nghttp2_frame *frame, size_t max_payload,
+ void *user_data);
+
+// Creates set-cookie-string for cookie based affinity. If |path| is
+// not empty, "; <path>" is added. If |secure| is true, "; Secure" is
+// added.
+StringRef create_affinity_cookie(BlockAllocator &balloc, const StringRef &name,
+ uint32_t affinity_cookie,
+ const StringRef &path, bool secure);
+
+// Returns true if |secure| indicates that Secure attribute should be
+// set.
+bool require_cookie_secure_attribute(SessionAffinityCookieSecure secure,
+ const StringRef &scheme);
+
+// Returns RFC 7838 alt-svc header field value.
+StringRef create_altsvc_header_value(BlockAllocator &balloc,
+ const std::vector<AltSvc> &altsvcs);
+
+// Returns true if either of the following conditions holds:
+// - scheme is https and encrypted is true
+// - scheme is http and encrypted is false
+// Otherwise returns false.
+bool check_http_scheme(const StringRef &scheme, bool encrypted);
+
+} // namespace http
+
+} // namespace shrpx
+
+#endif // SHRPX_HTTP_H
diff --git a/src/shrpx_http2_downstream_connection.cc b/src/shrpx_http2_downstream_connection.cc
new file mode 100644
index 0000000..ee48799
--- /dev/null
+++ b/src/shrpx_http2_downstream_connection.cc
@@ -0,0 +1,621 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_http2_downstream_connection.h"
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+
+#include "llhttp.h"
+
+#include "shrpx_client_handler.h"
+#include "shrpx_upstream.h"
+#include "shrpx_downstream.h"
+#include "shrpx_config.h"
+#include "shrpx_error.h"
+#include "shrpx_http.h"
+#include "shrpx_http2_session.h"
+#include "shrpx_worker.h"
+#include "shrpx_log.h"
+#include "http2.h"
+#include "util.h"
+#include "ssl_compat.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+Http2DownstreamConnection::Http2DownstreamConnection(Http2Session *http2session)
+ : dlnext(nullptr),
+ dlprev(nullptr),
+ http2session_(http2session),
+ sd_(nullptr) {}
+
+Http2DownstreamConnection::~Http2DownstreamConnection() {
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "Deleting";
+ }
+ if (downstream_) {
+ downstream_->disable_downstream_rtimer();
+ downstream_->disable_downstream_wtimer();
+
+ uint32_t error_code;
+ if (downstream_->get_request_state() == DownstreamState::STREAM_CLOSED &&
+ downstream_->get_upgraded()) {
+ // For upgraded connection, send NO_ERROR. Should we consider
+ // request states other than DownstreamState::STREAM_CLOSED ?
+ error_code = NGHTTP2_NO_ERROR;
+ } else {
+ error_code = NGHTTP2_INTERNAL_ERROR;
+ }
+
+ if (http2session_->get_state() == Http2SessionState::CONNECTED &&
+ downstream_->get_downstream_stream_id() != -1) {
+ submit_rst_stream(downstream_, error_code);
+
+ auto &resp = downstream_->response();
+
+ http2session_->consume(downstream_->get_downstream_stream_id(),
+ resp.unconsumed_body_length);
+
+ resp.unconsumed_body_length = 0;
+
+ http2session_->signal_write();
+ }
+ }
+ http2session_->remove_downstream_connection(this);
+
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "Deleted";
+ }
+}
+
+int Http2DownstreamConnection::attach_downstream(Downstream *downstream) {
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "Attaching to DOWNSTREAM:" << downstream;
+ }
+ http2session_->add_downstream_connection(this);
+ http2session_->signal_write();
+
+ downstream_ = downstream;
+ downstream_->reset_downstream_rtimer();
+
+ auto &req = downstream_->request();
+
+ // HTTP/2 disables HTTP Upgrade.
+ if (req.method != HTTP_CONNECT && req.connect_proto == ConnectProto::NONE) {
+ req.upgrade_request = false;
+ }
+
+ return 0;
+}
+
+void Http2DownstreamConnection::detach_downstream(Downstream *downstream) {
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "Detaching from DOWNSTREAM:" << downstream;
+ }
+
+ auto &resp = downstream_->response();
+
+ if (downstream_->get_downstream_stream_id() != -1) {
+ if (submit_rst_stream(downstream) == 0) {
+ http2session_->signal_write();
+ }
+
+ http2session_->consume(downstream_->get_downstream_stream_id(),
+ resp.unconsumed_body_length);
+
+ resp.unconsumed_body_length = 0;
+
+ http2session_->signal_write();
+ }
+
+ downstream->disable_downstream_rtimer();
+ downstream->disable_downstream_wtimer();
+ downstream_ = nullptr;
+}
+
+int Http2DownstreamConnection::submit_rst_stream(Downstream *downstream,
+ uint32_t error_code) {
+ int rv = -1;
+ if (http2session_->get_state() == Http2SessionState::CONNECTED &&
+ downstream->get_downstream_stream_id() != -1) {
+ switch (downstream->get_response_state()) {
+ case DownstreamState::MSG_RESET:
+ case DownstreamState::MSG_BAD_HEADER:
+ case DownstreamState::MSG_COMPLETE:
+ break;
+ default:
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "Submit RST_STREAM for DOWNSTREAM:" << downstream
+ << ", stream_id="
+ << downstream->get_downstream_stream_id()
+ << ", error_code=" << error_code;
+ }
+ rv = http2session_->submit_rst_stream(
+ downstream->get_downstream_stream_id(), error_code);
+ }
+ }
+ return rv;
+}
+
+namespace {
+ssize_t http2_data_read_callback(nghttp2_session *session, int32_t stream_id,
+ uint8_t *buf, size_t length,
+ uint32_t *data_flags,
+ nghttp2_data_source *source, void *user_data) {
+ int rv;
+ auto sd = static_cast<StreamData *>(
+ nghttp2_session_get_stream_user_data(session, stream_id));
+ if (!sd || !sd->dconn) {
+ return NGHTTP2_ERR_DEFERRED;
+ }
+ auto dconn = sd->dconn;
+ auto downstream = dconn->get_downstream();
+ if (!downstream) {
+ // In this case, RST_STREAM should have been issued. But depending
+ // on the priority, DATA frame may come first.
+ return NGHTTP2_ERR_DEFERRED;
+ }
+ const auto &req = downstream->request();
+ auto input = downstream->get_request_buf();
+
+ auto nread = std::min(input->rleft(), length);
+ auto input_empty = input->rleft() == nread;
+
+ *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY;
+
+ if (input_empty &&
+ downstream->get_request_state() == DownstreamState::MSG_COMPLETE &&
+ // If connection is upgraded, don't set EOF flag, since HTTP/1
+ // will set MSG_COMPLETE to request state after upgrade response
+ // header is seen.
+ (!req.upgrade_request ||
+ (downstream->get_response_state() == DownstreamState::HEADER_COMPLETE &&
+ !downstream->get_upgraded()))) {
+
+ *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+
+ const auto &trailers = req.fs.trailers();
+ if (!trailers.empty()) {
+ std::vector<nghttp2_nv> nva;
+ nva.reserve(trailers.size());
+ http2::copy_headers_to_nva_nocopy(nva, trailers, http2::HDOP_STRIP_ALL);
+ if (!nva.empty()) {
+ rv = nghttp2_submit_trailer(session, stream_id, nva.data(), nva.size());
+ if (rv != 0) {
+ if (nghttp2_is_fatal(rv)) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ } else {
+ *data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
+ }
+ }
+ }
+ }
+
+ if (nread == 0 && (*data_flags & NGHTTP2_DATA_FLAG_EOF) == 0) {
+ downstream->disable_downstream_wtimer();
+
+ return NGHTTP2_ERR_DEFERRED;
+ }
+
+ return nread;
+}
+} // namespace
+
+int Http2DownstreamConnection::push_request_headers() {
+ int rv;
+ if (!downstream_) {
+ return 0;
+ }
+ if (!http2session_->can_push_request(downstream_)) {
+ // The HTTP2 session to the backend has not been established or
+ // connection is now being checked. This function will be called
+ // again just after it is established.
+ downstream_->set_request_pending(true);
+ http2session_->start_checking_connection();
+ return 0;
+ }
+
+ downstream_->set_request_pending(false);
+
+ const auto &req = downstream_->request();
+
+ if (req.connect_proto != ConnectProto::NONE &&
+ !http2session_->get_allow_connect_proto()) {
+ return -1;
+ }
+
+ auto &balloc = downstream_->get_block_allocator();
+
+ auto config = get_config();
+ auto &httpconf = config->http;
+ auto &http2conf = config->http2;
+
+ auto no_host_rewrite = httpconf.no_host_rewrite || config->http2_proxy ||
+ req.regular_connect_method();
+
+ // http2session_ has already in CONNECTED state, so we can get
+ // addr_idx here.
+ const auto &downstream_hostport = http2session_->get_addr()->hostport;
+
+ // For HTTP/1.0 request, there is no authority in request. In that
+ // case, we use backend server's host nonetheless.
+ auto authority = StringRef(downstream_hostport);
+
+ if (no_host_rewrite && !req.authority.empty()) {
+ authority = req.authority;
+ }
+
+ downstream_->set_request_downstream_host(authority);
+
+ size_t num_cookies = 0;
+ if (!http2conf.no_cookie_crumbling) {
+ num_cookies = downstream_->count_crumble_request_cookie();
+ }
+
+ // 11 means:
+ // 1. :method
+ // 2. :scheme
+ // 3. :path
+ // 4. :authority (or host)
+ // 5. :protocol (optional)
+ // 6. via (optional)
+ // 7. x-forwarded-for (optional)
+ // 8. x-forwarded-proto (optional)
+ // 9. te (optional)
+ // 10. forwarded (optional)
+ // 11. early-data (optional)
+ auto nva = std::vector<nghttp2_nv>();
+ nva.reserve(req.fs.headers().size() + 11 + num_cookies +
+ httpconf.add_request_headers.size());
+
+ if (req.connect_proto == ConnectProto::WEBSOCKET) {
+ nva.push_back(http2::make_nv_ll(":method", "CONNECT"));
+ nva.push_back(http2::make_nv_ll(":protocol", "websocket"));
+ } else {
+ nva.push_back(http2::make_nv_ls_nocopy(
+ ":method", http2::to_method_string(req.method)));
+ }
+
+ if (!req.regular_connect_method()) {
+ assert(!req.scheme.empty());
+
+ auto addr = http2session_->get_addr();
+ assert(addr);
+ // We will handle more protocol scheme upgrade in the future.
+ if (addr->tls && addr->upgrade_scheme && req.scheme == "http") {
+ nva.push_back(http2::make_nv_ll(":scheme", "https"));
+ } else {
+ nva.push_back(http2::make_nv_ls_nocopy(":scheme", req.scheme));
+ }
+
+ if (req.method == HTTP_OPTIONS && req.path.empty()) {
+ nva.push_back(http2::make_nv_ll(":path", "*"));
+ } else {
+ nva.push_back(http2::make_nv_ls_nocopy(":path", req.path));
+ }
+
+ if (!req.no_authority || req.connect_proto != ConnectProto::NONE) {
+ nva.push_back(http2::make_nv_ls_nocopy(":authority", authority));
+ } else {
+ nva.push_back(http2::make_nv_ls_nocopy("host", authority));
+ }
+ } else {
+ nva.push_back(http2::make_nv_ls_nocopy(":authority", authority));
+ }
+
+ auto &fwdconf = httpconf.forwarded;
+ auto &xffconf = httpconf.xff;
+ auto &xfpconf = httpconf.xfp;
+ auto &earlydataconf = httpconf.early_data;
+
+ uint32_t build_flags =
+ (fwdconf.strip_incoming ? http2::HDOP_STRIP_FORWARDED : 0) |
+ (xffconf.strip_incoming ? http2::HDOP_STRIP_X_FORWARDED_FOR : 0) |
+ (xfpconf.strip_incoming ? http2::HDOP_STRIP_X_FORWARDED_PROTO : 0) |
+ (earlydataconf.strip_incoming ? http2::HDOP_STRIP_EARLY_DATA : 0) |
+ http2::HDOP_STRIP_SEC_WEBSOCKET_KEY;
+
+ http2::copy_headers_to_nva_nocopy(nva, req.fs.headers(), build_flags);
+
+ if (!http2conf.no_cookie_crumbling) {
+ downstream_->crumble_request_cookie(nva);
+ }
+
+ auto upstream = downstream_->get_upstream();
+ auto handler = upstream->get_client_handler();
+
+#if defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_BORINGSSL)
+ auto conn = handler->get_connection();
+
+ if (conn->tls.ssl && !SSL_is_init_finished(conn->tls.ssl)) {
+ nva.push_back(http2::make_nv_ll("early-data", "1"));
+ }
+#endif // NGHTTP2_GENUINE_OPENSSL || NGHTTP2_OPENSSL_IS_BORINGSSL
+
+ auto fwd =
+ fwdconf.strip_incoming ? nullptr : req.fs.header(http2::HD_FORWARDED);
+
+ if (fwdconf.params) {
+ auto params = fwdconf.params;
+
+ if (config->http2_proxy || req.regular_connect_method()) {
+ params &= ~FORWARDED_PROTO;
+ }
+
+ auto value = http::create_forwarded(
+ balloc, params, handler->get_forwarded_by(),
+ handler->get_forwarded_for(), req.authority, req.scheme);
+
+ if (fwd || !value.empty()) {
+ if (fwd) {
+ if (value.empty()) {
+ value = fwd->value;
+ } else {
+ value = concat_string_ref(balloc, fwd->value,
+ StringRef::from_lit(", "), value);
+ }
+ }
+
+ nva.push_back(http2::make_nv_ls_nocopy("forwarded", value));
+ }
+ } else if (fwd) {
+ nva.push_back(http2::make_nv_ls_nocopy("forwarded", fwd->value));
+ }
+
+ auto xff = xffconf.strip_incoming ? nullptr
+ : req.fs.header(http2::HD_X_FORWARDED_FOR);
+
+ if (xffconf.add) {
+ StringRef xff_value;
+ const auto &addr = upstream->get_client_handler()->get_ipaddr();
+ if (xff) {
+ xff_value = concat_string_ref(balloc, xff->value,
+ StringRef::from_lit(", "), addr);
+ } else {
+ xff_value = addr;
+ }
+ nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-for", xff_value));
+ } else if (xff) {
+ nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-for", xff->value));
+ }
+
+ if (!config->http2_proxy && !req.regular_connect_method()) {
+ auto xfp = xfpconf.strip_incoming
+ ? nullptr
+ : req.fs.header(http2::HD_X_FORWARDED_PROTO);
+
+ if (xfpconf.add) {
+ StringRef xfp_value;
+ // We use same protocol with :scheme header field
+ if (xfp) {
+ xfp_value = concat_string_ref(balloc, xfp->value,
+ StringRef::from_lit(", "), req.scheme);
+ } else {
+ xfp_value = req.scheme;
+ }
+ nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-proto", xfp_value));
+ } else if (xfp) {
+ nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-proto", xfp->value));
+ }
+ }
+
+ auto via = req.fs.header(http2::HD_VIA);
+ if (httpconf.no_via) {
+ if (via) {
+ nva.push_back(http2::make_nv_ls_nocopy("via", (*via).value));
+ }
+ } else {
+ size_t vialen = 16;
+ if (via) {
+ vialen += via->value.size() + 2;
+ }
+
+ auto iov = make_byte_ref(balloc, vialen + 1);
+ auto p = iov.base;
+
+ if (via) {
+ p = std::copy(std::begin(via->value), std::end(via->value), p);
+ p = util::copy_lit(p, ", ");
+ }
+ p = http::create_via_header_value(p, req.http_major, req.http_minor);
+ *p = '\0';
+
+ nva.push_back(http2::make_nv_ls_nocopy("via", StringRef{iov.base, p}));
+ }
+
+ auto te = req.fs.header(http2::HD_TE);
+ // HTTP/1 upstream request can contain keyword other than
+ // "trailers". We just forward "trailers".
+ // TODO more strict handling required here.
+ if (te && http2::contains_trailers(te->value)) {
+ nva.push_back(http2::make_nv_ll("te", "trailers"));
+ }
+
+ for (auto &p : httpconf.add_request_headers) {
+ nva.push_back(http2::make_nv_nocopy(p.name, p.value));
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ std::stringstream ss;
+ for (auto &nv : nva) {
+ if (util::streq_l("authorization", nv.name, nv.namelen)) {
+ ss << TTY_HTTP_HD << StringRef{nv.name, nv.namelen} << TTY_RST
+ << ": <redacted>\n";
+ continue;
+ }
+ ss << TTY_HTTP_HD << StringRef{nv.name, nv.namelen} << TTY_RST << ": "
+ << StringRef{nv.value, nv.valuelen} << "\n";
+ }
+ DCLOG(INFO, this) << "HTTP request headers\n" << ss.str();
+ }
+
+ auto transfer_encoding = req.fs.header(http2::HD_TRANSFER_ENCODING);
+
+ nghttp2_data_provider *data_prdptr = nullptr;
+ nghttp2_data_provider data_prd;
+
+ // Add body as long as transfer-encoding is given even if
+ // req.fs.content_length == 0 to forward trailer fields.
+ if (req.method == HTTP_CONNECT || req.connect_proto != ConnectProto::NONE ||
+ transfer_encoding || req.fs.content_length > 0 || req.http2_expect_body) {
+ // Request-body is expected.
+ data_prd = {{}, http2_data_read_callback};
+ data_prdptr = &data_prd;
+ }
+
+ rv = http2session_->submit_request(this, nva.data(), nva.size(), data_prdptr);
+ if (rv != 0) {
+ DCLOG(FATAL, this) << "nghttp2_submit_request() failed";
+ return -1;
+ }
+
+ if (data_prdptr) {
+ downstream_->reset_downstream_wtimer();
+ }
+
+ http2session_->signal_write();
+ return 0;
+}
+
+int Http2DownstreamConnection::push_upload_data_chunk(const uint8_t *data,
+ size_t datalen) {
+ if (!downstream_->get_request_header_sent()) {
+ auto output = downstream_->get_blocked_request_buf();
+ auto &req = downstream_->request();
+ output->append(data, datalen);
+ req.unconsumed_body_length += datalen;
+ return 0;
+ }
+
+ int rv;
+ auto output = downstream_->get_request_buf();
+ output->append(data, datalen);
+ if (downstream_->get_downstream_stream_id() != -1) {
+ rv = http2session_->resume_data(this);
+ if (rv != 0) {
+ return -1;
+ }
+
+ downstream_->ensure_downstream_wtimer();
+
+ http2session_->signal_write();
+ }
+ return 0;
+}
+
+int Http2DownstreamConnection::end_upload_data() {
+ if (!downstream_->get_request_header_sent()) {
+ downstream_->set_blocked_request_data_eof(true);
+ return 0;
+ }
+
+ int rv;
+ if (downstream_->get_downstream_stream_id() != -1) {
+ rv = http2session_->resume_data(this);
+ if (rv != 0) {
+ return -1;
+ }
+
+ downstream_->ensure_downstream_wtimer();
+
+ http2session_->signal_write();
+ }
+ return 0;
+}
+
+int Http2DownstreamConnection::resume_read(IOCtrlReason reason,
+ size_t consumed) {
+ int rv;
+
+ if (http2session_->get_state() != Http2SessionState::CONNECTED) {
+ return 0;
+ }
+
+ if (!downstream_ || downstream_->get_downstream_stream_id() == -1) {
+ return 0;
+ }
+
+ if (consumed > 0) {
+ rv = http2session_->consume(downstream_->get_downstream_stream_id(),
+ consumed);
+
+ if (rv != 0) {
+ return -1;
+ }
+
+ auto &resp = downstream_->response();
+
+ resp.unconsumed_body_length -= consumed;
+
+ http2session_->signal_write();
+ }
+
+ return 0;
+}
+
+int Http2DownstreamConnection::on_read() { return 0; }
+
+int Http2DownstreamConnection::on_write() { return 0; }
+
+void Http2DownstreamConnection::attach_stream_data(StreamData *sd) {
+ // It is possible sd->dconn is not NULL. sd is detached when
+ // on_stream_close_callback. Before that, after MSG_COMPLETE is set
+ // to Downstream::set_response_state(), upstream's readcb is called
+ // and execution path eventually could reach here. Since the
+ // response was already handled, we just detach sd.
+ detach_stream_data();
+ sd_ = sd;
+ sd_->dconn = this;
+}
+
+StreamData *Http2DownstreamConnection::detach_stream_data() {
+ if (sd_) {
+ auto sd = sd_;
+ sd_ = nullptr;
+ sd->dconn = nullptr;
+ return sd;
+ }
+ return nullptr;
+}
+
+int Http2DownstreamConnection::on_timeout() {
+ if (!downstream_) {
+ return 0;
+ }
+
+ return submit_rst_stream(downstream_, NGHTTP2_NO_ERROR);
+}
+
+const std::shared_ptr<DownstreamAddrGroup> &
+Http2DownstreamConnection::get_downstream_addr_group() const {
+ return http2session_->get_downstream_addr_group();
+}
+
+DownstreamAddr *Http2DownstreamConnection::get_addr() const { return nullptr; }
+
+} // namespace shrpx
diff --git a/src/shrpx_http2_downstream_connection.h b/src/shrpx_http2_downstream_connection.h
new file mode 100644
index 0000000..0fc7d91
--- /dev/null
+++ b/src/shrpx_http2_downstream_connection.h
@@ -0,0 +1,88 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_HTTP2_DOWNSTREAM_CONNECTION_H
+#define SHRPX_HTTP2_DOWNSTREAM_CONNECTION_H
+
+#include "shrpx.h"
+
+#include <openssl/ssl.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "shrpx_downstream_connection.h"
+
+namespace shrpx {
+
+struct StreamData;
+class Http2Session;
+class DownstreamConnectionPool;
+
+class Http2DownstreamConnection : public DownstreamConnection {
+public:
+ Http2DownstreamConnection(Http2Session *http2session);
+ virtual ~Http2DownstreamConnection();
+ virtual int attach_downstream(Downstream *downstream);
+ virtual void detach_downstream(Downstream *downstream);
+
+ virtual int push_request_headers();
+ virtual int push_upload_data_chunk(const uint8_t *data, size_t datalen);
+ virtual int end_upload_data();
+
+ virtual void pause_read(IOCtrlReason reason) {}
+ virtual int resume_read(IOCtrlReason reason, size_t consumed);
+ virtual void force_resume_read() {}
+
+ virtual int on_read();
+ virtual int on_write();
+ virtual int on_timeout();
+
+ virtual void on_upstream_change(Upstream *upstream) {}
+
+ // This object is not poolable because we don't have facility to
+ // migrate to another Http2Session object.
+ virtual bool poolable() const { return false; }
+
+ virtual const std::shared_ptr<DownstreamAddrGroup> &
+ get_downstream_addr_group() const;
+ virtual DownstreamAddr *get_addr() const;
+
+ int send();
+
+ void attach_stream_data(StreamData *sd);
+ StreamData *detach_stream_data();
+
+ int submit_rst_stream(Downstream *downstream,
+ uint32_t error_code = NGHTTP2_INTERNAL_ERROR);
+
+ Http2DownstreamConnection *dlnext, *dlprev;
+
+private:
+ Http2Session *http2session_;
+ StreamData *sd_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_HTTP2_DOWNSTREAM_CONNECTION_H
diff --git a/src/shrpx_http2_session.cc b/src/shrpx_http2_session.cc
new file mode 100644
index 0000000..f58ed2f
--- /dev/null
+++ b/src/shrpx_http2_session.cc
@@ -0,0 +1,2426 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_http2_session.h"
+
+#include <netinet/tcp.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+
+#include <vector>
+
+#include <openssl/err.h>
+
+#include "shrpx_upstream.h"
+#include "shrpx_downstream.h"
+#include "shrpx_config.h"
+#include "shrpx_error.h"
+#include "shrpx_http2_downstream_connection.h"
+#include "shrpx_client_handler.h"
+#include "shrpx_tls.h"
+#include "shrpx_http.h"
+#include "shrpx_worker.h"
+#include "shrpx_connect_blocker.h"
+#include "shrpx_log.h"
+#include "http2.h"
+#include "util.h"
+#include "base64.h"
+#include "tls.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+namespace {
+constexpr ev_tstamp CONNCHK_TIMEOUT = 5.;
+constexpr ev_tstamp CONNCHK_PING_TIMEOUT = 1.;
+} // namespace
+
+namespace {
+constexpr size_t MAX_BUFFER_SIZE = 32_k;
+} // namespace
+
+namespace {
+void connchk_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto http2session = static_cast<Http2Session *>(w->data);
+
+ ev_timer_stop(loop, w);
+
+ switch (http2session->get_connection_check_state()) {
+ case ConnectionCheck::STARTED:
+ // ping timeout; disconnect
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, http2session) << "ping timeout";
+ }
+
+ delete http2session;
+
+ return;
+ default:
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, http2session) << "connection check required";
+ }
+ http2session->set_connection_check_state(ConnectionCheck::REQUIRED);
+ }
+}
+} // namespace
+
+namespace {
+void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto http2session = static_cast<Http2Session *>(w->data);
+
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, http2session) << "SETTINGS timeout";
+ }
+
+ downstream_failure(http2session->get_addr(), http2session->get_raddr());
+
+ if (http2session->terminate_session(NGHTTP2_SETTINGS_TIMEOUT) != 0) {
+ delete http2session;
+
+ return;
+ }
+ http2session->signal_write();
+}
+} // namespace
+
+namespace {
+void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto conn = static_cast<Connection *>(w->data);
+ auto http2session = static_cast<Http2Session *>(conn->data);
+
+ if (w == &conn->rt && !conn->expired_rt()) {
+ return;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, http2session) << "Timeout";
+ }
+
+ http2session->on_timeout();
+
+ delete http2session;
+}
+} // namespace
+
+namespace {
+void readcb(struct ev_loop *loop, ev_io *w, int revents) {
+ int rv;
+ auto conn = static_cast<Connection *>(w->data);
+ auto http2session = static_cast<Http2Session *>(conn->data);
+ rv = http2session->do_read();
+ if (rv != 0) {
+ delete http2session;
+
+ return;
+ }
+ http2session->connection_alive();
+}
+} // namespace
+
+namespace {
+void writecb(struct ev_loop *loop, ev_io *w, int revents) {
+ int rv;
+ auto conn = static_cast<Connection *>(w->data);
+ auto http2session = static_cast<Http2Session *>(conn->data);
+ rv = http2session->do_write();
+ if (rv != 0) {
+ delete http2session;
+
+ return;
+ }
+ http2session->reset_connection_check_timer_if_not_checking();
+}
+} // namespace
+
+namespace {
+void initiate_connection_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto http2session = static_cast<Http2Session *>(w->data);
+ ev_timer_stop(loop, w);
+ if (http2session->initiate_connection() != 0) {
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, http2session) << "Could not initiate backend connection";
+ }
+
+ delete http2session;
+
+ return;
+ }
+}
+} // namespace
+
+namespace {
+void prepare_cb(struct ev_loop *loop, ev_prepare *w, int revents) {
+ auto http2session = static_cast<Http2Session *>(w->data);
+ http2session->check_retire();
+}
+} // namespace
+
+Http2Session::Http2Session(struct ev_loop *loop, SSL_CTX *ssl_ctx,
+ Worker *worker,
+ const std::shared_ptr<DownstreamAddrGroup> &group,
+ DownstreamAddr *addr)
+ : dlnext(nullptr),
+ dlprev(nullptr),
+ conn_(loop, -1, nullptr, worker->get_mcpool(),
+ group->shared_addr->timeout.write, group->shared_addr->timeout.read,
+ {}, {}, writecb, readcb, timeoutcb, this,
+ get_config()->tls.dyn_rec.warmup_threshold,
+ get_config()->tls.dyn_rec.idle_timeout, Proto::HTTP2),
+ wb_(worker->get_mcpool()),
+ worker_(worker),
+ ssl_ctx_(ssl_ctx),
+ group_(group),
+ addr_(addr),
+ session_(nullptr),
+ raddr_(nullptr),
+ state_(Http2SessionState::DISCONNECTED),
+ connection_check_state_(ConnectionCheck::NONE),
+ freelist_zone_(FreelistZone::NONE),
+ settings_recved_(false),
+ allow_connect_proto_(false) {
+ read_ = write_ = &Http2Session::noop;
+
+ on_read_ = &Http2Session::read_noop;
+ on_write_ = &Http2Session::write_noop;
+
+ // We will reuse this many times, so use repeat timeout value. The
+ // timeout value is set later.
+ ev_timer_init(&connchk_timer_, connchk_timeout_cb, 0., 0.);
+
+ connchk_timer_.data = this;
+
+ // SETTINGS ACK timeout is 10 seconds for now. We will reuse this
+ // many times, so use repeat timeout value.
+ ev_timer_init(&settings_timer_, settings_timeout_cb, 0., 0.);
+
+ settings_timer_.data = this;
+
+ ev_timer_init(&initiate_connection_timer_, initiate_connection_cb, 0., 0.);
+ initiate_connection_timer_.data = this;
+
+ ev_prepare_init(&prep_, prepare_cb);
+ prep_.data = this;
+ ev_prepare_start(loop, &prep_);
+}
+
+Http2Session::~Http2Session() {
+ exclude_from_scheduling();
+ disconnect(should_hard_fail());
+}
+
+int Http2Session::disconnect(bool hard) {
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, this) << "Disconnecting";
+ }
+ nghttp2_session_del(session_);
+ session_ = nullptr;
+
+ wb_.reset();
+
+ if (dns_query_) {
+ auto dns_tracker = worker_->get_dns_tracker();
+ dns_tracker->cancel(dns_query_.get());
+ }
+
+ conn_.rlimit.stopw();
+ conn_.wlimit.stopw();
+
+ ev_prepare_stop(conn_.loop, &prep_);
+
+ ev_timer_stop(conn_.loop, &initiate_connection_timer_);
+ ev_timer_stop(conn_.loop, &settings_timer_);
+ ev_timer_stop(conn_.loop, &connchk_timer_);
+
+ read_ = write_ = &Http2Session::noop;
+
+ on_read_ = &Http2Session::read_noop;
+ on_write_ = &Http2Session::write_noop;
+
+ conn_.disconnect();
+
+ if (proxy_htp_) {
+ proxy_htp_.reset();
+ }
+
+ connection_check_state_ = ConnectionCheck::NONE;
+ state_ = Http2SessionState::DISCONNECTED;
+
+ // When deleting Http2DownstreamConnection, it calls this object's
+ // remove_downstream_connection(). The multiple
+ // Http2DownstreamConnection objects belong to the same
+ // ClientHandler object if upstream is h2. So be careful when you
+ // delete ClientHandler here.
+ //
+ // We allow creating new pending Http2DownstreamConnection with this
+ // object. Upstream::on_downstream_reset() may add
+ // Http2DownstreamConnection to another Http2Session.
+
+ for (auto dc = dconns_.head; dc;) {
+ auto next = dc->dlnext;
+ auto downstream = dc->get_downstream();
+ auto upstream = downstream->get_upstream();
+
+ // Failure is allowed only for HTTP/1 upstream where upstream is
+ // not shared by multiple Downstreams.
+ if (upstream->on_downstream_reset(downstream, hard) != 0) {
+ delete upstream->get_client_handler();
+ }
+
+ // dc was deleted
+ dc = next;
+ }
+
+ auto streams = std::move(streams_);
+ for (auto s = streams.head; s;) {
+ auto next = s->dlnext;
+ delete s;
+ s = next;
+ }
+
+ return 0;
+}
+
+int Http2Session::resolve_name() {
+ auto dns_query = std::make_unique<DNSQuery>(
+ addr_->host, [this](DNSResolverStatus status, const Address *result) {
+ int rv;
+
+ if (status == DNSResolverStatus::OK) {
+ *resolved_addr_ = *result;
+ util::set_port(*this->resolved_addr_, this->addr_->port);
+ }
+
+ rv = this->initiate_connection();
+ if (rv != 0) {
+ delete this;
+ }
+ });
+ resolved_addr_ = std::make_unique<Address>();
+ auto dns_tracker = worker_->get_dns_tracker();
+ switch (dns_tracker->resolve(resolved_addr_.get(), dns_query.get())) {
+ case DNSResolverStatus::ERROR:
+ return -1;
+ case DNSResolverStatus::RUNNING:
+ dns_query_ = std::move(dns_query);
+ state_ = Http2SessionState::RESOLVING_NAME;
+ return 0;
+ case DNSResolverStatus::OK:
+ util::set_port(*resolved_addr_, addr_->port);
+ return 0;
+ default:
+ assert(0);
+ abort();
+ }
+}
+
+namespace {
+int htp_hdrs_completecb(llhttp_t *htp);
+} // namespace
+
+namespace {
+constexpr llhttp_settings_t htp_hooks = {
+ nullptr, // llhttp_cb on_message_begin;
+ nullptr, // llhttp_data_cb on_url;
+ nullptr, // llhttp_data_cb on_status;
+ nullptr, // llhttp_data_cb on_method;
+ nullptr, // llhttp_data_cb on_version;
+ nullptr, // llhttp_data_cb on_header_field;
+ nullptr, // llhttp_data_cb on_header_value;
+ nullptr, // llhttp_data_cb on_chunk_extension_name;
+ nullptr, // llhttp_data_cb on_chunk_extension_value;
+ htp_hdrs_completecb, // llhttp_cb on_headers_complete;
+ nullptr, // llhttp_data_cb on_body;
+ nullptr, // llhttp_cb on_message_complete;
+ nullptr, // llhttp_cb on_url_complete;
+ nullptr, // llhttp_cb on_status_complete;
+ nullptr, // llhttp_cb on_method_complete;
+ nullptr, // llhttp_cb on_version_complete;
+ nullptr, // llhttp_cb on_header_field_complete;
+ nullptr, // llhttp_cb on_header_value_complete;
+ nullptr, // llhttp_cb on_chunk_extension_name_complete;
+ nullptr, // llhttp_cb on_chunk_extension_value_complete;
+ nullptr, // llhttp_cb on_chunk_header;
+ nullptr, // llhttp_cb on_chunk_complete;
+ nullptr, // llhttp_cb on_reset;
+};
+} // namespace
+
+int Http2Session::initiate_connection() {
+ int rv = 0;
+
+ auto worker_blocker = worker_->get_connect_blocker();
+
+ if (state_ == Http2SessionState::DISCONNECTED ||
+ state_ == Http2SessionState::RESOLVING_NAME) {
+ if (worker_blocker->blocked()) {
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, this)
+ << "Worker wide backend connection was blocked temporarily";
+ }
+ return -1;
+ }
+ }
+
+ auto &downstreamconf = *get_config()->conn.downstream;
+
+ const auto &proxy = get_config()->downstream_http_proxy;
+ if (!proxy.host.empty() && state_ == Http2SessionState::DISCONNECTED) {
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, this) << "Connecting to the proxy " << proxy.host << ":"
+ << proxy.port;
+ }
+
+ conn_.fd = util::create_nonblock_socket(proxy.addr.su.storage.ss_family);
+
+ if (conn_.fd == -1) {
+ auto error = errno;
+ SSLOG(WARN, this) << "Backend proxy socket() failed; addr="
+ << util::to_numeric_addr(&proxy.addr)
+ << ", errno=" << error;
+
+ worker_blocker->on_failure();
+ return -1;
+ }
+
+ rv = connect(conn_.fd, &proxy.addr.su.sa, proxy.addr.len);
+ if (rv != 0 && errno != EINPROGRESS) {
+ auto error = errno;
+ SSLOG(WARN, this) << "Backend proxy connect() failed; addr="
+ << util::to_numeric_addr(&proxy.addr)
+ << ", errno=" << error;
+
+ worker_blocker->on_failure();
+
+ return -1;
+ }
+
+ raddr_ = &proxy.addr;
+
+ worker_blocker->on_success();
+
+ ev_io_set(&conn_.rev, conn_.fd, EV_READ);
+ ev_io_set(&conn_.wev, conn_.fd, EV_WRITE);
+
+ conn_.wlimit.startw();
+
+ conn_.wt.repeat = downstreamconf.timeout.connect;
+ ev_timer_again(conn_.loop, &conn_.wt);
+
+ write_ = &Http2Session::connected;
+
+ on_read_ = &Http2Session::downstream_read_proxy;
+ on_write_ = &Http2Session::downstream_connect_proxy;
+
+ proxy_htp_ = std::make_unique<llhttp_t>();
+ llhttp_init(proxy_htp_.get(), HTTP_RESPONSE, &htp_hooks);
+ proxy_htp_->data = this;
+
+ state_ = Http2SessionState::PROXY_CONNECTING;
+
+ return 0;
+ }
+
+ if (state_ == Http2SessionState::DISCONNECTED ||
+ state_ == Http2SessionState::PROXY_CONNECTED ||
+ state_ == Http2SessionState::RESOLVING_NAME) {
+ if (LOG_ENABLED(INFO)) {
+ if (state_ != Http2SessionState::RESOLVING_NAME) {
+ SSLOG(INFO, this) << "Connecting to downstream server";
+ }
+ }
+ if (addr_->tls) {
+ assert(ssl_ctx_);
+
+ if (state_ != Http2SessionState::RESOLVING_NAME) {
+ auto ssl = tls::create_ssl(ssl_ctx_);
+ if (!ssl) {
+ return -1;
+ }
+
+ tls::setup_downstream_http2_alpn(ssl);
+
+ conn_.set_ssl(ssl);
+ conn_.tls.client_session_cache = &addr_->tls_session_cache;
+
+ auto sni_name =
+ addr_->sni.empty() ? StringRef{addr_->host} : StringRef{addr_->sni};
+
+ if (!util::numeric_host(sni_name.c_str())) {
+ // TLS extensions: SNI. There is no documentation about the return
+ // code for this function (actually this is macro wrapping SSL_ctrl
+ // at the time of this writing).
+ SSL_set_tlsext_host_name(conn_.tls.ssl, sni_name.c_str());
+ }
+
+ auto tls_session = tls::reuse_tls_session(addr_->tls_session_cache);
+ if (tls_session) {
+ SSL_set_session(conn_.tls.ssl, tls_session);
+ SSL_SESSION_free(tls_session);
+ }
+ }
+
+ if (state_ == Http2SessionState::DISCONNECTED) {
+ if (addr_->dns) {
+ rv = resolve_name();
+ if (rv != 0) {
+ downstream_failure(addr_, nullptr);
+ return -1;
+ }
+ if (state_ == Http2SessionState::RESOLVING_NAME) {
+ return 0;
+ }
+ raddr_ = resolved_addr_.get();
+ } else {
+ raddr_ = &addr_->addr;
+ }
+ }
+
+ if (state_ == Http2SessionState::RESOLVING_NAME) {
+ if (dns_query_->status == DNSResolverStatus::ERROR) {
+ downstream_failure(addr_, nullptr);
+ return -1;
+ }
+ assert(dns_query_->status == DNSResolverStatus::OK);
+ state_ = Http2SessionState::DISCONNECTED;
+ dns_query_.reset();
+ raddr_ = resolved_addr_.get();
+ }
+
+ // If state_ == Http2SessionState::PROXY_CONNECTED, we have
+ // connected to the proxy using conn_.fd and tunnel has been
+ // established.
+ if (state_ == Http2SessionState::DISCONNECTED) {
+ assert(conn_.fd == -1);
+
+ conn_.fd = util::create_nonblock_socket(raddr_->su.storage.ss_family);
+ if (conn_.fd == -1) {
+ auto error = errno;
+ SSLOG(WARN, this)
+ << "socket() failed; addr=" << util::to_numeric_addr(raddr_)
+ << ", errno=" << error;
+
+ worker_blocker->on_failure();
+ return -1;
+ }
+
+ worker_blocker->on_success();
+
+ rv = connect(conn_.fd,
+ // TODO maybe not thread-safe?
+ const_cast<sockaddr *>(&raddr_->su.sa), raddr_->len);
+ if (rv != 0 && errno != EINPROGRESS) {
+ auto error = errno;
+ SSLOG(WARN, this)
+ << "connect() failed; addr=" << util::to_numeric_addr(raddr_)
+ << ", errno=" << error;
+
+ downstream_failure(addr_, raddr_);
+ return -1;
+ }
+
+ ev_io_set(&conn_.rev, conn_.fd, EV_READ);
+ ev_io_set(&conn_.wev, conn_.fd, EV_WRITE);
+ }
+
+ conn_.prepare_client_handshake();
+ } else {
+ if (state_ == Http2SessionState::DISCONNECTED) {
+ // Without TLS and proxy.
+ if (addr_->dns) {
+ rv = resolve_name();
+ if (rv != 0) {
+ downstream_failure(addr_, nullptr);
+ return -1;
+ }
+ if (state_ == Http2SessionState::RESOLVING_NAME) {
+ return 0;
+ }
+ raddr_ = resolved_addr_.get();
+ } else {
+ raddr_ = &addr_->addr;
+ }
+ }
+
+ if (state_ == Http2SessionState::RESOLVING_NAME) {
+ if (dns_query_->status == DNSResolverStatus::ERROR) {
+ downstream_failure(addr_, nullptr);
+ return -1;
+ }
+ assert(dns_query_->status == DNSResolverStatus::OK);
+ state_ = Http2SessionState::DISCONNECTED;
+ dns_query_.reset();
+ raddr_ = resolved_addr_.get();
+ }
+
+ if (state_ == Http2SessionState::DISCONNECTED) {
+ // Without TLS and proxy.
+ assert(conn_.fd == -1);
+
+ conn_.fd = util::create_nonblock_socket(raddr_->su.storage.ss_family);
+
+ if (conn_.fd == -1) {
+ auto error = errno;
+ SSLOG(WARN, this)
+ << "socket() failed; addr=" << util::to_numeric_addr(raddr_)
+ << ", errno=" << error;
+
+ worker_blocker->on_failure();
+ return -1;
+ }
+
+ worker_blocker->on_success();
+
+ rv = connect(conn_.fd, const_cast<sockaddr *>(&raddr_->su.sa),
+ raddr_->len);
+ if (rv != 0 && errno != EINPROGRESS) {
+ auto error = errno;
+ SSLOG(WARN, this)
+ << "connect() failed; addr=" << util::to_numeric_addr(raddr_)
+ << ", errno=" << error;
+
+ downstream_failure(addr_, raddr_);
+ return -1;
+ }
+
+ ev_io_set(&conn_.rev, conn_.fd, EV_READ);
+ ev_io_set(&conn_.wev, conn_.fd, EV_WRITE);
+ }
+ }
+
+ // We have been already connected when no TLS and proxy is used.
+ if (state_ == Http2SessionState::PROXY_CONNECTED) {
+ on_read_ = &Http2Session::read_noop;
+ on_write_ = &Http2Session::write_noop;
+
+ return connected();
+ }
+
+ write_ = &Http2Session::connected;
+
+ state_ = Http2SessionState::CONNECTING;
+ conn_.wlimit.startw();
+
+ conn_.wt.repeat = downstreamconf.timeout.connect;
+ ev_timer_again(conn_.loop, &conn_.wt);
+
+ return 0;
+ }
+
+ // Unreachable
+ assert(0);
+
+ return 0;
+}
+
+namespace {
+int htp_hdrs_completecb(llhttp_t *htp) {
+ auto http2session = static_cast<Http2Session *>(htp->data);
+
+ // We only read HTTP header part. If tunneling succeeds, response
+ // body is a different protocol (HTTP/2 in this case), we don't read
+ // them here.
+
+ // We just check status code here
+ if (htp->status_code / 100 == 2) {
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, http2session) << "Tunneling success";
+ }
+ http2session->set_state(Http2SessionState::PROXY_CONNECTED);
+
+ return HPE_PAUSED;
+ }
+
+ SSLOG(WARN, http2session) << "Tunneling failed: " << htp->status_code;
+ http2session->set_state(Http2SessionState::PROXY_FAILED);
+
+ return HPE_PAUSED;
+}
+} // namespace
+
+int Http2Session::downstream_read_proxy(const uint8_t *data, size_t datalen) {
+ auto htperr = llhttp_execute(proxy_htp_.get(),
+ reinterpret_cast<const char *>(data), datalen);
+ if (htperr == HPE_PAUSED) {
+ switch (state_) {
+ case Http2SessionState::PROXY_CONNECTED:
+ // Initiate SSL/TLS handshake through established tunnel.
+ if (initiate_connection() != 0) {
+ return -1;
+ }
+ return 0;
+ case Http2SessionState::PROXY_FAILED:
+ return -1;
+ default:
+ break;
+ }
+ // should not be here
+ assert(0);
+ }
+
+ if (htperr != HPE_OK) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int Http2Session::downstream_connect_proxy() {
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, this) << "Connected to the proxy";
+ }
+
+ std::string req = "CONNECT ";
+ req.append(addr_->hostport.c_str(), addr_->hostport.size());
+ if (addr_->port == 80 || addr_->port == 443) {
+ req += ':';
+ req += util::utos(addr_->port);
+ }
+ req += " HTTP/1.1\r\nHost: ";
+ req += addr_->host;
+ req += "\r\n";
+ const auto &proxy = get_config()->downstream_http_proxy;
+ if (!proxy.userinfo.empty()) {
+ req += "Proxy-Authorization: Basic ";
+ req += base64::encode(std::begin(proxy.userinfo), std::end(proxy.userinfo));
+ req += "\r\n";
+ }
+ req += "\r\n";
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, this) << "HTTP proxy request headers\n" << req;
+ }
+ wb_.append(req);
+
+ on_write_ = &Http2Session::write_noop;
+
+ signal_write();
+ return 0;
+}
+
+void Http2Session::add_downstream_connection(Http2DownstreamConnection *dconn) {
+ dconns_.append(dconn);
+ ++addr_->num_dconn;
+}
+
+void Http2Session::remove_downstream_connection(
+ Http2DownstreamConnection *dconn) {
+ --addr_->num_dconn;
+ dconns_.remove(dconn);
+ dconn->detach_stream_data();
+
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, this) << "Remove downstream";
+ }
+
+ if (freelist_zone_ == FreelistZone::NONE && !max_concurrency_reached()) {
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, this) << "Append to http2_extra_freelist, addr=" << addr_
+ << ", freelist.size="
+ << addr_->http2_extra_freelist.size();
+ }
+
+ add_to_extra_freelist();
+ }
+}
+
+void Http2Session::remove_stream_data(StreamData *sd) {
+ streams_.remove(sd);
+ if (sd->dconn) {
+ sd->dconn->detach_stream_data();
+ }
+ delete sd;
+}
+
+int Http2Session::submit_request(Http2DownstreamConnection *dconn,
+ const nghttp2_nv *nva, size_t nvlen,
+ const nghttp2_data_provider *data_prd) {
+ assert(state_ == Http2SessionState::CONNECTED);
+ auto sd = std::make_unique<StreamData>();
+ sd->dlnext = sd->dlprev = nullptr;
+ // TODO Specify nullptr to pri_spec for now
+ auto stream_id =
+ nghttp2_submit_request(session_, nullptr, nva, nvlen, data_prd, sd.get());
+ if (stream_id < 0) {
+ SSLOG(FATAL, this) << "nghttp2_submit_request() failed: "
+ << nghttp2_strerror(stream_id);
+ return -1;
+ }
+
+ dconn->attach_stream_data(sd.get());
+ dconn->get_downstream()->set_downstream_stream_id(stream_id);
+ streams_.append(sd.release());
+
+ return 0;
+}
+
+int Http2Session::submit_rst_stream(int32_t stream_id, uint32_t error_code) {
+ assert(state_ == Http2SessionState::CONNECTED);
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, this) << "RST_STREAM stream_id=" << stream_id
+ << " with error_code=" << error_code;
+ }
+ int rv = nghttp2_submit_rst_stream(session_, NGHTTP2_FLAG_NONE, stream_id,
+ error_code);
+ if (rv != 0) {
+ SSLOG(FATAL, this) << "nghttp2_submit_rst_stream() failed: "
+ << nghttp2_strerror(rv);
+ return -1;
+ }
+ return 0;
+}
+
+nghttp2_session *Http2Session::get_session() const { return session_; }
+
+int Http2Session::resume_data(Http2DownstreamConnection *dconn) {
+ assert(state_ == Http2SessionState::CONNECTED);
+ auto downstream = dconn->get_downstream();
+ int rv = nghttp2_session_resume_data(session_,
+ downstream->get_downstream_stream_id());
+ switch (rv) {
+ case 0:
+ case NGHTTP2_ERR_INVALID_ARGUMENT:
+ return 0;
+ default:
+ SSLOG(FATAL, this) << "nghttp2_resume_session() failed: "
+ << nghttp2_strerror(rv);
+ return -1;
+ }
+}
+
+namespace {
+void call_downstream_readcb(Http2Session *http2session,
+ Downstream *downstream) {
+ auto upstream = downstream->get_upstream();
+ if (!upstream) {
+ return;
+ }
+ if (upstream->downstream_read(downstream->get_downstream_connection()) != 0) {
+ delete upstream->get_client_handler();
+ }
+}
+} // namespace
+
+namespace {
+int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
+ uint32_t error_code, void *user_data) {
+ auto http2session = static_cast<Http2Session *>(user_data);
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, http2session)
+ << "Stream stream_id=" << stream_id
+ << " is being closed with error code " << error_code;
+ }
+ auto sd = static_cast<StreamData *>(
+ nghttp2_session_get_stream_user_data(session, stream_id));
+ if (sd == 0) {
+ // We might get this close callback when pushed streams are
+ // closed.
+ return 0;
+ }
+ auto dconn = sd->dconn;
+ if (dconn) {
+ auto downstream = dconn->get_downstream();
+ auto upstream = downstream->get_upstream();
+
+ if (downstream->get_downstream_stream_id() % 2 == 0 &&
+ downstream->get_request_state() == DownstreamState::INITIAL) {
+ // Downstream is canceled in backend before it is submitted in
+ // frontend session.
+
+ // This will avoid to send RST_STREAM to backend
+ downstream->set_response_state(DownstreamState::MSG_RESET);
+ upstream->cancel_premature_downstream(downstream);
+ } else {
+ if (downstream->get_upgraded() && downstream->get_response_state() ==
+ DownstreamState::HEADER_COMPLETE) {
+ // For tunneled connection, we have to submit RST_STREAM to
+ // upstream *after* whole response body is sent. We just set
+ // MSG_COMPLETE here. Upstream will take care of that.
+ downstream->get_upstream()->on_downstream_body_complete(downstream);
+ downstream->set_response_state(DownstreamState::MSG_COMPLETE);
+ } else if (error_code == NGHTTP2_NO_ERROR) {
+ switch (downstream->get_response_state()) {
+ case DownstreamState::MSG_COMPLETE:
+ case DownstreamState::MSG_BAD_HEADER:
+ break;
+ default:
+ downstream->set_response_state(DownstreamState::MSG_RESET);
+ }
+ } else if (downstream->get_response_state() !=
+ DownstreamState::MSG_BAD_HEADER) {
+ downstream->set_response_state(DownstreamState::MSG_RESET);
+ }
+ if (downstream->get_response_state() == DownstreamState::MSG_RESET &&
+ downstream->get_response_rst_stream_error_code() ==
+ NGHTTP2_NO_ERROR) {
+ downstream->set_response_rst_stream_error_code(error_code);
+ }
+ call_downstream_readcb(http2session, downstream);
+ }
+ // dconn may be deleted
+ }
+ // The life time of StreamData ends here
+ http2session->remove_stream_data(sd);
+ return 0;
+}
+} // namespace
+
+void Http2Session::start_settings_timer() {
+ auto &downstreamconf = get_config()->http2.downstream;
+
+ ev_timer_set(&settings_timer_, downstreamconf.timeout.settings, 0.);
+ ev_timer_start(conn_.loop, &settings_timer_);
+}
+
+void Http2Session::stop_settings_timer() {
+ ev_timer_stop(conn_.loop, &settings_timer_);
+}
+
+namespace {
+int on_header_callback2(nghttp2_session *session, const nghttp2_frame *frame,
+ nghttp2_rcbuf *name, nghttp2_rcbuf *value,
+ uint8_t flags, void *user_data) {
+ auto http2session = static_cast<Http2Session *>(user_data);
+ auto sd = static_cast<StreamData *>(
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+ if (!sd || !sd->dconn) {
+ return 0;
+ }
+ auto downstream = sd->dconn->get_downstream();
+
+ auto namebuf = nghttp2_rcbuf_get_buf(name);
+ auto valuebuf = nghttp2_rcbuf_get_buf(value);
+
+ auto &resp = downstream->response();
+ auto &httpconf = get_config()->http;
+
+ switch (frame->hd.type) {
+ case NGHTTP2_HEADERS: {
+ auto trailer = frame->headers.cat == NGHTTP2_HCAT_HEADERS &&
+ !downstream->get_expect_final_response();
+
+ if (resp.fs.buffer_size() + namebuf.len + valuebuf.len >
+ httpconf.response_header_field_buffer ||
+ resp.fs.num_fields() >= httpconf.max_response_header_fields) {
+ if (LOG_ENABLED(INFO)) {
+ DLOG(INFO, downstream)
+ << "Too large or many header field size="
+ << resp.fs.buffer_size() + namebuf.len + valuebuf.len
+ << ", num=" << resp.fs.num_fields() + 1;
+ }
+
+ if (trailer) {
+ // We don't care trailer part exceeds header size limit; just
+ // discard it.
+ return 0;
+ }
+
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+
+ auto token = http2::lookup_token(namebuf.base, namebuf.len);
+ auto no_index = flags & NGHTTP2_NV_FLAG_NO_INDEX;
+
+ downstream->add_rcbuf(name);
+ downstream->add_rcbuf(value);
+
+ if (trailer) {
+ // just store header fields for trailer part
+ resp.fs.add_trailer_token(StringRef{namebuf.base, namebuf.len},
+ StringRef{valuebuf.base, valuebuf.len},
+ no_index, token);
+ return 0;
+ }
+
+ resp.fs.add_header_token(StringRef{namebuf.base, namebuf.len},
+ StringRef{valuebuf.base, valuebuf.len}, no_index,
+ token);
+ return 0;
+ }
+ case NGHTTP2_PUSH_PROMISE: {
+ auto promised_stream_id = frame->push_promise.promised_stream_id;
+ auto promised_sd = static_cast<StreamData *>(
+ nghttp2_session_get_stream_user_data(session, promised_stream_id));
+ if (!promised_sd || !promised_sd->dconn) {
+ http2session->submit_rst_stream(promised_stream_id, NGHTTP2_CANCEL);
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+
+ auto promised_downstream = promised_sd->dconn->get_downstream();
+
+ auto namebuf = nghttp2_rcbuf_get_buf(name);
+ auto valuebuf = nghttp2_rcbuf_get_buf(value);
+
+ assert(promised_downstream);
+
+ auto &promised_req = promised_downstream->request();
+
+ // We use request header limit for PUSH_PROMISE
+ if (promised_req.fs.buffer_size() + namebuf.len + valuebuf.len >
+ httpconf.request_header_field_buffer ||
+ promised_req.fs.num_fields() >= httpconf.max_request_header_fields) {
+ if (LOG_ENABLED(INFO)) {
+ DLOG(INFO, downstream)
+ << "Too large or many header field size="
+ << promised_req.fs.buffer_size() + namebuf.len + valuebuf.len
+ << ", num=" << promised_req.fs.num_fields() + 1;
+ }
+
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+
+ promised_downstream->add_rcbuf(name);
+ promised_downstream->add_rcbuf(value);
+
+ auto token = http2::lookup_token(namebuf.base, namebuf.len);
+ promised_req.fs.add_header_token(StringRef{namebuf.base, namebuf.len},
+ StringRef{valuebuf.base, valuebuf.len},
+ flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
+
+ return 0;
+ }
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int on_invalid_header_callback2(nghttp2_session *session,
+ const nghttp2_frame *frame, nghttp2_rcbuf *name,
+ nghttp2_rcbuf *value, uint8_t flags,
+ void *user_data) {
+ auto http2session = static_cast<Http2Session *>(user_data);
+ auto sd = static_cast<StreamData *>(
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+ if (!sd || !sd->dconn) {
+ return 0;
+ }
+
+ int32_t stream_id;
+
+ if (frame->hd.type == NGHTTP2_PUSH_PROMISE) {
+ stream_id = frame->push_promise.promised_stream_id;
+ } else {
+ stream_id = frame->hd.stream_id;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ auto namebuf = nghttp2_rcbuf_get_buf(name);
+ auto valuebuf = nghttp2_rcbuf_get_buf(value);
+
+ SSLOG(INFO, http2session)
+ << "Invalid header field for stream_id=" << stream_id
+ << " in frame type=" << static_cast<uint32_t>(frame->hd.type)
+ << ": name=[" << StringRef{namebuf.base, namebuf.len} << "], value=["
+ << StringRef{valuebuf.base, valuebuf.len} << "]";
+ }
+
+ http2session->submit_rst_stream(stream_id, NGHTTP2_PROTOCOL_ERROR);
+
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+}
+} // namespace
+
+namespace {
+int on_begin_headers_callback(nghttp2_session *session,
+ const nghttp2_frame *frame, void *user_data) {
+ auto http2session = static_cast<Http2Session *>(user_data);
+
+ switch (frame->hd.type) {
+ case NGHTTP2_HEADERS: {
+ if (frame->headers.cat != NGHTTP2_HCAT_RESPONSE &&
+ frame->headers.cat != NGHTTP2_HCAT_PUSH_RESPONSE) {
+ return 0;
+ }
+ auto sd = static_cast<StreamData *>(
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+ if (!sd || !sd->dconn) {
+ http2session->submit_rst_stream(frame->hd.stream_id,
+ NGHTTP2_INTERNAL_ERROR);
+ return 0;
+ }
+ return 0;
+ }
+ case NGHTTP2_PUSH_PROMISE: {
+ auto promised_stream_id = frame->push_promise.promised_stream_id;
+ auto sd = static_cast<StreamData *>(
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+ if (!sd || !sd->dconn) {
+ http2session->submit_rst_stream(promised_stream_id, NGHTTP2_CANCEL);
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+
+ auto downstream = sd->dconn->get_downstream();
+
+ assert(downstream);
+ assert(downstream->get_downstream_stream_id() == frame->hd.stream_id);
+
+ if (http2session->handle_downstream_push_promise(downstream,
+ promised_stream_id) != 0) {
+ http2session->submit_rst_stream(promised_stream_id, NGHTTP2_CANCEL);
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+
+ return 0;
+ }
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int on_response_headers(Http2Session *http2session, Downstream *downstream,
+ nghttp2_session *session, const nghttp2_frame *frame) {
+ int rv;
+
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+ const auto &req = downstream->request();
+ auto &resp = downstream->response();
+
+ auto &nva = resp.fs.headers();
+
+ auto config = get_config();
+ auto &loggingconf = config->logging;
+
+ downstream->set_expect_final_response(false);
+
+ auto status = resp.fs.header(http2::HD__STATUS);
+ // libnghttp2 guarantees this exists and can be parsed
+ assert(status);
+ auto status_code = http2::parse_http_status_code(status->value);
+
+ resp.http_status = status_code;
+ resp.http_major = 2;
+ resp.http_minor = 0;
+
+ downstream->set_downstream_addr_group(
+ http2session->get_downstream_addr_group());
+ downstream->set_addr(http2session->get_addr());
+
+ if (LOG_ENABLED(INFO)) {
+ std::stringstream ss;
+ for (auto &nv : nva) {
+ ss << TTY_HTTP_HD << nv.name << TTY_RST << ": " << nv.value << "\n";
+ }
+ SSLOG(INFO, http2session)
+ << "HTTP response headers. stream_id=" << frame->hd.stream_id << "\n"
+ << ss.str();
+ }
+
+ if (downstream->get_non_final_response()) {
+
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, http2session) << "This is non-final response.";
+ }
+
+ downstream->set_expect_final_response(true);
+ rv = upstream->on_downstream_header_complete(downstream);
+
+ // Now Dowstream's response headers are erased.
+
+ if (rv != 0) {
+ http2session->submit_rst_stream(frame->hd.stream_id,
+ NGHTTP2_PROTOCOL_ERROR);
+ downstream->set_response_state(DownstreamState::MSG_RESET);
+ }
+
+ return 0;
+ }
+
+ downstream->set_response_state(DownstreamState::HEADER_COMPLETE);
+ downstream->check_upgrade_fulfilled_http2();
+
+ if (downstream->get_upgraded()) {
+ resp.connection_close = true;
+ // On upgrade success, both ends can send data
+ if (upstream->resume_read(SHRPX_NO_BUFFER, downstream, 0) != 0) {
+ // If resume_read fails, just drop connection. Not ideal.
+ delete handler;
+ return -1;
+ }
+ downstream->set_request_state(DownstreamState::HEADER_COMPLETE);
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, http2session)
+ << "HTTP upgrade success. stream_id=" << frame->hd.stream_id;
+ }
+ } else {
+ auto content_length = resp.fs.header(http2::HD_CONTENT_LENGTH);
+ if (content_length) {
+ // libnghttp2 guarantees this can be parsed
+ resp.fs.content_length = util::parse_uint(content_length->value);
+ }
+
+ if (resp.fs.content_length == -1 && downstream->expect_response_body()) {
+ // Here we have response body but Content-Length is not known in
+ // advance.
+ if (req.http_major <= 0 || (req.http_major == 1 && req.http_minor == 0)) {
+ // We simply close connection for pre-HTTP/1.1 in this case.
+ resp.connection_close = true;
+ } else {
+ // Otherwise, use chunked encoding to keep upstream connection
+ // open. In HTTP2, we are supposed not to receive
+ // transfer-encoding.
+ resp.fs.add_header_token(StringRef::from_lit("transfer-encoding"),
+ StringRef::from_lit("chunked"), false,
+ http2::HD_TRANSFER_ENCODING);
+ downstream->set_chunked_response(true);
+ }
+ }
+ }
+
+ if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+ resp.headers_only = true;
+ }
+
+ if (loggingconf.access.write_early && downstream->accesslog_ready()) {
+ handler->write_accesslog(downstream);
+ downstream->set_accesslog_written(true);
+ }
+
+ rv = upstream->on_downstream_header_complete(downstream);
+ if (rv != 0) {
+ // Handling early return (in other words, response was hijacked by
+ // mruby scripting).
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ http2session->submit_rst_stream(frame->hd.stream_id, NGHTTP2_CANCEL);
+ } else {
+ http2session->submit_rst_stream(frame->hd.stream_id,
+ NGHTTP2_INTERNAL_ERROR);
+ downstream->set_response_state(DownstreamState::MSG_RESET);
+ }
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame,
+ void *user_data) {
+ int rv;
+ auto http2session = static_cast<Http2Session *>(user_data);
+
+ switch (frame->hd.type) {
+ case NGHTTP2_DATA: {
+ auto sd = static_cast<StreamData *>(
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+ if (!sd || !sd->dconn) {
+ return 0;
+ }
+ auto downstream = sd->dconn->get_downstream();
+ auto upstream = downstream->get_upstream();
+ rv = upstream->on_downstream_body(downstream, nullptr, 0, true);
+ if (rv != 0) {
+ http2session->submit_rst_stream(frame->hd.stream_id,
+ NGHTTP2_INTERNAL_ERROR);
+ downstream->set_response_state(DownstreamState::MSG_RESET);
+
+ } else if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+
+ downstream->disable_downstream_rtimer();
+
+ if (downstream->get_response_state() ==
+ DownstreamState::HEADER_COMPLETE) {
+
+ downstream->set_response_state(DownstreamState::MSG_COMPLETE);
+
+ rv = upstream->on_downstream_body_complete(downstream);
+
+ if (rv != 0) {
+ downstream->set_response_state(DownstreamState::MSG_RESET);
+ }
+ }
+ }
+
+ call_downstream_readcb(http2session, downstream);
+ return 0;
+ }
+ case NGHTTP2_HEADERS: {
+ auto sd = static_cast<StreamData *>(
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+ if (!sd || !sd->dconn) {
+ return 0;
+ }
+ auto downstream = sd->dconn->get_downstream();
+
+ if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE ||
+ frame->headers.cat == NGHTTP2_HCAT_PUSH_RESPONSE) {
+ rv = on_response_headers(http2session, downstream, session, frame);
+
+ if (rv != 0) {
+ return 0;
+ }
+ } else if (frame->headers.cat == NGHTTP2_HCAT_HEADERS) {
+ if (downstream->get_expect_final_response()) {
+ rv = on_response_headers(http2session, downstream, session, frame);
+
+ if (rv != 0) {
+ return 0;
+ }
+ }
+ }
+
+ if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+ downstream->disable_downstream_rtimer();
+
+ if (downstream->get_response_state() ==
+ DownstreamState::HEADER_COMPLETE) {
+ downstream->set_response_state(DownstreamState::MSG_COMPLETE);
+
+ auto upstream = downstream->get_upstream();
+
+ rv = upstream->on_downstream_body_complete(downstream);
+
+ if (rv != 0) {
+ downstream->set_response_state(DownstreamState::MSG_RESET);
+ }
+ }
+ } else {
+ downstream->reset_downstream_rtimer();
+ }
+
+ // This may delete downstream
+ call_downstream_readcb(http2session, downstream);
+
+ return 0;
+ }
+ case NGHTTP2_RST_STREAM: {
+ auto sd = static_cast<StreamData *>(
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+ if (sd && sd->dconn) {
+ auto downstream = sd->dconn->get_downstream();
+ downstream->set_response_rst_stream_error_code(
+ frame->rst_stream.error_code);
+ call_downstream_readcb(http2session, downstream);
+ }
+ return 0;
+ }
+ case NGHTTP2_SETTINGS: {
+ if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) {
+ http2session->on_settings_received(frame);
+ return 0;
+ }
+
+ http2session->stop_settings_timer();
+
+ auto addr = http2session->get_addr();
+ auto &connect_blocker = addr->connect_blocker;
+
+ connect_blocker->on_success();
+
+ return 0;
+ }
+ case NGHTTP2_PING:
+ if (frame->hd.flags & NGHTTP2_FLAG_ACK) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "PING ACK received";
+ }
+ http2session->connection_alive();
+ }
+ return 0;
+ case NGHTTP2_PUSH_PROMISE: {
+ auto promised_stream_id = frame->push_promise.promised_stream_id;
+
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, http2session)
+ << "Received downstream PUSH_PROMISE stream_id="
+ << frame->hd.stream_id
+ << ", promised_stream_id=" << promised_stream_id;
+ }
+
+ auto sd = static_cast<StreamData *>(
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+ if (!sd || !sd->dconn) {
+ http2session->submit_rst_stream(promised_stream_id, NGHTTP2_CANCEL);
+ return 0;
+ }
+
+ auto downstream = sd->dconn->get_downstream();
+
+ assert(downstream);
+ assert(downstream->get_downstream_stream_id() == frame->hd.stream_id);
+
+ auto promised_sd = static_cast<StreamData *>(
+ nghttp2_session_get_stream_user_data(session, promised_stream_id));
+ if (!promised_sd || !promised_sd->dconn) {
+ http2session->submit_rst_stream(promised_stream_id, NGHTTP2_CANCEL);
+ return 0;
+ }
+
+ auto promised_downstream = promised_sd->dconn->get_downstream();
+
+ assert(promised_downstream);
+
+ if (http2session->handle_downstream_push_promise_complete(
+ downstream, promised_downstream) != 0) {
+ http2session->submit_rst_stream(promised_stream_id, NGHTTP2_CANCEL);
+ return 0;
+ }
+
+ return 0;
+ }
+ case NGHTTP2_GOAWAY:
+ if (LOG_ENABLED(INFO)) {
+ auto debug_data = util::ascii_dump(frame->goaway.opaque_data,
+ frame->goaway.opaque_data_len);
+
+ SSLOG(INFO, http2session)
+ << "GOAWAY received: last-stream-id=" << frame->goaway.last_stream_id
+ << ", error_code=" << frame->goaway.error_code
+ << ", debug_data=" << debug_data;
+ }
+ return 0;
+ default:
+ return 0;
+ }
+}
+} // namespace
+
+namespace {
+int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
+ int32_t stream_id, const uint8_t *data,
+ size_t len, void *user_data) {
+ int rv;
+ auto http2session = static_cast<Http2Session *>(user_data);
+ auto sd = static_cast<StreamData *>(
+ nghttp2_session_get_stream_user_data(session, stream_id));
+ if (!sd || !sd->dconn) {
+ http2session->submit_rst_stream(stream_id, NGHTTP2_INTERNAL_ERROR);
+
+ if (http2session->consume(stream_id, len) != 0) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+ }
+ auto downstream = sd->dconn->get_downstream();
+ if (!downstream->expect_response_body()) {
+ http2session->submit_rst_stream(stream_id, NGHTTP2_INTERNAL_ERROR);
+
+ if (http2session->consume(stream_id, len) != 0) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+ }
+
+ // We don't want DATA after non-final response, which is illegal in
+ // HTTP.
+ if (downstream->get_non_final_response()) {
+ http2session->submit_rst_stream(stream_id, NGHTTP2_PROTOCOL_ERROR);
+
+ if (http2session->consume(stream_id, len) != 0) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+ }
+
+ downstream->reset_downstream_rtimer();
+
+ auto &resp = downstream->response();
+
+ resp.recv_body_length += len;
+ resp.unconsumed_body_length += len;
+
+ auto upstream = downstream->get_upstream();
+ rv = upstream->on_downstream_body(downstream, data, len, false);
+ if (rv != 0) {
+ http2session->submit_rst_stream(stream_id, NGHTTP2_INTERNAL_ERROR);
+
+ if (http2session->consume(stream_id, len) != 0) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+
+ downstream->set_response_state(DownstreamState::MSG_RESET);
+ }
+
+ call_downstream_readcb(http2session, downstream);
+ return 0;
+}
+} // namespace
+
+namespace {
+int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame,
+ void *user_data) {
+ auto http2session = static_cast<Http2Session *>(user_data);
+
+ if (frame->hd.type == NGHTTP2_DATA || frame->hd.type == NGHTTP2_HEADERS) {
+ auto sd = static_cast<StreamData *>(
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+
+ if (!sd || !sd->dconn) {
+ return 0;
+ }
+
+ auto downstream = sd->dconn->get_downstream();
+
+ if (frame->hd.type == NGHTTP2_HEADERS &&
+ frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
+ downstream->set_request_header_sent(true);
+ auto src = downstream->get_blocked_request_buf();
+ if (src->rleft()) {
+ auto dest = downstream->get_request_buf();
+ src->remove(*dest);
+ if (http2session->resume_data(sd->dconn) != 0) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ downstream->ensure_downstream_wtimer();
+ }
+ }
+
+ if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) {
+ return 0;
+ }
+
+ downstream->reset_downstream_rtimer();
+
+ return 0;
+ }
+
+ if (frame->hd.type == NGHTTP2_SETTINGS &&
+ (frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) {
+ http2session->start_settings_timer();
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+int on_frame_not_send_callback(nghttp2_session *session,
+ const nghttp2_frame *frame, int lib_error_code,
+ void *user_data) {
+ auto http2session = static_cast<Http2Session *>(user_data);
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, http2session) << "Failed to send control frame type="
+ << static_cast<uint32_t>(frame->hd.type)
+ << ", lib_error_code=" << lib_error_code << ": "
+ << nghttp2_strerror(lib_error_code);
+ }
+ if (frame->hd.type != NGHTTP2_HEADERS ||
+ lib_error_code == NGHTTP2_ERR_STREAM_CLOSED ||
+ lib_error_code == NGHTTP2_ERR_STREAM_CLOSING) {
+ return 0;
+ }
+
+ auto sd = static_cast<StreamData *>(
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+ if (!sd) {
+ return 0;
+ }
+ if (!sd->dconn) {
+ return 0;
+ }
+ auto downstream = sd->dconn->get_downstream();
+
+ if (lib_error_code == NGHTTP2_ERR_START_STREAM_NOT_ALLOWED) {
+ // Migrate to another downstream connection.
+ auto upstream = downstream->get_upstream();
+
+ if (upstream->on_downstream_reset(downstream, false)) {
+ // This should be done for h1 upstream only. Deleting
+ // ClientHandler for h2 upstream may lead to crash.
+ delete upstream->get_client_handler();
+ }
+
+ return 0;
+ }
+
+ // To avoid stream hanging around, flag DownstreamState::MSG_RESET.
+ downstream->set_response_state(DownstreamState::MSG_RESET);
+ call_downstream_readcb(http2session, downstream);
+
+ return 0;
+}
+} // namespace
+
+namespace {
+constexpr auto PADDING = std::array<uint8_t, 256>{};
+} // namespace
+
+namespace {
+int send_data_callback(nghttp2_session *session, nghttp2_frame *frame,
+ const uint8_t *framehd, size_t length,
+ nghttp2_data_source *source, void *user_data) {
+ auto http2session = static_cast<Http2Session *>(user_data);
+ auto sd = static_cast<StreamData *>(
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+
+ if (sd == nullptr) {
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+
+ auto dconn = sd->dconn;
+ auto downstream = dconn->get_downstream();
+ auto input = downstream->get_request_buf();
+ auto wb = http2session->get_request_buf();
+
+ size_t padlen = 0;
+
+ wb->append(framehd, 9);
+ if (frame->data.padlen > 0) {
+ padlen = frame->data.padlen - 1;
+ wb->append(static_cast<uint8_t>(padlen));
+ }
+
+ input->remove(*wb, length);
+
+ wb->append(PADDING.data(), padlen);
+
+ if (input->rleft() == 0) {
+ downstream->disable_downstream_wtimer();
+ } else {
+ downstream->reset_downstream_wtimer();
+ }
+
+ if (length > 0) {
+ // This is important because it will handle flow control
+ // stuff.
+ if (downstream->get_upstream()->resume_read(SHRPX_NO_BUFFER, downstream,
+ length) != 0) {
+ // In this case, downstream may be deleted.
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+
+ // Here sd->dconn could be nullptr, because
+ // Upstream::resume_read() may delete downstream which will delete
+ // dconn. Is this still really true?
+ }
+
+ return 0;
+}
+} // namespace
+
+nghttp2_session_callbacks *create_http2_downstream_callbacks() {
+ int rv;
+ nghttp2_session_callbacks *callbacks;
+
+ rv = nghttp2_session_callbacks_new(&callbacks);
+
+ if (rv != 0) {
+ return nullptr;
+ }
+
+ nghttp2_session_callbacks_set_on_stream_close_callback(
+ callbacks, on_stream_close_callback);
+
+ nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
+ on_frame_recv_callback);
+
+ nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
+ callbacks, on_data_chunk_recv_callback);
+
+ nghttp2_session_callbacks_set_on_frame_send_callback(callbacks,
+ on_frame_send_callback);
+
+ nghttp2_session_callbacks_set_on_frame_not_send_callback(
+ callbacks, on_frame_not_send_callback);
+
+ nghttp2_session_callbacks_set_on_header_callback2(callbacks,
+ on_header_callback2);
+
+ nghttp2_session_callbacks_set_on_invalid_header_callback2(
+ callbacks, on_invalid_header_callback2);
+
+ nghttp2_session_callbacks_set_on_begin_headers_callback(
+ callbacks, on_begin_headers_callback);
+
+ nghttp2_session_callbacks_set_send_data_callback(callbacks,
+ send_data_callback);
+
+ if (get_config()->padding) {
+ nghttp2_session_callbacks_set_select_padding_callback(
+ callbacks, http::select_padding_callback);
+ }
+
+ return callbacks;
+}
+
+int Http2Session::connection_made() {
+ int rv;
+
+ state_ = Http2SessionState::CONNECTED;
+
+ on_write_ = &Http2Session::downstream_write;
+ on_read_ = &Http2Session::downstream_read;
+
+ if (addr_->tls) {
+ const unsigned char *next_proto = nullptr;
+ unsigned int next_proto_len = 0;
+
+ SSL_get0_alpn_selected(conn_.tls.ssl, &next_proto, &next_proto_len);
+
+ if (!next_proto) {
+ downstream_failure(addr_, raddr_);
+ return -1;
+ }
+
+ auto proto = StringRef{next_proto, next_proto_len};
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, this) << "Negotiated next protocol: " << proto;
+ }
+ if (!util::check_h2_is_selected(proto)) {
+ downstream_failure(addr_, raddr_);
+ return -1;
+ }
+ }
+
+ auto config = get_config();
+ auto &http2conf = config->http2;
+
+ rv = nghttp2_session_client_new2(&session_, http2conf.downstream.callbacks,
+ this, http2conf.downstream.option);
+
+ if (rv != 0) {
+ return -1;
+ }
+
+ std::array<nghttp2_settings_entry, 5> entry;
+ size_t nentry = 3;
+ entry[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
+ entry[0].value = http2conf.downstream.max_concurrent_streams;
+
+ entry[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+ entry[1].value = http2conf.downstream.window_size;
+
+ entry[2].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES;
+ entry[2].value = 1;
+
+ if (http2conf.no_server_push || config->http2_proxy) {
+ entry[nentry].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
+ entry[nentry].value = 0;
+ ++nentry;
+ }
+
+ if (http2conf.downstream.decoder_dynamic_table_size !=
+ NGHTTP2_DEFAULT_HEADER_TABLE_SIZE) {
+ entry[nentry].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
+ entry[nentry].value = http2conf.downstream.decoder_dynamic_table_size;
+ ++nentry;
+ }
+
+ rv = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, entry.data(),
+ nentry);
+ if (rv != 0) {
+ return -1;
+ }
+
+ rv = nghttp2_session_set_local_window_size(
+ session_, NGHTTP2_FLAG_NONE, 0,
+ http2conf.downstream.connection_window_size);
+ if (rv != 0) {
+ return -1;
+ }
+
+ reset_connection_check_timer(CONNCHK_TIMEOUT);
+
+ submit_pending_requests();
+
+ signal_write();
+ return 0;
+}
+
+int Http2Session::do_read() { return read_(*this); }
+int Http2Session::do_write() { return write_(*this); }
+
+int Http2Session::on_read(const uint8_t *data, size_t datalen) {
+ return on_read_(*this, data, datalen);
+}
+
+int Http2Session::on_write() { return on_write_(*this); }
+
+int Http2Session::downstream_read(const uint8_t *data, size_t datalen) {
+ ssize_t rv;
+
+ rv = nghttp2_session_mem_recv(session_, data, datalen);
+ if (rv < 0) {
+ SSLOG(ERROR, this) << "nghttp2_session_mem_recv() returned error: "
+ << nghttp2_strerror(rv);
+ return -1;
+ }
+
+ if (nghttp2_session_want_read(session_) == 0 &&
+ nghttp2_session_want_write(session_) == 0 && wb_.rleft() == 0) {
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, this) << "No more read/write for this HTTP2 session";
+ }
+ return -1;
+ }
+
+ signal_write();
+ return 0;
+}
+
+int Http2Session::downstream_write() {
+ for (;;) {
+ const uint8_t *data;
+ auto datalen = nghttp2_session_mem_send(session_, &data);
+ if (datalen < 0) {
+ SSLOG(ERROR, this) << "nghttp2_session_mem_send() returned error: "
+ << nghttp2_strerror(datalen);
+ return -1;
+ }
+ if (datalen == 0) {
+ break;
+ }
+ wb_.append(data, datalen);
+
+ if (wb_.rleft() >= MAX_BUFFER_SIZE) {
+ break;
+ }
+ }
+
+ if (nghttp2_session_want_read(session_) == 0 &&
+ nghttp2_session_want_write(session_) == 0 && wb_.rleft() == 0) {
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, this) << "No more read/write for this session";
+ }
+ return -1;
+ }
+
+ return 0;
+}
+
+void Http2Session::signal_write() {
+ switch (state_) {
+ case Http2SessionState::DISCONNECTED:
+ if (!ev_is_active(&initiate_connection_timer_)) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Start connecting to backend server";
+ }
+ // Since the timer is set to 0., these will feed 2 events. We
+ // will stop the timer in the initiate_connection_timer_ to void
+ // 2nd event.
+ ev_timer_start(conn_.loop, &initiate_connection_timer_);
+ ev_feed_event(conn_.loop, &initiate_connection_timer_, 0);
+ }
+ break;
+ case Http2SessionState::CONNECTED:
+ conn_.wlimit.startw();
+ break;
+ default:
+ break;
+ }
+}
+
+struct ev_loop *Http2Session::get_loop() const { return conn_.loop; }
+
+ev_io *Http2Session::get_wev() { return &conn_.wev; }
+
+Http2SessionState Http2Session::get_state() const { return state_; }
+
+void Http2Session::set_state(Http2SessionState state) { state_ = state; }
+
+int Http2Session::terminate_session(uint32_t error_code) {
+ int rv;
+ rv = nghttp2_session_terminate_session(session_, error_code);
+ if (rv != 0) {
+ return -1;
+ }
+ return 0;
+}
+
+SSL *Http2Session::get_ssl() const { return conn_.tls.ssl; }
+
+int Http2Session::consume(int32_t stream_id, size_t len) {
+ int rv;
+
+ if (!session_) {
+ return 0;
+ }
+
+ rv = nghttp2_session_consume(session_, stream_id, len);
+
+ if (rv != 0) {
+ SSLOG(WARN, this) << "nghttp2_session_consume() returned error: "
+ << nghttp2_strerror(rv);
+
+ return -1;
+ }
+
+ return 0;
+}
+
+bool Http2Session::can_push_request(const Downstream *downstream) const {
+ auto &req = downstream->request();
+ return state_ == Http2SessionState::CONNECTED &&
+ connection_check_state_ == ConnectionCheck::NONE &&
+ (req.connect_proto == ConnectProto::NONE || settings_recved_);
+}
+
+void Http2Session::start_checking_connection() {
+ if (state_ != Http2SessionState::CONNECTED ||
+ connection_check_state_ != ConnectionCheck::REQUIRED) {
+ return;
+ }
+ connection_check_state_ = ConnectionCheck::STARTED;
+
+ SSLOG(INFO, this) << "Start checking connection";
+ // If connection is down, we may get error when writing data. Issue
+ // ping frame to see whether connection is alive.
+ nghttp2_submit_ping(session_, NGHTTP2_FLAG_NONE, nullptr);
+
+ // set ping timeout and start timer again
+ reset_connection_check_timer(CONNCHK_PING_TIMEOUT);
+
+ signal_write();
+}
+
+void Http2Session::reset_connection_check_timer(ev_tstamp t) {
+ connchk_timer_.repeat = t;
+ ev_timer_again(conn_.loop, &connchk_timer_);
+}
+
+void Http2Session::reset_connection_check_timer_if_not_checking() {
+ if (connection_check_state_ != ConnectionCheck::NONE) {
+ return;
+ }
+
+ reset_connection_check_timer(CONNCHK_TIMEOUT);
+}
+
+void Http2Session::connection_alive() {
+ reset_connection_check_timer(CONNCHK_TIMEOUT);
+
+ if (connection_check_state_ == ConnectionCheck::NONE) {
+ return;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, this) << "Connection alive";
+ }
+
+ connection_check_state_ = ConnectionCheck::NONE;
+
+ submit_pending_requests();
+}
+
+void Http2Session::submit_pending_requests() {
+ for (auto dconn = dconns_.head; dconn; dconn = dconn->dlnext) {
+ auto downstream = dconn->get_downstream();
+
+ if (!downstream->get_request_pending() ||
+ !downstream->request_submission_ready()) {
+ continue;
+ }
+
+ auto &req = downstream->request();
+ if (req.connect_proto != ConnectProto::NONE && !settings_recved_) {
+ continue;
+ }
+
+ auto upstream = downstream->get_upstream();
+
+ if (dconn->push_request_headers() != 0) {
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, this) << "backend request failed";
+ }
+
+ upstream->on_downstream_abort_request(downstream, 400);
+
+ continue;
+ }
+
+ upstream->resume_read(SHRPX_NO_BUFFER, downstream, 0);
+ }
+}
+
+void Http2Session::set_connection_check_state(ConnectionCheck state) {
+ connection_check_state_ = state;
+}
+
+ConnectionCheck Http2Session::get_connection_check_state() const {
+ return connection_check_state_;
+}
+
+int Http2Session::noop() { return 0; }
+
+int Http2Session::read_noop(const uint8_t *data, size_t datalen) { return 0; }
+
+int Http2Session::write_noop() { return 0; }
+
+int Http2Session::connected() {
+ auto sock_error = util::get_socket_error(conn_.fd);
+ if (sock_error != 0) {
+ SSLOG(WARN, this) << "Backend connect failed; addr="
+ << util::to_numeric_addr(raddr_)
+ << ": errno=" << sock_error;
+
+ downstream_failure(addr_, raddr_);
+
+ return -1;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, this) << "Connection established";
+ }
+
+ // Reset timeout for write. Previously, we set timeout for connect.
+ conn_.wt.repeat = group_->shared_addr->timeout.write;
+ ev_timer_again(conn_.loop, &conn_.wt);
+
+ conn_.rlimit.startw();
+ conn_.again_rt();
+
+ read_ = &Http2Session::read_clear;
+ write_ = &Http2Session::write_clear;
+
+ if (state_ == Http2SessionState::PROXY_CONNECTING) {
+ return do_write();
+ }
+
+ if (conn_.tls.ssl) {
+ read_ = &Http2Session::tls_handshake;
+ write_ = &Http2Session::tls_handshake;
+
+ return do_write();
+ }
+
+ if (connection_made() != 0) {
+ state_ = Http2SessionState::CONNECT_FAILING;
+ return -1;
+ }
+
+ return 0;
+}
+
+int Http2Session::read_clear() {
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ std::array<uint8_t, 16_k> buf;
+
+ for (;;) {
+ auto nread = conn_.read_clear(buf.data(), buf.size());
+
+ if (nread == 0) {
+ return write_clear();
+ }
+
+ if (nread < 0) {
+ return nread;
+ }
+
+ if (on_read(buf.data(), nread) != 0) {
+ return -1;
+ }
+ }
+}
+
+int Http2Session::write_clear() {
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ std::array<struct iovec, MAX_WR_IOVCNT> iov;
+
+ for (;;) {
+ if (wb_.rleft() > 0) {
+ auto iovcnt = wb_.riovec(iov.data(), iov.size());
+ auto nwrite = conn_.writev_clear(iov.data(), iovcnt);
+
+ if (nwrite == 0) {
+ return 0;
+ }
+
+ if (nwrite < 0) {
+ // We may have pending data in receive buffer which may
+ // contain part of response body. So keep reading. Invoke
+ // read event to get read(2) error just in case.
+ ev_feed_event(conn_.loop, &conn_.rev, EV_READ);
+ write_ = &Http2Session::write_void;
+ break;
+ }
+
+ wb_.drain(nwrite);
+ continue;
+ }
+
+ if (on_write() != 0) {
+ return -1;
+ }
+ if (wb_.rleft() == 0) {
+ break;
+ }
+ }
+
+ conn_.wlimit.stopw();
+ ev_timer_stop(conn_.loop, &conn_.wt);
+
+ return 0;
+}
+
+int Http2Session::tls_handshake() {
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ ERR_clear_error();
+
+ auto rv = conn_.tls_handshake();
+
+ if (rv == SHRPX_ERR_INPROGRESS) {
+ return 0;
+ }
+
+ if (rv < 0) {
+ downstream_failure(addr_, raddr_);
+
+ return rv;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, this) << "SSL/TLS handshake completed";
+ }
+
+ if (!get_config()->tls.insecure &&
+ tls::check_cert(conn_.tls.ssl, addr_, raddr_) != 0) {
+ downstream_failure(addr_, raddr_);
+
+ return -1;
+ }
+
+ read_ = &Http2Session::read_tls;
+ write_ = &Http2Session::write_tls;
+
+ if (connection_made() != 0) {
+ state_ = Http2SessionState::CONNECT_FAILING;
+ return -1;
+ }
+
+ return 0;
+}
+
+int Http2Session::read_tls() {
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ std::array<uint8_t, 16_k> buf;
+
+ ERR_clear_error();
+
+ for (;;) {
+ auto nread = conn_.read_tls(buf.data(), buf.size());
+
+ if (nread == 0) {
+ return write_tls();
+ }
+
+ if (nread < 0) {
+ return nread;
+ }
+
+ if (on_read(buf.data(), nread) != 0) {
+ return -1;
+ }
+ }
+}
+
+int Http2Session::write_tls() {
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ ERR_clear_error();
+
+ struct iovec iov;
+
+ for (;;) {
+ if (wb_.rleft() > 0) {
+ auto iovcnt = wb_.riovec(&iov, 1);
+ if (iovcnt != 1) {
+ assert(0);
+ return -1;
+ }
+ auto nwrite = conn_.write_tls(iov.iov_base, iov.iov_len);
+
+ if (nwrite == 0) {
+ return 0;
+ }
+
+ if (nwrite < 0) {
+ // We may have pending data in receive buffer which may
+ // contain part of response body. So keep reading. Invoke
+ // read event to get read(2) error just in case.
+ ev_feed_event(conn_.loop, &conn_.rev, EV_READ);
+ write_ = &Http2Session::write_void;
+ break;
+ }
+
+ wb_.drain(nwrite);
+
+ continue;
+ }
+
+ if (on_write() != 0) {
+ return -1;
+ }
+ if (wb_.rleft() == 0) {
+ conn_.start_tls_write_idle();
+ break;
+ }
+ }
+
+ conn_.wlimit.stopw();
+ ev_timer_stop(conn_.loop, &conn_.wt);
+
+ return 0;
+}
+
+int Http2Session::write_void() {
+ conn_.wlimit.stopw();
+ return 0;
+}
+
+bool Http2Session::should_hard_fail() const {
+ switch (state_) {
+ case Http2SessionState::PROXY_CONNECTING:
+ case Http2SessionState::PROXY_FAILED:
+ return true;
+ case Http2SessionState::DISCONNECTED: {
+ const auto &proxy = get_config()->downstream_http_proxy;
+ return !proxy.host.empty();
+ }
+ default:
+ return false;
+ }
+}
+
+DownstreamAddr *Http2Session::get_addr() const { return addr_; }
+
+int Http2Session::handle_downstream_push_promise(Downstream *downstream,
+ int32_t promised_stream_id) {
+ auto upstream = downstream->get_upstream();
+ if (!upstream->push_enabled()) {
+ return -1;
+ }
+
+ auto promised_downstream =
+ upstream->on_downstream_push_promise(downstream, promised_stream_id);
+ if (!promised_downstream) {
+ return -1;
+ }
+
+ // Now we have Downstream object for pushed stream.
+ // promised_downstream->get_stream() still returns 0.
+
+ auto handler = upstream->get_client_handler();
+
+ auto promised_dconn = std::make_unique<Http2DownstreamConnection>(this);
+ promised_dconn->set_client_handler(handler);
+
+ auto ptr = promised_dconn.get();
+
+ if (promised_downstream->attach_downstream_connection(
+ std::move(promised_dconn)) != 0) {
+ return -1;
+ }
+
+ auto promised_sd = std::make_unique<StreamData>();
+
+ nghttp2_session_set_stream_user_data(session_, promised_stream_id,
+ promised_sd.get());
+
+ ptr->attach_stream_data(promised_sd.get());
+ streams_.append(promised_sd.release());
+
+ return 0;
+}
+
+int Http2Session::handle_downstream_push_promise_complete(
+ Downstream *downstream, Downstream *promised_downstream) {
+ auto &promised_req = promised_downstream->request();
+
+ auto &promised_balloc = promised_downstream->get_block_allocator();
+
+ auto authority = promised_req.fs.header(http2::HD__AUTHORITY);
+ auto path = promised_req.fs.header(http2::HD__PATH);
+ auto method = promised_req.fs.header(http2::HD__METHOD);
+ auto scheme = promised_req.fs.header(http2::HD__SCHEME);
+
+ if (!authority) {
+ authority = promised_req.fs.header(http2::HD_HOST);
+ }
+
+ auto method_token = http2::lookup_method_token(method->value);
+ if (method_token == -1) {
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, this) << "Unrecognized method: " << method->value;
+ }
+
+ return -1;
+ }
+
+ // TODO Rewrite authority if we enabled rewrite host. But we
+ // really don't know how to rewrite host. Should we use the same
+ // host in associated stream?
+ if (authority) {
+ promised_req.authority = authority->value;
+ }
+ promised_req.method = method_token;
+ // libnghttp2 ensures that we don't have CONNECT method in
+ // PUSH_PROMISE, and guarantees that :scheme exists.
+ if (scheme) {
+ promised_req.scheme = scheme->value;
+ }
+
+ // For server-wide OPTIONS request, path is empty.
+ if (method_token != HTTP_OPTIONS || path->value != "*") {
+ promised_req.path = http2::rewrite_clean_path(promised_balloc, path->value);
+ }
+
+ promised_downstream->inspect_http2_request();
+
+ auto upstream = promised_downstream->get_upstream();
+
+ promised_downstream->set_request_state(DownstreamState::MSG_COMPLETE);
+ promised_downstream->set_request_header_sent(true);
+
+ if (upstream->on_downstream_push_promise_complete(downstream,
+ promised_downstream) != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+size_t Http2Session::get_num_dconns() const { return dconns_.size(); }
+
+bool Http2Session::max_concurrency_reached(size_t extra) const {
+ if (!session_) {
+ return dconns_.size() + extra >= 100;
+ }
+
+ // If session does not allow further requests, it effectively means
+ // that maximum concurrency is reached.
+ return !nghttp2_session_check_request_allowed(session_) ||
+ dconns_.size() + extra >=
+ nghttp2_session_get_remote_settings(
+ session_, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS);
+}
+
+const std::shared_ptr<DownstreamAddrGroup> &
+Http2Session::get_downstream_addr_group() const {
+ return group_;
+}
+
+void Http2Session::add_to_extra_freelist() {
+ if (freelist_zone_ != FreelistZone::NONE) {
+ return;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, this) << "Append to http2_extra_freelist, addr=" << addr_
+ << ", freelist.size="
+ << addr_->http2_extra_freelist.size();
+ }
+
+ freelist_zone_ = FreelistZone::EXTRA;
+ addr_->http2_extra_freelist.append(this);
+}
+
+void Http2Session::remove_from_freelist() {
+ switch (freelist_zone_) {
+ case FreelistZone::NONE:
+ return;
+ case FreelistZone::EXTRA:
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, this) << "Remove from http2_extra_freelist, addr=" << addr_
+ << ", freelist.size="
+ << addr_->http2_extra_freelist.size();
+ }
+ addr_->http2_extra_freelist.remove(this);
+ break;
+ case FreelistZone::GONE:
+ return;
+ }
+
+ freelist_zone_ = FreelistZone::NONE;
+}
+
+void Http2Session::exclude_from_scheduling() {
+ remove_from_freelist();
+ freelist_zone_ = FreelistZone::GONE;
+}
+
+DefaultMemchunks *Http2Session::get_request_buf() { return &wb_; }
+
+void Http2Session::on_timeout() {
+ switch (state_) {
+ case Http2SessionState::PROXY_CONNECTING: {
+ auto worker_blocker = worker_->get_connect_blocker();
+ worker_blocker->on_failure();
+ break;
+ }
+ case Http2SessionState::CONNECTING:
+ SSLOG(WARN, this) << "Connect time out; addr="
+ << util::to_numeric_addr(raddr_);
+
+ downstream_failure(addr_, raddr_);
+ break;
+ default:
+ break;
+ }
+}
+
+void Http2Session::check_retire() {
+ if (!group_->retired) {
+ return;
+ }
+
+ ev_prepare_stop(conn_.loop, &prep_);
+
+ if (!session_) {
+ return;
+ }
+
+ auto last_stream_id = nghttp2_session_get_last_proc_stream_id(session_);
+ nghttp2_submit_goaway(session_, NGHTTP2_FLAG_NONE, last_stream_id,
+ NGHTTP2_NO_ERROR, nullptr, 0);
+
+ signal_write();
+}
+
+const Address *Http2Session::get_raddr() const { return raddr_; }
+
+void Http2Session::on_settings_received(const nghttp2_frame *frame) {
+ // TODO This effectively disallows nghttpx to change its behaviour
+ // based on the 2nd SETTINGS.
+ if (settings_recved_) {
+ return;
+ }
+
+ settings_recved_ = true;
+
+ for (size_t i = 0; i < frame->settings.niv; ++i) {
+ auto &ent = frame->settings.iv[i];
+ if (ent.settings_id == NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL) {
+ allow_connect_proto_ = true;
+ break;
+ }
+ }
+
+ submit_pending_requests();
+}
+
+bool Http2Session::get_allow_connect_proto() const {
+ return allow_connect_proto_;
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_http2_session.h b/src/shrpx_http2_session.h
new file mode 100644
index 0000000..31b2545
--- /dev/null
+++ b/src/shrpx_http2_session.h
@@ -0,0 +1,296 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_HTTP2_SESSION_H
+#define SHRPX_HTTP2_SESSION_H
+
+#include "shrpx.h"
+
+#include <unordered_set>
+#include <memory>
+
+#include <openssl/ssl.h>
+
+#include <ev.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "llhttp.h"
+
+#include "shrpx_connection.h"
+#include "buffer.h"
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+class Http2DownstreamConnection;
+class Worker;
+class Downstream;
+struct DownstreamAddrGroup;
+struct DownstreamAddr;
+struct DNSQuery;
+
+struct StreamData {
+ StreamData *dlnext, *dlprev;
+ Http2DownstreamConnection *dconn;
+};
+
+enum class FreelistZone {
+ // Http2Session object is not linked in any freelist.
+ NONE,
+ // Http2Session object is linked in address scope
+ // http2_extra_freelist.
+ EXTRA,
+ // Http2Session object is about to be deleted, and it does not
+ // belong to any linked list.
+ GONE
+};
+
+enum class Http2SessionState {
+ // Disconnected
+ DISCONNECTED,
+ // Connecting proxy and making CONNECT request
+ PROXY_CONNECTING,
+ // Tunnel is established with proxy
+ PROXY_CONNECTED,
+ // Establishing tunnel is failed
+ PROXY_FAILED,
+ // Connecting to downstream and/or performing SSL/TLS handshake
+ CONNECTING,
+ // Connected to downstream
+ CONNECTED,
+ // Connection is started to fail
+ CONNECT_FAILING,
+ // Resolving host name
+ RESOLVING_NAME,
+};
+
+enum class ConnectionCheck {
+ // Connection checking is not required
+ NONE,
+ // Connection checking is required
+ REQUIRED,
+ // Connection checking has been started
+ STARTED,
+};
+
+class Http2Session {
+public:
+ Http2Session(struct ev_loop *loop, SSL_CTX *ssl_ctx, Worker *worker,
+ const std::shared_ptr<DownstreamAddrGroup> &group,
+ DownstreamAddr *addr);
+ ~Http2Session();
+
+ // If hard is true, all pending requests are abandoned and
+ // associated ClientHandlers will be deleted.
+ int disconnect(bool hard = false);
+ int initiate_connection();
+ int resolve_name();
+
+ void add_downstream_connection(Http2DownstreamConnection *dconn);
+ void remove_downstream_connection(Http2DownstreamConnection *dconn);
+
+ void remove_stream_data(StreamData *sd);
+
+ int submit_request(Http2DownstreamConnection *dconn, const nghttp2_nv *nva,
+ size_t nvlen, const nghttp2_data_provider *data_prd);
+
+ int submit_rst_stream(int32_t stream_id, uint32_t error_code);
+
+ int terminate_session(uint32_t error_code);
+
+ nghttp2_session *get_session() const;
+
+ int resume_data(Http2DownstreamConnection *dconn);
+
+ int connection_made();
+
+ int do_read();
+ int do_write();
+
+ int on_read(const uint8_t *data, size_t datalen);
+ int on_write();
+
+ int connected();
+ int read_clear();
+ int write_clear();
+ int tls_handshake();
+ int read_tls();
+ int write_tls();
+ // This is a special write function which just stop write event
+ // watcher.
+ int write_void();
+
+ int downstream_read_proxy(const uint8_t *data, size_t datalen);
+ int downstream_connect_proxy();
+
+ int downstream_read(const uint8_t *data, size_t datalen);
+ int downstream_write();
+
+ int noop();
+ int read_noop(const uint8_t *data, size_t datalen);
+ int write_noop();
+
+ void signal_write();
+
+ struct ev_loop *get_loop() const;
+
+ ev_io *get_wev();
+
+ Http2SessionState get_state() const;
+ void set_state(Http2SessionState state);
+
+ void start_settings_timer();
+ void stop_settings_timer();
+
+ SSL *get_ssl() const;
+
+ int consume(int32_t stream_id, size_t len);
+
+ // Returns true if request can be issued on downstream connection.
+ bool can_push_request(const Downstream *downstream) const;
+ // Initiates the connection checking if downstream connection has
+ // been established and connection checking is required.
+ void start_checking_connection();
+ // Resets connection check timer to timeout |t|. After timeout, we
+ // require connection checking. If connection checking is already
+ // enabled, this timeout is for PING ACK timeout.
+ void reset_connection_check_timer(ev_tstamp t);
+ void reset_connection_check_timer_if_not_checking();
+ // Signals that connection is alive. Internally
+ // reset_connection_check_timer() is called.
+ void connection_alive();
+ // Change connection check state.
+ void set_connection_check_state(ConnectionCheck state);
+ ConnectionCheck get_connection_check_state() const;
+
+ bool should_hard_fail() const;
+
+ void submit_pending_requests();
+
+ DownstreamAddr *get_addr() const;
+
+ const std::shared_ptr<DownstreamAddrGroup> &get_downstream_addr_group() const;
+
+ int handle_downstream_push_promise(Downstream *downstream,
+ int32_t promised_stream_id);
+ int handle_downstream_push_promise_complete(Downstream *downstream,
+ Downstream *promised_downstream);
+
+ // Returns number of downstream connections, including pushed
+ // streams.
+ size_t get_num_dconns() const;
+
+ // Adds to group scope http2_avail_freelist.
+ void add_to_avail_freelist();
+ // Adds to address scope http2_extra_freelist.
+ void add_to_extra_freelist();
+
+ // Removes this object from any freelist. If this object is not
+ // linked from any freelist, this function does nothing.
+ void remove_from_freelist();
+
+ // Removes this object form any freelist, and marks this object as
+ // not schedulable.
+ void exclude_from_scheduling();
+
+ // Returns true if the maximum concurrency is reached. In other
+ // words, the number of currently participated streams in this
+ // session is equal or greater than the max concurrent streams limit
+ // advertised by server. If |extra| is nonzero, it is added to the
+ // number of current concurrent streams when comparing against
+ // server initiated concurrency limit.
+ bool max_concurrency_reached(size_t extra = 0) const;
+
+ DefaultMemchunks *get_request_buf();
+
+ void on_timeout();
+
+ // This is called periodically using ev_prepare watcher, and if
+ // group_ is retired (backend has been replaced), send GOAWAY to
+ // shutdown the connection.
+ void check_retire();
+
+ // Returns address used to connect to backend. Could be nullptr.
+ const Address *get_raddr() const;
+
+ // This is called when SETTINGS frame without ACK flag set is
+ // received.
+ void on_settings_received(const nghttp2_frame *frame);
+
+ bool get_allow_connect_proto() const;
+
+ using ReadBuf = Buffer<8_k>;
+
+ Http2Session *dlnext, *dlprev;
+
+private:
+ Connection conn_;
+ DefaultMemchunks wb_;
+ ev_timer settings_timer_;
+ // This timer has 2 purpose: when it first timeout, set
+ // connection_check_state_ = ConnectionCheck::REQUIRED. After
+ // connection check has started, this timer is started again and
+ // traps PING ACK timeout.
+ ev_timer connchk_timer_;
+ // timer to initiate connection. usually, this fires immediately.
+ ev_timer initiate_connection_timer_;
+ ev_prepare prep_;
+ DList<Http2DownstreamConnection> dconns_;
+ DList<StreamData> streams_;
+ std::function<int(Http2Session &)> read_, write_;
+ std::function<int(Http2Session &, const uint8_t *, size_t)> on_read_;
+ std::function<int(Http2Session &)> on_write_;
+ // Used to parse the response from HTTP proxy
+ std::unique_ptr<llhttp_t> proxy_htp_;
+ Worker *worker_;
+ // NULL if no TLS is configured
+ SSL_CTX *ssl_ctx_;
+ std::shared_ptr<DownstreamAddrGroup> group_;
+ // Address of remote endpoint
+ DownstreamAddr *addr_;
+ nghttp2_session *session_;
+ // Actual remote address used to contact backend. This is initially
+ // nullptr, and may point to either &addr_->addr,
+ // resolved_addr_.get(), or HTTP proxy's address structure.
+ const Address *raddr_;
+ // Resolved IP address if dns parameter is used
+ std::unique_ptr<Address> resolved_addr_;
+ std::unique_ptr<DNSQuery> dns_query_;
+ Http2SessionState state_;
+ ConnectionCheck connection_check_state_;
+ FreelistZone freelist_zone_;
+ // true if SETTINGS without ACK is received from peer.
+ bool settings_recved_;
+ // true if peer enables RFC 8441 CONNECT protocol.
+ bool allow_connect_proto_;
+};
+
+nghttp2_session_callbacks *create_http2_downstream_callbacks();
+
+} // namespace shrpx
+
+#endif // SHRPX_HTTP2_SESSION_H
diff --git a/src/shrpx_http2_upstream.cc b/src/shrpx_http2_upstream.cc
new file mode 100644
index 0000000..c9f8a8c
--- /dev/null
+++ b/src/shrpx_http2_upstream.cc
@@ -0,0 +1,2404 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_http2_upstream.h"
+
+#include <netinet/tcp.h>
+#include <assert.h>
+#include <cerrno>
+#include <sstream>
+
+#include "shrpx_client_handler.h"
+#include "shrpx_https_upstream.h"
+#include "shrpx_downstream.h"
+#include "shrpx_downstream_connection.h"
+#include "shrpx_config.h"
+#include "shrpx_http.h"
+#include "shrpx_worker.h"
+#include "shrpx_http2_session.h"
+#include "shrpx_log.h"
+#ifdef HAVE_MRUBY
+# include "shrpx_mruby.h"
+#endif // HAVE_MRUBY
+#include "http2.h"
+#include "util.h"
+#include "base64.h"
+#include "app_helper.h"
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+namespace {
+constexpr size_t MAX_BUFFER_SIZE = 32_k;
+} // namespace
+
+namespace {
+int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
+ uint32_t error_code, void *user_data) {
+ auto upstream = static_cast<Http2Upstream *>(user_data);
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, upstream) << "Stream stream_id=" << stream_id
+ << " is being closed";
+ }
+
+ auto downstream = static_cast<Downstream *>(
+ nghttp2_session_get_stream_user_data(session, stream_id));
+
+ if (!downstream) {
+ return 0;
+ }
+
+ auto &req = downstream->request();
+
+ upstream->consume(stream_id, req.unconsumed_body_length);
+
+ req.unconsumed_body_length = 0;
+
+ if (downstream->get_request_state() == DownstreamState::CONNECT_FAIL) {
+ upstream->remove_downstream(downstream);
+ // downstream was deleted
+
+ return 0;
+ }
+
+ if (downstream->can_detach_downstream_connection()) {
+ // Keep-alive
+ downstream->detach_downstream_connection();
+ }
+
+ downstream->set_request_state(DownstreamState::STREAM_CLOSED);
+
+ // At this point, downstream read may be paused.
+
+ // If shrpx_downstream::push_request_headers() failed, the
+ // error is handled here.
+ upstream->remove_downstream(downstream);
+ // downstream was deleted
+
+ // How to test this case? Request sufficient large download
+ // and make client send RST_STREAM after it gets first DATA
+ // frame chunk.
+
+ return 0;
+}
+} // namespace
+
+int Http2Upstream::upgrade_upstream(HttpsUpstream *http) {
+ int rv;
+
+ auto &balloc = http->get_downstream()->get_block_allocator();
+
+ auto http2_settings = http->get_downstream()->get_http2_settings();
+ http2_settings = util::to_base64(balloc, http2_settings);
+
+ auto settings_payload = base64::decode(balloc, std::begin(http2_settings),
+ std::end(http2_settings));
+
+ rv = nghttp2_session_upgrade2(
+ session_, settings_payload.byte(), settings_payload.size(),
+ http->get_downstream()->request().method == HTTP_HEAD, nullptr);
+ if (rv != 0) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, this) << "nghttp2_session_upgrade() returned error: "
+ << nghttp2_strerror(rv);
+ }
+ return -1;
+ }
+ pre_upstream_.reset(http);
+ auto downstream = http->pop_downstream();
+ downstream->reset_upstream(this);
+ downstream->set_stream_id(1);
+ downstream->reset_upstream_rtimer();
+ downstream->set_stream_id(1);
+
+ auto ptr = downstream.get();
+
+ nghttp2_session_set_stream_user_data(session_, 1, ptr);
+ downstream_queue_.add_pending(std::move(downstream));
+ downstream_queue_.mark_active(ptr);
+
+ // TODO This might not be necessary
+ handler_->stop_read_timer();
+
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, this) << "Connection upgraded to HTTP/2";
+ }
+
+ return 0;
+}
+
+void Http2Upstream::start_settings_timer() {
+ ev_timer_start(handler_->get_loop(), &settings_timer_);
+}
+
+void Http2Upstream::stop_settings_timer() {
+ ev_timer_stop(handler_->get_loop(), &settings_timer_);
+}
+
+namespace {
+int on_header_callback2(nghttp2_session *session, const nghttp2_frame *frame,
+ nghttp2_rcbuf *name, nghttp2_rcbuf *value,
+ uint8_t flags, void *user_data) {
+ auto namebuf = nghttp2_rcbuf_get_buf(name);
+ auto valuebuf = nghttp2_rcbuf_get_buf(value);
+ auto config = get_config();
+
+ if (config->http2.upstream.debug.frame_debug) {
+ verbose_on_header_callback(session, frame, namebuf.base, namebuf.len,
+ valuebuf.base, valuebuf.len, flags, user_data);
+ }
+ if (frame->hd.type != NGHTTP2_HEADERS) {
+ return 0;
+ }
+ auto upstream = static_cast<Http2Upstream *>(user_data);
+ auto downstream = static_cast<Downstream *>(
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+ if (!downstream) {
+ return 0;
+ }
+
+ auto &req = downstream->request();
+
+ auto &httpconf = config->http;
+
+ if (req.fs.buffer_size() + namebuf.len + valuebuf.len >
+ httpconf.request_header_field_buffer ||
+ req.fs.num_fields() >= httpconf.max_request_header_fields) {
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return 0;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, upstream) << "Too large or many header field size="
+ << req.fs.buffer_size() + namebuf.len + valuebuf.len
+ << ", num=" << req.fs.num_fields() + 1;
+ }
+
+ // just ignore header fields if this is trailer part.
+ if (frame->headers.cat == NGHTTP2_HCAT_HEADERS) {
+ return 0;
+ }
+
+ if (upstream->error_reply(downstream, 431) != 0) {
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+
+ return 0;
+ }
+
+ auto token = http2::lookup_token(namebuf.base, namebuf.len);
+ auto no_index = flags & NGHTTP2_NV_FLAG_NO_INDEX;
+
+ downstream->add_rcbuf(name);
+ downstream->add_rcbuf(value);
+
+ if (frame->headers.cat == NGHTTP2_HCAT_HEADERS) {
+ // just store header fields for trailer part
+ req.fs.add_trailer_token(StringRef{namebuf.base, namebuf.len},
+ StringRef{valuebuf.base, valuebuf.len}, no_index,
+ token);
+ return 0;
+ }
+
+ req.fs.add_header_token(StringRef{namebuf.base, namebuf.len},
+ StringRef{valuebuf.base, valuebuf.len}, no_index,
+ token);
+ return 0;
+}
+} // namespace
+
+namespace {
+int on_invalid_header_callback2(nghttp2_session *session,
+ const nghttp2_frame *frame, nghttp2_rcbuf *name,
+ nghttp2_rcbuf *value, uint8_t flags,
+ void *user_data) {
+ auto upstream = static_cast<Http2Upstream *>(user_data);
+ auto downstream = static_cast<Downstream *>(
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+ if (!downstream) {
+ return 0;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ auto namebuf = nghttp2_rcbuf_get_buf(name);
+ auto valuebuf = nghttp2_rcbuf_get_buf(value);
+
+ ULOG(INFO, upstream) << "Invalid header field for stream_id="
+ << frame->hd.stream_id << ": name=["
+ << StringRef{namebuf.base, namebuf.len} << "], value=["
+ << StringRef{valuebuf.base, valuebuf.len} << "]";
+ }
+
+ upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
+
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+}
+} // namespace
+
+namespace {
+int on_begin_headers_callback(nghttp2_session *session,
+ const nghttp2_frame *frame, void *user_data) {
+ auto upstream = static_cast<Http2Upstream *>(user_data);
+
+ if (frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
+ return 0;
+ }
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, upstream) << "Received upstream request HEADERS stream_id="
+ << frame->hd.stream_id;
+ }
+
+ upstream->on_start_request(frame);
+
+ return 0;
+}
+} // namespace
+
+void Http2Upstream::on_start_request(const nghttp2_frame *frame) {
+ auto downstream = std::make_unique<Downstream>(this, handler_->get_mcpool(),
+ frame->hd.stream_id);
+ nghttp2_session_set_stream_user_data(session_, frame->hd.stream_id,
+ downstream.get());
+
+ downstream->reset_upstream_rtimer();
+
+ handler_->repeat_read_timer();
+
+ auto &req = downstream->request();
+
+ // Although, we deprecated minor version from HTTP/2, we supply
+ // minor version 0 to use via header field in a conventional way.
+ req.http_major = 2;
+ req.http_minor = 0;
+
+ add_pending_downstream(std::move(downstream));
+
+ ++num_requests_;
+
+ auto config = get_config();
+ auto &httpconf = config->http;
+ if (httpconf.max_requests <= num_requests_) {
+ start_graceful_shutdown();
+ }
+}
+
+int Http2Upstream::on_request_headers(Downstream *downstream,
+ const nghttp2_frame *frame) {
+ auto lgconf = log_config();
+ lgconf->update_tstamp(std::chrono::system_clock::now());
+ auto &req = downstream->request();
+ req.tstamp = lgconf->tstamp;
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return 0;
+ }
+
+ auto &nva = req.fs.headers();
+
+ if (LOG_ENABLED(INFO)) {
+ std::stringstream ss;
+ for (auto &nv : nva) {
+ if (nv.name == "authorization") {
+ ss << TTY_HTTP_HD << nv.name << TTY_RST << ": <redacted>\n";
+ continue;
+ }
+ ss << TTY_HTTP_HD << nv.name << TTY_RST << ": " << nv.value << "\n";
+ }
+ ULOG(INFO, this) << "HTTP request headers. stream_id="
+ << downstream->get_stream_id() << "\n"
+ << ss.str();
+ }
+
+ auto config = get_config();
+ auto &dump = config->http2.upstream.debug.dump;
+
+ if (dump.request_header) {
+ http2::dump_nv(dump.request_header, nva);
+ }
+
+ auto content_length = req.fs.header(http2::HD_CONTENT_LENGTH);
+ if (content_length) {
+ // libnghttp2 guarantees this can be parsed
+ req.fs.content_length = util::parse_uint(content_length->value);
+ }
+
+ // presence of mandatory header fields are guaranteed by libnghttp2.
+ auto authority = req.fs.header(http2::HD__AUTHORITY);
+ auto path = req.fs.header(http2::HD__PATH);
+ auto method = req.fs.header(http2::HD__METHOD);
+ auto scheme = req.fs.header(http2::HD__SCHEME);
+
+ auto method_token = http2::lookup_method_token(method->value);
+ if (method_token == -1) {
+ if (error_reply(downstream, 501) != 0) {
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+ return 0;
+ }
+
+ auto faddr = handler_->get_upstream_addr();
+
+ // For HTTP/2 proxy, we require :authority.
+ if (method_token != HTTP_CONNECT && config->http2_proxy &&
+ faddr->alt_mode == UpstreamAltMode::NONE && !authority) {
+ rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
+ return 0;
+ }
+
+ req.method = method_token;
+ if (scheme) {
+ req.scheme = scheme->value;
+ }
+
+ // nghttp2 library guarantees either :authority or host exist
+ if (!authority) {
+ req.no_authority = true;
+ authority = req.fs.header(http2::HD_HOST);
+ }
+
+ if (authority) {
+ req.authority = authority->value;
+ }
+
+ if (path) {
+ if (method_token == HTTP_OPTIONS &&
+ path->value == StringRef::from_lit("*")) {
+ // Server-wide OPTIONS request. Path is empty.
+ } else if (config->http2_proxy &&
+ faddr->alt_mode == UpstreamAltMode::NONE) {
+ req.path = path->value;
+ } else {
+ req.path = http2::rewrite_clean_path(downstream->get_block_allocator(),
+ path->value);
+ }
+ }
+
+ auto connect_proto = req.fs.header(http2::HD__PROTOCOL);
+ if (connect_proto) {
+ if (connect_proto->value != "websocket") {
+ if (error_reply(downstream, 400) != 0) {
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+ return 0;
+ }
+ req.connect_proto = ConnectProto::WEBSOCKET;
+ }
+
+ if (!(frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) {
+ req.http2_expect_body = true;
+ } else if (req.fs.content_length == -1) {
+ // If END_STREAM flag is set to HEADERS frame, we are sure that
+ // content-length is 0.
+ req.fs.content_length = 0;
+ }
+
+ downstream->inspect_http2_request();
+
+ downstream->set_request_state(DownstreamState::HEADER_COMPLETE);
+
+ if (config->http.require_http_scheme &&
+ !http::check_http_scheme(req.scheme, handler_->get_ssl() != nullptr)) {
+ if (error_reply(downstream, 400) != 0) {
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+ return 0;
+ }
+
+#ifdef HAVE_MRUBY
+ auto worker = handler_->get_worker();
+ auto mruby_ctx = worker->get_mruby_context();
+
+ if (mruby_ctx->run_on_request_proc(downstream) != 0) {
+ if (error_reply(downstream, 500) != 0) {
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+ return 0;
+ }
+#endif // HAVE_MRUBY
+
+ if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+ downstream->disable_upstream_rtimer();
+
+ downstream->set_request_state(DownstreamState::MSG_COMPLETE);
+ }
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return 0;
+ }
+
+ start_downstream(downstream);
+
+ return 0;
+}
+
+void Http2Upstream::start_downstream(Downstream *downstream) {
+ if (downstream_queue_.can_activate(downstream->request().authority)) {
+ initiate_downstream(downstream);
+ return;
+ }
+
+ downstream_queue_.mark_blocked(downstream);
+}
+
+void Http2Upstream::initiate_downstream(Downstream *downstream) {
+ int rv;
+
+#ifdef HAVE_MRUBY
+ DownstreamConnection *dconn_ptr;
+#endif // HAVE_MRUBY
+
+ for (;;) {
+ auto dconn = handler_->get_downstream_connection(rv, downstream);
+ if (!dconn) {
+ if (rv == SHRPX_ERR_TLS_REQUIRED) {
+ rv = redirect_to_https(downstream);
+ } else {
+ rv = error_reply(downstream, 502);
+ }
+ if (rv != 0) {
+ rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
+ }
+
+ downstream->set_request_state(DownstreamState::CONNECT_FAIL);
+ downstream_queue_.mark_failure(downstream);
+
+ return;
+ }
+
+#ifdef HAVE_MRUBY
+ dconn_ptr = dconn.get();
+#endif // HAVE_MRUBY
+ rv = downstream->attach_downstream_connection(std::move(dconn));
+ if (rv == 0) {
+ break;
+ }
+ }
+
+#ifdef HAVE_MRUBY
+ const auto &group = dconn_ptr->get_downstream_addr_group();
+ if (group) {
+ const auto &mruby_ctx = group->shared_addr->mruby_ctx;
+ if (mruby_ctx->run_on_request_proc(downstream) != 0) {
+ if (error_reply(downstream, 500) != 0) {
+ rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
+ }
+
+ downstream_queue_.mark_failure(downstream);
+
+ return;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return;
+ }
+ }
+#endif // HAVE_MRUBY
+
+ rv = downstream->push_request_headers();
+ if (rv != 0) {
+
+ if (error_reply(downstream, 502) != 0) {
+ rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
+ }
+
+ downstream_queue_.mark_failure(downstream);
+
+ return;
+ }
+
+ downstream_queue_.mark_active(downstream);
+
+ auto &req = downstream->request();
+ if (!req.http2_expect_body) {
+ rv = downstream->end_upload_data();
+ if (rv != 0) {
+ rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
+ }
+ }
+
+ return;
+}
+
+namespace {
+int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame,
+ void *user_data) {
+ if (get_config()->http2.upstream.debug.frame_debug) {
+ verbose_on_frame_recv_callback(session, frame, user_data);
+ }
+ auto upstream = static_cast<Http2Upstream *>(user_data);
+ auto handler = upstream->get_client_handler();
+
+ switch (frame->hd.type) {
+ case NGHTTP2_DATA: {
+ auto downstream = static_cast<Downstream *>(
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+ if (!downstream) {
+ return 0;
+ }
+
+ if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+ downstream->disable_upstream_rtimer();
+
+ if (downstream->end_upload_data() != 0) {
+ if (downstream->get_response_state() != DownstreamState::MSG_COMPLETE) {
+ upstream->rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
+ }
+ }
+
+ downstream->set_request_state(DownstreamState::MSG_COMPLETE);
+ }
+
+ return 0;
+ }
+ case NGHTTP2_HEADERS: {
+ auto downstream = static_cast<Downstream *>(
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+ if (!downstream) {
+ return 0;
+ }
+
+ if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
+ downstream->reset_upstream_rtimer();
+
+ handler->stop_read_timer();
+
+ return upstream->on_request_headers(downstream, frame);
+ }
+
+ if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+ downstream->disable_upstream_rtimer();
+
+ if (downstream->end_upload_data() != 0) {
+ if (downstream->get_response_state() != DownstreamState::MSG_COMPLETE) {
+ upstream->rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
+ }
+ }
+
+ downstream->set_request_state(DownstreamState::MSG_COMPLETE);
+ }
+
+ return 0;
+ }
+ case NGHTTP2_SETTINGS:
+ if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) {
+ return 0;
+ }
+ upstream->stop_settings_timer();
+ return 0;
+ case NGHTTP2_GOAWAY:
+ if (LOG_ENABLED(INFO)) {
+ auto debug_data = util::ascii_dump(frame->goaway.opaque_data,
+ frame->goaway.opaque_data_len);
+
+ ULOG(INFO, upstream) << "GOAWAY received: last-stream-id="
+ << frame->goaway.last_stream_id
+ << ", error_code=" << frame->goaway.error_code
+ << ", debug_data=" << debug_data;
+ }
+ return 0;
+ default:
+ return 0;
+ }
+}
+} // namespace
+
+namespace {
+int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
+ int32_t stream_id, const uint8_t *data,
+ size_t len, void *user_data) {
+ auto upstream = static_cast<Http2Upstream *>(user_data);
+ auto downstream = static_cast<Downstream *>(
+ nghttp2_session_get_stream_user_data(session, stream_id));
+
+ if (!downstream) {
+ if (upstream->consume(stream_id, len) != 0) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+ }
+
+ downstream->reset_upstream_rtimer();
+
+ if (downstream->push_upload_data_chunk(data, len) != 0) {
+ if (downstream->get_response_state() != DownstreamState::MSG_COMPLETE) {
+ upstream->rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
+ }
+
+ if (upstream->consume(stream_id, len) != 0) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame,
+ void *user_data) {
+ if (get_config()->http2.upstream.debug.frame_debug) {
+ verbose_on_frame_send_callback(session, frame, user_data);
+ }
+ auto upstream = static_cast<Http2Upstream *>(user_data);
+ auto handler = upstream->get_client_handler();
+
+ switch (frame->hd.type) {
+ case NGHTTP2_DATA:
+ case NGHTTP2_HEADERS: {
+ if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) {
+ return 0;
+ }
+ // RST_STREAM if request is still incomplete.
+ auto stream_id = frame->hd.stream_id;
+ auto downstream = static_cast<Downstream *>(
+ nghttp2_session_get_stream_user_data(session, stream_id));
+
+ if (!downstream) {
+ return 0;
+ }
+
+ // For tunneling, issue RST_STREAM to finish the stream.
+ if (downstream->get_upgraded() ||
+ nghttp2_session_get_stream_remote_close(session, stream_id) == 0) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, upstream)
+ << "Send RST_STREAM to "
+ << (downstream->get_upgraded() ? "tunneled " : "")
+ << "stream stream_id=" << downstream->get_stream_id()
+ << " to finish off incomplete request";
+ }
+
+ upstream->rst_stream(downstream, NGHTTP2_NO_ERROR);
+ }
+
+ return 0;
+ }
+ case NGHTTP2_SETTINGS:
+ if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) {
+ upstream->start_settings_timer();
+ }
+ return 0;
+ case NGHTTP2_PUSH_PROMISE: {
+ auto promised_stream_id = frame->push_promise.promised_stream_id;
+
+ if (nghttp2_session_get_stream_user_data(session, promised_stream_id)) {
+ // In case of push from backend, downstream object was already
+ // created.
+ return 0;
+ }
+
+ auto promised_downstream = std::make_unique<Downstream>(
+ upstream, handler->get_mcpool(), promised_stream_id);
+ auto &req = promised_downstream->request();
+
+ // As long as we use nghttp2_session_mem_send(), setting stream
+ // user data here should not fail. This is because this callback
+ // is called just after frame was serialized. So no worries about
+ // hanging Downstream.
+ nghttp2_session_set_stream_user_data(session, promised_stream_id,
+ promised_downstream.get());
+
+ promised_downstream->set_assoc_stream_id(frame->hd.stream_id);
+ promised_downstream->disable_upstream_rtimer();
+
+ req.http_major = 2;
+ req.http_minor = 0;
+
+ req.fs.content_length = 0;
+ req.http2_expect_body = false;
+
+ auto &promised_balloc = promised_downstream->get_block_allocator();
+
+ for (size_t i = 0; i < frame->push_promise.nvlen; ++i) {
+ auto &nv = frame->push_promise.nva[i];
+
+ auto name =
+ make_string_ref(promised_balloc, StringRef{nv.name, nv.namelen});
+ auto value =
+ make_string_ref(promised_balloc, StringRef{nv.value, nv.valuelen});
+
+ auto token = http2::lookup_token(nv.name, nv.namelen);
+ switch (token) {
+ case http2::HD__METHOD:
+ req.method = http2::lookup_method_token(value);
+ break;
+ case http2::HD__SCHEME:
+ req.scheme = value;
+ break;
+ case http2::HD__AUTHORITY:
+ req.authority = value;
+ break;
+ case http2::HD__PATH:
+ req.path = http2::rewrite_clean_path(promised_balloc, value);
+ break;
+ }
+ req.fs.add_header_token(name, value, nv.flags & NGHTTP2_NV_FLAG_NO_INDEX,
+ token);
+ }
+
+ promised_downstream->inspect_http2_request();
+
+ promised_downstream->set_request_state(DownstreamState::MSG_COMPLETE);
+
+ // a bit weird but start_downstream() expects that given
+ // downstream is in pending queue.
+ auto ptr = promised_downstream.get();
+ upstream->add_pending_downstream(std::move(promised_downstream));
+
+#ifdef HAVE_MRUBY
+ auto worker = handler->get_worker();
+ auto mruby_ctx = worker->get_mruby_context();
+
+ if (mruby_ctx->run_on_request_proc(ptr) != 0) {
+ if (upstream->error_reply(ptr, 500) != 0) {
+ upstream->rst_stream(ptr, NGHTTP2_INTERNAL_ERROR);
+ return 0;
+ }
+ return 0;
+ }
+#endif // HAVE_MRUBY
+
+ upstream->start_downstream(ptr);
+
+ return 0;
+ }
+ case NGHTTP2_GOAWAY:
+ if (LOG_ENABLED(INFO)) {
+ auto debug_data = util::ascii_dump(frame->goaway.opaque_data,
+ frame->goaway.opaque_data_len);
+
+ ULOG(INFO, upstream) << "Sending GOAWAY: last-stream-id="
+ << frame->goaway.last_stream_id
+ << ", error_code=" << frame->goaway.error_code
+ << ", debug_data=" << debug_data;
+ }
+ return 0;
+ default:
+ return 0;
+ }
+}
+} // namespace
+
+namespace {
+int on_frame_not_send_callback(nghttp2_session *session,
+ const nghttp2_frame *frame, int lib_error_code,
+ void *user_data) {
+ auto upstream = static_cast<Http2Upstream *>(user_data);
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, upstream) << "Failed to send control frame type="
+ << static_cast<uint32_t>(frame->hd.type)
+ << ", lib_error_code=" << lib_error_code << ":"
+ << nghttp2_strerror(lib_error_code);
+ }
+ if (frame->hd.type == NGHTTP2_HEADERS &&
+ lib_error_code != NGHTTP2_ERR_STREAM_CLOSED &&
+ lib_error_code != NGHTTP2_ERR_STREAM_CLOSING) {
+ // To avoid stream hanging around, issue RST_STREAM.
+ auto downstream = static_cast<Downstream *>(
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+ if (downstream) {
+ upstream->rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
+ }
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+constexpr auto PADDING = std::array<uint8_t, 256>{};
+} // namespace
+
+namespace {
+int send_data_callback(nghttp2_session *session, nghttp2_frame *frame,
+ const uint8_t *framehd, size_t length,
+ nghttp2_data_source *source, void *user_data) {
+ auto downstream = static_cast<Downstream *>(source->ptr);
+ auto upstream = static_cast<Http2Upstream *>(downstream->get_upstream());
+ auto body = downstream->get_response_buf();
+
+ auto wb = upstream->get_response_buf();
+
+ size_t padlen = 0;
+
+ wb->append(framehd, 9);
+ if (frame->data.padlen > 0) {
+ padlen = frame->data.padlen - 1;
+ wb->append(static_cast<uint8_t>(padlen));
+ }
+
+ body->remove(*wb, length);
+
+ wb->append(PADDING.data(), padlen);
+
+ if (body->rleft() == 0) {
+ downstream->disable_upstream_wtimer();
+ } else {
+ downstream->reset_upstream_wtimer();
+ }
+
+ if (length > 0 && downstream->resume_read(SHRPX_NO_BUFFER, length) != 0) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+
+ // We have to add length here, so that we can log this amount of
+ // data transferred.
+ downstream->response_sent_body_length += length;
+
+ auto max_buffer_size = upstream->get_max_buffer_size();
+
+ return wb->rleft() >= max_buffer_size ? NGHTTP2_ERR_PAUSE : 0;
+}
+} // namespace
+
+namespace {
+uint32_t infer_upstream_rst_stream_error_code(uint32_t downstream_error_code) {
+ // NGHTTP2_REFUSED_STREAM is important because it tells upstream
+ // client to retry.
+ switch (downstream_error_code) {
+ case NGHTTP2_NO_ERROR:
+ case NGHTTP2_REFUSED_STREAM:
+ return downstream_error_code;
+ default:
+ return NGHTTP2_INTERNAL_ERROR;
+ }
+}
+} // namespace
+
+namespace {
+void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto upstream = static_cast<Http2Upstream *>(w->data);
+ auto handler = upstream->get_client_handler();
+ ULOG(INFO, upstream) << "SETTINGS timeout";
+ if (upstream->terminate_session(NGHTTP2_SETTINGS_TIMEOUT) != 0) {
+ delete handler;
+ return;
+ }
+ handler->signal_write();
+}
+} // namespace
+
+namespace {
+void shutdown_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto upstream = static_cast<Http2Upstream *>(w->data);
+ auto handler = upstream->get_client_handler();
+ upstream->submit_goaway();
+ handler->signal_write();
+}
+} // namespace
+
+namespace {
+void prepare_cb(struct ev_loop *loop, ev_prepare *w, int revents) {
+ auto upstream = static_cast<Http2Upstream *>(w->data);
+ upstream->check_shutdown();
+}
+} // namespace
+
+void Http2Upstream::submit_goaway() {
+ auto last_stream_id = nghttp2_session_get_last_proc_stream_id(session_);
+ nghttp2_submit_goaway(session_, NGHTTP2_FLAG_NONE, last_stream_id,
+ NGHTTP2_NO_ERROR, nullptr, 0);
+}
+
+void Http2Upstream::check_shutdown() {
+ auto worker = handler_->get_worker();
+
+ if (!worker->get_graceful_shutdown()) {
+ return;
+ }
+
+ ev_prepare_stop(handler_->get_loop(), &prep_);
+
+ start_graceful_shutdown();
+}
+
+void Http2Upstream::start_graceful_shutdown() {
+ int rv;
+ if (ev_is_active(&shutdown_timer_)) {
+ return;
+ }
+
+ rv = nghttp2_submit_shutdown_notice(session_);
+ if (rv != 0) {
+ ULOG(FATAL, this) << "nghttp2_submit_shutdown_notice() failed: "
+ << nghttp2_strerror(rv);
+ return;
+ }
+
+ handler_->signal_write();
+
+ ev_timer_start(handler_->get_loop(), &shutdown_timer_);
+}
+
+nghttp2_session_callbacks *create_http2_upstream_callbacks() {
+ int rv;
+ nghttp2_session_callbacks *callbacks;
+
+ rv = nghttp2_session_callbacks_new(&callbacks);
+
+ if (rv != 0) {
+ return nullptr;
+ }
+
+ nghttp2_session_callbacks_set_on_stream_close_callback(
+ callbacks, on_stream_close_callback);
+
+ nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
+ on_frame_recv_callback);
+
+ nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
+ callbacks, on_data_chunk_recv_callback);
+
+ nghttp2_session_callbacks_set_on_frame_send_callback(callbacks,
+ on_frame_send_callback);
+
+ nghttp2_session_callbacks_set_on_frame_not_send_callback(
+ callbacks, on_frame_not_send_callback);
+
+ nghttp2_session_callbacks_set_on_header_callback2(callbacks,
+ on_header_callback2);
+
+ nghttp2_session_callbacks_set_on_invalid_header_callback2(
+ callbacks, on_invalid_header_callback2);
+
+ nghttp2_session_callbacks_set_on_begin_headers_callback(
+ callbacks, on_begin_headers_callback);
+
+ nghttp2_session_callbacks_set_send_data_callback(callbacks,
+ send_data_callback);
+
+ auto config = get_config();
+
+ if (config->padding) {
+ nghttp2_session_callbacks_set_select_padding_callback(
+ callbacks, http::select_padding_callback);
+ }
+
+ if (config->http2.upstream.debug.frame_debug) {
+ nghttp2_session_callbacks_set_error_callback2(callbacks,
+ verbose_error_callback);
+ }
+
+ return callbacks;
+}
+
+namespace {
+size_t downstream_queue_size(Worker *worker) {
+ auto &downstreamconf = *worker->get_downstream_config();
+
+ if (get_config()->http2_proxy) {
+ return downstreamconf.connections_per_host;
+ }
+
+ return downstreamconf.connections_per_frontend;
+}
+} // namespace
+
+Http2Upstream::Http2Upstream(ClientHandler *handler)
+ : wb_(handler->get_worker()->get_mcpool()),
+ downstream_queue_(downstream_queue_size(handler->get_worker()),
+ !get_config()->http2_proxy),
+ handler_(handler),
+ session_(nullptr),
+ max_buffer_size_(MAX_BUFFER_SIZE),
+ num_requests_(0) {
+ int rv;
+
+ auto config = get_config();
+ auto &http2conf = config->http2;
+
+ auto faddr = handler_->get_upstream_addr();
+
+ rv =
+ nghttp2_session_server_new2(&session_, http2conf.upstream.callbacks, this,
+ faddr->alt_mode != UpstreamAltMode::NONE
+ ? http2conf.upstream.alt_mode_option
+ : http2conf.upstream.option);
+
+ assert(rv == 0);
+
+ flow_control_ = true;
+
+ // TODO Maybe call from outside?
+ std::array<nghttp2_settings_entry, 5> entry;
+ size_t nentry = 3;
+
+ entry[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
+ entry[0].value = http2conf.upstream.max_concurrent_streams;
+
+ entry[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+ if (faddr->alt_mode != UpstreamAltMode::NONE) {
+ entry[1].value = (1u << 31) - 1;
+ } else {
+ entry[1].value = http2conf.upstream.window_size;
+ }
+
+ entry[2].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES;
+ entry[2].value = 1;
+
+ if (!config->http2_proxy) {
+ entry[nentry].settings_id = NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL;
+ entry[nentry].value = 1;
+ ++nentry;
+ }
+
+ if (http2conf.upstream.decoder_dynamic_table_size !=
+ NGHTTP2_DEFAULT_HEADER_TABLE_SIZE) {
+ entry[nentry].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
+ entry[nentry].value = http2conf.upstream.decoder_dynamic_table_size;
+ ++nentry;
+ }
+
+ rv = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, entry.data(),
+ nentry);
+ if (rv != 0) {
+ ULOG(ERROR, this) << "nghttp2_submit_settings() returned error: "
+ << nghttp2_strerror(rv);
+ }
+
+ auto window_size = faddr->alt_mode != UpstreamAltMode::NONE
+ ? std::numeric_limits<int32_t>::max()
+ : http2conf.upstream.optimize_window_size
+ ? std::min(http2conf.upstream.connection_window_size,
+ NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE)
+ : http2conf.upstream.connection_window_size;
+
+ rv = nghttp2_session_set_local_window_size(session_, NGHTTP2_FLAG_NONE, 0,
+ window_size);
+
+ if (rv != 0) {
+ ULOG(ERROR, this)
+ << "nghttp2_session_set_local_window_size() returned error: "
+ << nghttp2_strerror(rv);
+ }
+
+ // We wait for SETTINGS ACK at least 10 seconds.
+ ev_timer_init(&settings_timer_, settings_timeout_cb,
+ http2conf.upstream.timeout.settings, 0.);
+
+ settings_timer_.data = this;
+
+ // timer for 2nd GOAWAY. HTTP/2 spec recommend 1 RTT. We wait for
+ // 2 seconds.
+ ev_timer_init(&shutdown_timer_, shutdown_timeout_cb, 2., 0);
+ shutdown_timer_.data = this;
+
+ ev_prepare_init(&prep_, prepare_cb);
+ prep_.data = this;
+ ev_prepare_start(handler_->get_loop(), &prep_);
+
+#if defined(TCP_INFO) && defined(TCP_NOTSENT_LOWAT)
+ if (http2conf.upstream.optimize_write_buffer_size) {
+ auto conn = handler_->get_connection();
+ conn->tls_dyn_rec_warmup_threshold = 0;
+
+ uint32_t pollout_thres = 1;
+ rv = setsockopt(conn->fd, IPPROTO_TCP, TCP_NOTSENT_LOWAT, &pollout_thres,
+ static_cast<socklen_t>(sizeof(pollout_thres)));
+
+ if (rv != 0) {
+ if (LOG_ENABLED(INFO)) {
+ auto error = errno;
+ LOG(INFO) << "setsockopt(TCP_NOTSENT_LOWAT, " << pollout_thres
+ << ") failed: errno=" << error;
+ }
+ }
+ }
+#endif // defined(TCP_INFO) && defined(TCP_NOTSENT_LOWAT)
+
+ handler_->reset_upstream_read_timeout(
+ config->conn.upstream.timeout.http2_read);
+
+ handler_->signal_write();
+}
+
+Http2Upstream::~Http2Upstream() {
+ nghttp2_session_del(session_);
+ ev_prepare_stop(handler_->get_loop(), &prep_);
+ ev_timer_stop(handler_->get_loop(), &shutdown_timer_);
+ ev_timer_stop(handler_->get_loop(), &settings_timer_);
+}
+
+int Http2Upstream::on_read() {
+ ssize_t rv = 0;
+ auto rb = handler_->get_rb();
+ auto rlimit = handler_->get_rlimit();
+
+ if (rb->rleft()) {
+ rv = nghttp2_session_mem_recv(session_, rb->pos(), rb->rleft());
+ if (rv < 0) {
+ if (rv != NGHTTP2_ERR_BAD_CLIENT_MAGIC) {
+ ULOG(ERROR, this) << "nghttp2_session_mem_recv() returned error: "
+ << nghttp2_strerror(rv);
+ }
+ return -1;
+ }
+
+ // nghttp2_session_mem_recv should consume all input bytes on
+ // success.
+ assert(static_cast<size_t>(rv) == rb->rleft());
+ rb->reset();
+ rlimit->startw();
+ }
+
+ if (nghttp2_session_want_read(session_) == 0 &&
+ nghttp2_session_want_write(session_) == 0 && wb_.rleft() == 0) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, this) << "No more read/write for this HTTP2 session";
+ }
+ return -1;
+ }
+
+ handler_->signal_write();
+ return 0;
+}
+
+// After this function call, downstream may be deleted.
+int Http2Upstream::on_write() {
+ int rv;
+ auto config = get_config();
+ auto &http2conf = config->http2;
+
+ if ((http2conf.upstream.optimize_write_buffer_size ||
+ http2conf.upstream.optimize_window_size) &&
+ handler_->get_ssl()) {
+ auto conn = handler_->get_connection();
+ TCPHint hint;
+ rv = conn->get_tcp_hint(&hint);
+ if (rv == 0) {
+ if (http2conf.upstream.optimize_write_buffer_size) {
+ max_buffer_size_ = std::min(MAX_BUFFER_SIZE, hint.write_buffer_size);
+ }
+
+ if (http2conf.upstream.optimize_window_size) {
+ auto faddr = handler_->get_upstream_addr();
+ if (faddr->alt_mode == UpstreamAltMode::NONE) {
+ auto window_size = std::min(http2conf.upstream.connection_window_size,
+ static_cast<int32_t>(hint.rwin * 2));
+
+ rv = nghttp2_session_set_local_window_size(
+ session_, NGHTTP2_FLAG_NONE, 0, window_size);
+ if (rv != 0) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, this)
+ << "nghttp2_session_set_local_window_size() with window_size="
+ << window_size << " failed: " << nghttp2_strerror(rv);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ for (;;) {
+ if (wb_.rleft() >= max_buffer_size_) {
+ return 0;
+ }
+
+ const uint8_t *data;
+ auto datalen = nghttp2_session_mem_send(session_, &data);
+
+ if (datalen < 0) {
+ ULOG(ERROR, this) << "nghttp2_session_mem_send() returned error: "
+ << nghttp2_strerror(datalen);
+ return -1;
+ }
+ if (datalen == 0) {
+ break;
+ }
+ wb_.append(data, datalen);
+ }
+
+ if (nghttp2_session_want_read(session_) == 0 &&
+ nghttp2_session_want_write(session_) == 0 && wb_.rleft() == 0) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, this) << "No more read/write for this HTTP2 session";
+ }
+ return -1;
+ }
+
+ return 0;
+}
+
+ClientHandler *Http2Upstream::get_client_handler() const { return handler_; }
+
+int Http2Upstream::downstream_read(DownstreamConnection *dconn) {
+ auto downstream = dconn->get_downstream();
+
+ if (downstream->get_response_state() == DownstreamState::MSG_RESET) {
+ // The downstream stream was reset (canceled). In this case,
+ // RST_STREAM to the upstream and delete downstream connection
+ // here. Deleting downstream will be taken place at
+ // on_stream_close_callback.
+ rst_stream(downstream,
+ infer_upstream_rst_stream_error_code(
+ downstream->get_response_rst_stream_error_code()));
+ downstream->pop_downstream_connection();
+ // dconn was deleted
+ dconn = nullptr;
+ } else if (downstream->get_response_state() ==
+ DownstreamState::MSG_BAD_HEADER) {
+ if (error_reply(downstream, 502) != 0) {
+ return -1;
+ }
+ downstream->pop_downstream_connection();
+ // dconn was deleted
+ dconn = nullptr;
+ } else {
+ auto rv = downstream->on_read();
+ if (rv == SHRPX_ERR_EOF) {
+ if (downstream->get_request_header_sent()) {
+ return downstream_eof(dconn);
+ }
+ return SHRPX_ERR_RETRY;
+ }
+ if (rv == SHRPX_ERR_DCONN_CANCELED) {
+ downstream->pop_downstream_connection();
+ handler_->signal_write();
+ return 0;
+ }
+ if (rv != 0) {
+ if (rv != SHRPX_ERR_NETWORK) {
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, dconn) << "HTTP parser failure";
+ }
+ }
+ return downstream_error(dconn, Downstream::EVENT_ERROR);
+ }
+
+ if (downstream->can_detach_downstream_connection()) {
+ // Keep-alive
+ downstream->detach_downstream_connection();
+ }
+ }
+
+ handler_->signal_write();
+
+ // At this point, downstream may be deleted.
+
+ return 0;
+}
+
+int Http2Upstream::downstream_write(DownstreamConnection *dconn) {
+ int rv;
+ rv = dconn->on_write();
+ if (rv == SHRPX_ERR_NETWORK) {
+ return downstream_error(dconn, Downstream::EVENT_ERROR);
+ }
+ if (rv != 0) {
+ return rv;
+ }
+ return 0;
+}
+
+int Http2Upstream::downstream_eof(DownstreamConnection *dconn) {
+ auto downstream = dconn->get_downstream();
+
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, dconn) << "EOF. stream_id=" << downstream->get_stream_id();
+ }
+
+ // Delete downstream connection. If we don't delete it here, it will
+ // be pooled in on_stream_close_callback.
+ downstream->pop_downstream_connection();
+ // dconn was deleted
+ dconn = nullptr;
+ // downstream will be deleted in on_stream_close_callback.
+ if (downstream->get_response_state() == DownstreamState::HEADER_COMPLETE) {
+ // Server may indicate the end of the request by EOF
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, this) << "Downstream body was ended by EOF";
+ }
+ downstream->set_response_state(DownstreamState::MSG_COMPLETE);
+
+ // For tunneled connection, MSG_COMPLETE signals
+ // downstream_data_read_callback to send RST_STREAM after pending
+ // response body is sent. This is needed to ensure that RST_STREAM
+ // is sent after all pending data are sent.
+ on_downstream_body_complete(downstream);
+ } else if (downstream->get_response_state() !=
+ DownstreamState::MSG_COMPLETE) {
+ // If stream was not closed, then we set MSG_COMPLETE and let
+ // on_stream_close_callback delete downstream.
+ if (error_reply(downstream, 502) != 0) {
+ return -1;
+ }
+ }
+ handler_->signal_write();
+ // At this point, downstream may be deleted.
+ return 0;
+}
+
+int Http2Upstream::downstream_error(DownstreamConnection *dconn, int events) {
+ auto downstream = dconn->get_downstream();
+
+ if (LOG_ENABLED(INFO)) {
+ if (events & Downstream::EVENT_ERROR) {
+ DCLOG(INFO, dconn) << "Downstream network/general error";
+ } else {
+ DCLOG(INFO, dconn) << "Timeout";
+ }
+ if (downstream->get_upgraded()) {
+ DCLOG(INFO, dconn) << "Note: this is tunnel connection";
+ }
+ }
+
+ // Delete downstream connection. If we don't delete it here, it will
+ // be pooled in on_stream_close_callback.
+ downstream->pop_downstream_connection();
+ // dconn was deleted
+ dconn = nullptr;
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ // For SSL tunneling, we issue RST_STREAM. For other types of
+ // stream, we don't have to do anything since response was
+ // complete.
+ if (downstream->get_upgraded()) {
+ rst_stream(downstream, NGHTTP2_NO_ERROR);
+ }
+ } else {
+ if (downstream->get_response_state() == DownstreamState::HEADER_COMPLETE) {
+ if (downstream->get_upgraded()) {
+ on_downstream_body_complete(downstream);
+ } else {
+ rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
+ }
+ } else {
+ unsigned int status;
+ if (events & Downstream::EVENT_TIMEOUT) {
+ if (downstream->get_request_header_sent()) {
+ status = 504;
+ } else {
+ status = 408;
+ }
+ } else {
+ status = 502;
+ }
+ if (error_reply(downstream, status) != 0) {
+ return -1;
+ }
+ }
+ downstream->set_response_state(DownstreamState::MSG_COMPLETE);
+ }
+ handler_->signal_write();
+ // At this point, downstream may be deleted.
+ return 0;
+}
+
+int Http2Upstream::rst_stream(Downstream *downstream, uint32_t error_code) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, this) << "RST_STREAM stream_id=" << downstream->get_stream_id()
+ << " with error_code=" << error_code;
+ }
+ int rv;
+ rv = nghttp2_submit_rst_stream(session_, NGHTTP2_FLAG_NONE,
+ downstream->get_stream_id(), error_code);
+ if (rv < NGHTTP2_ERR_FATAL) {
+ ULOG(FATAL, this) << "nghttp2_submit_rst_stream() failed: "
+ << nghttp2_strerror(rv);
+ return -1;
+ }
+ return 0;
+}
+
+int Http2Upstream::terminate_session(uint32_t error_code) {
+ int rv;
+ rv = nghttp2_session_terminate_session(session_, error_code);
+ if (rv != 0) {
+ return -1;
+ }
+ return 0;
+}
+
+namespace {
+ssize_t downstream_data_read_callback(nghttp2_session *session,
+ int32_t stream_id, uint8_t *buf,
+ size_t length, uint32_t *data_flags,
+ nghttp2_data_source *source,
+ void *user_data) {
+ int rv;
+ auto downstream = static_cast<Downstream *>(source->ptr);
+ auto body = downstream->get_response_buf();
+ assert(body);
+ auto upstream = static_cast<Http2Upstream *>(user_data);
+
+ const auto &resp = downstream->response();
+
+ auto nread = std::min(body->rleft(), length);
+
+ auto max_buffer_size = upstream->get_max_buffer_size();
+
+ auto buffer = upstream->get_response_buf();
+
+ if (max_buffer_size <
+ std::min(nread, static_cast<size_t>(256)) + 9 + buffer->rleft()) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, upstream) << "Buffer is almost full. Skip write DATA";
+ }
+ return NGHTTP2_ERR_PAUSE;
+ }
+
+ nread = std::min(nread, max_buffer_size - 9 - buffer->rleft());
+
+ auto body_empty = body->rleft() == nread;
+
+ *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY;
+
+ if (body_empty &&
+ downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+
+ *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+
+ if (!downstream->get_upgraded()) {
+ const auto &trailers = resp.fs.trailers();
+ if (!trailers.empty()) {
+ std::vector<nghttp2_nv> nva;
+ nva.reserve(trailers.size());
+ http2::copy_headers_to_nva_nocopy(nva, trailers, http2::HDOP_STRIP_ALL);
+ if (!nva.empty()) {
+ rv = nghttp2_submit_trailer(session, stream_id, nva.data(),
+ nva.size());
+ if (rv != 0) {
+ if (nghttp2_is_fatal(rv)) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ } else {
+ *data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
+ }
+ }
+ }
+ }
+ }
+
+ if (nread == 0 && ((*data_flags) & NGHTTP2_DATA_FLAG_EOF) == 0) {
+ downstream->disable_upstream_wtimer();
+ return NGHTTP2_ERR_DEFERRED;
+ }
+
+ return nread;
+}
+} // namespace
+
+int Http2Upstream::send_reply(Downstream *downstream, const uint8_t *body,
+ size_t bodylen) {
+ int rv;
+
+ nghttp2_data_provider data_prd, *data_prd_ptr = nullptr;
+
+ if (bodylen) {
+ data_prd.source.ptr = downstream;
+ data_prd.read_callback = downstream_data_read_callback;
+ data_prd_ptr = &data_prd;
+ }
+
+ const auto &resp = downstream->response();
+ auto config = get_config();
+ auto &httpconf = config->http;
+
+ auto &balloc = downstream->get_block_allocator();
+
+ const auto &headers = resp.fs.headers();
+ auto nva = std::vector<nghttp2_nv>();
+ // 2 for :status and server
+ nva.reserve(2 + headers.size() + httpconf.add_response_headers.size());
+
+ auto response_status = http2::stringify_status(balloc, resp.http_status);
+
+ nva.push_back(http2::make_nv_ls_nocopy(":status", response_status));
+
+ for (auto &kv : headers) {
+ if (kv.name.empty() || kv.name[0] == ':') {
+ continue;
+ }
+ switch (kv.token) {
+ case http2::HD_CONNECTION:
+ case http2::HD_KEEP_ALIVE:
+ case http2::HD_PROXY_CONNECTION:
+ case http2::HD_TE:
+ case http2::HD_TRANSFER_ENCODING:
+ case http2::HD_UPGRADE:
+ continue;
+ }
+ nva.push_back(http2::make_nv_nocopy(kv.name, kv.value, kv.no_index));
+ }
+
+ if (!resp.fs.header(http2::HD_SERVER)) {
+ nva.push_back(http2::make_nv_ls_nocopy("server", config->http.server_name));
+ }
+
+ for (auto &p : httpconf.add_response_headers) {
+ nva.push_back(http2::make_nv_nocopy(p.name, p.value));
+ }
+
+ rv = nghttp2_submit_response(session_, downstream->get_stream_id(),
+ nva.data(), nva.size(), data_prd_ptr);
+ if (nghttp2_is_fatal(rv)) {
+ ULOG(FATAL, this) << "nghttp2_submit_response() failed: "
+ << nghttp2_strerror(rv);
+ return -1;
+ }
+
+ auto buf = downstream->get_response_buf();
+
+ buf->append(body, bodylen);
+
+ downstream->set_response_state(DownstreamState::MSG_COMPLETE);
+
+ if (data_prd_ptr) {
+ downstream->reset_upstream_wtimer();
+ }
+
+ return 0;
+}
+
+int Http2Upstream::error_reply(Downstream *downstream,
+ unsigned int status_code) {
+ int rv;
+ auto &resp = downstream->response();
+
+ auto &balloc = downstream->get_block_allocator();
+
+ auto html = http::create_error_html(balloc, status_code);
+ resp.http_status = status_code;
+ auto body = downstream->get_response_buf();
+ body->append(html);
+ downstream->set_response_state(DownstreamState::MSG_COMPLETE);
+
+ nghttp2_data_provider data_prd;
+ data_prd.source.ptr = downstream;
+ data_prd.read_callback = downstream_data_read_callback;
+
+ auto lgconf = log_config();
+ lgconf->update_tstamp(std::chrono::system_clock::now());
+
+ auto response_status = http2::stringify_status(balloc, status_code);
+ auto content_length = util::make_string_ref_uint(balloc, html.size());
+ auto date = make_string_ref(balloc, lgconf->tstamp->time_http);
+
+ auto nva = std::array<nghttp2_nv, 5>{
+ {http2::make_nv_ls_nocopy(":status", response_status),
+ http2::make_nv_ll("content-type", "text/html; charset=UTF-8"),
+ http2::make_nv_ls_nocopy("server", get_config()->http.server_name),
+ http2::make_nv_ls_nocopy("content-length", content_length),
+ http2::make_nv_ls_nocopy("date", date)}};
+
+ rv = nghttp2_submit_response(session_, downstream->get_stream_id(),
+ nva.data(), nva.size(), &data_prd);
+ if (rv < NGHTTP2_ERR_FATAL) {
+ ULOG(FATAL, this) << "nghttp2_submit_response() failed: "
+ << nghttp2_strerror(rv);
+ return -1;
+ }
+
+ downstream->reset_upstream_wtimer();
+
+ return 0;
+}
+
+void Http2Upstream::add_pending_downstream(
+ std::unique_ptr<Downstream> downstream) {
+ downstream_queue_.add_pending(std::move(downstream));
+}
+
+void Http2Upstream::remove_downstream(Downstream *downstream) {
+ if (downstream->accesslog_ready()) {
+ handler_->write_accesslog(downstream);
+ }
+
+ nghttp2_session_set_stream_user_data(session_, downstream->get_stream_id(),
+ nullptr);
+
+ auto next_downstream = downstream_queue_.remove_and_get_blocked(downstream);
+
+ if (next_downstream) {
+ initiate_downstream(next_downstream);
+ }
+
+ if (downstream_queue_.get_downstreams() == nullptr) {
+ // There is no downstream at the moment. Start idle timer now.
+ handler_->repeat_read_timer();
+ }
+}
+
+// WARNING: Never call directly or indirectly nghttp2_session_send or
+// nghttp2_session_recv. These calls may delete downstream.
+int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
+ int rv;
+
+ const auto &req = downstream->request();
+ auto &resp = downstream->response();
+
+ auto &balloc = downstream->get_block_allocator();
+
+ if (LOG_ENABLED(INFO)) {
+ if (downstream->get_non_final_response()) {
+ DLOG(INFO, downstream) << "HTTP non-final response header";
+ } else {
+ DLOG(INFO, downstream) << "HTTP response header completed";
+ }
+ }
+
+ auto config = get_config();
+ auto &httpconf = config->http;
+
+ if (!config->http2_proxy && !httpconf.no_location_rewrite) {
+ downstream->rewrite_location_response_header(req.scheme);
+ }
+
+#ifdef HAVE_MRUBY
+ if (!downstream->get_non_final_response()) {
+ auto dconn = downstream->get_downstream_connection();
+ const auto &group = dconn->get_downstream_addr_group();
+ if (group) {
+ const auto &dmruby_ctx = group->shared_addr->mruby_ctx;
+
+ if (dmruby_ctx->run_on_response_proc(downstream) != 0) {
+ if (error_reply(downstream, 500) != 0) {
+ return -1;
+ }
+ // Returning -1 will signal deletion of dconn.
+ return -1;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return -1;
+ }
+ }
+
+ auto worker = handler_->get_worker();
+ auto mruby_ctx = worker->get_mruby_context();
+
+ if (mruby_ctx->run_on_response_proc(downstream) != 0) {
+ if (error_reply(downstream, 500) != 0) {
+ return -1;
+ }
+ // Returning -1 will signal deletion of dconn.
+ return -1;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return -1;
+ }
+ }
+#endif // HAVE_MRUBY
+
+ auto &http2conf = config->http2;
+
+ // We need some conditions that must be fulfilled to initiate server
+ // push.
+ //
+ // * Server push is disabled for http2 proxy or client proxy, since
+ // incoming headers are mixed origins. We don't know how to
+ // reliably determine the authority yet.
+ //
+ // * We need non-final response or 200 response code for associated
+ // resource. This is too restrictive, we will review this later.
+ //
+ // * We requires GET or POST for associated resource. Probably we
+ // don't want to push for HEAD request. Not sure other methods
+ // are also eligible for push.
+ if (!http2conf.no_server_push &&
+ nghttp2_session_get_remote_settings(session_,
+ NGHTTP2_SETTINGS_ENABLE_PUSH) == 1 &&
+ !config->http2_proxy && (downstream->get_stream_id() % 2) &&
+ resp.fs.header(http2::HD_LINK) &&
+ (downstream->get_non_final_response() || resp.http_status == 200) &&
+ (req.method == HTTP_GET || req.method == HTTP_POST)) {
+
+ if (prepare_push_promise(downstream) != 0) {
+ // Continue to send response even if push was failed.
+ }
+ }
+
+ auto nva = std::vector<nghttp2_nv>();
+ // 6 means :status and possible server, via, x-http2-push, alt-svc,
+ // and set-cookie (for affinity cookie) header field.
+ nva.reserve(resp.fs.headers().size() + 6 +
+ httpconf.add_response_headers.size());
+
+ if (downstream->get_non_final_response()) {
+ auto response_status = http2::stringify_status(balloc, resp.http_status);
+
+ nva.push_back(http2::make_nv_ls_nocopy(":status", response_status));
+
+ http2::copy_headers_to_nva_nocopy(nva, resp.fs.headers(),
+ http2::HDOP_STRIP_ALL);
+
+ if (LOG_ENABLED(INFO)) {
+ log_response_headers(downstream, nva);
+ }
+
+ rv = nghttp2_submit_headers(session_, NGHTTP2_FLAG_NONE,
+ downstream->get_stream_id(), nullptr,
+ nva.data(), nva.size(), nullptr);
+
+ resp.fs.clear_headers();
+
+ if (rv != 0) {
+ ULOG(FATAL, this) << "nghttp2_submit_headers() failed";
+ return -1;
+ }
+
+ return 0;
+ }
+
+ auto striphd_flags = http2::HDOP_STRIP_ALL & ~http2::HDOP_STRIP_VIA;
+ StringRef response_status;
+
+ if (req.connect_proto == ConnectProto::WEBSOCKET && resp.http_status == 101) {
+ response_status = http2::stringify_status(balloc, 200);
+ striphd_flags |= http2::HDOP_STRIP_SEC_WEBSOCKET_ACCEPT;
+ } else {
+ response_status = http2::stringify_status(balloc, resp.http_status);
+ }
+
+ nva.push_back(http2::make_nv_ls_nocopy(":status", response_status));
+
+ http2::copy_headers_to_nva_nocopy(nva, resp.fs.headers(), striphd_flags);
+
+ if (!config->http2_proxy && !httpconf.no_server_rewrite) {
+ nva.push_back(http2::make_nv_ls_nocopy("server", httpconf.server_name));
+ } else {
+ auto server = resp.fs.header(http2::HD_SERVER);
+ if (server) {
+ nva.push_back(http2::make_nv_ls_nocopy("server", (*server).value));
+ }
+ }
+
+ if (!req.regular_connect_method() || !downstream->get_upgraded()) {
+ auto affinity_cookie = downstream->get_affinity_cookie_to_send();
+ if (affinity_cookie) {
+ auto dconn = downstream->get_downstream_connection();
+ assert(dconn);
+ auto &group = dconn->get_downstream_addr_group();
+ auto &shared_addr = group->shared_addr;
+ auto &cookieconf = shared_addr->affinity.cookie;
+ auto secure =
+ http::require_cookie_secure_attribute(cookieconf.secure, req.scheme);
+ auto cookie_str = http::create_affinity_cookie(
+ balloc, cookieconf.name, affinity_cookie, cookieconf.path, secure);
+ nva.push_back(http2::make_nv_ls_nocopy("set-cookie", cookie_str));
+ }
+ }
+
+ if (!resp.fs.header(http2::HD_ALT_SVC)) {
+ // We won't change or alter alt-svc from backend for now
+ if (!httpconf.http2_altsvc_header_value.empty()) {
+ nva.push_back(http2::make_nv_ls_nocopy(
+ "alt-svc", httpconf.http2_altsvc_header_value));
+ }
+ }
+
+ auto via = resp.fs.header(http2::HD_VIA);
+ if (httpconf.no_via) {
+ if (via) {
+ nva.push_back(http2::make_nv_ls_nocopy("via", (*via).value));
+ }
+ } else {
+ // we don't create more than 16 bytes in
+ // http::create_via_header_value.
+ size_t len = 16;
+ if (via) {
+ len += via->value.size() + 2;
+ }
+
+ auto iov = make_byte_ref(balloc, len + 1);
+ auto p = iov.base;
+ if (via) {
+ p = std::copy(std::begin(via->value), std::end(via->value), p);
+ p = util::copy_lit(p, ", ");
+ }
+ p = http::create_via_header_value(p, resp.http_major, resp.http_minor);
+ *p = '\0';
+
+ nva.push_back(http2::make_nv_ls_nocopy("via", StringRef{iov.base, p}));
+ }
+
+ for (auto &p : httpconf.add_response_headers) {
+ nva.push_back(http2::make_nv_nocopy(p.name, p.value));
+ }
+
+ if (downstream->get_stream_id() % 2 == 0) {
+ // This header field is basically for human on client side to
+ // figure out that the resource is pushed.
+ nva.push_back(http2::make_nv_ll("x-http2-push", "1"));
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ log_response_headers(downstream, nva);
+ }
+
+ if (http2conf.upstream.debug.dump.response_header) {
+ http2::dump_nv(http2conf.upstream.debug.dump.response_header, nva.data(),
+ nva.size());
+ }
+
+ auto priority = resp.fs.header(http2::HD_PRIORITY);
+ if (priority) {
+ nghttp2_extpri extpri;
+
+ if (nghttp2_session_get_extpri_stream_priority(
+ session_, &extpri, downstream->get_stream_id()) == 0 &&
+ nghttp2_extpri_parse_priority(&extpri, priority->value.byte(),
+ priority->value.size()) == 0) {
+ rv = nghttp2_session_change_extpri_stream_priority(
+ session_, downstream->get_stream_id(), &extpri,
+ /* ignore_client_signal = */ 1);
+ if (rv != 0) {
+ ULOG(ERROR, this) << "nghttp2_session_change_extpri_stream_priority: "
+ << nghttp2_strerror(rv);
+ }
+ }
+ }
+
+ nghttp2_data_provider data_prd;
+ data_prd.source.ptr = downstream;
+ data_prd.read_callback = downstream_data_read_callback;
+
+ nghttp2_data_provider *data_prdptr;
+
+ if (downstream->expect_response_body() ||
+ downstream->expect_response_trailer()) {
+ data_prdptr = &data_prd;
+ } else {
+ data_prdptr = nullptr;
+ }
+
+ rv = nghttp2_submit_response(session_, downstream->get_stream_id(),
+ nva.data(), nva.size(), data_prdptr);
+ if (rv != 0) {
+ ULOG(FATAL, this) << "nghttp2_submit_response() failed";
+ return -1;
+ }
+
+ if (data_prdptr) {
+ downstream->reset_upstream_wtimer();
+ }
+
+ return 0;
+}
+
+// WARNING: Never call directly or indirectly nghttp2_session_send or
+// nghttp2_session_recv. These calls may delete downstream.
+int Http2Upstream::on_downstream_body(Downstream *downstream,
+ const uint8_t *data, size_t len,
+ bool flush) {
+ auto body = downstream->get_response_buf();
+ body->append(data, len);
+
+ if (flush) {
+ nghttp2_session_resume_data(session_, downstream->get_stream_id());
+
+ downstream->ensure_upstream_wtimer();
+ }
+
+ return 0;
+}
+
+// WARNING: Never call directly or indirectly nghttp2_session_send or
+// nghttp2_session_recv. These calls may delete downstream.
+int Http2Upstream::on_downstream_body_complete(Downstream *downstream) {
+ if (LOG_ENABLED(INFO)) {
+ DLOG(INFO, downstream) << "HTTP response completed";
+ }
+
+ auto &resp = downstream->response();
+
+ if (!downstream->validate_response_recv_body_length()) {
+ rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
+ resp.connection_close = true;
+ return 0;
+ }
+
+ nghttp2_session_resume_data(session_, downstream->get_stream_id());
+ downstream->ensure_upstream_wtimer();
+
+ return 0;
+}
+
+bool Http2Upstream::get_flow_control() const { return flow_control_; }
+
+void Http2Upstream::pause_read(IOCtrlReason reason) {}
+
+int Http2Upstream::resume_read(IOCtrlReason reason, Downstream *downstream,
+ size_t consumed) {
+ if (get_flow_control()) {
+ if (consume(downstream->get_stream_id(), consumed) != 0) {
+ return -1;
+ }
+
+ auto &req = downstream->request();
+
+ req.consume(consumed);
+ }
+
+ handler_->signal_write();
+ return 0;
+}
+
+int Http2Upstream::on_downstream_abort_request(Downstream *downstream,
+ unsigned int status_code) {
+ int rv;
+
+ rv = error_reply(downstream, status_code);
+
+ if (rv != 0) {
+ return -1;
+ }
+
+ handler_->signal_write();
+ return 0;
+}
+
+int Http2Upstream::on_downstream_abort_request_with_https_redirect(
+ Downstream *downstream) {
+ int rv;
+
+ rv = redirect_to_https(downstream);
+ if (rv != 0) {
+ return -1;
+ }
+
+ handler_->signal_write();
+ return 0;
+}
+
+int Http2Upstream::redirect_to_https(Downstream *downstream) {
+ auto &req = downstream->request();
+ if (req.regular_connect_method() || req.scheme != "http") {
+ return error_reply(downstream, 400);
+ }
+
+ auto authority = util::extract_host(req.authority);
+ if (authority.empty()) {
+ return error_reply(downstream, 400);
+ }
+
+ auto &balloc = downstream->get_block_allocator();
+ auto config = get_config();
+ auto &httpconf = config->http;
+
+ StringRef loc;
+ if (httpconf.redirect_https_port == StringRef::from_lit("443")) {
+ loc = concat_string_ref(balloc, StringRef::from_lit("https://"), authority,
+ req.path);
+ } else {
+ loc = concat_string_ref(balloc, StringRef::from_lit("https://"), authority,
+ StringRef::from_lit(":"),
+ httpconf.redirect_https_port, req.path);
+ }
+
+ auto &resp = downstream->response();
+ resp.http_status = 308;
+ resp.fs.add_header_token(StringRef::from_lit("location"), loc, false,
+ http2::HD_LOCATION);
+
+ return send_reply(downstream, nullptr, 0);
+}
+
+int Http2Upstream::consume(int32_t stream_id, size_t len) {
+ int rv;
+
+ auto faddr = handler_->get_upstream_addr();
+
+ if (faddr->alt_mode != UpstreamAltMode::NONE) {
+ return 0;
+ }
+
+ rv = nghttp2_session_consume(session_, stream_id, len);
+
+ if (rv != 0) {
+ ULOG(WARN, this) << "nghttp2_session_consume() returned error: "
+ << nghttp2_strerror(rv);
+ return -1;
+ }
+
+ return 0;
+}
+
+void Http2Upstream::log_response_headers(
+ Downstream *downstream, const std::vector<nghttp2_nv> &nva) const {
+ std::stringstream ss;
+ for (auto &nv : nva) {
+ ss << TTY_HTTP_HD << StringRef{nv.name, nv.namelen} << TTY_RST << ": "
+ << StringRef{nv.value, nv.valuelen} << "\n";
+ }
+ ULOG(INFO, this) << "HTTP response headers. stream_id="
+ << downstream->get_stream_id() << "\n"
+ << ss.str();
+}
+
+int Http2Upstream::on_timeout(Downstream *downstream) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, this) << "Stream timeout stream_id="
+ << downstream->get_stream_id();
+ }
+
+ rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
+ handler_->signal_write();
+
+ return 0;
+}
+
+void Http2Upstream::on_handler_delete() {
+ for (auto d = downstream_queue_.get_downstreams(); d; d = d->dlnext) {
+ if (d->get_dispatch_state() == DispatchState::ACTIVE &&
+ d->accesslog_ready()) {
+ handler_->write_accesslog(d);
+ }
+ }
+}
+
+int Http2Upstream::on_downstream_reset(Downstream *downstream, bool no_retry) {
+ int rv;
+
+ if (downstream->get_dispatch_state() != DispatchState::ACTIVE) {
+ // This is error condition when we failed push_request_headers()
+ // in initiate_downstream(). Otherwise, we have
+ // DispatchState::ACTIVE state, or we did not set
+ // DownstreamConnection.
+ downstream->pop_downstream_connection();
+ handler_->signal_write();
+
+ return 0;
+ }
+
+ if (!downstream->request_submission_ready()) {
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ // We have got all response body already. Send it off.
+ downstream->pop_downstream_connection();
+ return 0;
+ }
+ // pushed stream is handled here
+ rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
+ downstream->pop_downstream_connection();
+
+ handler_->signal_write();
+
+ return 0;
+ }
+
+ downstream->pop_downstream_connection();
+
+ downstream->add_retry();
+
+ std::unique_ptr<DownstreamConnection> dconn;
+
+ rv = 0;
+
+ if (no_retry || downstream->no_more_retry()) {
+ goto fail;
+ }
+
+ // downstream connection is clean; we can retry with new
+ // downstream connection.
+
+ for (;;) {
+ auto dconn = handler_->get_downstream_connection(rv, downstream);
+ if (!dconn) {
+ goto fail;
+ }
+
+ rv = downstream->attach_downstream_connection(std::move(dconn));
+ if (rv == 0) {
+ break;
+ }
+ }
+
+ rv = downstream->push_request_headers();
+ if (rv != 0) {
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ if (rv == SHRPX_ERR_TLS_REQUIRED) {
+ rv = on_downstream_abort_request_with_https_redirect(downstream);
+ } else {
+ rv = on_downstream_abort_request(downstream, 502);
+ }
+ if (rv != 0) {
+ rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
+ }
+ downstream->pop_downstream_connection();
+
+ handler_->signal_write();
+
+ return 0;
+}
+
+int Http2Upstream::prepare_push_promise(Downstream *downstream) {
+ int rv;
+
+ const auto &req = downstream->request();
+ auto &resp = downstream->response();
+
+ auto base = http2::get_pure_path_component(req.path);
+ if (base.empty()) {
+ return 0;
+ }
+
+ auto &balloc = downstream->get_block_allocator();
+
+ for (auto &kv : resp.fs.headers()) {
+ if (kv.token != http2::HD_LINK) {
+ continue;
+ }
+ for (auto &link : http2::parse_link_header(kv.value)) {
+ StringRef scheme, authority, path;
+
+ rv = http2::construct_push_component(balloc, scheme, authority, path,
+ base, link.uri);
+ if (rv != 0) {
+ continue;
+ }
+
+ if (scheme.empty()) {
+ scheme = req.scheme;
+ }
+
+ if (authority.empty()) {
+ authority = req.authority;
+ }
+
+ if (resp.is_resource_pushed(scheme, authority, path)) {
+ continue;
+ }
+
+ rv = submit_push_promise(scheme, authority, path, downstream);
+ if (rv != 0) {
+ return -1;
+ }
+
+ resp.resource_pushed(scheme, authority, path);
+ }
+ }
+ return 0;
+}
+
+int Http2Upstream::submit_push_promise(const StringRef &scheme,
+ const StringRef &authority,
+ const StringRef &path,
+ Downstream *downstream) {
+ const auto &req = downstream->request();
+
+ std::vector<nghttp2_nv> nva;
+ // 4 for :method, :scheme, :path and :authority
+ nva.reserve(4 + req.fs.headers().size());
+
+ // just use "GET" for now
+ nva.push_back(http2::make_nv_ll(":method", "GET"));
+ nva.push_back(http2::make_nv_ls_nocopy(":scheme", scheme));
+ nva.push_back(http2::make_nv_ls_nocopy(":path", path));
+ nva.push_back(http2::make_nv_ls_nocopy(":authority", authority));
+
+ for (auto &kv : req.fs.headers()) {
+ switch (kv.token) {
+ // TODO generate referer
+ case http2::HD__AUTHORITY:
+ case http2::HD__SCHEME:
+ case http2::HD__METHOD:
+ case http2::HD__PATH:
+ continue;
+ case http2::HD_ACCEPT_ENCODING:
+ case http2::HD_ACCEPT_LANGUAGE:
+ case http2::HD_CACHE_CONTROL:
+ case http2::HD_HOST:
+ case http2::HD_USER_AGENT:
+ nva.push_back(http2::make_nv_nocopy(kv.name, kv.value, kv.no_index));
+ break;
+ }
+ }
+
+ auto promised_stream_id = nghttp2_submit_push_promise(
+ session_, NGHTTP2_FLAG_NONE, downstream->get_stream_id(), nva.data(),
+ nva.size(), nullptr);
+
+ if (promised_stream_id < 0) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, this) << "nghttp2_submit_push_promise() failed: "
+ << nghttp2_strerror(promised_stream_id);
+ }
+ if (nghttp2_is_fatal(promised_stream_id)) {
+ return -1;
+ }
+ return 0;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ std::stringstream ss;
+ for (auto &nv : nva) {
+ ss << TTY_HTTP_HD << StringRef{nv.name, nv.namelen} << TTY_RST << ": "
+ << StringRef{nv.value, nv.valuelen} << "\n";
+ }
+ ULOG(INFO, this) << "HTTP push request headers. promised_stream_id="
+ << promised_stream_id << "\n"
+ << ss.str();
+ }
+
+ return 0;
+}
+
+bool Http2Upstream::push_enabled() const {
+ auto config = get_config();
+ return !(config->http2.no_server_push ||
+ nghttp2_session_get_remote_settings(
+ session_, NGHTTP2_SETTINGS_ENABLE_PUSH) == 0 ||
+ config->http2_proxy);
+}
+
+int Http2Upstream::initiate_push(Downstream *downstream, const StringRef &uri) {
+ int rv;
+
+ if (uri.empty() || !push_enabled() ||
+ (downstream->get_stream_id() % 2) == 0) {
+ return 0;
+ }
+
+ const auto &req = downstream->request();
+
+ auto base = http2::get_pure_path_component(req.path);
+ if (base.empty()) {
+ return -1;
+ }
+
+ auto &balloc = downstream->get_block_allocator();
+
+ StringRef scheme, authority, path;
+
+ rv = http2::construct_push_component(balloc, scheme, authority, path, base,
+ uri);
+ if (rv != 0) {
+ return -1;
+ }
+
+ if (scheme.empty()) {
+ scheme = req.scheme;
+ }
+
+ if (authority.empty()) {
+ authority = req.authority;
+ }
+
+ auto &resp = downstream->response();
+
+ if (resp.is_resource_pushed(scheme, authority, path)) {
+ return 0;
+ }
+
+ rv = submit_push_promise(scheme, authority, path, downstream);
+
+ if (rv != 0) {
+ return -1;
+ }
+
+ resp.resource_pushed(scheme, authority, path);
+
+ return 0;
+}
+
+int Http2Upstream::response_riovec(struct iovec *iov, int iovcnt) const {
+ if (iovcnt == 0 || wb_.rleft() == 0) {
+ return 0;
+ }
+
+ return wb_.riovec(iov, iovcnt);
+}
+
+void Http2Upstream::response_drain(size_t n) { wb_.drain(n); }
+
+bool Http2Upstream::response_empty() const { return wb_.rleft() == 0; }
+
+DefaultMemchunks *Http2Upstream::get_response_buf() { return &wb_; }
+
+Downstream *
+Http2Upstream::on_downstream_push_promise(Downstream *downstream,
+ int32_t promised_stream_id) {
+ // promised_stream_id is for backend HTTP/2 session, not for
+ // frontend.
+ auto promised_downstream =
+ std::make_unique<Downstream>(this, handler_->get_mcpool(), 0);
+ auto &promised_req = promised_downstream->request();
+
+ promised_downstream->set_downstream_stream_id(promised_stream_id);
+ // Set associated stream in frontend
+ promised_downstream->set_assoc_stream_id(downstream->get_stream_id());
+
+ promised_downstream->disable_upstream_rtimer();
+
+ promised_req.http_major = 2;
+ promised_req.http_minor = 0;
+
+ promised_req.fs.content_length = 0;
+ promised_req.http2_expect_body = false;
+
+ auto ptr = promised_downstream.get();
+ add_pending_downstream(std::move(promised_downstream));
+ downstream_queue_.mark_active(ptr);
+
+ return ptr;
+}
+
+int Http2Upstream::on_downstream_push_promise_complete(
+ Downstream *downstream, Downstream *promised_downstream) {
+ std::vector<nghttp2_nv> nva;
+
+ const auto &promised_req = promised_downstream->request();
+ const auto &headers = promised_req.fs.headers();
+
+ nva.reserve(headers.size());
+
+ for (auto &kv : headers) {
+ nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index));
+ }
+
+ auto promised_stream_id = nghttp2_submit_push_promise(
+ session_, NGHTTP2_FLAG_NONE, downstream->get_stream_id(), nva.data(),
+ nva.size(), promised_downstream);
+ if (promised_stream_id < 0) {
+ return -1;
+ }
+
+ promised_downstream->set_stream_id(promised_stream_id);
+
+ return 0;
+}
+
+void Http2Upstream::cancel_premature_downstream(
+ Downstream *promised_downstream) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, this) << "Remove premature promised stream "
+ << promised_downstream;
+ }
+ downstream_queue_.remove_and_get_blocked(promised_downstream, false);
+}
+
+size_t Http2Upstream::get_max_buffer_size() const { return max_buffer_size_; }
+
+} // namespace shrpx
diff --git a/src/shrpx_http2_upstream.h b/src/shrpx_http2_upstream.h
new file mode 100644
index 0000000..739c6ff
--- /dev/null
+++ b/src/shrpx_http2_upstream.h
@@ -0,0 +1,150 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_HTTP2_UPSTREAM_H
+#define SHRPX_HTTP2_UPSTREAM_H
+
+#include "shrpx.h"
+
+#include <memory>
+
+#include <ev.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "shrpx_upstream.h"
+#include "shrpx_downstream_queue.h"
+#include "memchunk.h"
+#include "buffer.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+class ClientHandler;
+class HttpsUpstream;
+
+class Http2Upstream : public Upstream {
+public:
+ Http2Upstream(ClientHandler *handler);
+ virtual ~Http2Upstream();
+ virtual int on_read();
+ virtual int on_write();
+ virtual int on_timeout(Downstream *downstream);
+ virtual int on_downstream_abort_request(Downstream *downstream,
+ unsigned int status_code);
+ virtual int
+ on_downstream_abort_request_with_https_redirect(Downstream *downstream);
+ virtual ClientHandler *get_client_handler() const;
+
+ virtual int downstream_read(DownstreamConnection *dconn);
+ virtual int downstream_write(DownstreamConnection *dconn);
+ virtual int downstream_eof(DownstreamConnection *dconn);
+ virtual int downstream_error(DownstreamConnection *dconn, int events);
+
+ void add_pending_downstream(std::unique_ptr<Downstream> downstream);
+ void remove_downstream(Downstream *downstream);
+
+ int rst_stream(Downstream *downstream, uint32_t error_code);
+ int terminate_session(uint32_t error_code);
+ int error_reply(Downstream *downstream, unsigned int status_code);
+
+ virtual void pause_read(IOCtrlReason reason);
+ virtual int resume_read(IOCtrlReason reason, Downstream *downstream,
+ size_t consumed);
+
+ virtual int on_downstream_header_complete(Downstream *downstream);
+ virtual int on_downstream_body(Downstream *downstream, const uint8_t *data,
+ size_t len, bool flush);
+ virtual int on_downstream_body_complete(Downstream *downstream);
+
+ virtual void on_handler_delete();
+ virtual int on_downstream_reset(Downstream *downstream, bool no_retry);
+ virtual int send_reply(Downstream *downstream, const uint8_t *body,
+ size_t bodylen);
+ virtual int initiate_push(Downstream *downstream, const StringRef &uri);
+ virtual int response_riovec(struct iovec *iov, int iovcnt) const;
+ virtual void response_drain(size_t n);
+ virtual bool response_empty() const;
+
+ virtual Downstream *on_downstream_push_promise(Downstream *downstream,
+ int32_t promised_stream_id);
+ virtual int
+ on_downstream_push_promise_complete(Downstream *downstream,
+ Downstream *promised_downstream);
+ virtual bool push_enabled() const;
+ virtual void cancel_premature_downstream(Downstream *promised_downstream);
+
+ bool get_flow_control() const;
+ // Perform HTTP/2 upgrade from |upstream|. On success, this object
+ // takes ownership of the |upstream|. This function returns 0 if it
+ // succeeds, or -1.
+ int upgrade_upstream(HttpsUpstream *upstream);
+ void start_settings_timer();
+ void stop_settings_timer();
+ int consume(int32_t stream_id, size_t len);
+ void log_response_headers(Downstream *downstream,
+ const std::vector<nghttp2_nv> &nva) const;
+ void start_downstream(Downstream *downstream);
+ void initiate_downstream(Downstream *downstream);
+
+ void submit_goaway();
+ void check_shutdown();
+ // Starts graceful shutdown period.
+ void start_graceful_shutdown();
+
+ int prepare_push_promise(Downstream *downstream);
+ int submit_push_promise(const StringRef &scheme, const StringRef &authority,
+ const StringRef &path, Downstream *downstream);
+
+ // Called when new request has started.
+ void on_start_request(const nghttp2_frame *frame);
+ int on_request_headers(Downstream *downstream, const nghttp2_frame *frame);
+
+ DefaultMemchunks *get_response_buf();
+
+ size_t get_max_buffer_size() const;
+
+ int redirect_to_https(Downstream *downstream);
+
+private:
+ DefaultMemchunks wb_;
+ std::unique_ptr<HttpsUpstream> pre_upstream_;
+ DownstreamQueue downstream_queue_;
+ ev_timer settings_timer_;
+ ev_timer shutdown_timer_;
+ ev_prepare prep_;
+ ClientHandler *handler_;
+ nghttp2_session *session_;
+ size_t max_buffer_size_;
+ // The number of requests seen so far.
+ size_t num_requests_;
+ bool flow_control_;
+};
+
+nghttp2_session_callbacks *create_http2_upstream_callbacks();
+
+} // namespace shrpx
+
+#endif // SHRPX_HTTP2_UPSTREAM_H
diff --git a/src/shrpx_http3_upstream.cc b/src/shrpx_http3_upstream.cc
new file mode 100644
index 0000000..f8ae1ce
--- /dev/null
+++ b/src/shrpx_http3_upstream.cc
@@ -0,0 +1,2906 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2021 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_http3_upstream.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <netinet/udp.h>
+
+#include <cstdio>
+
+#include <ngtcp2/ngtcp2_crypto.h>
+
+#include "shrpx_client_handler.h"
+#include "shrpx_downstream.h"
+#include "shrpx_downstream_connection.h"
+#include "shrpx_log.h"
+#include "shrpx_quic.h"
+#include "shrpx_worker.h"
+#include "shrpx_http.h"
+#include "shrpx_connection_handler.h"
+#ifdef HAVE_MRUBY
+# include "shrpx_mruby.h"
+#endif // HAVE_MRUBY
+#include "http3.h"
+#include "util.h"
+#include "ssl_compat.h"
+
+namespace shrpx {
+
+namespace {
+void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto upstream = static_cast<Http3Upstream *>(w->data);
+
+ if (upstream->handle_expiry() != 0 || upstream->on_write() != 0) {
+ goto fail;
+ }
+
+ return;
+
+fail:
+ auto handler = upstream->get_client_handler();
+
+ delete handler;
+}
+} // namespace
+
+namespace {
+void shutdown_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto upstream = static_cast<Http3Upstream *>(w->data);
+ auto handler = upstream->get_client_handler();
+
+ if (upstream->submit_goaway() != 0) {
+ delete handler;
+ }
+}
+} // namespace
+
+namespace {
+void prepare_cb(struct ev_loop *loop, ev_prepare *w, int revent) {
+ auto upstream = static_cast<Http3Upstream *>(w->data);
+ auto handler = upstream->get_client_handler();
+
+ if (upstream->check_shutdown() != 0) {
+ delete handler;
+ }
+}
+} // namespace
+
+namespace {
+size_t downstream_queue_size(Worker *worker) {
+ auto &downstreamconf = *worker->get_downstream_config();
+
+ if (get_config()->http2_proxy) {
+ return downstreamconf.connections_per_host;
+ }
+
+ return downstreamconf.connections_per_frontend;
+}
+} // namespace
+
+namespace {
+ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) {
+ auto conn = static_cast<Connection *>(conn_ref->user_data);
+ auto handler = static_cast<ClientHandler *>(conn->data);
+ auto upstream = static_cast<Http3Upstream *>(handler->get_upstream());
+ return upstream->get_conn();
+}
+} // namespace
+
+Http3Upstream::Http3Upstream(ClientHandler *handler)
+ : handler_{handler},
+ qlog_fd_{-1},
+ hashed_scid_{},
+ conn_{nullptr},
+ httpconn_{nullptr},
+ downstream_queue_{downstream_queue_size(handler->get_worker()),
+ !get_config()->http2_proxy},
+ retry_close_{false},
+ tx_{
+ .data = std::unique_ptr<uint8_t[]>(new uint8_t[64_k]),
+ } {
+ auto conn = handler_->get_connection();
+ conn->conn_ref.get_conn = shrpx::get_conn;
+
+ ev_timer_init(&timer_, timeoutcb, 0., 0.);
+ timer_.data = this;
+
+ ngtcp2_ccerr_default(&last_error_);
+
+ ev_timer_init(&shutdown_timer_, shutdown_timeout_cb, 0., 0.);
+ shutdown_timer_.data = this;
+
+ ev_prepare_init(&prep_, prepare_cb);
+ prep_.data = this;
+ ev_prepare_start(handler_->get_loop(), &prep_);
+}
+
+Http3Upstream::~Http3Upstream() {
+ auto loop = handler_->get_loop();
+
+ ev_prepare_stop(loop, &prep_);
+ ev_timer_stop(loop, &shutdown_timer_);
+ ev_timer_stop(loop, &timer_);
+
+ nghttp3_conn_del(httpconn_);
+
+ ngtcp2_conn_del(conn_);
+
+ if (qlog_fd_ != -1) {
+ close(qlog_fd_);
+ }
+}
+
+namespace {
+void log_printf(void *user_data, const char *fmt, ...) {
+ va_list ap;
+ std::array<char, 4096> buf;
+
+ va_start(ap, fmt);
+ auto nwrite = vsnprintf(buf.data(), buf.size(), fmt, ap);
+ va_end(ap);
+
+ if (static_cast<size_t>(nwrite) >= buf.size()) {
+ nwrite = buf.size() - 1;
+ }
+
+ buf[nwrite++] = '\n';
+
+ while (write(fileno(stderr), buf.data(), nwrite) == -1 && errno == EINTR)
+ ;
+}
+} // namespace
+
+namespace {
+void qlog_write(void *user_data, uint32_t flags, const void *data,
+ size_t datalen) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+
+ upstream->qlog_write(data, datalen, flags & NGTCP2_QLOG_WRITE_FLAG_FIN);
+}
+} // namespace
+
+void Http3Upstream::qlog_write(const void *data, size_t datalen, bool fin) {
+ assert(qlog_fd_ != -1);
+
+ while (write(qlog_fd_, data, datalen) == -1 && errno == EINTR)
+ ;
+
+ if (fin) {
+ close(qlog_fd_);
+ qlog_fd_ = -1;
+ }
+}
+
+namespace {
+void rand(uint8_t *dest, size_t destlen, const ngtcp2_rand_ctx *rand_ctx) {
+ util::random_bytes(dest, dest + destlen,
+ *static_cast<std::mt19937 *>(rand_ctx->native_handle));
+}
+} // namespace
+
+namespace {
+int get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, uint8_t *token,
+ size_t cidlen, void *user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+ auto handler = upstream->get_client_handler();
+ auto worker = handler->get_worker();
+ auto conn_handler = worker->get_connection_handler();
+ auto &qkms = conn_handler->get_quic_keying_materials();
+ auto &qkm = qkms->keying_materials.front();
+
+ if (generate_quic_connection_id(*cid, cidlen, worker->get_cid_prefix(),
+ qkm.id, qkm.cid_encryption_key.data()) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ if (generate_quic_stateless_reset_token(token, *cid, qkm.secret.data(),
+ qkm.secret.size()) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ auto quic_connection_handler = worker->get_quic_connection_handler();
+
+ quic_connection_handler->add_connection_id(*cid, handler);
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int remove_connection_id(ngtcp2_conn *conn, const ngtcp2_cid *cid,
+ void *user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+ auto handler = upstream->get_client_handler();
+ auto worker = handler->get_worker();
+ auto quic_conn_handler = worker->get_quic_connection_handler();
+
+ quic_conn_handler->remove_connection_id(*cid);
+
+ return 0;
+}
+} // namespace
+
+void Http3Upstream::http_begin_request_headers(int64_t stream_id) {
+ auto downstream =
+ std::make_unique<Downstream>(this, handler_->get_mcpool(), stream_id);
+ nghttp3_conn_set_stream_user_data(httpconn_, stream_id, downstream.get());
+
+ downstream->reset_upstream_rtimer();
+
+ handler_->repeat_read_timer();
+
+ auto &req = downstream->request();
+ req.http_major = 3;
+ req.http_minor = 0;
+
+ add_pending_downstream(std::move(downstream));
+}
+
+void Http3Upstream::add_pending_downstream(
+ std::unique_ptr<Downstream> downstream) {
+ downstream_queue_.add_pending(std::move(downstream));
+}
+
+namespace {
+int recv_stream_data(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id,
+ uint64_t offset, const uint8_t *data, size_t datalen,
+ void *user_data, void *stream_user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+
+ if (upstream->recv_stream_data(flags, stream_id, data, datalen) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Http3Upstream::recv_stream_data(uint32_t flags, int64_t stream_id,
+ const uint8_t *data, size_t datalen) {
+ assert(httpconn_);
+
+ auto nconsumed = nghttp3_conn_read_stream(
+ httpconn_, stream_id, data, datalen, flags & NGTCP2_STREAM_DATA_FLAG_FIN);
+ if (nconsumed < 0) {
+ ULOG(ERROR, this) << "nghttp3_conn_read_stream: "
+ << nghttp3_strerror(nconsumed);
+ ngtcp2_ccerr_set_application_error(
+ &last_error_, nghttp3_err_infer_quic_app_error_code(nconsumed), nullptr,
+ 0);
+ return -1;
+ }
+
+ ngtcp2_conn_extend_max_stream_offset(conn_, stream_id, nconsumed);
+ ngtcp2_conn_extend_max_offset(conn_, nconsumed);
+
+ return 0;
+}
+
+namespace {
+int stream_close(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+
+ if (!(flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)) {
+ app_error_code = NGHTTP3_H3_NO_ERROR;
+ }
+
+ if (upstream->stream_close(stream_id, app_error_code) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Http3Upstream::stream_close(int64_t stream_id, uint64_t app_error_code) {
+ if (!httpconn_) {
+ return 0;
+ }
+
+ auto rv = nghttp3_conn_close_stream(httpconn_, stream_id, app_error_code);
+ switch (rv) {
+ case 0:
+ break;
+ case NGHTTP3_ERR_STREAM_NOT_FOUND:
+ if (ngtcp2_is_bidi_stream(stream_id)) {
+ ngtcp2_conn_extend_max_streams_bidi(conn_, 1);
+ }
+ break;
+ default:
+ ULOG(ERROR, this) << "nghttp3_conn_close_stream: " << nghttp3_strerror(rv);
+ ngtcp2_ccerr_set_application_error(
+ &last_error_, nghttp3_err_infer_quic_app_error_code(rv), nullptr, 0);
+ return -1;
+ }
+
+ return 0;
+}
+
+namespace {
+int acked_stream_data_offset(ngtcp2_conn *conn, int64_t stream_id,
+ uint64_t offset, uint64_t datalen, void *user_data,
+ void *stream_user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+
+ if (upstream->acked_stream_data_offset(stream_id, datalen) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Http3Upstream::acked_stream_data_offset(int64_t stream_id,
+ uint64_t datalen) {
+ if (!httpconn_) {
+ return 0;
+ }
+
+ auto rv = nghttp3_conn_add_ack_offset(httpconn_, stream_id, datalen);
+ if (rv != 0) {
+ ULOG(ERROR, this) << "nghttp3_conn_add_ack_offset: "
+ << nghttp3_strerror(rv);
+ return -1;
+ }
+
+ return 0;
+}
+
+namespace {
+int extend_max_stream_data(ngtcp2_conn *conn, int64_t stream_id,
+ uint64_t max_data, void *user_data,
+ void *stream_user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+
+ if (upstream->extend_max_stream_data(stream_id) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Http3Upstream::extend_max_stream_data(int64_t stream_id) {
+ if (!httpconn_) {
+ return 0;
+ }
+
+ auto rv = nghttp3_conn_unblock_stream(httpconn_, stream_id);
+ if (rv != 0) {
+ ULOG(ERROR, this) << "nghttp3_conn_unblock_stream: "
+ << nghttp3_strerror(rv);
+ return -1;
+ }
+
+ return 0;
+}
+
+namespace {
+int extend_max_remote_streams_bidi(ngtcp2_conn *conn, uint64_t max_streams,
+ void *user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+
+ upstream->extend_max_remote_streams_bidi(max_streams);
+
+ return 0;
+}
+} // namespace
+
+void Http3Upstream::extend_max_remote_streams_bidi(uint64_t max_streams) {
+ nghttp3_conn_set_max_client_streams_bidi(httpconn_, max_streams);
+}
+
+namespace {
+int stream_reset(ngtcp2_conn *conn, int64_t stream_id, uint64_t final_size,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+
+ if (upstream->http_shutdown_stream_read(stream_id) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Http3Upstream::http_shutdown_stream_read(int64_t stream_id) {
+ if (!httpconn_) {
+ return 0;
+ }
+
+ auto rv = nghttp3_conn_shutdown_stream_read(httpconn_, stream_id);
+ if (rv != 0) {
+ ULOG(ERROR, this) << "nghttp3_conn_shutdown_stream_read: "
+ << nghttp3_strerror(rv);
+ return -1;
+ }
+
+ return 0;
+}
+
+namespace {
+int stream_stop_sending(ngtcp2_conn *conn, int64_t stream_id,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+
+ if (upstream->http_shutdown_stream_read(stream_id) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int handshake_completed(ngtcp2_conn *conn, void *user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+
+ if (upstream->handshake_completed() != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Http3Upstream::handshake_completed() {
+ handler_->set_alpn_from_conn();
+
+ auto alpn = handler_->get_alpn();
+ if (alpn.empty()) {
+ ULOG(ERROR, this) << "NO ALPN was negotiated";
+ return -1;
+ }
+
+ auto path = ngtcp2_conn_get_path(conn_);
+
+ return send_new_token(&path->remote);
+}
+
+namespace {
+int path_validation(ngtcp2_conn *conn, uint32_t flags, const ngtcp2_path *path,
+ const ngtcp2_path *old_path,
+ ngtcp2_path_validation_result res, void *user_data) {
+ if (res != NGTCP2_PATH_VALIDATION_RESULT_SUCCESS ||
+ !(flags & NGTCP2_PATH_VALIDATION_FLAG_NEW_TOKEN)) {
+ return 0;
+ }
+
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+ if (upstream->send_new_token(&path->remote) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Http3Upstream::send_new_token(const ngtcp2_addr *remote_addr) {
+ std::array<uint8_t, NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN + 1> token;
+ size_t tokenlen;
+
+ auto worker = handler_->get_worker();
+ auto conn_handler = worker->get_connection_handler();
+ auto &qkms = conn_handler->get_quic_keying_materials();
+ auto &qkm = qkms->keying_materials.front();
+
+ if (generate_token(token.data(), tokenlen, remote_addr->addr,
+ remote_addr->addrlen, qkm.secret.data(),
+ qkm.secret.size()) != 0) {
+ return -1;
+ }
+
+ assert(tokenlen == NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN);
+
+ token[tokenlen++] = qkm.id;
+
+ auto rv = ngtcp2_conn_submit_new_token(conn_, token.data(), tokenlen);
+ if (rv != 0) {
+ ULOG(ERROR, this) << "ngtcp2_conn_submit_new_token: "
+ << ngtcp2_strerror(rv);
+ return -1;
+ }
+
+ return 0;
+}
+
+namespace {
+int recv_tx_key(ngtcp2_conn *conn, ngtcp2_encryption_level level,
+ void *user_data) {
+ if (level != NGTCP2_ENCRYPTION_LEVEL_1RTT) {
+ return 0;
+ }
+
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+ if (upstream->setup_httpconn() != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Http3Upstream::init(const UpstreamAddr *faddr, const Address &remote_addr,
+ const Address &local_addr,
+ const ngtcp2_pkt_hd &initial_hd,
+ const ngtcp2_cid *odcid, const uint8_t *token,
+ size_t tokenlen, ngtcp2_token_type token_type) {
+ int rv;
+
+ auto worker = handler_->get_worker();
+ auto conn_handler = worker->get_connection_handler();
+
+ auto callbacks = ngtcp2_callbacks{
+ nullptr, // client_initial
+ ngtcp2_crypto_recv_client_initial_cb,
+ ngtcp2_crypto_recv_crypto_data_cb,
+ shrpx::handshake_completed,
+ nullptr, // recv_version_negotiation
+ ngtcp2_crypto_encrypt_cb,
+ ngtcp2_crypto_decrypt_cb,
+ ngtcp2_crypto_hp_mask_cb,
+ shrpx::recv_stream_data,
+ shrpx::acked_stream_data_offset,
+ nullptr, // stream_open
+ shrpx::stream_close,
+ nullptr, // recv_stateless_reset
+ nullptr, // recv_retry
+ nullptr, // extend_max_local_streams_bidi
+ nullptr, // extend_max_local_streams_uni
+ rand,
+ get_new_connection_id,
+ remove_connection_id,
+ ngtcp2_crypto_update_key_cb,
+ shrpx::path_validation,
+ nullptr, // select_preferred_addr
+ shrpx::stream_reset,
+ shrpx::extend_max_remote_streams_bidi,
+ nullptr, // extend_max_remote_streams_uni
+ shrpx::extend_max_stream_data,
+ nullptr, // dcid_status
+ nullptr, // handshake_confirmed
+ nullptr, // recv_new_token
+ ngtcp2_crypto_delete_crypto_aead_ctx_cb,
+ ngtcp2_crypto_delete_crypto_cipher_ctx_cb,
+ nullptr, // recv_datagram
+ nullptr, // ack_datagram
+ nullptr, // lost_datagram
+ ngtcp2_crypto_get_path_challenge_data_cb,
+ shrpx::stream_stop_sending,
+ nullptr, // version_negotiation
+ nullptr, // recv_rx_key
+ shrpx::recv_tx_key,
+ };
+
+ auto config = get_config();
+ auto &quicconf = config->quic;
+ auto &http3conf = config->http3;
+
+ auto &qkms = conn_handler->get_quic_keying_materials();
+ auto &qkm = qkms->keying_materials.front();
+
+ ngtcp2_cid scid;
+
+ if (generate_quic_connection_id(scid, SHRPX_QUIC_SCIDLEN,
+ worker->get_cid_prefix(), qkm.id,
+ qkm.cid_encryption_key.data()) != 0) {
+ return -1;
+ }
+
+ ngtcp2_settings settings;
+ ngtcp2_settings_default(&settings);
+ if (quicconf.upstream.debug.log) {
+ settings.log_printf = log_printf;
+ }
+
+ if (!quicconf.upstream.qlog.dir.empty()) {
+ auto fd = open_qlog_file(quicconf.upstream.qlog.dir, scid);
+ if (fd != -1) {
+ qlog_fd_ = fd;
+ settings.qlog_write = shrpx::qlog_write;
+ }
+ }
+
+ settings.initial_ts = quic_timestamp();
+ settings.initial_rtt = static_cast<ngtcp2_tstamp>(
+ quicconf.upstream.initial_rtt * NGTCP2_SECONDS);
+ settings.cc_algo = quicconf.upstream.congestion_controller;
+ settings.max_window = http3conf.upstream.max_connection_window_size;
+ settings.max_stream_window = http3conf.upstream.max_window_size;
+ settings.max_tx_udp_payload_size = SHRPX_QUIC_MAX_UDP_PAYLOAD_SIZE;
+ settings.rand_ctx.native_handle = &worker->get_randgen();
+ settings.token = token;
+ settings.tokenlen = tokenlen;
+ settings.token_type = token_type;
+ settings.initial_pkt_num = std::uniform_int_distribution<uint32_t>(
+ 0, std::numeric_limits<int32_t>::max())(worker->get_randgen());
+
+ ngtcp2_transport_params params;
+ ngtcp2_transport_params_default(&params);
+ params.initial_max_streams_bidi = http3conf.upstream.max_concurrent_streams;
+ // The minimum number of unidirectional streams required for HTTP/3.
+ params.initial_max_streams_uni = 3;
+ params.initial_max_data = http3conf.upstream.connection_window_size;
+ params.initial_max_stream_data_bidi_remote = http3conf.upstream.window_size;
+ params.initial_max_stream_data_uni = http3conf.upstream.window_size;
+ params.max_idle_timeout = static_cast<ngtcp2_tstamp>(
+ quicconf.upstream.timeout.idle * NGTCP2_SECONDS);
+
+#ifdef NGHTTP2_OPENSSL_IS_BORINGSSL
+ if (quicconf.upstream.early_data) {
+ ngtcp2_transport_params early_data_params;
+
+ ngtcp2_transport_params_default(&early_data_params);
+
+ early_data_params.initial_max_stream_data_bidi_local =
+ params.initial_max_stream_data_bidi_local;
+ early_data_params.initial_max_stream_data_bidi_remote =
+ params.initial_max_stream_data_bidi_remote;
+ early_data_params.initial_max_stream_data_uni =
+ params.initial_max_stream_data_uni;
+ early_data_params.initial_max_data = params.initial_max_data;
+ early_data_params.initial_max_streams_bidi =
+ params.initial_max_streams_bidi;
+ early_data_params.initial_max_streams_uni = params.initial_max_streams_uni;
+
+ // TODO include HTTP/3 SETTINGS
+
+ std::array<uint8_t, 128> quic_early_data_ctx;
+
+ auto quic_early_data_ctxlen = ngtcp2_transport_params_encode(
+ quic_early_data_ctx.data(), quic_early_data_ctx.size(),
+ &early_data_params);
+
+ assert(quic_early_data_ctxlen > 0);
+ assert(static_cast<size_t>(quic_early_data_ctxlen) <=
+ quic_early_data_ctx.size());
+
+ if (SSL_set_quic_early_data_context(handler_->get_ssl(),
+ quic_early_data_ctx.data(),
+ quic_early_data_ctxlen) != 1) {
+ ULOG(ERROR, this) << "SSL_set_quic_early_data_context failed";
+ return -1;
+ }
+ }
+#endif // NGHTTP2_OPENSSL_IS_BORINGSSL
+
+ if (odcid) {
+ params.original_dcid = *odcid;
+ params.retry_scid = initial_hd.dcid;
+ params.retry_scid_present = 1;
+ } else {
+ params.original_dcid = initial_hd.dcid;
+ }
+
+ params.original_dcid_present = 1;
+
+ rv = generate_quic_stateless_reset_token(
+ params.stateless_reset_token, scid, qkm.secret.data(), qkm.secret.size());
+ if (rv != 0) {
+ ULOG(ERROR, this) << "generate_quic_stateless_reset_token failed";
+ return -1;
+ }
+ params.stateless_reset_token_present = 1;
+
+ auto path = ngtcp2_path{
+ {
+ const_cast<sockaddr *>(&local_addr.su.sa),
+ static_cast<socklen_t>(local_addr.len),
+ },
+ {
+ const_cast<sockaddr *>(&remote_addr.su.sa),
+ static_cast<socklen_t>(remote_addr.len),
+ },
+ const_cast<UpstreamAddr *>(faddr),
+ };
+
+ rv = ngtcp2_conn_server_new(&conn_, &initial_hd.scid, &scid, &path,
+ initial_hd.version, &callbacks, &settings,
+ &params, nullptr, this);
+ if (rv != 0) {
+ ULOG(ERROR, this) << "ngtcp2_conn_server_new: " << ngtcp2_strerror(rv);
+ return -1;
+ }
+
+ ngtcp2_conn_set_tls_native_handle(conn_, handler_->get_ssl());
+
+ auto quic_connection_handler = worker->get_quic_connection_handler();
+
+ if (generate_quic_hashed_connection_id(hashed_scid_, remote_addr, local_addr,
+ initial_hd.dcid) != 0) {
+ return -1;
+ }
+
+ quic_connection_handler->add_connection_id(hashed_scid_, handler_);
+ quic_connection_handler->add_connection_id(scid, handler_);
+
+ return 0;
+}
+
+int Http3Upstream::on_read() { return 0; }
+
+int Http3Upstream::on_write() {
+ int rv;
+
+ if (tx_.send_blocked) {
+ rv = send_blocked_packet();
+ if (rv != 0) {
+ return -1;
+ }
+
+ if (tx_.send_blocked) {
+ return 0;
+ }
+ }
+
+ handler_->get_connection()->wlimit.stopw();
+
+ if (write_streams() != 0) {
+ return -1;
+ }
+
+ if (httpconn_ && nghttp3_conn_is_drained(httpconn_)) {
+ return -1;
+ }
+
+ reset_timer();
+
+ return 0;
+}
+
+int Http3Upstream::write_streams() {
+ std::array<nghttp3_vec, 16> vec;
+ auto max_udp_payload_size = ngtcp2_conn_get_max_tx_udp_payload_size(conn_);
+#ifdef UDP_SEGMENT
+ auto path_max_udp_payload_size =
+ ngtcp2_conn_get_path_max_tx_udp_payload_size(conn_);
+#endif // UDP_SEGMENT
+ auto max_pktcnt = ngtcp2_conn_get_send_quantum(conn_) / max_udp_payload_size;
+ ngtcp2_pkt_info pi, prev_pi;
+ uint8_t *bufpos = tx_.data.get();
+ ngtcp2_path_storage ps, prev_ps;
+ size_t pktcnt = 0;
+ int rv;
+ size_t gso_size = 0;
+ auto ts = quic_timestamp();
+
+ ngtcp2_path_storage_zero(&ps);
+ ngtcp2_path_storage_zero(&prev_ps);
+
+ for (;;) {
+ int64_t stream_id = -1;
+ int fin = 0;
+ nghttp3_ssize sveccnt = 0;
+
+ if (httpconn_ && ngtcp2_conn_get_max_data_left(conn_)) {
+ sveccnt = nghttp3_conn_writev_stream(httpconn_, &stream_id, &fin,
+ vec.data(), vec.size());
+ if (sveccnt < 0) {
+ ULOG(ERROR, this) << "nghttp3_conn_writev_stream: "
+ << nghttp3_strerror(sveccnt);
+ ngtcp2_ccerr_set_application_error(
+ &last_error_, nghttp3_err_infer_quic_app_error_code(sveccnt),
+ nullptr, 0);
+ return handle_error();
+ }
+ }
+
+ ngtcp2_ssize ndatalen;
+ auto v = vec.data();
+ auto vcnt = static_cast<size_t>(sveccnt);
+
+ uint32_t flags = NGTCP2_WRITE_STREAM_FLAG_MORE;
+ if (fin) {
+ flags |= NGTCP2_WRITE_STREAM_FLAG_FIN;
+ }
+
+ auto nwrite = ngtcp2_conn_writev_stream(
+ conn_, &ps.path, &pi, bufpos, max_udp_payload_size, &ndatalen, flags,
+ stream_id, reinterpret_cast<const ngtcp2_vec *>(v), vcnt, ts);
+ if (nwrite < 0) {
+ switch (nwrite) {
+ case NGTCP2_ERR_STREAM_DATA_BLOCKED:
+ assert(ndatalen == -1);
+ nghttp3_conn_block_stream(httpconn_, stream_id);
+ continue;
+ case NGTCP2_ERR_STREAM_SHUT_WR:
+ assert(ndatalen == -1);
+ nghttp3_conn_shutdown_stream_write(httpconn_, stream_id);
+ continue;
+ case NGTCP2_ERR_WRITE_MORE:
+ assert(ndatalen >= 0);
+ rv = nghttp3_conn_add_write_offset(httpconn_, stream_id, ndatalen);
+ if (rv != 0) {
+ ULOG(ERROR, this)
+ << "nghttp3_conn_add_write_offset: " << nghttp3_strerror(rv);
+ ngtcp2_ccerr_set_application_error(
+ &last_error_, nghttp3_err_infer_quic_app_error_code(rv), nullptr,
+ 0);
+ return handle_error();
+ }
+ continue;
+ }
+
+ assert(ndatalen == -1);
+
+ ULOG(ERROR, this) << "ngtcp2_conn_writev_stream: "
+ << ngtcp2_strerror(nwrite);
+
+ ngtcp2_ccerr_set_liberr(&last_error_, nwrite, nullptr, 0);
+
+ return handle_error();
+ } else if (ndatalen >= 0) {
+ rv = nghttp3_conn_add_write_offset(httpconn_, stream_id, ndatalen);
+ if (rv != 0) {
+ ULOG(ERROR, this) << "nghttp3_conn_add_write_offset: "
+ << nghttp3_strerror(rv);
+ ngtcp2_ccerr_set_application_error(
+ &last_error_, nghttp3_err_infer_quic_app_error_code(rv), nullptr,
+ 0);
+ return handle_error();
+ }
+ }
+
+ if (nwrite == 0) {
+ if (bufpos - tx_.data.get()) {
+ auto faddr = static_cast<UpstreamAddr *>(prev_ps.path.user_data);
+ auto data = tx_.data.get();
+ auto datalen = bufpos - data;
+
+ rv = send_packet(faddr, prev_ps.path.remote.addr,
+ prev_ps.path.remote.addrlen, prev_ps.path.local.addr,
+ prev_ps.path.local.addrlen, prev_pi, data, datalen,
+ gso_size);
+ if (rv == SHRPX_ERR_SEND_BLOCKED) {
+ on_send_blocked(faddr, prev_ps.path.remote, prev_ps.path.local,
+ prev_pi, data, datalen, gso_size);
+
+ signal_write_upstream_addr(faddr);
+ }
+ }
+
+ ngtcp2_conn_update_pkt_tx_time(conn_, ts);
+
+ return 0;
+ }
+
+ bufpos += nwrite;
+
+#ifdef UDP_SEGMENT
+ if (pktcnt == 0) {
+ ngtcp2_path_copy(&prev_ps.path, &ps.path);
+ prev_pi = pi;
+ gso_size = nwrite;
+ } else if (!ngtcp2_path_eq(&prev_ps.path, &ps.path) ||
+ prev_pi.ecn != pi.ecn ||
+ static_cast<size_t>(nwrite) > gso_size ||
+ (gso_size > path_max_udp_payload_size &&
+ static_cast<size_t>(nwrite) != gso_size)) {
+ auto faddr = static_cast<UpstreamAddr *>(prev_ps.path.user_data);
+ auto data = tx_.data.get();
+ auto datalen = bufpos - data - nwrite;
+
+ rv = send_packet(faddr, prev_ps.path.remote.addr,
+ prev_ps.path.remote.addrlen, prev_ps.path.local.addr,
+ prev_ps.path.local.addrlen, prev_pi, data, datalen,
+ gso_size);
+ switch (rv) {
+ case SHRPX_ERR_SEND_BLOCKED:
+ on_send_blocked(faddr, prev_ps.path.remote, prev_ps.path.local, prev_pi,
+ data, datalen, gso_size);
+
+ on_send_blocked(static_cast<UpstreamAddr *>(ps.path.user_data),
+ ps.path.remote, ps.path.local, pi, bufpos - nwrite,
+ nwrite, 0);
+
+ signal_write_upstream_addr(faddr);
+
+ break;
+ default: {
+ auto faddr = static_cast<UpstreamAddr *>(ps.path.user_data);
+ auto data = bufpos - nwrite;
+
+ rv = send_packet(faddr, ps.path.remote.addr, ps.path.remote.addrlen,
+ ps.path.local.addr, ps.path.local.addrlen, pi, data,
+ nwrite, 0);
+ if (rv == SHRPX_ERR_SEND_BLOCKED) {
+ on_send_blocked(faddr, ps.path.remote, ps.path.local, pi, data,
+ nwrite, 0);
+
+ signal_write_upstream_addr(faddr);
+ }
+ }
+ }
+
+ ngtcp2_conn_update_pkt_tx_time(conn_, ts);
+
+ return 0;
+ }
+
+ if (++pktcnt == max_pktcnt || static_cast<size_t>(nwrite) < gso_size) {
+ auto faddr = static_cast<UpstreamAddr *>(ps.path.user_data);
+ auto data = tx_.data.get();
+ auto datalen = bufpos - data;
+
+ rv = send_packet(faddr, ps.path.remote.addr, ps.path.remote.addrlen,
+ ps.path.local.addr, ps.path.local.addrlen, pi, data,
+ datalen, gso_size);
+ if (rv == SHRPX_ERR_SEND_BLOCKED) {
+ on_send_blocked(faddr, ps.path.remote, ps.path.local, pi, data, datalen,
+ gso_size);
+
+ signal_write_upstream_addr(faddr);
+ }
+
+ ngtcp2_conn_update_pkt_tx_time(conn_, ts);
+
+ return 0;
+ }
+#else // !UDP_SEGMENT
+ auto faddr = static_cast<UpstreamAddr *>(ps.path.user_data);
+ auto data = tx_.data.get();
+ auto datalen = bufpos - data;
+
+ rv = send_packet(faddr, ps.path.remote.addr, ps.path.remote.addrlen,
+ ps.path.local.addr, ps.path.local.addrlen, pi, data,
+ datalen, 0);
+ if (rv == SHRPX_ERR_SEND_BLOCKED) {
+ on_send_blocked(faddr, ps.path.remote, ps.path.local, pi, data, datalen,
+ 0);
+
+ ngtcp2_conn_update_pkt_tx_time(conn_, ts);
+
+ signal_write_upstream_addr(faddr);
+
+ return 0;
+ }
+
+ if (++pktcnt == max_pktcnt) {
+ ngtcp2_conn_update_pkt_tx_time(conn_, ts);
+
+ return 0;
+ }
+
+ bufpos = tx_.data.get();
+#endif // !UDP_SEGMENT
+ }
+
+ return 0;
+}
+
+int Http3Upstream::on_timeout(Downstream *downstream) { return 0; }
+
+int Http3Upstream::on_downstream_abort_request(Downstream *downstream,
+ unsigned int status_code) {
+ int rv;
+
+ rv = error_reply(downstream, status_code);
+
+ if (rv != 0) {
+ return -1;
+ }
+
+ handler_->signal_write();
+
+ return 0;
+}
+
+int Http3Upstream::on_downstream_abort_request_with_https_redirect(
+ Downstream *downstream) {
+ assert(0);
+ abort();
+}
+
+namespace {
+uint64_t
+infer_upstream_shutdown_stream_error_code(uint32_t downstream_error_code) {
+ // NGHTTP2_REFUSED_STREAM is important because it tells upstream
+ // client to retry.
+ switch (downstream_error_code) {
+ case NGHTTP2_NO_ERROR:
+ return NGHTTP3_H3_NO_ERROR;
+ case NGHTTP2_REFUSED_STREAM:
+ return NGHTTP3_H3_REQUEST_REJECTED;
+ default:
+ return NGHTTP3_H3_INTERNAL_ERROR;
+ }
+}
+} // namespace
+
+int Http3Upstream::downstream_read(DownstreamConnection *dconn) {
+ auto downstream = dconn->get_downstream();
+
+ if (downstream->get_response_state() == DownstreamState::MSG_RESET) {
+ // The downstream stream was reset (canceled). In this case,
+ // RST_STREAM to the upstream and delete downstream connection
+ // here. Deleting downstream will be taken place at
+ // on_stream_close_callback.
+ shutdown_stream(downstream,
+ infer_upstream_shutdown_stream_error_code(
+ downstream->get_response_rst_stream_error_code()));
+ downstream->pop_downstream_connection();
+ // dconn was deleted
+ dconn = nullptr;
+ } else if (downstream->get_response_state() ==
+ DownstreamState::MSG_BAD_HEADER) {
+ if (error_reply(downstream, 502) != 0) {
+ return -1;
+ }
+ downstream->pop_downstream_connection();
+ // dconn was deleted
+ dconn = nullptr;
+ } else {
+ auto rv = downstream->on_read();
+ if (rv == SHRPX_ERR_EOF) {
+ if (downstream->get_request_header_sent()) {
+ return downstream_eof(dconn);
+ }
+ return SHRPX_ERR_RETRY;
+ }
+ if (rv == SHRPX_ERR_DCONN_CANCELED) {
+ downstream->pop_downstream_connection();
+ handler_->signal_write();
+ return 0;
+ }
+ if (rv != 0) {
+ if (rv != SHRPX_ERR_NETWORK) {
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, dconn) << "HTTP parser failure";
+ }
+ }
+ return downstream_error(dconn, Downstream::EVENT_ERROR);
+ }
+
+ if (downstream->can_detach_downstream_connection()) {
+ // Keep-alive
+ downstream->detach_downstream_connection();
+ }
+ }
+
+ handler_->signal_write();
+
+ // At this point, downstream may be deleted.
+
+ return 0;
+}
+
+int Http3Upstream::downstream_write(DownstreamConnection *dconn) {
+ int rv;
+ rv = dconn->on_write();
+ if (rv == SHRPX_ERR_NETWORK) {
+ return downstream_error(dconn, Downstream::EVENT_ERROR);
+ }
+ if (rv != 0) {
+ return rv;
+ }
+ return 0;
+}
+
+int Http3Upstream::downstream_eof(DownstreamConnection *dconn) {
+ auto downstream = dconn->get_downstream();
+
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, dconn) << "EOF. stream_id=" << downstream->get_stream_id();
+ }
+
+ // Delete downstream connection. If we don't delete it here, it will
+ // be pooled in on_stream_close_callback.
+ downstream->pop_downstream_connection();
+ // dconn was deleted
+ dconn = nullptr;
+ // downstream will be deleted in on_stream_close_callback.
+ if (downstream->get_response_state() == DownstreamState::HEADER_COMPLETE) {
+ // Server may indicate the end of the request by EOF
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, this) << "Downstream body was ended by EOF";
+ }
+ downstream->set_response_state(DownstreamState::MSG_COMPLETE);
+
+ // For tunneled connection, MSG_COMPLETE signals
+ // downstream_read_data_callback to send RST_STREAM after pending
+ // response body is sent. This is needed to ensure that RST_STREAM
+ // is sent after all pending data are sent.
+ if (on_downstream_body_complete(downstream) != 0) {
+ return -1;
+ }
+ } else if (downstream->get_response_state() !=
+ DownstreamState::MSG_COMPLETE) {
+ // If stream was not closed, then we set MSG_COMPLETE and let
+ // on_stream_close_callback delete downstream.
+ if (error_reply(downstream, 502) != 0) {
+ return -1;
+ }
+ }
+ handler_->signal_write();
+ // At this point, downstream may be deleted.
+ return 0;
+}
+
+int Http3Upstream::downstream_error(DownstreamConnection *dconn, int events) {
+ auto downstream = dconn->get_downstream();
+
+ if (LOG_ENABLED(INFO)) {
+ if (events & Downstream::EVENT_ERROR) {
+ DCLOG(INFO, dconn) << "Downstream network/general error";
+ } else {
+ DCLOG(INFO, dconn) << "Timeout";
+ }
+ if (downstream->get_upgraded()) {
+ DCLOG(INFO, dconn) << "Note: this is tunnel connection";
+ }
+ }
+
+ // Delete downstream connection. If we don't delete it here, it will
+ // be pooled in on_stream_close_callback.
+ downstream->pop_downstream_connection();
+ // dconn was deleted
+ dconn = nullptr;
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ // For SSL tunneling, we issue RST_STREAM. For other types of
+ // stream, we don't have to do anything since response was
+ // complete.
+ if (downstream->get_upgraded()) {
+ shutdown_stream(downstream, NGHTTP3_H3_NO_ERROR);
+ }
+ } else {
+ if (downstream->get_response_state() == DownstreamState::HEADER_COMPLETE) {
+ if (downstream->get_upgraded()) {
+ if (on_downstream_body_complete(downstream) != 0) {
+ return -1;
+ }
+ } else {
+ shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR);
+ }
+ } else {
+ unsigned int status;
+ if (events & Downstream::EVENT_TIMEOUT) {
+ if (downstream->get_request_header_sent()) {
+ status = 504;
+ } else {
+ status = 408;
+ }
+ } else {
+ status = 502;
+ }
+ if (error_reply(downstream, status) != 0) {
+ return -1;
+ }
+ }
+ downstream->set_response_state(DownstreamState::MSG_COMPLETE);
+ }
+ handler_->signal_write();
+ // At this point, downstream may be deleted.
+ return 0;
+}
+
+ClientHandler *Http3Upstream::get_client_handler() const { return handler_; }
+
+namespace {
+nghttp3_ssize downstream_read_data_callback(nghttp3_conn *conn,
+ int64_t stream_id, nghttp3_vec *vec,
+ size_t veccnt, uint32_t *pflags,
+ void *conn_user_data,
+ void *stream_user_data) {
+ auto upstream = static_cast<Http3Upstream *>(conn_user_data);
+ auto downstream = static_cast<Downstream *>(stream_user_data);
+
+ assert(downstream);
+
+ auto body = downstream->get_response_buf();
+
+ assert(body);
+
+ if (downstream->get_response_state() != DownstreamState::MSG_COMPLETE &&
+ body->rleft_mark() == 0) {
+ downstream->disable_upstream_wtimer();
+ return NGHTTP3_ERR_WOULDBLOCK;
+ }
+
+ downstream->reset_upstream_wtimer();
+
+ veccnt = body->riovec_mark(reinterpret_cast<struct iovec *>(vec), veccnt);
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE &&
+ body->rleft_mark() == 0) {
+ *pflags |= NGHTTP3_DATA_FLAG_EOF;
+ }
+
+ assert((*pflags & NGHTTP3_DATA_FLAG_EOF) || veccnt);
+
+ downstream->response_sent_body_length += nghttp3_vec_len(vec, veccnt);
+
+ if ((*pflags & NGHTTP3_DATA_FLAG_EOF) &&
+ upstream->shutdown_stream_read(stream_id, NGHTTP3_H3_NO_ERROR) != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+
+ return veccnt;
+}
+} // namespace
+
+int Http3Upstream::on_downstream_header_complete(Downstream *downstream) {
+ int rv;
+
+ const auto &req = downstream->request();
+ auto &resp = downstream->response();
+
+ auto &balloc = downstream->get_block_allocator();
+
+ if (LOG_ENABLED(INFO)) {
+ if (downstream->get_non_final_response()) {
+ DLOG(INFO, downstream) << "HTTP non-final response header";
+ } else {
+ DLOG(INFO, downstream) << "HTTP response header completed";
+ }
+ }
+
+ auto config = get_config();
+ auto &httpconf = config->http;
+
+ if (!config->http2_proxy && !httpconf.no_location_rewrite) {
+ downstream->rewrite_location_response_header(req.scheme);
+ }
+
+#ifdef HAVE_MRUBY
+ if (!downstream->get_non_final_response()) {
+ auto dconn = downstream->get_downstream_connection();
+ const auto &group = dconn->get_downstream_addr_group();
+ if (group) {
+ const auto &dmruby_ctx = group->shared_addr->mruby_ctx;
+
+ if (dmruby_ctx->run_on_response_proc(downstream) != 0) {
+ if (error_reply(downstream, 500) != 0) {
+ return -1;
+ }
+ // Returning -1 will signal deletion of dconn.
+ return -1;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return -1;
+ }
+ }
+
+ auto worker = handler_->get_worker();
+ auto mruby_ctx = worker->get_mruby_context();
+
+ if (mruby_ctx->run_on_response_proc(downstream) != 0) {
+ if (error_reply(downstream, 500) != 0) {
+ return -1;
+ }
+ // Returning -1 will signal deletion of dconn.
+ return -1;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return -1;
+ }
+ }
+#endif // HAVE_MRUBY
+
+ auto nva = std::vector<nghttp3_nv>();
+ // 4 means :status and possible server, via, and set-cookie (for
+ // affinity cookie) header field.
+ nva.reserve(resp.fs.headers().size() + 4 +
+ httpconf.add_response_headers.size());
+
+ if (downstream->get_non_final_response()) {
+ auto response_status = http2::stringify_status(balloc, resp.http_status);
+
+ nva.push_back(http3::make_nv_ls_nocopy(":status", response_status));
+
+ http3::copy_headers_to_nva_nocopy(nva, resp.fs.headers(),
+ http2::HDOP_STRIP_ALL);
+
+ if (LOG_ENABLED(INFO)) {
+ log_response_headers(downstream, nva);
+ }
+
+ rv = nghttp3_conn_submit_info(httpconn_, downstream->get_stream_id(),
+ nva.data(), nva.size());
+
+ resp.fs.clear_headers();
+
+ if (rv != 0) {
+ ULOG(FATAL, this) << "nghttp3_conn_submit_info() failed";
+ return -1;
+ }
+
+ return 0;
+ }
+
+ auto striphd_flags = http2::HDOP_STRIP_ALL & ~http2::HDOP_STRIP_VIA;
+ StringRef response_status;
+
+ if (req.connect_proto == ConnectProto::WEBSOCKET && resp.http_status == 101) {
+ response_status = http2::stringify_status(balloc, 200);
+ striphd_flags |= http2::HDOP_STRIP_SEC_WEBSOCKET_ACCEPT;
+ } else {
+ response_status = http2::stringify_status(balloc, resp.http_status);
+ }
+
+ nva.push_back(http3::make_nv_ls_nocopy(":status", response_status));
+
+ http3::copy_headers_to_nva_nocopy(nva, resp.fs.headers(), striphd_flags);
+
+ if (!config->http2_proxy && !httpconf.no_server_rewrite) {
+ nva.push_back(http3::make_nv_ls_nocopy("server", httpconf.server_name));
+ } else {
+ auto server = resp.fs.header(http2::HD_SERVER);
+ if (server) {
+ nva.push_back(http3::make_nv_ls_nocopy("server", (*server).value));
+ }
+ }
+
+ if (!req.regular_connect_method() || !downstream->get_upgraded()) {
+ auto affinity_cookie = downstream->get_affinity_cookie_to_send();
+ if (affinity_cookie) {
+ auto dconn = downstream->get_downstream_connection();
+ assert(dconn);
+ auto &group = dconn->get_downstream_addr_group();
+ auto &shared_addr = group->shared_addr;
+ auto &cookieconf = shared_addr->affinity.cookie;
+ auto secure =
+ http::require_cookie_secure_attribute(cookieconf.secure, req.scheme);
+ auto cookie_str = http::create_affinity_cookie(
+ balloc, cookieconf.name, affinity_cookie, cookieconf.path, secure);
+ nva.push_back(http3::make_nv_ls_nocopy("set-cookie", cookie_str));
+ }
+ }
+
+ auto via = resp.fs.header(http2::HD_VIA);
+ if (httpconf.no_via) {
+ if (via) {
+ nva.push_back(http3::make_nv_ls_nocopy("via", (*via).value));
+ }
+ } else {
+ // we don't create more than 16 bytes in
+ // http::create_via_header_value.
+ size_t len = 16;
+ if (via) {
+ len += via->value.size() + 2;
+ }
+
+ auto iov = make_byte_ref(balloc, len + 1);
+ auto p = iov.base;
+ if (via) {
+ p = std::copy(std::begin(via->value), std::end(via->value), p);
+ p = util::copy_lit(p, ", ");
+ }
+ p = http::create_via_header_value(p, resp.http_major, resp.http_minor);
+ *p = '\0';
+
+ nva.push_back(http3::make_nv_ls_nocopy("via", StringRef{iov.base, p}));
+ }
+
+ for (auto &p : httpconf.add_response_headers) {
+ nva.push_back(http3::make_nv_nocopy(p.name, p.value));
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ log_response_headers(downstream, nva);
+ }
+
+ auto priority = resp.fs.header(http2::HD_PRIORITY);
+ if (priority) {
+ nghttp3_pri pri;
+
+ if (nghttp3_conn_get_stream_priority(httpconn_, &pri,
+ downstream->get_stream_id()) == 0 &&
+ nghttp3_pri_parse_priority(&pri, priority->value.byte(),
+ priority->value.size()) == 0) {
+ rv = nghttp3_conn_set_server_stream_priority(
+ httpconn_, downstream->get_stream_id(), &pri);
+ if (rv != 0) {
+ ULOG(ERROR, this) << "nghttp3_conn_set_server_stream_priority: "
+ << nghttp3_strerror(rv);
+ }
+ }
+ }
+
+ nghttp3_data_reader data_read;
+ data_read.read_data = downstream_read_data_callback;
+
+ nghttp3_data_reader *data_readptr;
+
+ if (downstream->expect_response_body() ||
+ downstream->expect_response_trailer()) {
+ data_readptr = &data_read;
+ } else {
+ data_readptr = nullptr;
+ }
+
+ rv = nghttp3_conn_submit_response(httpconn_, downstream->get_stream_id(),
+ nva.data(), nva.size(), data_readptr);
+ if (rv != 0) {
+ ULOG(FATAL, this) << "nghttp3_conn_submit_response() failed";
+ return -1;
+ }
+
+ if (data_readptr) {
+ downstream->reset_upstream_wtimer();
+ } else if (shutdown_stream_read(downstream->get_stream_id(),
+ NGHTTP3_H3_NO_ERROR) != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int Http3Upstream::on_downstream_body(Downstream *downstream,
+ const uint8_t *data, size_t len,
+ bool flush) {
+ auto body = downstream->get_response_buf();
+ body->append(data, len);
+
+ if (flush) {
+ nghttp3_conn_resume_stream(httpconn_, downstream->get_stream_id());
+
+ downstream->ensure_upstream_wtimer();
+ }
+
+ return 0;
+}
+
+int Http3Upstream::on_downstream_body_complete(Downstream *downstream) {
+ if (LOG_ENABLED(INFO)) {
+ DLOG(INFO, downstream) << "HTTP response completed";
+ }
+
+ auto &resp = downstream->response();
+
+ if (!downstream->validate_response_recv_body_length()) {
+ shutdown_stream(downstream, NGHTTP3_H3_GENERAL_PROTOCOL_ERROR);
+ resp.connection_close = true;
+ return 0;
+ }
+
+ if (!downstream->get_upgraded()) {
+ const auto &trailers = resp.fs.trailers();
+ if (!trailers.empty()) {
+ std::vector<nghttp3_nv> nva;
+ nva.reserve(trailers.size());
+ http3::copy_headers_to_nva_nocopy(nva, trailers, http2::HDOP_STRIP_ALL);
+ if (!nva.empty()) {
+ auto rv = nghttp3_conn_submit_trailers(
+ httpconn_, downstream->get_stream_id(), nva.data(), nva.size());
+ if (rv != 0) {
+ ULOG(FATAL, this) << "nghttp3_conn_submit_trailers() failed: "
+ << nghttp3_strerror(rv);
+ return -1;
+ }
+ }
+ }
+ }
+
+ nghttp3_conn_resume_stream(httpconn_, downstream->get_stream_id());
+ downstream->ensure_upstream_wtimer();
+
+ return 0;
+}
+
+void Http3Upstream::on_handler_delete() {
+ for (auto d = downstream_queue_.get_downstreams(); d; d = d->dlnext) {
+ if (d->get_dispatch_state() == DispatchState::ACTIVE &&
+ d->accesslog_ready()) {
+ handler_->write_accesslog(d);
+ }
+ }
+
+ auto worker = handler_->get_worker();
+ auto quic_conn_handler = worker->get_quic_connection_handler();
+
+ std::vector<ngtcp2_cid> scids(ngtcp2_conn_get_scid(conn_, nullptr) + 1);
+ ngtcp2_conn_get_scid(conn_, scids.data());
+ scids.back() = hashed_scid_;
+
+ for (auto &cid : scids) {
+ quic_conn_handler->remove_connection_id(cid);
+ }
+
+ if (retry_close_ || last_error_.type == NGTCP2_CCERR_TYPE_IDLE_CLOSE) {
+ return;
+ }
+
+ // If this is not idle close, send CONNECTION_CLOSE.
+ if (!ngtcp2_conn_in_closing_period(conn_) &&
+ !ngtcp2_conn_in_draining_period(conn_)) {
+ ngtcp2_path_storage ps;
+ ngtcp2_pkt_info pi;
+ conn_close_.resize(SHRPX_QUIC_CONN_CLOSE_PKTLEN);
+
+ ngtcp2_path_storage_zero(&ps);
+
+ ngtcp2_ccerr ccerr;
+ ngtcp2_ccerr_default(&ccerr);
+
+ if (worker->get_graceful_shutdown() &&
+ !ngtcp2_conn_get_handshake_completed(conn_)) {
+ ccerr.error_code = NGTCP2_CONNECTION_REFUSED;
+ }
+
+ auto nwrite = ngtcp2_conn_write_connection_close(
+ conn_, &ps.path, &pi, conn_close_.data(), conn_close_.size(), &ccerr,
+ quic_timestamp());
+ if (nwrite < 0) {
+ if (nwrite != NGTCP2_ERR_INVALID_STATE) {
+ ULOG(ERROR, this) << "ngtcp2_conn_write_connection_close: "
+ << ngtcp2_strerror(nwrite);
+ }
+
+ return;
+ }
+
+ conn_close_.resize(nwrite);
+
+ send_packet(static_cast<UpstreamAddr *>(ps.path.user_data),
+ ps.path.remote.addr, ps.path.remote.addrlen, ps.path.local.addr,
+ ps.path.local.addrlen, pi, conn_close_.data(), nwrite, 0);
+ }
+
+ auto d =
+ static_cast<ev_tstamp>(ngtcp2_conn_get_pto(conn_) * 3) / NGTCP2_SECONDS;
+
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, this) << "Enter close-wait period " << d << "s with "
+ << conn_close_.size() << " bytes sentinel packet";
+ }
+
+ auto cw = std::make_unique<CloseWait>(worker, std::move(scids),
+ std::move(conn_close_), d);
+
+ quic_conn_handler->add_close_wait(cw.release());
+}
+
+int Http3Upstream::on_downstream_reset(Downstream *downstream, bool no_retry) {
+ int rv;
+
+ if (downstream->get_dispatch_state() != DispatchState::ACTIVE) {
+ // This is error condition when we failed push_request_headers()
+ // in initiate_downstream(). Otherwise, we have
+ // DispatchState::ACTIVE state, or we did not set
+ // DownstreamConnection.
+ downstream->pop_downstream_connection();
+ handler_->signal_write();
+
+ return 0;
+ }
+
+ if (!downstream->request_submission_ready()) {
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ // We have got all response body already. Send it off.
+ downstream->pop_downstream_connection();
+ return 0;
+ }
+ // pushed stream is handled here
+ shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR);
+ downstream->pop_downstream_connection();
+
+ handler_->signal_write();
+
+ return 0;
+ }
+
+ downstream->pop_downstream_connection();
+
+ downstream->add_retry();
+
+ std::unique_ptr<DownstreamConnection> dconn;
+
+ rv = 0;
+
+ if (no_retry || downstream->no_more_retry()) {
+ goto fail;
+ }
+
+ // downstream connection is clean; we can retry with new
+ // downstream connection.
+
+ for (;;) {
+ auto dconn = handler_->get_downstream_connection(rv, downstream);
+ if (!dconn) {
+ goto fail;
+ }
+
+ rv = downstream->attach_downstream_connection(std::move(dconn));
+ if (rv == 0) {
+ break;
+ }
+ }
+
+ rv = downstream->push_request_headers();
+ if (rv != 0) {
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ if (rv == SHRPX_ERR_TLS_REQUIRED) {
+ assert(0);
+ abort();
+ }
+
+ rv = on_downstream_abort_request(downstream, 502);
+ if (rv != 0) {
+ shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR);
+ }
+ downstream->pop_downstream_connection();
+
+ handler_->signal_write();
+
+ return 0;
+}
+
+void Http3Upstream::pause_read(IOCtrlReason reason) {}
+
+int Http3Upstream::resume_read(IOCtrlReason reason, Downstream *downstream,
+ size_t consumed) {
+ consume(downstream->get_stream_id(), consumed);
+
+ auto &req = downstream->request();
+
+ req.consume(consumed);
+
+ handler_->signal_write();
+
+ return 0;
+}
+
+int Http3Upstream::send_reply(Downstream *downstream, const uint8_t *body,
+ size_t bodylen) {
+ int rv;
+
+ nghttp3_data_reader data_read, *data_read_ptr = nullptr;
+
+ if (bodylen) {
+ data_read.read_data = downstream_read_data_callback;
+ data_read_ptr = &data_read;
+ }
+
+ const auto &resp = downstream->response();
+ auto config = get_config();
+ auto &httpconf = config->http;
+
+ auto &balloc = downstream->get_block_allocator();
+
+ const auto &headers = resp.fs.headers();
+ auto nva = std::vector<nghttp3_nv>();
+ // 2 for :status and server
+ nva.reserve(2 + headers.size() + httpconf.add_response_headers.size());
+
+ auto response_status = http2::stringify_status(balloc, resp.http_status);
+
+ nva.push_back(http3::make_nv_ls_nocopy(":status", response_status));
+
+ for (auto &kv : headers) {
+ if (kv.name.empty() || kv.name[0] == ':') {
+ continue;
+ }
+ switch (kv.token) {
+ case http2::HD_CONNECTION:
+ case http2::HD_KEEP_ALIVE:
+ case http2::HD_PROXY_CONNECTION:
+ case http2::HD_TE:
+ case http2::HD_TRANSFER_ENCODING:
+ case http2::HD_UPGRADE:
+ continue;
+ }
+ nva.push_back(http3::make_nv_nocopy(kv.name, kv.value, kv.no_index));
+ }
+
+ if (!resp.fs.header(http2::HD_SERVER)) {
+ nva.push_back(http3::make_nv_ls_nocopy("server", config->http.server_name));
+ }
+
+ for (auto &p : httpconf.add_response_headers) {
+ nva.push_back(http3::make_nv_nocopy(p.name, p.value));
+ }
+
+ rv = nghttp3_conn_submit_response(httpconn_, downstream->get_stream_id(),
+ nva.data(), nva.size(), data_read_ptr);
+ if (nghttp3_err_is_fatal(rv)) {
+ ULOG(FATAL, this) << "nghttp3_conn_submit_response() failed: "
+ << nghttp3_strerror(rv);
+ return -1;
+ }
+
+ auto buf = downstream->get_response_buf();
+
+ buf->append(body, bodylen);
+
+ downstream->set_response_state(DownstreamState::MSG_COMPLETE);
+
+ if (data_read_ptr) {
+ downstream->reset_upstream_wtimer();
+ }
+
+ if (shutdown_stream_read(downstream->get_stream_id(), NGHTTP3_H3_NO_ERROR) !=
+ 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int Http3Upstream::initiate_push(Downstream *downstream, const StringRef &uri) {
+ return 0;
+}
+
+int Http3Upstream::response_riovec(struct iovec *iov, int iovcnt) const {
+ return 0;
+}
+
+void Http3Upstream::response_drain(size_t n) {}
+
+bool Http3Upstream::response_empty() const { return false; }
+
+Downstream *
+Http3Upstream::on_downstream_push_promise(Downstream *downstream,
+ int32_t promised_stream_id) {
+ return nullptr;
+}
+
+int Http3Upstream::on_downstream_push_promise_complete(
+ Downstream *downstream, Downstream *promised_downstream) {
+ return 0;
+}
+
+bool Http3Upstream::push_enabled() const { return false; }
+
+void Http3Upstream::cancel_premature_downstream(
+ Downstream *promised_downstream) {}
+
+int Http3Upstream::on_read(const UpstreamAddr *faddr,
+ const Address &remote_addr,
+ const Address &local_addr, const ngtcp2_pkt_info &pi,
+ const uint8_t *data, size_t datalen) {
+ int rv;
+
+ auto path = ngtcp2_path{
+ {
+ const_cast<sockaddr *>(&local_addr.su.sa),
+ static_cast<socklen_t>(local_addr.len),
+ },
+ {
+ const_cast<sockaddr *>(&remote_addr.su.sa),
+ static_cast<socklen_t>(remote_addr.len),
+ },
+ const_cast<UpstreamAddr *>(faddr),
+ };
+
+ rv = ngtcp2_conn_read_pkt(conn_, &path, &pi, data, datalen, quic_timestamp());
+ if (rv != 0) {
+ switch (rv) {
+ case NGTCP2_ERR_DRAINING:
+ return -1;
+ case NGTCP2_ERR_RETRY: {
+ auto worker = handler_->get_worker();
+ auto quic_conn_handler = worker->get_quic_connection_handler();
+
+ if (worker->get_graceful_shutdown()) {
+ ngtcp2_ccerr_set_transport_error(&last_error_,
+ NGTCP2_CONNECTION_REFUSED, nullptr, 0);
+
+ return handle_error();
+ }
+
+ ngtcp2_version_cid vc;
+
+ rv =
+ ngtcp2_pkt_decode_version_cid(&vc, data, datalen, SHRPX_QUIC_SCIDLEN);
+ if (rv != 0) {
+ return -1;
+ }
+
+ retry_close_ = true;
+
+ quic_conn_handler->send_retry(handler_->get_upstream_addr(), vc.version,
+ vc.dcid, vc.dcidlen, vc.scid, vc.scidlen,
+ remote_addr, local_addr, datalen * 3);
+
+ return -1;
+ }
+ case NGTCP2_ERR_CRYPTO:
+ if (!last_error_.error_code) {
+ ngtcp2_ccerr_set_tls_alert(
+ &last_error_, ngtcp2_conn_get_tls_alert(conn_), nullptr, 0);
+ }
+ break;
+ case NGTCP2_ERR_DROP_CONN:
+ return -1;
+ default:
+ if (!last_error_.error_code) {
+ ngtcp2_ccerr_set_liberr(&last_error_, rv, nullptr, 0);
+ }
+ }
+
+ ULOG(ERROR, this) << "ngtcp2_conn_read_pkt: " << ngtcp2_strerror(rv);
+
+ return handle_error();
+ }
+
+ return 0;
+}
+
+int Http3Upstream::send_packet(const UpstreamAddr *faddr,
+ const sockaddr *remote_sa, size_t remote_salen,
+ const sockaddr *local_sa, size_t local_salen,
+ const ngtcp2_pkt_info &pi, const uint8_t *data,
+ size_t datalen, size_t gso_size) {
+ auto rv = quic_send_packet(faddr, remote_sa, remote_salen, local_sa,
+ local_salen, pi, data, datalen, gso_size);
+ switch (rv) {
+ case 0:
+ return 0;
+ // With GSO, sendmsg may fail with EINVAL if UDP payload is too
+ // large.
+ case -EINVAL:
+ case -EMSGSIZE:
+ // Let the packet lost.
+ break;
+ case -EAGAIN:
+#if EAGAIN != EWOULDBLOCK
+ case -EWOULDBLOCK:
+#endif // EAGAIN != EWOULDBLOCK
+ return SHRPX_ERR_SEND_BLOCKED;
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+void Http3Upstream::on_send_blocked(const UpstreamAddr *faddr,
+ const ngtcp2_addr &remote_addr,
+ const ngtcp2_addr &local_addr,
+ const ngtcp2_pkt_info &pi,
+ const uint8_t *data, size_t datalen,
+ size_t gso_size) {
+ assert(tx_.num_blocked || !tx_.send_blocked);
+ assert(tx_.num_blocked < 2);
+
+ tx_.send_blocked = true;
+
+ auto &p = tx_.blocked[tx_.num_blocked++];
+
+ memcpy(&p.local_addr.su, local_addr.addr, local_addr.addrlen);
+ memcpy(&p.remote_addr.su, remote_addr.addr, remote_addr.addrlen);
+
+ p.local_addr.len = local_addr.addrlen;
+ p.remote_addr.len = remote_addr.addrlen;
+ p.faddr = faddr;
+ p.pi = pi;
+ p.data = data;
+ p.datalen = datalen;
+ p.gso_size = gso_size;
+}
+
+int Http3Upstream::send_blocked_packet() {
+ int rv;
+
+ assert(tx_.send_blocked);
+
+ for (; tx_.num_blocked_sent < tx_.num_blocked; ++tx_.num_blocked_sent) {
+ auto &p = tx_.blocked[tx_.num_blocked_sent];
+
+ rv = send_packet(p.faddr, &p.remote_addr.su.sa, p.remote_addr.len,
+ &p.local_addr.su.sa, p.local_addr.len, p.pi, p.data,
+ p.datalen, p.gso_size);
+ if (rv == SHRPX_ERR_SEND_BLOCKED) {
+ signal_write_upstream_addr(p.faddr);
+
+ return 0;
+ }
+ }
+
+ tx_.send_blocked = false;
+ tx_.num_blocked = 0;
+ tx_.num_blocked_sent = 0;
+
+ return 0;
+}
+
+void Http3Upstream::signal_write_upstream_addr(const UpstreamAddr *faddr) {
+ auto conn = handler_->get_connection();
+
+ if (faddr->fd != conn->wev.fd) {
+ if (ev_is_active(&conn->wev)) {
+ ev_io_stop(handler_->get_loop(), &conn->wev);
+ }
+
+ ev_io_set(&conn->wev, faddr->fd, EV_WRITE);
+ }
+
+ conn->wlimit.startw();
+}
+
+int Http3Upstream::handle_error() {
+ if (ngtcp2_conn_in_closing_period(conn_) ||
+ ngtcp2_conn_in_draining_period(conn_)) {
+ return -1;
+ }
+
+ ngtcp2_path_storage ps;
+ ngtcp2_pkt_info pi;
+
+ ngtcp2_path_storage_zero(&ps);
+
+ auto ts = quic_timestamp();
+
+ conn_close_.resize(SHRPX_QUIC_CONN_CLOSE_PKTLEN);
+
+ auto nwrite = ngtcp2_conn_write_connection_close(
+ conn_, &ps.path, &pi, conn_close_.data(), conn_close_.size(),
+ &last_error_, ts);
+ if (nwrite < 0) {
+ ULOG(ERROR, this) << "ngtcp2_conn_write_connection_close: "
+ << ngtcp2_strerror(nwrite);
+ return -1;
+ }
+
+ conn_close_.resize(nwrite);
+
+ if (nwrite == 0) {
+ return -1;
+ }
+
+ send_packet(static_cast<UpstreamAddr *>(ps.path.user_data),
+ ps.path.remote.addr, ps.path.remote.addrlen, ps.path.local.addr,
+ ps.path.local.addrlen, pi, conn_close_.data(), nwrite, 0);
+
+ return -1;
+}
+
+int Http3Upstream::handle_expiry() {
+ int rv;
+
+ auto ts = quic_timestamp();
+
+ rv = ngtcp2_conn_handle_expiry(conn_, ts);
+ if (rv != 0) {
+ if (rv == NGTCP2_ERR_IDLE_CLOSE) {
+ ULOG(INFO, this) << "Idle connection timeout";
+ } else {
+ ULOG(ERROR, this) << "ngtcp2_conn_handle_expiry: " << ngtcp2_strerror(rv);
+ }
+ ngtcp2_ccerr_set_liberr(&last_error_, rv, nullptr, 0);
+ return handle_error();
+ }
+
+ return 0;
+}
+
+void Http3Upstream::reset_timer() {
+ auto ts = quic_timestamp();
+ auto expiry_ts = ngtcp2_conn_get_expiry(conn_);
+ auto loop = handler_->get_loop();
+
+ if (expiry_ts <= ts) {
+ ev_feed_event(loop, &timer_, EV_TIMER);
+ return;
+ }
+
+ timer_.repeat = static_cast<ev_tstamp>(expiry_ts - ts) / NGTCP2_SECONDS;
+
+ ev_timer_again(loop, &timer_);
+}
+
+namespace {
+int http_deferred_consume(nghttp3_conn *conn, int64_t stream_id,
+ size_t nconsumed, void *user_data,
+ void *stream_user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+
+ upstream->consume(stream_id, nconsumed);
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int http_acked_stream_data(nghttp3_conn *conn, int64_t stream_id,
+ uint64_t datalen, void *user_data,
+ void *stream_user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+ auto downstream = static_cast<Downstream *>(stream_user_data);
+
+ assert(downstream);
+
+ if (upstream->http_acked_stream_data(downstream, datalen) != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Http3Upstream::http_acked_stream_data(Downstream *downstream,
+ uint64_t datalen) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, this) << "Stream " << downstream->get_stream_id() << " "
+ << datalen << " bytes acknowledged";
+ }
+
+ auto body = downstream->get_response_buf();
+ auto drained = body->drain_mark(datalen);
+ (void)drained;
+
+ assert(datalen == drained);
+
+ if (downstream->resume_read(SHRPX_NO_BUFFER, datalen) != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+namespace {
+int http_begin_request_headers(nghttp3_conn *conn, int64_t stream_id,
+ void *user_data, void *stream_user_data) {
+ if (!ngtcp2_is_bidi_stream(stream_id)) {
+ return 0;
+ }
+
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+ upstream->http_begin_request_headers(stream_id);
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int http_recv_request_header(nghttp3_conn *conn, int64_t stream_id,
+ int32_t token, nghttp3_rcbuf *name,
+ nghttp3_rcbuf *value, uint8_t flags,
+ void *user_data, void *stream_user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+ auto downstream = static_cast<Downstream *>(stream_user_data);
+
+ if (!downstream || downstream->get_stop_reading()) {
+ return 0;
+ }
+
+ if (upstream->http_recv_request_header(downstream, token, name, value, flags,
+ /* trailer = */ false) != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int http_recv_request_trailer(nghttp3_conn *conn, int64_t stream_id,
+ int32_t token, nghttp3_rcbuf *name,
+ nghttp3_rcbuf *value, uint8_t flags,
+ void *user_data, void *stream_user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+ auto downstream = static_cast<Downstream *>(stream_user_data);
+
+ if (!downstream || downstream->get_stop_reading()) {
+ return 0;
+ }
+
+ if (upstream->http_recv_request_header(downstream, token, name, value, flags,
+ /* trailer = */ true) != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Http3Upstream::http_recv_request_header(Downstream *downstream,
+ int32_t h3token,
+ nghttp3_rcbuf *name,
+ nghttp3_rcbuf *value, uint8_t flags,
+ bool trailer) {
+ auto namebuf = nghttp3_rcbuf_get_buf(name);
+ auto valuebuf = nghttp3_rcbuf_get_buf(value);
+ auto &req = downstream->request();
+ auto config = get_config();
+ auto &httpconf = config->http;
+
+ if (req.fs.buffer_size() + namebuf.len + valuebuf.len >
+ httpconf.request_header_field_buffer ||
+ req.fs.num_fields() >= httpconf.max_request_header_fields) {
+ downstream->set_stop_reading(true);
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return 0;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, this) << "Too large or many header field size="
+ << req.fs.buffer_size() + namebuf.len + valuebuf.len
+ << ", num=" << req.fs.num_fields() + 1;
+ }
+
+ // just ignore if this is a trailer part.
+ if (trailer) {
+ return 0;
+ }
+
+ if (error_reply(downstream, 431) != 0) {
+ return -1;
+ }
+
+ return 0;
+ }
+
+ auto token = http2::lookup_token(namebuf.base, namebuf.len);
+ auto no_index = flags & NGHTTP3_NV_FLAG_NEVER_INDEX;
+
+ downstream->add_rcbuf(name);
+ downstream->add_rcbuf(value);
+
+ if (trailer) {
+ req.fs.add_trailer_token(StringRef{namebuf.base, namebuf.len},
+ StringRef{valuebuf.base, valuebuf.len}, no_index,
+ token);
+ return 0;
+ }
+
+ req.fs.add_header_token(StringRef{namebuf.base, namebuf.len},
+ StringRef{valuebuf.base, valuebuf.len}, no_index,
+ token);
+ return 0;
+}
+
+namespace {
+int http_end_request_headers(nghttp3_conn *conn, int64_t stream_id, int fin,
+ void *user_data, void *stream_user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+ auto handler = upstream->get_client_handler();
+ auto downstream = static_cast<Downstream *>(stream_user_data);
+
+ if (!downstream || downstream->get_stop_reading()) {
+ return 0;
+ }
+
+ if (upstream->http_end_request_headers(downstream, fin) != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+
+ downstream->reset_upstream_rtimer();
+ handler->stop_read_timer();
+
+ return 0;
+}
+} // namespace
+
+int Http3Upstream::http_end_request_headers(Downstream *downstream, int fin) {
+ auto lgconf = log_config();
+ lgconf->update_tstamp(std::chrono::system_clock::now());
+ auto &req = downstream->request();
+ req.tstamp = lgconf->tstamp;
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return 0;
+ }
+
+ auto &nva = req.fs.headers();
+
+ if (LOG_ENABLED(INFO)) {
+ std::stringstream ss;
+ for (auto &nv : nva) {
+ if (nv.name == "authorization") {
+ ss << TTY_HTTP_HD << nv.name << TTY_RST << ": <redacted>\n";
+ continue;
+ }
+ ss << TTY_HTTP_HD << nv.name << TTY_RST << ": " << nv.value << "\n";
+ }
+ ULOG(INFO, this) << "HTTP request headers. stream_id="
+ << downstream->get_stream_id() << "\n"
+ << ss.str();
+ }
+
+ auto content_length = req.fs.header(http2::HD_CONTENT_LENGTH);
+ if (content_length) {
+ // libnghttp3 guarantees this can be parsed
+ req.fs.content_length = util::parse_uint(content_length->value);
+ }
+
+ // presence of mandatory header fields are guaranteed by libnghttp3.
+ auto authority = req.fs.header(http2::HD__AUTHORITY);
+ auto path = req.fs.header(http2::HD__PATH);
+ auto method = req.fs.header(http2::HD__METHOD);
+ auto scheme = req.fs.header(http2::HD__SCHEME);
+
+ auto method_token = http2::lookup_method_token(method->value);
+ if (method_token == -1) {
+ if (error_reply(downstream, 501) != 0) {
+ return -1;
+ }
+ return 0;
+ }
+
+ auto faddr = handler_->get_upstream_addr();
+
+ auto config = get_config();
+
+ // For HTTP/2 proxy, we require :authority.
+ if (method_token != HTTP_CONNECT && config->http2_proxy &&
+ faddr->alt_mode == UpstreamAltMode::NONE && !authority) {
+ shutdown_stream(downstream, NGHTTP3_H3_GENERAL_PROTOCOL_ERROR);
+ return 0;
+ }
+
+ req.method = method_token;
+ if (scheme) {
+ req.scheme = scheme->value;
+ }
+
+ // nghttp2 library guarantees either :authority or host exist
+ if (!authority) {
+ req.no_authority = true;
+ authority = req.fs.header(http2::HD_HOST);
+ }
+
+ if (authority) {
+ req.authority = authority->value;
+ }
+
+ if (path) {
+ if (method_token == HTTP_OPTIONS &&
+ path->value == StringRef::from_lit("*")) {
+ // Server-wide OPTIONS request. Path is empty.
+ } else if (config->http2_proxy &&
+ faddr->alt_mode == UpstreamAltMode::NONE) {
+ req.path = path->value;
+ } else {
+ req.path = http2::rewrite_clean_path(downstream->get_block_allocator(),
+ path->value);
+ }
+ }
+
+ auto connect_proto = req.fs.header(http2::HD__PROTOCOL);
+ if (connect_proto) {
+ if (connect_proto->value != "websocket") {
+ if (error_reply(downstream, 400) != 0) {
+ return -1;
+ }
+ return 0;
+ }
+ req.connect_proto = ConnectProto::WEBSOCKET;
+ }
+
+ if (!fin) {
+ req.http2_expect_body = true;
+ } else if (req.fs.content_length == -1) {
+ req.fs.content_length = 0;
+ }
+
+ downstream->inspect_http2_request();
+
+ downstream->set_request_state(DownstreamState::HEADER_COMPLETE);
+
+ if (config->http.require_http_scheme &&
+ !http::check_http_scheme(req.scheme, /* encrypted = */ true)) {
+ if (error_reply(downstream, 400) != 0) {
+ return -1;
+ }
+ }
+
+#ifdef HAVE_MRUBY
+ auto worker = handler_->get_worker();
+ auto mruby_ctx = worker->get_mruby_context();
+
+ if (mruby_ctx->run_on_request_proc(downstream) != 0) {
+ if (error_reply(downstream, 500) != 0) {
+ return -1;
+ }
+ return 0;
+ }
+#endif // HAVE_MRUBY
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return 0;
+ }
+
+ start_downstream(downstream);
+
+ return 0;
+}
+
+void Http3Upstream::start_downstream(Downstream *downstream) {
+ if (downstream_queue_.can_activate(downstream->request().authority)) {
+ initiate_downstream(downstream);
+ return;
+ }
+
+ downstream_queue_.mark_blocked(downstream);
+}
+
+void Http3Upstream::initiate_downstream(Downstream *downstream) {
+ int rv;
+
+#ifdef HAVE_MRUBY
+ DownstreamConnection *dconn_ptr;
+#endif // HAVE_MRUBY
+
+ for (;;) {
+ auto dconn = handler_->get_downstream_connection(rv, downstream);
+ if (!dconn) {
+ if (rv == SHRPX_ERR_TLS_REQUIRED) {
+ assert(0);
+ abort();
+ }
+
+ rv = error_reply(downstream, 502);
+ if (rv != 0) {
+ shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR);
+ }
+
+ downstream->set_request_state(DownstreamState::CONNECT_FAIL);
+ downstream_queue_.mark_failure(downstream);
+
+ return;
+ }
+
+#ifdef HAVE_MRUBY
+ dconn_ptr = dconn.get();
+#endif // HAVE_MRUBY
+ rv = downstream->attach_downstream_connection(std::move(dconn));
+ if (rv == 0) {
+ break;
+ }
+ }
+
+#ifdef HAVE_MRUBY
+ const auto &group = dconn_ptr->get_downstream_addr_group();
+ if (group) {
+ const auto &mruby_ctx = group->shared_addr->mruby_ctx;
+ if (mruby_ctx->run_on_request_proc(downstream) != 0) {
+ if (error_reply(downstream, 500) != 0) {
+ shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR);
+ }
+
+ downstream_queue_.mark_failure(downstream);
+
+ return;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return;
+ }
+ }
+#endif // HAVE_MRUBY
+
+ rv = downstream->push_request_headers();
+ if (rv != 0) {
+
+ if (error_reply(downstream, 502) != 0) {
+ shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR);
+ }
+
+ downstream_queue_.mark_failure(downstream);
+
+ return;
+ }
+
+ downstream_queue_.mark_active(downstream);
+
+ auto &req = downstream->request();
+ if (!req.http2_expect_body) {
+ rv = downstream->end_upload_data();
+ if (rv != 0) {
+ shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR);
+ }
+ }
+}
+
+namespace {
+int http_recv_data(nghttp3_conn *conn, int64_t stream_id, const uint8_t *data,
+ size_t datalen, void *user_data, void *stream_user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+ auto downstream = static_cast<Downstream *>(stream_user_data);
+
+ if (upstream->http_recv_data(downstream, data, datalen) != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Http3Upstream::http_recv_data(Downstream *downstream, const uint8_t *data,
+ size_t datalen) {
+ downstream->reset_upstream_rtimer();
+
+ if (downstream->push_upload_data_chunk(data, datalen) != 0) {
+ if (downstream->get_response_state() != DownstreamState::MSG_COMPLETE) {
+ shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR);
+ }
+
+ consume(downstream->get_stream_id(), datalen);
+
+ return 0;
+ }
+
+ return 0;
+}
+
+namespace {
+int http_end_stream(nghttp3_conn *conn, int64_t stream_id, void *user_data,
+ void *stream_user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+ auto downstream = static_cast<Downstream *>(stream_user_data);
+
+ if (!downstream || downstream->get_stop_reading()) {
+ return 0;
+ }
+
+ if (upstream->http_end_stream(downstream) != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Http3Upstream::http_end_stream(Downstream *downstream) {
+ downstream->disable_upstream_rtimer();
+
+ if (downstream->end_upload_data() != 0) {
+ if (downstream->get_response_state() != DownstreamState::MSG_COMPLETE) {
+ shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR);
+ }
+ }
+
+ downstream->set_request_state(DownstreamState::MSG_COMPLETE);
+
+ return 0;
+}
+
+namespace {
+int http_stream_close(nghttp3_conn *conn, int64_t stream_id,
+ uint64_t app_error_code, void *conn_user_data,
+ void *stream_user_data) {
+ auto upstream = static_cast<Http3Upstream *>(conn_user_data);
+ auto downstream = static_cast<Downstream *>(stream_user_data);
+
+ if (!downstream) {
+ return 0;
+ }
+
+ if (upstream->http_stream_close(downstream, app_error_code) != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Http3Upstream::http_stream_close(Downstream *downstream,
+ uint64_t app_error_code) {
+ auto stream_id = downstream->get_stream_id();
+
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, this) << "Stream stream_id=" << stream_id
+ << " is being closed with app_error_code="
+ << app_error_code;
+
+ auto body = downstream->get_response_buf();
+
+ ULOG(INFO, this) << "response unacked_left=" << body->rleft()
+ << " not_sent=" << body->rleft_mark();
+ }
+
+ auto &req = downstream->request();
+
+ consume(stream_id, req.unconsumed_body_length);
+
+ req.unconsumed_body_length = 0;
+
+ ngtcp2_conn_extend_max_streams_bidi(conn_, 1);
+
+ if (downstream->get_request_state() == DownstreamState::CONNECT_FAIL) {
+ remove_downstream(downstream);
+ // downstream was deleted
+
+ return 0;
+ }
+
+ if (downstream->can_detach_downstream_connection()) {
+ // Keep-alive
+ downstream->detach_downstream_connection();
+ }
+
+ downstream->set_request_state(DownstreamState::STREAM_CLOSED);
+
+ // At this point, downstream read may be paused.
+
+ // If shrpx_downstream::push_request_headers() failed, the
+ // error is handled here.
+ remove_downstream(downstream);
+ // downstream was deleted
+
+ return 0;
+}
+
+namespace {
+int http_stop_sending(nghttp3_conn *conn, int64_t stream_id,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+
+ if (upstream->http_stop_sending(stream_id, app_error_code) != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Http3Upstream::http_stop_sending(int64_t stream_id,
+ uint64_t app_error_code) {
+ auto rv =
+ ngtcp2_conn_shutdown_stream_read(conn_, 0, stream_id, app_error_code);
+ if (ngtcp2_err_is_fatal(rv)) {
+ ULOG(ERROR, this) << "ngtcp2_conn_shutdown_stream_read: "
+ << ngtcp2_strerror(rv);
+ return -1;
+ }
+
+ return 0;
+}
+
+namespace {
+int http_reset_stream(nghttp3_conn *conn, int64_t stream_id,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+
+ if (upstream->http_reset_stream(stream_id, app_error_code) != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Http3Upstream::http_reset_stream(int64_t stream_id,
+ uint64_t app_error_code) {
+ auto rv =
+ ngtcp2_conn_shutdown_stream_write(conn_, 0, stream_id, app_error_code);
+ if (ngtcp2_err_is_fatal(rv)) {
+ ULOG(ERROR, this) << "ngtcp2_conn_shutdown_stream_write: "
+ << ngtcp2_strerror(rv);
+ return -1;
+ }
+
+ return 0;
+}
+
+int Http3Upstream::setup_httpconn() {
+ int rv;
+
+ if (ngtcp2_conn_get_streams_uni_left(conn_) < 3) {
+ return -1;
+ }
+
+ nghttp3_callbacks callbacks{
+ shrpx::http_acked_stream_data,
+ shrpx::http_stream_close,
+ shrpx::http_recv_data,
+ http_deferred_consume,
+ shrpx::http_begin_request_headers,
+ shrpx::http_recv_request_header,
+ shrpx::http_end_request_headers,
+ nullptr, // begin_trailers
+ shrpx::http_recv_request_trailer,
+ nullptr, // end_trailers
+ shrpx::http_stop_sending,
+ shrpx::http_end_stream,
+ shrpx::http_reset_stream,
+ };
+
+ auto config = get_config();
+
+ nghttp3_settings settings;
+ nghttp3_settings_default(&settings);
+ settings.qpack_max_dtable_capacity = 4_k;
+
+ if (!config->http2_proxy) {
+ settings.enable_connect_protocol = 1;
+ }
+
+ auto mem = nghttp3_mem_default();
+
+ rv = nghttp3_conn_server_new(&httpconn_, &callbacks, &settings, mem, this);
+ if (rv != 0) {
+ ULOG(ERROR, this) << "nghttp3_conn_server_new: " << nghttp3_strerror(rv);
+ return -1;
+ }
+
+ auto params = ngtcp2_conn_get_local_transport_params(conn_);
+
+ nghttp3_conn_set_max_client_streams_bidi(httpconn_,
+ params->initial_max_streams_bidi);
+
+ int64_t ctrl_stream_id;
+
+ rv = ngtcp2_conn_open_uni_stream(conn_, &ctrl_stream_id, nullptr);
+ if (rv != 0) {
+ ULOG(ERROR, this) << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv);
+ return -1;
+ }
+
+ rv = nghttp3_conn_bind_control_stream(httpconn_, ctrl_stream_id);
+ if (rv != 0) {
+ ULOG(ERROR, this) << "nghttp3_conn_bind_control_stream: "
+ << nghttp3_strerror(rv);
+ return -1;
+ }
+
+ int64_t qpack_enc_stream_id, qpack_dec_stream_id;
+
+ rv = ngtcp2_conn_open_uni_stream(conn_, &qpack_enc_stream_id, nullptr);
+ if (rv != 0) {
+ ULOG(ERROR, this) << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv);
+ return -1;
+ }
+
+ rv = ngtcp2_conn_open_uni_stream(conn_, &qpack_dec_stream_id, nullptr);
+ if (rv != 0) {
+ ULOG(ERROR, this) << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv);
+ return -1;
+ }
+
+ rv = nghttp3_conn_bind_qpack_streams(httpconn_, qpack_enc_stream_id,
+ qpack_dec_stream_id);
+ if (rv != 0) {
+ ULOG(ERROR, this) << "nghttp3_conn_bind_qpack_streams: "
+ << nghttp3_strerror(rv);
+ return -1;
+ }
+
+ return 0;
+}
+
+int Http3Upstream::error_reply(Downstream *downstream,
+ unsigned int status_code) {
+ int rv;
+ auto &resp = downstream->response();
+
+ auto &balloc = downstream->get_block_allocator();
+
+ auto html = http::create_error_html(balloc, status_code);
+ resp.http_status = status_code;
+ auto body = downstream->get_response_buf();
+ body->append(html);
+ downstream->set_response_state(DownstreamState::MSG_COMPLETE);
+
+ nghttp3_data_reader data_read;
+ data_read.read_data = downstream_read_data_callback;
+
+ auto lgconf = log_config();
+ lgconf->update_tstamp(std::chrono::system_clock::now());
+
+ auto response_status = http2::stringify_status(balloc, status_code);
+ auto content_length = util::make_string_ref_uint(balloc, html.size());
+ auto date = make_string_ref(balloc, lgconf->tstamp->time_http);
+
+ auto nva = std::array<nghttp3_nv, 5>{
+ {http3::make_nv_ls_nocopy(":status", response_status),
+ http3::make_nv_ll("content-type", "text/html; charset=UTF-8"),
+ http3::make_nv_ls_nocopy("server", get_config()->http.server_name),
+ http3::make_nv_ls_nocopy("content-length", content_length),
+ http3::make_nv_ls_nocopy("date", date)}};
+
+ rv = nghttp3_conn_submit_response(httpconn_, downstream->get_stream_id(),
+ nva.data(), nva.size(), &data_read);
+ if (nghttp3_err_is_fatal(rv)) {
+ ULOG(FATAL, this) << "nghttp3_conn_submit_response() failed: "
+ << nghttp3_strerror(rv);
+ return -1;
+ }
+
+ downstream->reset_upstream_wtimer();
+
+ if (shutdown_stream_read(downstream->get_stream_id(), NGHTTP3_H3_NO_ERROR) !=
+ 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int Http3Upstream::shutdown_stream(Downstream *downstream,
+ uint64_t app_error_code) {
+ auto stream_id = downstream->get_stream_id();
+
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, this) << "Shutdown stream_id=" << stream_id
+ << " with app_error_code=" << app_error_code;
+ }
+
+ auto rv = ngtcp2_conn_shutdown_stream(conn_, 0, stream_id, app_error_code);
+ if (rv != 0) {
+ ULOG(FATAL, this) << "ngtcp2_conn_shutdown_stream() failed: "
+ << ngtcp2_strerror(rv);
+ return -1;
+ }
+
+ return 0;
+}
+
+int Http3Upstream::shutdown_stream_read(int64_t stream_id,
+ uint64_t app_error_code) {
+ auto rv = ngtcp2_conn_shutdown_stream_read(conn_, 0, stream_id,
+ NGHTTP3_H3_NO_ERROR);
+ if (ngtcp2_err_is_fatal(rv)) {
+ ULOG(FATAL, this) << "ngtcp2_conn_shutdown_stream_read: "
+ << ngtcp2_strerror(rv);
+ return -1;
+ }
+
+ return 0;
+}
+
+void Http3Upstream::consume(int64_t stream_id, size_t nconsumed) {
+ ngtcp2_conn_extend_max_stream_offset(conn_, stream_id, nconsumed);
+ ngtcp2_conn_extend_max_offset(conn_, nconsumed);
+}
+
+void Http3Upstream::remove_downstream(Downstream *downstream) {
+ if (downstream->accesslog_ready()) {
+ handler_->write_accesslog(downstream);
+ }
+
+ nghttp3_conn_set_stream_user_data(httpconn_, downstream->get_stream_id(),
+ nullptr);
+
+ auto next_downstream = downstream_queue_.remove_and_get_blocked(downstream);
+
+ if (next_downstream) {
+ initiate_downstream(next_downstream);
+ }
+
+ if (downstream_queue_.get_downstreams() == nullptr) {
+ // There is no downstream at the moment. Start idle timer now.
+ handler_->repeat_read_timer();
+ }
+}
+
+void Http3Upstream::log_response_headers(
+ Downstream *downstream, const std::vector<nghttp3_nv> &nva) const {
+ std::stringstream ss;
+ for (auto &nv : nva) {
+ ss << TTY_HTTP_HD << StringRef{nv.name, nv.namelen} << TTY_RST << ": "
+ << StringRef{nv.value, nv.valuelen} << "\n";
+ }
+ ULOG(INFO, this) << "HTTP response headers. stream_id="
+ << downstream->get_stream_id() << "\n"
+ << ss.str();
+}
+
+int Http3Upstream::check_shutdown() {
+ auto worker = handler_->get_worker();
+
+ if (!worker->get_graceful_shutdown()) {
+ return 0;
+ }
+
+ ev_prepare_stop(handler_->get_loop(), &prep_);
+
+ return start_graceful_shutdown();
+}
+
+int Http3Upstream::start_graceful_shutdown() {
+ int rv;
+
+ if (ev_is_active(&shutdown_timer_)) {
+ return 0;
+ }
+
+ if (!httpconn_) {
+ return -1;
+ }
+
+ rv = nghttp3_conn_submit_shutdown_notice(httpconn_);
+ if (rv != 0) {
+ ULOG(FATAL, this) << "nghttp3_conn_submit_shutdown_notice: "
+ << nghttp3_strerror(rv);
+ return -1;
+ }
+
+ handler_->signal_write();
+
+ auto t = ngtcp2_conn_get_pto(conn_);
+
+ ev_timer_set(&shutdown_timer_, static_cast<ev_tstamp>(t * 3) / NGTCP2_SECONDS,
+ 0.);
+ ev_timer_start(handler_->get_loop(), &shutdown_timer_);
+
+ return 0;
+}
+
+int Http3Upstream::submit_goaway() {
+ int rv;
+
+ rv = nghttp3_conn_shutdown(httpconn_);
+ if (rv != 0) {
+ ULOG(FATAL, this) << "nghttp3_conn_shutdown: " << nghttp3_strerror(rv);
+ return -1;
+ }
+
+ handler_->signal_write();
+
+ return 0;
+}
+
+int Http3Upstream::open_qlog_file(const StringRef &dir,
+ const ngtcp2_cid &scid) const {
+ std::array<char, sizeof("20141115T125824.741+0900")> buf;
+
+ auto path = dir.str();
+ path += '/';
+ path +=
+ util::format_iso8601_basic(buf.data(), std::chrono::system_clock::now());
+ path += '-';
+ path += util::format_hex(scid.data, scid.datalen);
+ path += ".sqlog";
+
+ int fd;
+
+#ifdef O_CLOEXEC
+ while ((fd = open(path.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC,
+ S_IRUSR | S_IWUSR | S_IRGRP)) == -1 &&
+ errno == EINTR)
+ ;
+#else // !O_CLOEXEC
+ while ((fd = open(path.c_str(), O_WRONLY | O_CREAT | O_TRUNC,
+ S_IRUSR | S_IWUSR | S_IRGRP)) == -1 &&
+ errno == EINTR)
+ ;
+
+ if (fd != -1) {
+ util::make_socket_closeonexec(fd);
+ }
+#endif // !O_CLOEXEC
+
+ if (fd == -1) {
+ auto error = errno;
+ ULOG(ERROR, this) << "Failed to open qlog file " << path
+ << ": errno=" << error;
+ return -1;
+ }
+
+ return fd;
+}
+
+ngtcp2_conn *Http3Upstream::get_conn() const { return conn_; }
+
+} // namespace shrpx
diff --git a/src/shrpx_http3_upstream.h b/src/shrpx_http3_upstream.h
new file mode 100644
index 0000000..89dfc17
--- /dev/null
+++ b/src/shrpx_http3_upstream.h
@@ -0,0 +1,193 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2021 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_HTTP3_UPSTREAM_H
+#define SHRPX_HTTP3_UPSTREAM_H
+
+#include "shrpx.h"
+
+#include <ngtcp2/ngtcp2.h>
+#include <nghttp3/nghttp3.h>
+
+#include "shrpx_upstream.h"
+#include "shrpx_downstream_queue.h"
+#include "quic.h"
+#include "network.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+struct UpstreamAddr;
+
+class Http3Upstream : public Upstream {
+public:
+ Http3Upstream(ClientHandler *handler);
+ virtual ~Http3Upstream();
+
+ virtual int on_read();
+ virtual int on_write();
+ virtual int on_timeout(Downstream *downstream);
+ virtual int on_downstream_abort_request(Downstream *downstream,
+ unsigned int status_code);
+ virtual int
+ on_downstream_abort_request_with_https_redirect(Downstream *downstream);
+ virtual int downstream_read(DownstreamConnection *dconn);
+ virtual int downstream_write(DownstreamConnection *dconn);
+ virtual int downstream_eof(DownstreamConnection *dconn);
+ virtual int downstream_error(DownstreamConnection *dconn, int events);
+ virtual ClientHandler *get_client_handler() const;
+
+ virtual int on_downstream_header_complete(Downstream *downstream);
+ virtual int on_downstream_body(Downstream *downstream, const uint8_t *data,
+ size_t len, bool flush);
+ virtual int on_downstream_body_complete(Downstream *downstream);
+
+ virtual void on_handler_delete();
+ virtual int on_downstream_reset(Downstream *downstream, bool no_retry);
+
+ virtual void pause_read(IOCtrlReason reason);
+ virtual int resume_read(IOCtrlReason reason, Downstream *downstream,
+ size_t consumed);
+ virtual int send_reply(Downstream *downstream, const uint8_t *body,
+ size_t bodylen);
+
+ virtual int initiate_push(Downstream *downstream, const StringRef &uri);
+
+ virtual int response_riovec(struct iovec *iov, int iovcnt) const;
+ virtual void response_drain(size_t n);
+ virtual bool response_empty() const;
+
+ virtual Downstream *on_downstream_push_promise(Downstream *downstream,
+ int32_t promised_stream_id);
+ virtual int
+ on_downstream_push_promise_complete(Downstream *downstream,
+ Downstream *promised_downstream);
+ virtual bool push_enabled() const;
+ virtual void cancel_premature_downstream(Downstream *promised_downstream);
+
+ int init(const UpstreamAddr *faddr, const Address &remote_addr,
+ const Address &local_addr, const ngtcp2_pkt_hd &initial_hd,
+ const ngtcp2_cid *odcid, const uint8_t *token, size_t tokenlen,
+ ngtcp2_token_type token_type);
+
+ int on_read(const UpstreamAddr *faddr, const Address &remote_addr,
+ const Address &local_addr, const ngtcp2_pkt_info &pi,
+ const uint8_t *data, size_t datalen);
+
+ int write_streams();
+
+ int handle_error();
+
+ int handle_expiry();
+ void reset_timer();
+
+ int setup_httpconn();
+ void add_pending_downstream(std::unique_ptr<Downstream> downstream);
+ int recv_stream_data(uint32_t flags, int64_t stream_id, const uint8_t *data,
+ size_t datalen);
+ int acked_stream_data_offset(int64_t stream_id, uint64_t datalen);
+ int extend_max_stream_data(int64_t stream_id);
+ void extend_max_remote_streams_bidi(uint64_t max_streams);
+ int error_reply(Downstream *downstream, unsigned int status_code);
+ void http_begin_request_headers(int64_t stream_id);
+ int http_recv_request_header(Downstream *downstream, int32_t token,
+ nghttp3_rcbuf *name, nghttp3_rcbuf *value,
+ uint8_t flags, bool trailer);
+ int http_end_request_headers(Downstream *downstream, int fin);
+ int http_end_stream(Downstream *downstream);
+ void start_downstream(Downstream *downstream);
+ void initiate_downstream(Downstream *downstream);
+ int shutdown_stream(Downstream *downstream, uint64_t app_error_code);
+ int shutdown_stream_read(int64_t stream_id, uint64_t app_error_code);
+ int http_stream_close(Downstream *downstream, uint64_t app_error_code);
+ void consume(int64_t stream_id, size_t nconsumed);
+ void remove_downstream(Downstream *downstream);
+ int stream_close(int64_t stream_id, uint64_t app_error_code);
+ void log_response_headers(Downstream *downstream,
+ const std::vector<nghttp3_nv> &nva) const;
+ int http_acked_stream_data(Downstream *downstream, uint64_t datalen);
+ int http_shutdown_stream_read(int64_t stream_id);
+ int http_reset_stream(int64_t stream_id, uint64_t app_error_code);
+ int http_stop_sending(int64_t stream_id, uint64_t app_error_code);
+ int http_recv_data(Downstream *downstream, const uint8_t *data,
+ size_t datalen);
+ int handshake_completed();
+ int check_shutdown();
+ int start_graceful_shutdown();
+ int submit_goaway();
+ int send_packet(const UpstreamAddr *faddr, const sockaddr *remote_sa,
+ size_t remote_salen, const sockaddr *local_sa,
+ size_t local_salen, const ngtcp2_pkt_info &pi,
+ const uint8_t *data, size_t datalen, size_t gso_size);
+
+ void qlog_write(const void *data, size_t datalen, bool fin);
+ int open_qlog_file(const StringRef &dir, const ngtcp2_cid &scid) const;
+
+ void on_send_blocked(const UpstreamAddr *faddr,
+ const ngtcp2_addr &remote_addr,
+ const ngtcp2_addr &local_addr, const ngtcp2_pkt_info &pi,
+ const uint8_t *data, size_t datalen, size_t gso_size);
+ int send_blocked_packet();
+ void signal_write_upstream_addr(const UpstreamAddr *faddr);
+
+ ngtcp2_conn *get_conn() const;
+
+ int send_new_token(const ngtcp2_addr *remote_addr);
+
+private:
+ ClientHandler *handler_;
+ ev_timer timer_;
+ ev_timer shutdown_timer_;
+ ev_prepare prep_;
+ int qlog_fd_;
+ ngtcp2_cid hashed_scid_;
+ ngtcp2_conn *conn_;
+ ngtcp2_ccerr last_error_;
+ nghttp3_conn *httpconn_;
+ DownstreamQueue downstream_queue_;
+ bool retry_close_;
+ std::vector<uint8_t> conn_close_;
+
+ struct {
+ bool send_blocked;
+ size_t num_blocked;
+ size_t num_blocked_sent;
+ // blocked field is effective only when send_blocked is true.
+ struct {
+ const UpstreamAddr *faddr;
+ Address local_addr;
+ Address remote_addr;
+ ngtcp2_pkt_info pi;
+ const uint8_t *data;
+ size_t datalen;
+ size_t gso_size;
+ } blocked[2];
+ std::unique_ptr<uint8_t[]> data;
+ } tx_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_HTTP3_UPSTREAM_H
diff --git a/src/shrpx_http_downstream_connection.cc b/src/shrpx_http_downstream_connection.cc
new file mode 100644
index 0000000..4164b8b
--- /dev/null
+++ b/src/shrpx_http_downstream_connection.cc
@@ -0,0 +1,1617 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_http_downstream_connection.h"
+
+#include <openssl/rand.h>
+
+#include "shrpx_client_handler.h"
+#include "shrpx_upstream.h"
+#include "shrpx_downstream.h"
+#include "shrpx_config.h"
+#include "shrpx_error.h"
+#include "shrpx_http.h"
+#include "shrpx_log_config.h"
+#include "shrpx_connect_blocker.h"
+#include "shrpx_downstream_connection_pool.h"
+#include "shrpx_worker.h"
+#include "shrpx_http2_session.h"
+#include "shrpx_tls.h"
+#include "shrpx_log.h"
+#include "http2.h"
+#include "util.h"
+#include "ssl_compat.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+namespace {
+void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto conn = static_cast<Connection *>(w->data);
+ auto dconn = static_cast<HttpDownstreamConnection *>(conn->data);
+
+ if (w == &conn->rt && !conn->expired_rt()) {
+ return;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, dconn) << "Time out";
+ }
+
+ auto downstream = dconn->get_downstream();
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+ auto &resp = downstream->response();
+
+ // Do this so that dconn is not pooled
+ resp.connection_close = true;
+
+ if (upstream->downstream_error(dconn, Downstream::EVENT_TIMEOUT) != 0) {
+ delete handler;
+ }
+}
+} // namespace
+
+namespace {
+void retry_downstream_connection(Downstream *downstream,
+ unsigned int status_code) {
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+
+ assert(!downstream->get_request_header_sent());
+
+ downstream->add_retry();
+
+ if (downstream->no_more_retry()) {
+ delete handler;
+ return;
+ }
+
+ downstream->pop_downstream_connection();
+ auto buf = downstream->get_request_buf();
+ buf->reset();
+
+ int rv;
+
+ for (;;) {
+ auto ndconn = handler->get_downstream_connection(rv, downstream);
+ if (!ndconn) {
+ break;
+ }
+ if (downstream->attach_downstream_connection(std::move(ndconn)) != 0) {
+ continue;
+ }
+ if (downstream->push_request_headers() == 0) {
+ return;
+ }
+ }
+
+ downstream->set_request_state(DownstreamState::CONNECT_FAIL);
+
+ if (rv == SHRPX_ERR_TLS_REQUIRED) {
+ rv = upstream->on_downstream_abort_request_with_https_redirect(downstream);
+ } else {
+ rv = upstream->on_downstream_abort_request(downstream, status_code);
+ }
+
+ if (rv != 0) {
+ delete handler;
+ }
+}
+} // namespace
+
+namespace {
+void connect_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto conn = static_cast<Connection *>(w->data);
+ auto dconn = static_cast<HttpDownstreamConnection *>(conn->data);
+ auto addr = dconn->get_addr();
+ auto raddr = dconn->get_raddr();
+
+ DCLOG(WARN, dconn) << "Connect time out; addr="
+ << util::to_numeric_addr(raddr);
+
+ downstream_failure(addr, raddr);
+
+ auto downstream = dconn->get_downstream();
+
+ retry_downstream_connection(downstream, 504);
+}
+} // namespace
+
+namespace {
+void backend_retry(Downstream *downstream) {
+ retry_downstream_connection(downstream, 502);
+}
+} // namespace
+
+namespace {
+void readcb(struct ev_loop *loop, ev_io *w, int revents) {
+ int rv;
+ auto conn = static_cast<Connection *>(w->data);
+ auto dconn = static_cast<HttpDownstreamConnection *>(conn->data);
+ auto downstream = dconn->get_downstream();
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+
+ rv = upstream->downstream_read(dconn);
+ if (rv != 0) {
+ if (rv == SHRPX_ERR_RETRY) {
+ backend_retry(downstream);
+ return;
+ }
+
+ delete handler;
+ }
+}
+} // namespace
+
+namespace {
+void writecb(struct ev_loop *loop, ev_io *w, int revents) {
+ int rv;
+ auto conn = static_cast<Connection *>(w->data);
+ auto dconn = static_cast<HttpDownstreamConnection *>(conn->data);
+ auto downstream = dconn->get_downstream();
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+
+ rv = upstream->downstream_write(dconn);
+ if (rv == SHRPX_ERR_RETRY) {
+ backend_retry(downstream);
+ return;
+ }
+
+ if (rv != 0) {
+ delete handler;
+ }
+}
+} // namespace
+
+namespace {
+void connectcb(struct ev_loop *loop, ev_io *w, int revents) {
+ auto conn = static_cast<Connection *>(w->data);
+ auto dconn = static_cast<HttpDownstreamConnection *>(conn->data);
+ auto downstream = dconn->get_downstream();
+ if (dconn->connected() != 0) {
+ backend_retry(downstream);
+ return;
+ }
+ writecb(loop, w, revents);
+}
+} // namespace
+
+HttpDownstreamConnection::HttpDownstreamConnection(
+ const std::shared_ptr<DownstreamAddrGroup> &group, DownstreamAddr *addr,
+ struct ev_loop *loop, Worker *worker)
+ : conn_(loop, -1, nullptr, worker->get_mcpool(),
+ group->shared_addr->timeout.write, group->shared_addr->timeout.read,
+ {}, {}, connectcb, readcb, connect_timeoutcb, this,
+ get_config()->tls.dyn_rec.warmup_threshold,
+ get_config()->tls.dyn_rec.idle_timeout, Proto::HTTP1),
+ on_read_(&HttpDownstreamConnection::noop),
+ on_write_(&HttpDownstreamConnection::noop),
+ signal_write_(&HttpDownstreamConnection::noop),
+ worker_(worker),
+ ssl_ctx_(worker->get_cl_ssl_ctx()),
+ group_(group),
+ addr_(addr),
+ raddr_(nullptr),
+ ioctrl_(&conn_.rlimit),
+ response_htp_{0},
+ first_write_done_(false),
+ reusable_(true),
+ request_header_written_(false) {}
+
+HttpDownstreamConnection::~HttpDownstreamConnection() {
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "Deleted";
+ }
+
+ if (dns_query_) {
+ auto dns_tracker = worker_->get_dns_tracker();
+ dns_tracker->cancel(dns_query_.get());
+ }
+}
+
+int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
+ int rv;
+
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "Attaching to DOWNSTREAM:" << downstream;
+ }
+
+ downstream_ = downstream;
+
+ rv = initiate_connection();
+ if (rv != 0) {
+ downstream_ = nullptr;
+ return rv;
+ }
+
+ return 0;
+}
+
+namespace {
+int htp_msg_begincb(llhttp_t *htp);
+int htp_hdr_keycb(llhttp_t *htp, const char *data, size_t len);
+int htp_hdr_valcb(llhttp_t *htp, const char *data, size_t len);
+int htp_hdrs_completecb(llhttp_t *htp);
+int htp_bodycb(llhttp_t *htp, const char *data, size_t len);
+int htp_msg_completecb(llhttp_t *htp);
+} // namespace
+
+namespace {
+constexpr llhttp_settings_t htp_hooks = {
+ htp_msg_begincb, // llhttp_cb on_message_begin;
+ nullptr, // llhttp_data_cb on_url;
+ nullptr, // llhttp_data_cb on_status;
+ nullptr, // llhttp_data_cb on_method;
+ nullptr, // llhttp_data_cb on_version;
+ htp_hdr_keycb, // llhttp_data_cb on_header_field;
+ htp_hdr_valcb, // llhttp_data_cb on_header_value;
+ nullptr, // llhttp_data_cb on_chunk_extension_name;
+ nullptr, // llhttp_data_cb on_chunk_extension_value;
+ htp_hdrs_completecb, // llhttp_cb on_headers_complete;
+ htp_bodycb, // llhttp_data_cb on_body;
+ htp_msg_completecb, // llhttp_cb on_message_complete;
+ nullptr, // llhttp_cb on_url_complete;
+ nullptr, // llhttp_cb on_status_complete;
+ nullptr, // llhttp_cb on_method_complete;
+ nullptr, // llhttp_cb on_version_complete;
+ nullptr, // llhttp_cb on_header_field_complete;
+ nullptr, // llhttp_cb on_header_value_complete;
+ nullptr, // llhttp_cb on_chunk_extension_name_complete;
+ nullptr, // llhttp_cb on_chunk_extension_value_complete;
+ nullptr, // llhttp_cb on_chunk_header;
+ nullptr, // llhttp_cb on_chunk_complete;
+ nullptr, // llhttp_cb on_reset;
+};
+} // namespace
+
+int HttpDownstreamConnection::initiate_connection() {
+ int rv;
+
+ auto worker_blocker = worker_->get_connect_blocker();
+ if (worker_blocker->blocked()) {
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this)
+ << "Worker wide backend connection was blocked temporarily";
+ }
+ return SHRPX_ERR_NETWORK;
+ }
+
+ auto &downstreamconf = *worker_->get_downstream_config();
+
+ if (conn_.fd == -1) {
+ auto check_dns_result = dns_query_.get() != nullptr;
+
+ if (check_dns_result) {
+ assert(addr_->dns);
+ }
+
+ auto &connect_blocker = addr_->connect_blocker;
+
+ if (connect_blocker->blocked()) {
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "Backend server " << addr_->host << ":"
+ << addr_->port << " was not available temporarily";
+ }
+
+ return SHRPX_ERR_NETWORK;
+ }
+
+ Address *raddr;
+
+ if (addr_->dns) {
+ if (!check_dns_result) {
+ auto dns_query = std::make_unique<DNSQuery>(
+ addr_->host,
+ [this](DNSResolverStatus status, const Address *result) {
+ int rv;
+
+ if (status == DNSResolverStatus::OK) {
+ *this->resolved_addr_ = *result;
+ }
+
+ rv = this->initiate_connection();
+ if (rv != 0) {
+ // This callback destroys |this|.
+ auto downstream = this->downstream_;
+ backend_retry(downstream);
+ }
+ });
+
+ auto dns_tracker = worker_->get_dns_tracker();
+
+ if (!resolved_addr_) {
+ resolved_addr_ = std::make_unique<Address>();
+ }
+ switch (dns_tracker->resolve(resolved_addr_.get(), dns_query.get())) {
+ case DNSResolverStatus::ERROR:
+ downstream_failure(addr_, nullptr);
+ return SHRPX_ERR_NETWORK;
+ case DNSResolverStatus::RUNNING:
+ dns_query_ = std::move(dns_query);
+ return 0;
+ case DNSResolverStatus::OK:
+ break;
+ default:
+ assert(0);
+ }
+ } else {
+ switch (dns_query_->status) {
+ case DNSResolverStatus::ERROR:
+ dns_query_.reset();
+ downstream_failure(addr_, nullptr);
+ return SHRPX_ERR_NETWORK;
+ case DNSResolverStatus::OK:
+ dns_query_.reset();
+ break;
+ default:
+ assert(0);
+ }
+ }
+
+ raddr = resolved_addr_.get();
+ util::set_port(*resolved_addr_, addr_->port);
+ } else {
+ raddr = &addr_->addr;
+ }
+
+ conn_.fd = util::create_nonblock_socket(raddr->su.storage.ss_family);
+
+ if (conn_.fd == -1) {
+ auto error = errno;
+ DCLOG(WARN, this) << "socket() failed; addr="
+ << util::to_numeric_addr(raddr) << ", errno=" << error;
+
+ worker_blocker->on_failure();
+
+ return SHRPX_ERR_NETWORK;
+ }
+
+ worker_blocker->on_success();
+
+ rv = connect(conn_.fd, &raddr->su.sa, raddr->len);
+ if (rv != 0 && errno != EINPROGRESS) {
+ auto error = errno;
+ DCLOG(WARN, this) << "connect() failed; addr="
+ << util::to_numeric_addr(raddr) << ", errno=" << error;
+
+ downstream_failure(addr_, raddr);
+
+ return SHRPX_ERR_NETWORK;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "Connecting to downstream server";
+ }
+
+ raddr_ = raddr;
+
+ if (addr_->tls) {
+ assert(ssl_ctx_);
+
+ auto ssl = tls::create_ssl(ssl_ctx_);
+ if (!ssl) {
+ return -1;
+ }
+
+ tls::setup_downstream_http1_alpn(ssl);
+
+ conn_.set_ssl(ssl);
+ conn_.tls.client_session_cache = &addr_->tls_session_cache;
+
+ auto sni_name =
+ addr_->sni.empty() ? StringRef{addr_->host} : StringRef{addr_->sni};
+ if (!util::numeric_host(sni_name.c_str())) {
+ SSL_set_tlsext_host_name(conn_.tls.ssl, sni_name.c_str());
+ }
+
+ auto session = tls::reuse_tls_session(addr_->tls_session_cache);
+ if (session) {
+ SSL_set_session(conn_.tls.ssl, session);
+ SSL_SESSION_free(session);
+ }
+
+ conn_.prepare_client_handshake();
+ }
+
+ ev_io_set(&conn_.wev, conn_.fd, EV_WRITE);
+ ev_io_set(&conn_.rev, conn_.fd, EV_READ);
+
+ conn_.wlimit.startw();
+
+ conn_.wt.repeat = downstreamconf.timeout.connect;
+ ev_timer_again(conn_.loop, &conn_.wt);
+ } else {
+ // we may set read timer cb to idle_timeoutcb. Reset again.
+ ev_set_cb(&conn_.rt, timeoutcb);
+ if (conn_.read_timeout < group_->shared_addr->timeout.read) {
+ conn_.read_timeout = group_->shared_addr->timeout.read;
+ conn_.last_read = std::chrono::steady_clock::now();
+ } else {
+ conn_.again_rt(group_->shared_addr->timeout.read);
+ }
+
+ ev_set_cb(&conn_.rev, readcb);
+
+ on_write_ = &HttpDownstreamConnection::write_first;
+ first_write_done_ = false;
+ request_header_written_ = false;
+ }
+
+ llhttp_init(&response_htp_, HTTP_RESPONSE, &htp_hooks);
+ response_htp_.data = downstream_;
+
+ return 0;
+}
+
+int HttpDownstreamConnection::push_request_headers() {
+ if (request_header_written_) {
+ signal_write();
+ return 0;
+ }
+
+ const auto &downstream_hostport = addr_->hostport;
+ const auto &req = downstream_->request();
+
+ auto &balloc = downstream_->get_block_allocator();
+
+ auto connect_method = req.regular_connect_method();
+
+ auto config = get_config();
+ auto &httpconf = config->http;
+
+ request_header_written_ = true;
+
+ // For HTTP/1.0 request, there is no authority in request. In that
+ // case, we use backend server's host nonetheless.
+ auto authority = StringRef(downstream_hostport);
+ auto no_host_rewrite =
+ httpconf.no_host_rewrite || config->http2_proxy || connect_method;
+
+ if (no_host_rewrite && !req.authority.empty()) {
+ authority = req.authority;
+ }
+
+ downstream_->set_request_downstream_host(authority);
+
+ auto buf = downstream_->get_request_buf();
+
+ // Assume that method and request path do not contain \r\n.
+ auto meth = http2::to_method_string(
+ req.connect_proto == ConnectProto::WEBSOCKET ? HTTP_GET : req.method);
+ buf->append(meth);
+ buf->append(' ');
+
+ if (connect_method) {
+ buf->append(authority);
+ } else if (config->http2_proxy) {
+ // Construct absolute-form request target because we are going to
+ // send a request to a HTTP/1 proxy.
+ assert(!req.scheme.empty());
+ buf->append(req.scheme);
+ buf->append("://");
+ buf->append(authority);
+ buf->append(req.path);
+ } else if (req.method == HTTP_OPTIONS && req.path.empty()) {
+ // Server-wide OPTIONS
+ buf->append("*");
+ } else {
+ buf->append(req.path);
+ }
+ buf->append(" HTTP/1.1\r\nHost: ");
+ buf->append(authority);
+ buf->append("\r\n");
+
+ auto &fwdconf = httpconf.forwarded;
+ auto &xffconf = httpconf.xff;
+ auto &xfpconf = httpconf.xfp;
+ auto &earlydataconf = httpconf.early_data;
+
+ uint32_t build_flags =
+ (fwdconf.strip_incoming ? http2::HDOP_STRIP_FORWARDED : 0) |
+ (xffconf.strip_incoming ? http2::HDOP_STRIP_X_FORWARDED_FOR : 0) |
+ (xfpconf.strip_incoming ? http2::HDOP_STRIP_X_FORWARDED_PROTO : 0) |
+ (earlydataconf.strip_incoming ? http2::HDOP_STRIP_EARLY_DATA : 0) |
+ ((req.http_major == 3 || req.http_major == 2)
+ ? http2::HDOP_STRIP_SEC_WEBSOCKET_KEY
+ : 0);
+
+ http2::build_http1_headers_from_headers(buf, req.fs.headers(), build_flags);
+
+ auto cookie = downstream_->assemble_request_cookie();
+ if (!cookie.empty()) {
+ buf->append("Cookie: ");
+ buf->append(cookie);
+ buf->append("\r\n");
+ }
+
+ // set transfer-encoding only when content-length is unknown and
+ // request body is expected.
+ if (req.method != HTTP_CONNECT && req.http2_expect_body &&
+ req.fs.content_length == -1) {
+ downstream_->set_chunked_request(true);
+ buf->append("Transfer-Encoding: chunked\r\n");
+ }
+
+ if (req.connect_proto == ConnectProto::WEBSOCKET) {
+ if (req.http_major == 3 || req.http_major == 2) {
+ std::array<uint8_t, 16> nonce;
+ if (RAND_bytes(nonce.data(), nonce.size()) != 1) {
+ return -1;
+ }
+ auto iov = make_byte_ref(balloc, base64::encode_length(nonce.size()) + 1);
+ auto p = base64::encode(std::begin(nonce), std::end(nonce), iov.base);
+ *p = '\0';
+ auto key = StringRef{iov.base, p};
+ downstream_->set_ws_key(key);
+
+ buf->append("Sec-Websocket-Key: ");
+ buf->append(key);
+ buf->append("\r\n");
+ }
+
+ buf->append("Upgrade: websocket\r\nConnection: Upgrade\r\n");
+ } else if (!connect_method && req.upgrade_request) {
+ auto connection = req.fs.header(http2::HD_CONNECTION);
+ if (connection) {
+ buf->append("Connection: ");
+ buf->append((*connection).value);
+ buf->append("\r\n");
+ }
+
+ auto upgrade = req.fs.header(http2::HD_UPGRADE);
+ if (upgrade) {
+ buf->append("Upgrade: ");
+ buf->append((*upgrade).value);
+ buf->append("\r\n");
+ }
+ } else if (req.connection_close) {
+ buf->append("Connection: close\r\n");
+ }
+
+ auto upstream = downstream_->get_upstream();
+ auto handler = upstream->get_client_handler();
+
+#if defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_BORINGSSL)
+ auto conn = handler->get_connection();
+
+ if (conn->tls.ssl && !SSL_is_init_finished(conn->tls.ssl)) {
+ buf->append("Early-Data: 1\r\n");
+ }
+#endif // NGHTTP2_GENUINE_OPENSSL || NGHTTP2_OPENSSL_IS_BORINGSSL
+
+ auto fwd =
+ fwdconf.strip_incoming ? nullptr : req.fs.header(http2::HD_FORWARDED);
+
+ if (fwdconf.params) {
+ auto params = fwdconf.params;
+
+ if (config->http2_proxy || connect_method) {
+ params &= ~FORWARDED_PROTO;
+ }
+
+ auto value = http::create_forwarded(
+ balloc, params, handler->get_forwarded_by(),
+ handler->get_forwarded_for(), req.authority, req.scheme);
+
+ if (fwd || !value.empty()) {
+ buf->append("Forwarded: ");
+ if (fwd) {
+ buf->append(fwd->value);
+
+ if (!value.empty()) {
+ buf->append(", ");
+ }
+ }
+ buf->append(value);
+ buf->append("\r\n");
+ }
+ } else if (fwd) {
+ buf->append("Forwarded: ");
+ buf->append(fwd->value);
+ buf->append("\r\n");
+ }
+
+ auto xff = xffconf.strip_incoming ? nullptr
+ : req.fs.header(http2::HD_X_FORWARDED_FOR);
+
+ if (xffconf.add) {
+ buf->append("X-Forwarded-For: ");
+ if (xff) {
+ buf->append((*xff).value);
+ buf->append(", ");
+ }
+ buf->append(client_handler_->get_ipaddr());
+ buf->append("\r\n");
+ } else if (xff) {
+ buf->append("X-Forwarded-For: ");
+ buf->append((*xff).value);
+ buf->append("\r\n");
+ }
+ if (!config->http2_proxy && !connect_method) {
+ auto xfp = xfpconf.strip_incoming
+ ? nullptr
+ : req.fs.header(http2::HD_X_FORWARDED_PROTO);
+
+ if (xfpconf.add) {
+ buf->append("X-Forwarded-Proto: ");
+ if (xfp) {
+ buf->append((*xfp).value);
+ buf->append(", ");
+ }
+ assert(!req.scheme.empty());
+ buf->append(req.scheme);
+ buf->append("\r\n");
+ } else if (xfp) {
+ buf->append("X-Forwarded-Proto: ");
+ buf->append((*xfp).value);
+ buf->append("\r\n");
+ }
+ }
+ auto via = req.fs.header(http2::HD_VIA);
+ if (httpconf.no_via) {
+ if (via) {
+ buf->append("Via: ");
+ buf->append((*via).value);
+ buf->append("\r\n");
+ }
+ } else {
+ buf->append("Via: ");
+ if (via) {
+ buf->append((*via).value);
+ buf->append(", ");
+ }
+ std::array<char, 16> viabuf;
+ auto end = http::create_via_header_value(viabuf.data(), req.http_major,
+ req.http_minor);
+ buf->append(viabuf.data(), end - viabuf.data());
+ buf->append("\r\n");
+ }
+
+ for (auto &p : httpconf.add_request_headers) {
+ buf->append(p.name);
+ buf->append(": ");
+ buf->append(p.value);
+ buf->append("\r\n");
+ }
+
+ buf->append("\r\n");
+
+ if (LOG_ENABLED(INFO)) {
+ std::string nhdrs;
+ for (auto chunk = buf->head; chunk; chunk = chunk->next) {
+ nhdrs.append(chunk->pos, chunk->last);
+ }
+ if (log_config()->errorlog_tty) {
+ nhdrs = http::colorizeHeaders(nhdrs.c_str());
+ }
+ DCLOG(INFO, this) << "HTTP request headers. stream_id="
+ << downstream_->get_stream_id() << "\n"
+ << nhdrs;
+ }
+
+ // Don't call signal_write() if we anticipate request body. We call
+ // signal_write() when we received request body chunk, and it
+ // enables us to send headers and data in one writev system call.
+ if (req.method == HTTP_CONNECT ||
+ downstream_->get_blocked_request_buf()->rleft() ||
+ (!req.http2_expect_body && req.fs.content_length == 0) ||
+ downstream_->get_expect_100_continue()) {
+ signal_write();
+ }
+
+ return 0;
+}
+
+int HttpDownstreamConnection::process_blocked_request_buf() {
+ auto src = downstream_->get_blocked_request_buf();
+
+ if (src->rleft()) {
+ auto dest = downstream_->get_request_buf();
+ auto chunked = downstream_->get_chunked_request();
+ if (chunked) {
+ auto chunk_size_hex = util::utox(src->rleft());
+ dest->append(chunk_size_hex);
+ dest->append("\r\n");
+ }
+
+ src->copy(*dest);
+
+ if (chunked) {
+ dest->append("\r\n");
+ }
+ }
+
+ if (downstream_->get_blocked_request_data_eof() &&
+ downstream_->get_chunked_request()) {
+ end_upload_data_chunk();
+ }
+
+ return 0;
+}
+
+int HttpDownstreamConnection::push_upload_data_chunk(const uint8_t *data,
+ size_t datalen) {
+ if (!downstream_->get_request_header_sent()) {
+ auto output = downstream_->get_blocked_request_buf();
+ auto &req = downstream_->request();
+ output->append(data, datalen);
+ req.unconsumed_body_length += datalen;
+ if (request_header_written_) {
+ signal_write();
+ }
+ return 0;
+ }
+
+ auto chunked = downstream_->get_chunked_request();
+ auto output = downstream_->get_request_buf();
+
+ if (chunked) {
+ auto chunk_size_hex = util::utox(datalen);
+ output->append(chunk_size_hex);
+ output->append("\r\n");
+ }
+
+ output->append(data, datalen);
+
+ if (chunked) {
+ output->append("\r\n");
+ }
+
+ signal_write();
+
+ return 0;
+}
+
+int HttpDownstreamConnection::end_upload_data() {
+ if (!downstream_->get_request_header_sent()) {
+ downstream_->set_blocked_request_data_eof(true);
+ if (request_header_written_) {
+ signal_write();
+ }
+ return 0;
+ }
+
+ signal_write();
+
+ if (!downstream_->get_chunked_request()) {
+ return 0;
+ }
+
+ end_upload_data_chunk();
+
+ return 0;
+}
+
+void HttpDownstreamConnection::end_upload_data_chunk() {
+ const auto &req = downstream_->request();
+
+ auto output = downstream_->get_request_buf();
+ const auto &trailers = req.fs.trailers();
+ if (trailers.empty()) {
+ output->append("0\r\n\r\n");
+ } else {
+ output->append("0\r\n");
+ http2::build_http1_headers_from_headers(output, trailers,
+ http2::HDOP_STRIP_ALL);
+ output->append("\r\n");
+ }
+}
+
+namespace {
+void remove_from_pool(HttpDownstreamConnection *dconn) {
+ auto addr = dconn->get_addr();
+ auto &dconn_pool = addr->dconn_pool;
+ dconn_pool->remove_downstream_connection(dconn);
+}
+} // namespace
+
+namespace {
+void idle_readcb(struct ev_loop *loop, ev_io *w, int revents) {
+ auto conn = static_cast<Connection *>(w->data);
+ auto dconn = static_cast<HttpDownstreamConnection *>(conn->data);
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, dconn) << "Idle connection EOF";
+ }
+
+ remove_from_pool(dconn);
+ // dconn was deleted
+}
+} // namespace
+
+namespace {
+void idle_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto conn = static_cast<Connection *>(w->data);
+ auto dconn = static_cast<HttpDownstreamConnection *>(conn->data);
+
+ if (w == &conn->rt && !conn->expired_rt()) {
+ return;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, dconn) << "Idle connection timeout";
+ }
+
+ remove_from_pool(dconn);
+ // dconn was deleted
+}
+} // namespace
+
+void HttpDownstreamConnection::detach_downstream(Downstream *downstream) {
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "Detaching from DOWNSTREAM:" << downstream;
+ }
+ downstream_ = nullptr;
+
+ ev_set_cb(&conn_.rev, idle_readcb);
+ ioctrl_.force_resume_read();
+
+ auto &downstreamconf = *worker_->get_downstream_config();
+
+ ev_set_cb(&conn_.rt, idle_timeoutcb);
+ if (conn_.read_timeout < downstreamconf.timeout.idle_read) {
+ conn_.read_timeout = downstreamconf.timeout.idle_read;
+ conn_.last_read = std::chrono::steady_clock::now();
+ } else {
+ conn_.again_rt(downstreamconf.timeout.idle_read);
+ }
+
+ conn_.wlimit.stopw();
+ ev_timer_stop(conn_.loop, &conn_.wt);
+}
+
+void HttpDownstreamConnection::pause_read(IOCtrlReason reason) {
+ ioctrl_.pause_read(reason);
+}
+
+int HttpDownstreamConnection::resume_read(IOCtrlReason reason,
+ size_t consumed) {
+ auto &downstreamconf = *worker_->get_downstream_config();
+
+ if (downstream_->get_response_buf()->rleft() <=
+ downstreamconf.request_buffer_size / 2) {
+ ioctrl_.resume_read(reason);
+ }
+
+ return 0;
+}
+
+void HttpDownstreamConnection::force_resume_read() {
+ ioctrl_.force_resume_read();
+}
+
+namespace {
+int htp_msg_begincb(llhttp_t *htp) {
+ auto downstream = static_cast<Downstream *>(htp->data);
+
+ if (downstream->get_response_state() != DownstreamState::INITIAL) {
+ return -1;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int htp_hdrs_completecb(llhttp_t *htp) {
+ auto downstream = static_cast<Downstream *>(htp->data);
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+ const auto &req = downstream->request();
+ auto &resp = downstream->response();
+ int rv;
+
+ auto &balloc = downstream->get_block_allocator();
+
+ for (auto &kv : resp.fs.headers()) {
+ kv.value = util::rstrip(balloc, kv.value);
+
+ if (kv.token == http2::HD_TRANSFER_ENCODING &&
+ !http2::check_transfer_encoding(kv.value)) {
+ return -1;
+ }
+ }
+
+ auto config = get_config();
+ auto &loggingconf = config->logging;
+
+ resp.http_status = htp->status_code;
+ resp.http_major = htp->http_major;
+ resp.http_minor = htp->http_minor;
+
+ if (resp.http_major > 1 || req.http_minor > 1) {
+ resp.http_major = 1;
+ resp.http_minor = 1;
+ return -1;
+ }
+
+ auto dconn = downstream->get_downstream_connection();
+
+ downstream->set_downstream_addr_group(dconn->get_downstream_addr_group());
+ downstream->set_addr(dconn->get_addr());
+
+ // Server MUST NOT send Transfer-Encoding with a status code 1xx or
+ // 204. Also server MUST NOT send Transfer-Encoding with a status
+ // code 2xx to a CONNECT request. Same holds true with
+ // Content-Length.
+ if (resp.http_status == 204) {
+ if (resp.fs.header(http2::HD_TRANSFER_ENCODING)) {
+ return -1;
+ }
+ // Some server send content-length: 0 for 204. Until they get
+ // fixed, we accept, but ignore it.
+
+ // Calling parse_content_length() detects duplicated
+ // content-length header fields.
+ if (resp.fs.parse_content_length() != 0) {
+ return -1;
+ }
+ if (resp.fs.content_length == 0) {
+ resp.fs.erase_content_length_and_transfer_encoding();
+ } else if (resp.fs.content_length != -1) {
+ return -1;
+ }
+ } else if (resp.http_status / 100 == 1 ||
+ (resp.http_status / 100 == 2 && req.method == HTTP_CONNECT)) {
+ // Server MUST NOT send Content-Length and Transfer-Encoding in
+ // these responses.
+ resp.fs.erase_content_length_and_transfer_encoding();
+ } else if (resp.fs.parse_content_length() != 0) {
+ downstream->set_response_state(DownstreamState::MSG_BAD_HEADER);
+ return -1;
+ }
+
+ // Check upgrade before processing non-final response, since if
+ // upgrade succeeded, 101 response is treated as final in nghttpx.
+ downstream->check_upgrade_fulfilled_http1();
+
+ if (downstream->get_non_final_response()) {
+ // Reset content-length because we reuse same Downstream for the
+ // next response.
+ resp.fs.content_length = -1;
+ // For non-final response code, we just call
+ // on_downstream_header_complete() without changing response
+ // state.
+ rv = upstream->on_downstream_header_complete(downstream);
+
+ if (rv != 0) {
+ return -1;
+ }
+
+ // Ignore response body for non-final response.
+ return 1;
+ }
+
+ resp.connection_close = !llhttp_should_keep_alive(htp);
+ downstream->set_response_state(DownstreamState::HEADER_COMPLETE);
+ downstream->inspect_http1_response();
+
+ if (htp->flags & F_CHUNKED) {
+ downstream->set_chunked_response(true);
+ }
+
+ auto transfer_encoding = resp.fs.header(http2::HD_TRANSFER_ENCODING);
+ if (transfer_encoding && !downstream->get_chunked_response()) {
+ resp.connection_close = true;
+ }
+
+ if (downstream->get_upgraded()) {
+ // content-length must be ignored for upgraded connection.
+ resp.fs.content_length = -1;
+ resp.connection_close = true;
+ // transfer-encoding not applied to upgraded connection
+ downstream->set_chunked_response(false);
+ } else if (http2::legacy_http1(req.http_major, req.http_minor)) {
+ if (resp.fs.content_length == -1) {
+ resp.connection_close = true;
+ }
+ downstream->set_chunked_response(false);
+ } else if (!downstream->expect_response_body()) {
+ downstream->set_chunked_response(false);
+ }
+
+ if (loggingconf.access.write_early && downstream->accesslog_ready()) {
+ handler->write_accesslog(downstream);
+ downstream->set_accesslog_written(true);
+ }
+
+ if (upstream->on_downstream_header_complete(downstream) != 0) {
+ return -1;
+ }
+
+ if (downstream->get_upgraded()) {
+ // Upgrade complete, read until EOF in both ends
+ if (upstream->resume_read(SHRPX_NO_BUFFER, downstream, 0) != 0) {
+ return -1;
+ }
+ downstream->set_request_state(DownstreamState::HEADER_COMPLETE);
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "HTTP upgrade success. stream_id="
+ << downstream->get_stream_id();
+ }
+ }
+
+ // Ignore the response body. HEAD response may contain
+ // Content-Length or Transfer-Encoding: chunked. Some server send
+ // 304 status code with nonzero Content-Length, but without response
+ // body. See
+ // https://tools.ietf.org/html/rfc7230#section-3.3
+
+ // TODO It seems that the cases other than HEAD are handled by
+ // llhttp. Need test.
+ return !http2::expect_response_body(req.method, resp.http_status);
+}
+} // namespace
+
+namespace {
+int ensure_header_field_buffer(const Downstream *downstream,
+ const HttpConfig &httpconf, size_t len) {
+ auto &resp = downstream->response();
+
+ if (resp.fs.buffer_size() + len > httpconf.response_header_field_buffer) {
+ if (LOG_ENABLED(INFO)) {
+ DLOG(INFO, downstream) << "Too large header header field size="
+ << resp.fs.buffer_size() + len;
+ }
+ return -1;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int ensure_max_header_fields(const Downstream *downstream,
+ const HttpConfig &httpconf) {
+ auto &resp = downstream->response();
+
+ if (resp.fs.num_fields() >= httpconf.max_response_header_fields) {
+ if (LOG_ENABLED(INFO)) {
+ DLOG(INFO, downstream)
+ << "Too many header field num=" << resp.fs.num_fields() + 1;
+ }
+ return -1;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int htp_hdr_keycb(llhttp_t *htp, const char *data, size_t len) {
+ auto downstream = static_cast<Downstream *>(htp->data);
+ auto &resp = downstream->response();
+ auto &httpconf = get_config()->http;
+
+ if (ensure_header_field_buffer(downstream, httpconf, len) != 0) {
+ return -1;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::INITIAL) {
+ if (resp.fs.header_key_prev()) {
+ resp.fs.append_last_header_key(data, len);
+ } else {
+ if (ensure_max_header_fields(downstream, httpconf) != 0) {
+ return -1;
+ }
+ resp.fs.alloc_add_header_name(StringRef{data, len});
+ }
+ } else {
+ // trailer part
+ if (resp.fs.trailer_key_prev()) {
+ resp.fs.append_last_trailer_key(data, len);
+ } else {
+ if (ensure_max_header_fields(downstream, httpconf) != 0) {
+ // Could not ignore this trailer field easily, since we may
+ // get its value in htp_hdr_valcb, and it will be added to
+ // wrong place or crash if trailer fields are currently empty.
+ return -1;
+ }
+ resp.fs.alloc_add_trailer_name(StringRef{data, len});
+ }
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+int htp_hdr_valcb(llhttp_t *htp, const char *data, size_t len) {
+ auto downstream = static_cast<Downstream *>(htp->data);
+ auto &resp = downstream->response();
+ auto &httpconf = get_config()->http;
+
+ if (ensure_header_field_buffer(downstream, httpconf, len) != 0) {
+ return -1;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::INITIAL) {
+ resp.fs.append_last_header_value(data, len);
+ } else {
+ resp.fs.append_last_trailer_value(data, len);
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+int htp_bodycb(llhttp_t *htp, const char *data, size_t len) {
+ auto downstream = static_cast<Downstream *>(htp->data);
+ auto &resp = downstream->response();
+
+ resp.recv_body_length += len;
+
+ return downstream->get_upstream()->on_downstream_body(
+ downstream, reinterpret_cast<const uint8_t *>(data), len, true);
+}
+} // namespace
+
+namespace {
+int htp_msg_completecb(llhttp_t *htp) {
+ auto downstream = static_cast<Downstream *>(htp->data);
+ auto &resp = downstream->response();
+ auto &balloc = downstream->get_block_allocator();
+
+ for (auto &kv : resp.fs.trailers()) {
+ kv.value = util::rstrip(balloc, kv.value);
+ }
+
+ // llhttp does not treat "200 connection established" response
+ // against CONNECT request, and in that case, this function is not
+ // called. But if HTTP Upgrade is made (e.g., WebSocket), this
+ // function is called, and llhttp_execute() returns just after that.
+ if (downstream->get_upgraded()) {
+ return 0;
+ }
+
+ if (downstream->get_non_final_response()) {
+ downstream->reset_response();
+
+ return 0;
+ }
+
+ downstream->set_response_state(DownstreamState::MSG_COMPLETE);
+ // Block reading another response message from (broken?)
+ // server. This callback is not called if the connection is
+ // tunneled.
+ downstream->pause_read(SHRPX_MSG_BLOCK);
+ return downstream->get_upstream()->on_downstream_body_complete(downstream);
+}
+} // namespace
+
+int HttpDownstreamConnection::write_first() {
+ int rv;
+
+ process_blocked_request_buf();
+
+ if (conn_.tls.ssl) {
+ rv = write_tls();
+ } else {
+ rv = write_clear();
+ }
+
+ if (rv != 0) {
+ return SHRPX_ERR_RETRY;
+ }
+
+ if (conn_.tls.ssl) {
+ on_write_ = &HttpDownstreamConnection::write_tls;
+ } else {
+ on_write_ = &HttpDownstreamConnection::write_clear;
+ }
+
+ first_write_done_ = true;
+ downstream_->set_request_header_sent(true);
+
+ auto buf = downstream_->get_blocked_request_buf();
+ buf->reset();
+
+ // upstream->resume_read() might be called in
+ // write_tls()/write_clear(), but before blocked_request_buf_ is
+ // reset. So upstream read might still be blocked. Let's do it
+ // again here.
+ auto input = downstream_->get_request_buf();
+ if (input->rleft() == 0) {
+ auto upstream = downstream_->get_upstream();
+ auto &req = downstream_->request();
+
+ upstream->resume_read(SHRPX_NO_BUFFER, downstream_,
+ req.unconsumed_body_length);
+ }
+
+ return 0;
+}
+
+int HttpDownstreamConnection::read_clear() {
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ std::array<uint8_t, 16_k> buf;
+ int rv;
+
+ for (;;) {
+ auto nread = conn_.read_clear(buf.data(), buf.size());
+ if (nread == 0) {
+ return 0;
+ }
+
+ if (nread < 0) {
+ if (nread == SHRPX_ERR_EOF && !downstream_->get_upgraded()) {
+ auto htperr = llhttp_finish(&response_htp_);
+ if (htperr != HPE_OK) {
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "HTTP response ended prematurely: "
+ << llhttp_errno_name(htperr);
+ }
+
+ return -1;
+ }
+ }
+
+ return nread;
+ }
+
+ rv = process_input(buf.data(), nread);
+ if (rv != 0) {
+ return rv;
+ }
+
+ if (!ev_is_active(&conn_.rev)) {
+ return 0;
+ }
+ }
+}
+
+int HttpDownstreamConnection::write_clear() {
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ auto upstream = downstream_->get_upstream();
+ auto input = downstream_->get_request_buf();
+
+ std::array<struct iovec, MAX_WR_IOVCNT> iov;
+
+ while (input->rleft() > 0) {
+ auto iovcnt = input->riovec(iov.data(), iov.size());
+
+ auto nwrite = conn_.writev_clear(iov.data(), iovcnt);
+
+ if (nwrite == 0) {
+ return 0;
+ }
+
+ if (nwrite < 0) {
+ if (!first_write_done_) {
+ return nwrite;
+ }
+ // We may have pending data in receive buffer which may contain
+ // part of response body. So keep reading. Invoke read event
+ // to get read(2) error just in case.
+ ev_feed_event(conn_.loop, &conn_.rev, EV_READ);
+ on_write_ = &HttpDownstreamConnection::noop;
+ reusable_ = false;
+ break;
+ }
+
+ input->drain(nwrite);
+ }
+
+ conn_.wlimit.stopw();
+ ev_timer_stop(conn_.loop, &conn_.wt);
+
+ if (input->rleft() == 0) {
+ auto &req = downstream_->request();
+
+ upstream->resume_read(SHRPX_NO_BUFFER, downstream_,
+ req.unconsumed_body_length);
+ }
+
+ return 0;
+}
+
+int HttpDownstreamConnection::tls_handshake() {
+ ERR_clear_error();
+
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ auto rv = conn_.tls_handshake();
+ if (rv == SHRPX_ERR_INPROGRESS) {
+ return 0;
+ }
+
+ if (rv < 0) {
+ downstream_failure(addr_, raddr_);
+
+ return rv;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "SSL/TLS handshake completed";
+ }
+
+ if (!get_config()->tls.insecure &&
+ tls::check_cert(conn_.tls.ssl, addr_, raddr_) != 0) {
+ downstream_failure(addr_, raddr_);
+
+ return -1;
+ }
+
+ auto &connect_blocker = addr_->connect_blocker;
+
+ signal_write_ = &HttpDownstreamConnection::actual_signal_write;
+
+ connect_blocker->on_success();
+
+ ev_set_cb(&conn_.rt, timeoutcb);
+ ev_set_cb(&conn_.wt, timeoutcb);
+
+ on_read_ = &HttpDownstreamConnection::read_tls;
+ on_write_ = &HttpDownstreamConnection::write_first;
+
+ // TODO Check negotiated ALPN
+
+ return on_write();
+}
+
+int HttpDownstreamConnection::read_tls() {
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ ERR_clear_error();
+
+ std::array<uint8_t, 16_k> buf;
+ int rv;
+
+ for (;;) {
+ auto nread = conn_.read_tls(buf.data(), buf.size());
+ if (nread == 0) {
+ return 0;
+ }
+
+ if (nread < 0) {
+ if (nread == SHRPX_ERR_EOF && !downstream_->get_upgraded()) {
+ auto htperr = llhttp_finish(&response_htp_);
+ if (htperr != HPE_OK) {
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "HTTP response ended prematurely: "
+ << llhttp_errno_name(htperr);
+ }
+
+ return -1;
+ }
+ }
+
+ return nread;
+ }
+
+ rv = process_input(buf.data(), nread);
+ if (rv != 0) {
+ return rv;
+ }
+
+ if (!ev_is_active(&conn_.rev)) {
+ return 0;
+ }
+ }
+}
+
+int HttpDownstreamConnection::write_tls() {
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ ERR_clear_error();
+
+ auto upstream = downstream_->get_upstream();
+ auto input = downstream_->get_request_buf();
+
+ struct iovec iov;
+
+ while (input->rleft() > 0) {
+ auto iovcnt = input->riovec(&iov, 1);
+ if (iovcnt != 1) {
+ assert(0);
+ return -1;
+ }
+ auto nwrite = conn_.write_tls(iov.iov_base, iov.iov_len);
+
+ if (nwrite == 0) {
+ return 0;
+ }
+
+ if (nwrite < 0) {
+ if (!first_write_done_) {
+ return nwrite;
+ }
+ // We may have pending data in receive buffer which may contain
+ // part of response body. So keep reading. Invoke read event
+ // to get read(2) error just in case.
+ ev_feed_event(conn_.loop, &conn_.rev, EV_READ);
+ on_write_ = &HttpDownstreamConnection::noop;
+ reusable_ = false;
+ break;
+ }
+
+ input->drain(nwrite);
+ }
+
+ conn_.wlimit.stopw();
+ ev_timer_stop(conn_.loop, &conn_.wt);
+
+ if (input->rleft() == 0) {
+ auto &req = downstream_->request();
+
+ upstream->resume_read(SHRPX_NO_BUFFER, downstream_,
+ req.unconsumed_body_length);
+ }
+
+ return 0;
+}
+
+int HttpDownstreamConnection::process_input(const uint8_t *data,
+ size_t datalen) {
+ int rv;
+
+ if (downstream_->get_upgraded()) {
+ // For upgraded connection, just pass data to the upstream.
+ rv = downstream_->get_upstream()->on_downstream_body(downstream_, data,
+ datalen, true);
+ if (rv != 0) {
+ return rv;
+ }
+
+ if (downstream_->response_buf_full()) {
+ downstream_->pause_read(SHRPX_NO_BUFFER);
+ return 0;
+ }
+
+ return 0;
+ }
+
+ auto htperr = llhttp_execute(&response_htp_,
+ reinterpret_cast<const char *>(data), datalen);
+ auto nproc =
+ htperr == HPE_OK
+ ? datalen
+ : static_cast<size_t>(reinterpret_cast<const uint8_t *>(
+ llhttp_get_error_pos(&response_htp_)) -
+ data);
+
+ if (htperr != HPE_OK &&
+ (!downstream_->get_upgraded() || htperr != HPE_PAUSED_UPGRADE)) {
+ // Handling early return (in other words, response was hijacked by
+ // mruby scripting).
+ if (downstream_->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return SHRPX_ERR_DCONN_CANCELED;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "HTTP parser failure: "
+ << "(" << llhttp_errno_name(htperr) << ") "
+ << llhttp_get_error_reason(&response_htp_);
+ }
+
+ return -1;
+ }
+
+ if (downstream_->get_upgraded()) {
+ if (nproc < datalen) {
+ // Data from data + nproc are for upgraded protocol.
+ rv = downstream_->get_upstream()->on_downstream_body(
+ downstream_, data + nproc, datalen - nproc, true);
+ if (rv != 0) {
+ return rv;
+ }
+
+ if (downstream_->response_buf_full()) {
+ downstream_->pause_read(SHRPX_NO_BUFFER);
+ return 0;
+ }
+ }
+ return 0;
+ }
+
+ if (downstream_->response_buf_full()) {
+ downstream_->pause_read(SHRPX_NO_BUFFER);
+ return 0;
+ }
+
+ return 0;
+}
+
+int HttpDownstreamConnection::connected() {
+ auto &connect_blocker = addr_->connect_blocker;
+
+ auto sock_error = util::get_socket_error(conn_.fd);
+ if (sock_error != 0) {
+ conn_.wlimit.stopw();
+
+ DCLOG(WARN, this) << "Backend connect failed; addr="
+ << util::to_numeric_addr(raddr_)
+ << ": errno=" << sock_error;
+
+ downstream_failure(addr_, raddr_);
+
+ return -1;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "Connected to downstream host";
+ }
+
+ // Reset timeout for write. Previously, we set timeout for connect.
+ conn_.wt.repeat = group_->shared_addr->timeout.write;
+ ev_timer_again(conn_.loop, &conn_.wt);
+
+ conn_.rlimit.startw();
+ conn_.again_rt();
+
+ ev_set_cb(&conn_.wev, writecb);
+
+ if (conn_.tls.ssl) {
+ on_read_ = &HttpDownstreamConnection::tls_handshake;
+ on_write_ = &HttpDownstreamConnection::tls_handshake;
+
+ return 0;
+ }
+
+ signal_write_ = &HttpDownstreamConnection::actual_signal_write;
+
+ connect_blocker->on_success();
+
+ ev_set_cb(&conn_.rt, timeoutcb);
+ ev_set_cb(&conn_.wt, timeoutcb);
+
+ on_read_ = &HttpDownstreamConnection::read_clear;
+ on_write_ = &HttpDownstreamConnection::write_first;
+
+ return 0;
+}
+
+int HttpDownstreamConnection::on_read() { return on_read_(*this); }
+
+int HttpDownstreamConnection::on_write() { return on_write_(*this); }
+
+void HttpDownstreamConnection::on_upstream_change(Upstream *upstream) {}
+
+void HttpDownstreamConnection::signal_write() { signal_write_(*this); }
+
+int HttpDownstreamConnection::actual_signal_write() {
+ ev_feed_event(conn_.loop, &conn_.wev, EV_WRITE);
+ return 0;
+}
+
+int HttpDownstreamConnection::noop() { return 0; }
+
+const std::shared_ptr<DownstreamAddrGroup> &
+HttpDownstreamConnection::get_downstream_addr_group() const {
+ return group_;
+}
+
+DownstreamAddr *HttpDownstreamConnection::get_addr() const { return addr_; }
+
+bool HttpDownstreamConnection::poolable() const {
+ return !group_->retired && reusable_;
+}
+
+const Address *HttpDownstreamConnection::get_raddr() const { return raddr_; }
+
+} // namespace shrpx
diff --git a/src/shrpx_http_downstream_connection.h b/src/shrpx_http_downstream_connection.h
new file mode 100644
index 0000000..a453f0d
--- /dev/null
+++ b/src/shrpx_http_downstream_connection.h
@@ -0,0 +1,124 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_HTTP_DOWNSTREAM_CONNECTION_H
+#define SHRPX_HTTP_DOWNSTREAM_CONNECTION_H
+
+#include "shrpx.h"
+
+#include "llhttp.h"
+
+#include "shrpx_downstream_connection.h"
+#include "shrpx_io_control.h"
+#include "shrpx_connection.h"
+
+namespace shrpx {
+
+class DownstreamConnectionPool;
+class Worker;
+struct DownstreamAddrGroup;
+struct DownstreamAddr;
+struct DNSQuery;
+
+class HttpDownstreamConnection : public DownstreamConnection {
+public:
+ HttpDownstreamConnection(const std::shared_ptr<DownstreamAddrGroup> &group,
+ DownstreamAddr *addr, struct ev_loop *loop,
+ Worker *worker);
+ virtual ~HttpDownstreamConnection();
+ virtual int attach_downstream(Downstream *downstream);
+ virtual void detach_downstream(Downstream *downstream);
+
+ virtual int push_request_headers();
+ virtual int push_upload_data_chunk(const uint8_t *data, size_t datalen);
+ virtual int end_upload_data();
+ void end_upload_data_chunk();
+
+ virtual void pause_read(IOCtrlReason reason);
+ virtual int resume_read(IOCtrlReason reason, size_t consumed);
+ virtual void force_resume_read();
+
+ virtual int on_read();
+ virtual int on_write();
+
+ virtual void on_upstream_change(Upstream *upstream);
+
+ virtual bool poolable() const;
+
+ virtual const std::shared_ptr<DownstreamAddrGroup> &
+ get_downstream_addr_group() const;
+ virtual DownstreamAddr *get_addr() const;
+
+ int initiate_connection();
+
+ int write_first();
+ int read_clear();
+ int write_clear();
+ int read_tls();
+ int write_tls();
+
+ int process_input(const uint8_t *data, size_t datalen);
+ int tls_handshake();
+
+ int connected();
+ void signal_write();
+ int actual_signal_write();
+
+ // Returns address used to connect to backend. Could be nullptr.
+ const Address *get_raddr() const;
+
+ int noop();
+
+ int process_blocked_request_buf();
+
+private:
+ Connection conn_;
+ std::function<int(HttpDownstreamConnection &)> on_read_, on_write_,
+ signal_write_;
+ Worker *worker_;
+ // nullptr if TLS is not used.
+ SSL_CTX *ssl_ctx_;
+ std::shared_ptr<DownstreamAddrGroup> group_;
+ // Address of remote endpoint
+ DownstreamAddr *addr_;
+ // Actual remote address used to contact backend. This is initially
+ // nullptr, and may point to either &addr_->addr, or
+ // resolved_addr_.get().
+ const Address *raddr_;
+ // Resolved IP address if dns parameter is used
+ std::unique_ptr<Address> resolved_addr_;
+ std::unique_ptr<DNSQuery> dns_query_;
+ IOControl ioctrl_;
+ llhttp_t response_htp_;
+ // true if first write succeeded.
+ bool first_write_done_;
+ // true if this object can be reused
+ bool reusable_;
+ // true if request header is written to request buffer.
+ bool request_header_written_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_HTTP_DOWNSTREAM_CONNECTION_H
diff --git a/src/shrpx_http_test.cc b/src/shrpx_http_test.cc
new file mode 100644
index 0000000..3ace870
--- /dev/null
+++ b/src/shrpx_http_test.cc
@@ -0,0 +1,168 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_http_test.h"
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+
+#include <cstdlib>
+
+#include <CUnit/CUnit.h>
+
+#include "shrpx_http.h"
+#include "shrpx_config.h"
+#include "shrpx_log.h"
+
+namespace shrpx {
+
+void test_shrpx_http_create_forwarded(void) {
+ BlockAllocator balloc(1024, 1024);
+
+ CU_ASSERT("by=\"example.com:3000\";for=\"[::1]\";host=\"www.example.com\";"
+ "proto=https" ==
+ http::create_forwarded(balloc,
+ FORWARDED_BY | FORWARDED_FOR |
+ FORWARDED_HOST | FORWARDED_PROTO,
+ StringRef::from_lit("example.com:3000"),
+ StringRef::from_lit("[::1]"),
+ StringRef::from_lit("www.example.com"),
+ StringRef::from_lit("https")));
+
+ CU_ASSERT("for=192.168.0.1" ==
+ http::create_forwarded(
+ balloc, FORWARDED_FOR, StringRef::from_lit("alpha"),
+ StringRef::from_lit("192.168.0.1"),
+ StringRef::from_lit("bravo"), StringRef::from_lit("charlie")));
+
+ CU_ASSERT("by=_hidden;for=\"[::1]\"" ==
+ http::create_forwarded(
+ balloc, FORWARDED_BY | FORWARDED_FOR,
+ StringRef::from_lit("_hidden"), StringRef::from_lit("[::1]"),
+ StringRef::from_lit(""), StringRef::from_lit("")));
+
+ CU_ASSERT("by=\"[::1]\";for=_hidden" ==
+ http::create_forwarded(
+ balloc, FORWARDED_BY | FORWARDED_FOR,
+ StringRef::from_lit("[::1]"), StringRef::from_lit("_hidden"),
+ StringRef::from_lit(""), StringRef::from_lit("")));
+
+ CU_ASSERT("" ==
+ http::create_forwarded(
+ balloc,
+ FORWARDED_BY | FORWARDED_FOR | FORWARDED_HOST | FORWARDED_PROTO,
+ StringRef::from_lit(""), StringRef::from_lit(""),
+ StringRef::from_lit(""), StringRef::from_lit("")));
+}
+
+void test_shrpx_http_create_via_header_value(void) {
+ std::array<char, 16> buf;
+
+ auto end = http::create_via_header_value(std::begin(buf), 1, 1);
+
+ CU_ASSERT(("1.1 nghttpx" == StringRef{std::begin(buf), end}));
+
+ std::fill(std::begin(buf), std::end(buf), '\0');
+
+ end = http::create_via_header_value(std::begin(buf), 2, 0);
+
+ CU_ASSERT(("2 nghttpx" == StringRef{std::begin(buf), end}));
+}
+
+void test_shrpx_http_create_affinity_cookie(void) {
+ BlockAllocator balloc(1024, 1024);
+ StringRef c;
+
+ c = http::create_affinity_cookie(balloc, StringRef::from_lit("cookie-val"),
+ 0xf1e2d3c4u, StringRef{}, false);
+
+ CU_ASSERT("cookie-val=f1e2d3c4" == c);
+
+ c = http::create_affinity_cookie(balloc, StringRef::from_lit("alpha"),
+ 0x00000000u, StringRef{}, true);
+
+ CU_ASSERT("alpha=00000000; Secure" == c);
+
+ c = http::create_affinity_cookie(balloc, StringRef::from_lit("bravo"),
+ 0x01111111u, StringRef::from_lit("bar"),
+ false);
+
+ CU_ASSERT("bravo=01111111; Path=bar" == c);
+
+ c = http::create_affinity_cookie(balloc, StringRef::from_lit("charlie"),
+ 0x01111111u, StringRef::from_lit("bar"),
+ true);
+
+ CU_ASSERT("charlie=01111111; Path=bar; Secure" == c);
+}
+
+void test_shrpx_http_create_altsvc_header_value(void) {
+ {
+ BlockAllocator balloc(1024, 1024);
+ std::vector<AltSvc> altsvcs{
+ AltSvc{
+ .protocol_id = StringRef::from_lit("h3"),
+ .host = StringRef::from_lit("127.0.0.1"),
+ .service = StringRef::from_lit("443"),
+ .params = StringRef::from_lit("ma=3600"),
+ },
+ };
+
+ CU_ASSERT(R"(h3="127.0.0.1:443"; ma=3600)" ==
+ http::create_altsvc_header_value(balloc, altsvcs));
+ }
+
+ {
+ BlockAllocator balloc(1024, 1024);
+ std::vector<AltSvc> altsvcs{
+ AltSvc{
+ .protocol_id = StringRef::from_lit("h3"),
+ .service = StringRef::from_lit("443"),
+ .params = StringRef::from_lit("ma=3600"),
+ },
+ AltSvc{
+ .protocol_id = StringRef::from_lit("h3%"),
+ .host = StringRef::from_lit("\"foo\""),
+ .service = StringRef::from_lit("4433"),
+ },
+ };
+
+ CU_ASSERT(R"(h3=":443"; ma=3600, h3%25="\"foo\":4433")" ==
+ http::create_altsvc_header_value(balloc, altsvcs));
+ }
+}
+
+void test_shrpx_http_check_http_scheme(void) {
+ CU_ASSERT(http::check_http_scheme(StringRef::from_lit("https"), true));
+ CU_ASSERT(!http::check_http_scheme(StringRef::from_lit("https"), false));
+ CU_ASSERT(!http::check_http_scheme(StringRef::from_lit("http"), true));
+ CU_ASSERT(http::check_http_scheme(StringRef::from_lit("http"), false));
+ CU_ASSERT(!http::check_http_scheme(StringRef::from_lit("foo"), true));
+ CU_ASSERT(!http::check_http_scheme(StringRef::from_lit("foo"), false));
+ CU_ASSERT(!http::check_http_scheme(StringRef{}, true));
+ CU_ASSERT(!http::check_http_scheme(StringRef{}, false));
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_http_test.h b/src/shrpx_http_test.h
new file mode 100644
index 0000000..d50ab53
--- /dev/null
+++ b/src/shrpx_http_test.h
@@ -0,0 +1,42 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_HTTP_TEST_H
+#define SHRPX_HTTP_TEST_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+namespace shrpx {
+
+void test_shrpx_http_create_forwarded(void);
+void test_shrpx_http_create_via_header_value(void);
+void test_shrpx_http_create_affinity_cookie(void);
+void test_shrpx_http_create_altsvc_header_value(void);
+void test_shrpx_http_check_http_scheme(void);
+
+} // namespace shrpx
+
+#endif // SHRPX_HTTP_TEST_H
diff --git a/src/shrpx_https_upstream.cc b/src/shrpx_https_upstream.cc
new file mode 100644
index 0000000..49d2088
--- /dev/null
+++ b/src/shrpx_https_upstream.cc
@@ -0,0 +1,1582 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_https_upstream.h"
+
+#include <cassert>
+#include <set>
+#include <sstream>
+
+#include "shrpx_client_handler.h"
+#include "shrpx_downstream.h"
+#include "shrpx_downstream_connection.h"
+#include "shrpx_http.h"
+#include "shrpx_config.h"
+#include "shrpx_error.h"
+#include "shrpx_log_config.h"
+#include "shrpx_worker.h"
+#include "shrpx_http2_session.h"
+#include "shrpx_log.h"
+#ifdef HAVE_MRUBY
+# include "shrpx_mruby.h"
+#endif // HAVE_MRUBY
+#include "http2.h"
+#include "util.h"
+#include "template.h"
+#include "base64.h"
+#include "url-parser/url_parser.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+namespace {
+int htp_msg_begin(llhttp_t *htp);
+int htp_uricb(llhttp_t *htp, const char *data, size_t len);
+int htp_hdr_keycb(llhttp_t *htp, const char *data, size_t len);
+int htp_hdr_valcb(llhttp_t *htp, const char *data, size_t len);
+int htp_hdrs_completecb(llhttp_t *htp);
+int htp_bodycb(llhttp_t *htp, const char *data, size_t len);
+int htp_msg_completecb(llhttp_t *htp);
+} // namespace
+
+namespace {
+constexpr llhttp_settings_t htp_hooks = {
+ htp_msg_begin, // llhttp_cb on_message_begin;
+ htp_uricb, // llhttp_data_cb on_url;
+ nullptr, // llhttp_data_cb on_status;
+ nullptr, // llhttp_data_cb on_method;
+ nullptr, // llhttp_data_cb on_version;
+ htp_hdr_keycb, // llhttp_data_cb on_header_field;
+ htp_hdr_valcb, // llhttp_data_cb on_header_value;
+ nullptr, // llhttp_data_cb on_chunk_extension_name;
+ nullptr, // llhttp_data_cb on_chunk_extension_value;
+ htp_hdrs_completecb, // llhttp_cb on_headers_complete;
+ htp_bodycb, // llhttp_data_cb on_body;
+ htp_msg_completecb, // llhttp_cb on_message_complete;
+ nullptr, // llhttp_cb on_url_complete;
+ nullptr, // llhttp_cb on_status_complete;
+ nullptr, // llhttp_cb on_method_complete;
+ nullptr, // llhttp_cb on_version_complete;
+ nullptr, // llhttp_cb on_header_field_complete;
+ nullptr, // llhttp_cb on_header_value_complete;
+ nullptr, // llhttp_cb on_chunk_extension_name_complete;
+ nullptr, // llhttp_cb on_chunk_extension_value_complete;
+ nullptr, // llhttp_cb on_chunk_header;
+ nullptr, // llhttp_cb on_chunk_complete;
+ nullptr, // llhttp_cb on_reset;
+};
+} // namespace
+
+HttpsUpstream::HttpsUpstream(ClientHandler *handler)
+ : handler_(handler),
+ current_header_length_(0),
+ ioctrl_(handler->get_rlimit()),
+ num_requests_(0) {
+ llhttp_init(&htp_, HTTP_REQUEST, &htp_hooks);
+ htp_.data = this;
+}
+
+HttpsUpstream::~HttpsUpstream() {}
+
+void HttpsUpstream::reset_current_header_length() {
+ current_header_length_ = 0;
+}
+
+void HttpsUpstream::on_start_request() {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, this) << "HTTP request started";
+ }
+ reset_current_header_length();
+
+ auto downstream =
+ std::make_unique<Downstream>(this, handler_->get_mcpool(), 0);
+
+ attach_downstream(std::move(downstream));
+
+ auto conn = handler_->get_connection();
+ auto &upstreamconf = get_config()->conn.upstream;
+
+ conn->rt.repeat = upstreamconf.timeout.read;
+
+ handler_->repeat_read_timer();
+
+ ++num_requests_;
+}
+
+namespace {
+int htp_msg_begin(llhttp_t *htp) {
+ auto upstream = static_cast<HttpsUpstream *>(htp->data);
+ upstream->on_start_request();
+ return 0;
+}
+} // namespace
+
+namespace {
+int htp_uricb(llhttp_t *htp, const char *data, size_t len) {
+ auto upstream = static_cast<HttpsUpstream *>(htp->data);
+ auto downstream = upstream->get_downstream();
+ auto &req = downstream->request();
+
+ auto &balloc = downstream->get_block_allocator();
+
+ // We happen to have the same value for method token.
+ req.method = htp->method;
+
+ if (req.fs.buffer_size() + len >
+ get_config()->http.request_header_field_buffer) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, upstream) << "Too large URI size="
+ << req.fs.buffer_size() + len;
+ }
+ assert(downstream->get_request_state() == DownstreamState::INITIAL);
+ downstream->set_request_state(
+ DownstreamState::HTTP1_REQUEST_HEADER_TOO_LARGE);
+ llhttp_set_error_reason(htp, "too long request URI");
+ return HPE_USER;
+ }
+
+ req.fs.add_extra_buffer_size(len);
+
+ if (req.method == HTTP_CONNECT) {
+ req.authority =
+ concat_string_ref(balloc, req.authority, StringRef{data, len});
+ } else {
+ req.path = concat_string_ref(balloc, req.path, StringRef{data, len});
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int htp_hdr_keycb(llhttp_t *htp, const char *data, size_t len) {
+ auto upstream = static_cast<HttpsUpstream *>(htp->data);
+ auto downstream = upstream->get_downstream();
+ auto &req = downstream->request();
+ auto &httpconf = get_config()->http;
+
+ if (req.fs.buffer_size() + len > httpconf.request_header_field_buffer) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, upstream) << "Too large header block size="
+ << req.fs.buffer_size() + len;
+ }
+ if (downstream->get_request_state() == DownstreamState::INITIAL) {
+ downstream->set_request_state(
+ DownstreamState::HTTP1_REQUEST_HEADER_TOO_LARGE);
+ }
+ llhttp_set_error_reason(htp, "too large header");
+ return HPE_USER;
+ }
+ if (downstream->get_request_state() == DownstreamState::INITIAL) {
+ if (req.fs.header_key_prev()) {
+ req.fs.append_last_header_key(data, len);
+ } else {
+ if (req.fs.num_fields() >= httpconf.max_request_header_fields) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, upstream)
+ << "Too many header field num=" << req.fs.num_fields() + 1;
+ }
+ downstream->set_request_state(
+ DownstreamState::HTTP1_REQUEST_HEADER_TOO_LARGE);
+ llhttp_set_error_reason(htp, "too many headers");
+ return HPE_USER;
+ }
+ req.fs.alloc_add_header_name(StringRef{data, len});
+ }
+ } else {
+ // trailer part
+ if (req.fs.trailer_key_prev()) {
+ req.fs.append_last_trailer_key(data, len);
+ } else {
+ if (req.fs.num_fields() >= httpconf.max_request_header_fields) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, upstream)
+ << "Too many header field num=" << req.fs.num_fields() + 1;
+ }
+ llhttp_set_error_reason(htp, "too many headers");
+ return HPE_USER;
+ }
+ req.fs.alloc_add_trailer_name(StringRef{data, len});
+ }
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+int htp_hdr_valcb(llhttp_t *htp, const char *data, size_t len) {
+ auto upstream = static_cast<HttpsUpstream *>(htp->data);
+ auto downstream = upstream->get_downstream();
+ auto &req = downstream->request();
+
+ if (req.fs.buffer_size() + len >
+ get_config()->http.request_header_field_buffer) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, upstream) << "Too large header block size="
+ << req.fs.buffer_size() + len;
+ }
+ if (downstream->get_request_state() == DownstreamState::INITIAL) {
+ downstream->set_request_state(
+ DownstreamState::HTTP1_REQUEST_HEADER_TOO_LARGE);
+ }
+ llhttp_set_error_reason(htp, "too large header");
+ return HPE_USER;
+ }
+ if (downstream->get_request_state() == DownstreamState::INITIAL) {
+ req.fs.append_last_header_value(data, len);
+ } else {
+ req.fs.append_last_trailer_value(data, len);
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+void rewrite_request_host_path_from_uri(BlockAllocator &balloc, Request &req,
+ const StringRef &uri,
+ http_parser_url &u) {
+ assert(u.field_set & (1 << UF_HOST));
+
+ // As per https://tools.ietf.org/html/rfc7230#section-5.4, we
+ // rewrite host header field with authority component.
+ auto authority = util::get_uri_field(uri.c_str(), u, UF_HOST);
+ // TODO properly check IPv6 numeric address
+ auto ipv6 = std::find(std::begin(authority), std::end(authority), ':') !=
+ std::end(authority);
+ auto authoritylen = authority.size();
+ if (ipv6) {
+ authoritylen += 2;
+ }
+ if (u.field_set & (1 << UF_PORT)) {
+ authoritylen += 1 + str_size("65535");
+ }
+ if (authoritylen > authority.size()) {
+ auto iovec = make_byte_ref(balloc, authoritylen + 1);
+ auto p = iovec.base;
+ if (ipv6) {
+ *p++ = '[';
+ }
+ p = std::copy(std::begin(authority), std::end(authority), p);
+ if (ipv6) {
+ *p++ = ']';
+ }
+
+ if (u.field_set & (1 << UF_PORT)) {
+ *p++ = ':';
+ p = util::utos(p, u.port);
+ }
+ *p = '\0';
+
+ req.authority = StringRef{iovec.base, p};
+ } else {
+ req.authority = authority;
+ }
+
+ req.scheme = util::get_uri_field(uri.c_str(), u, UF_SCHEMA);
+
+ StringRef path;
+ if (u.field_set & (1 << UF_PATH)) {
+ path = util::get_uri_field(uri.c_str(), u, UF_PATH);
+ } else if (req.method == HTTP_OPTIONS) {
+ // Server-wide OPTIONS takes following form in proxy request:
+ //
+ // OPTIONS http://example.org HTTP/1.1
+ //
+ // Notice that no slash after authority. See
+ // http://tools.ietf.org/html/rfc7230#section-5.3.4
+ req.path = StringRef::from_lit("");
+ // we ignore query component here
+ return;
+ } else {
+ path = StringRef::from_lit("/");
+ }
+
+ if (u.field_set & (1 << UF_QUERY)) {
+ auto &fdata = u.field_data[UF_QUERY];
+
+ if (u.field_set & (1 << UF_PATH)) {
+ auto q = util::get_uri_field(uri.c_str(), u, UF_QUERY);
+ path = StringRef{std::begin(path), std::end(q)};
+ } else {
+ path = concat_string_ref(balloc, path, StringRef::from_lit("?"),
+ StringRef{&uri[fdata.off], fdata.len});
+ }
+ }
+
+ req.path = http2::rewrite_clean_path(balloc, path);
+}
+} // namespace
+
+namespace {
+int htp_hdrs_completecb(llhttp_t *htp) {
+ int rv;
+ auto upstream = static_cast<HttpsUpstream *>(htp->data);
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, upstream) << "HTTP request headers completed";
+ }
+
+ auto handler = upstream->get_client_handler();
+
+ auto downstream = upstream->get_downstream();
+ auto &req = downstream->request();
+ auto &balloc = downstream->get_block_allocator();
+
+ for (auto &kv : req.fs.headers()) {
+ kv.value = util::rstrip(balloc, kv.value);
+
+ if (kv.token == http2::HD_TRANSFER_ENCODING &&
+ !http2::check_transfer_encoding(kv.value)) {
+ return -1;
+ }
+ }
+
+ auto lgconf = log_config();
+ lgconf->update_tstamp(std::chrono::system_clock::now());
+ req.tstamp = lgconf->tstamp;
+
+ req.http_major = htp->http_major;
+ req.http_minor = htp->http_minor;
+
+ req.connection_close = !llhttp_should_keep_alive(htp);
+
+ handler->stop_read_timer();
+
+ auto method = req.method;
+
+ if (LOG_ENABLED(INFO)) {
+ std::stringstream ss;
+ ss << http2::to_method_string(method) << " "
+ << (method == HTTP_CONNECT ? req.authority : req.path) << " "
+ << "HTTP/" << req.http_major << "." << req.http_minor << "\n";
+
+ for (const auto &kv : req.fs.headers()) {
+ if (kv.name == "authorization") {
+ ss << TTY_HTTP_HD << kv.name << TTY_RST << ": <redacted>\n";
+ continue;
+ }
+ ss << TTY_HTTP_HD << kv.name << TTY_RST << ": " << kv.value << "\n";
+ }
+
+ ULOG(INFO, upstream) << "HTTP request headers\n" << ss.str();
+ }
+
+ // set content-length if method is not CONNECT, and no
+ // transfer-encoding is given. If transfer-encoding is given, leave
+ // req.fs.content_length to -1.
+ if (method != HTTP_CONNECT && !req.fs.header(http2::HD_TRANSFER_ENCODING)) {
+ // llhttp sets 0 to htp->content_length if there is no
+ // content-length header field. If we don't have both
+ // transfer-encoding and content-length header field, we assume
+ // that there is no request body.
+ req.fs.content_length = htp->content_length;
+ }
+
+ auto host = req.fs.header(http2::HD_HOST);
+
+ if (req.http_major > 1 || req.http_minor > 1) {
+ req.http_major = 1;
+ req.http_minor = 1;
+ return -1;
+ }
+
+ if (req.http_major == 1 && req.http_minor == 1 && !host) {
+ return -1;
+ }
+
+ if (host) {
+ const auto &value = host->value;
+ // Not allow at least '"' or '\' in host. They are illegal in
+ // authority component, also they cause headaches when we put them
+ // in quoted-string.
+ if (std::find_if(std::begin(value), std::end(value), [](char c) {
+ return c == '"' || c == '\\';
+ }) != std::end(value)) {
+ return -1;
+ }
+ }
+
+ downstream->inspect_http1_request();
+
+ if (htp->flags & F_CHUNKED) {
+ downstream->set_chunked_request(true);
+ }
+
+ auto transfer_encoding = req.fs.header(http2::HD_TRANSFER_ENCODING);
+ if (transfer_encoding &&
+ http2::legacy_http1(req.http_major, req.http_minor)) {
+ return -1;
+ }
+
+ auto faddr = handler->get_upstream_addr();
+ auto config = get_config();
+
+ if (method != HTTP_CONNECT) {
+ http_parser_url u{};
+ rv = http_parser_parse_url(req.path.c_str(), req.path.size(), 0, &u);
+ if (rv != 0) {
+ // Expect to respond with 400 bad request
+ return -1;
+ }
+ // checking UF_HOST could be redundant, but just in case ...
+ if (!(u.field_set & (1 << UF_SCHEMA)) || !(u.field_set & (1 << UF_HOST))) {
+ req.no_authority = true;
+
+ if (method == HTTP_OPTIONS && req.path == StringRef::from_lit("*")) {
+ req.path = StringRef{};
+ } else {
+ req.path = http2::rewrite_clean_path(balloc, req.path);
+ }
+
+ if (host) {
+ req.authority = host->value;
+ }
+
+ if (handler->get_ssl()) {
+ req.scheme = StringRef::from_lit("https");
+ } else {
+ req.scheme = StringRef::from_lit("http");
+ }
+ } else {
+ rewrite_request_host_path_from_uri(balloc, req, req.path, u);
+ }
+ }
+
+ downstream->set_request_state(DownstreamState::HEADER_COMPLETE);
+
+ auto &resp = downstream->response();
+
+ if (config->http.require_http_scheme &&
+ !http::check_http_scheme(req.scheme, handler->get_ssl() != nullptr)) {
+ resp.http_status = 400;
+ return -1;
+ }
+
+#ifdef HAVE_MRUBY
+ auto worker = handler->get_worker();
+ auto mruby_ctx = worker->get_mruby_context();
+
+ if (mruby_ctx->run_on_request_proc(downstream) != 0) {
+ resp.http_status = 500;
+ return -1;
+ }
+#endif // HAVE_MRUBY
+
+ // mruby hook may change method value
+
+ if (req.no_authority && config->http2_proxy &&
+ faddr->alt_mode == UpstreamAltMode::NONE) {
+ // Request URI should be absolute-form for client proxy mode
+ return -1;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return 0;
+ }
+
+#ifdef HAVE_MRUBY
+ DownstreamConnection *dconn_ptr;
+#endif // HAVE_MRUBY
+
+ for (;;) {
+ auto dconn = handler->get_downstream_connection(rv, downstream);
+
+ if (!dconn) {
+ if (rv == SHRPX_ERR_TLS_REQUIRED) {
+ upstream->redirect_to_https(downstream);
+ }
+ downstream->set_request_state(DownstreamState::CONNECT_FAIL);
+ return -1;
+ }
+
+#ifdef HAVE_MRUBY
+ dconn_ptr = dconn.get();
+#endif // HAVE_MRUBY
+ if (downstream->attach_downstream_connection(std::move(dconn)) == 0) {
+ break;
+ }
+ }
+
+#ifdef HAVE_MRUBY
+ const auto &group = dconn_ptr->get_downstream_addr_group();
+ if (group) {
+ const auto &dmruby_ctx = group->shared_addr->mruby_ctx;
+
+ if (dmruby_ctx->run_on_request_proc(downstream) != 0) {
+ resp.http_status = 500;
+ return -1;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return 0;
+ }
+ }
+#endif // HAVE_MRUBY
+
+ rv = downstream->push_request_headers();
+
+ if (rv != 0) {
+ return -1;
+ }
+
+ if (faddr->alt_mode != UpstreamAltMode::NONE) {
+ // Normally, we forward expect: 100-continue to backend server,
+ // and let them decide whether responds with 100 Continue or not.
+ // For alternative mode, we have no backend, so just send 100
+ // Continue here to make the client happy.
+ if (downstream->get_expect_100_continue()) {
+ auto output = downstream->get_response_buf();
+ constexpr auto res = StringRef::from_lit("HTTP/1.1 100 Continue\r\n\r\n");
+ output->append(res);
+ handler->signal_write();
+ }
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int htp_bodycb(llhttp_t *htp, const char *data, size_t len) {
+ int rv;
+ auto upstream = static_cast<HttpsUpstream *>(htp->data);
+ auto downstream = upstream->get_downstream();
+ rv = downstream->push_upload_data_chunk(
+ reinterpret_cast<const uint8_t *>(data), len);
+ if (rv != 0) {
+ // Ignore error if response has been completed. We will end up in
+ // htp_msg_completecb, and request will end gracefully.
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return 0;
+ }
+
+ llhttp_set_error_reason(htp, "could not process request body");
+ return HPE_USER;
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+int htp_msg_completecb(llhttp_t *htp) {
+ int rv;
+ auto upstream = static_cast<HttpsUpstream *>(htp->data);
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, upstream) << "HTTP request completed";
+ }
+ auto handler = upstream->get_client_handler();
+ auto downstream = upstream->get_downstream();
+ auto &req = downstream->request();
+ auto &balloc = downstream->get_block_allocator();
+
+ for (auto &kv : req.fs.trailers()) {
+ kv.value = util::rstrip(balloc, kv.value);
+ }
+
+ downstream->set_request_state(DownstreamState::MSG_COMPLETE);
+ rv = downstream->end_upload_data();
+ if (rv != 0) {
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ // Here both response and request were completed. One of the
+ // reason why end_upload_data() failed is when we sent response
+ // in request phase hook. We only delete and proceed to the
+ // next request handling (if we don't close the connection). We
+ // first pause parser here just as we normally do, and call
+ // signal_write() to run on_write().
+ return HPE_PAUSED;
+ }
+ return -1;
+ }
+
+ if (handler->get_http2_upgrade_allowed() &&
+ downstream->get_http2_upgrade_request() &&
+ handler->perform_http2_upgrade(upstream) != 0) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, upstream) << "HTTP Upgrade to HTTP/2 failed";
+ }
+ }
+
+ // Stop further processing to complete this request
+ return HPE_PAUSED;
+}
+} // namespace
+
+// on_read() does not consume all available data in input buffer if
+// one http request is fully received.
+int HttpsUpstream::on_read() {
+ auto rb = handler_->get_rb();
+ auto rlimit = handler_->get_rlimit();
+ auto downstream = get_downstream();
+
+ if (rb->rleft() == 0 || handler_->get_should_close_after_write()) {
+ return 0;
+ }
+
+ // downstream can be nullptr here, because it is initialized in the
+ // callback chain called by llhttp_execute()
+ if (downstream && downstream->get_upgraded()) {
+
+ auto rv = downstream->push_upload_data_chunk(rb->pos(), rb->rleft());
+
+ if (rv != 0) {
+ return -1;
+ }
+
+ rb->reset();
+ rlimit->startw();
+
+ if (downstream->request_buf_full()) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, this) << "Downstream request buf is full";
+ }
+ pause_read(SHRPX_NO_BUFFER);
+
+ return 0;
+ }
+
+ return 0;
+ }
+
+ if (downstream) {
+ // To avoid reading next pipelined request
+ switch (downstream->get_request_state()) {
+ case DownstreamState::INITIAL:
+ case DownstreamState::HEADER_COMPLETE:
+ break;
+ default:
+ return 0;
+ }
+ }
+
+ // llhttp_execute() does nothing once it entered error state.
+ auto htperr = llhttp_execute(&htp_, reinterpret_cast<const char *>(rb->pos()),
+ rb->rleft());
+
+ if (htperr == HPE_PAUSED_UPGRADE &&
+ rb->pos() ==
+ reinterpret_cast<const uint8_t *>(llhttp_get_error_pos(&htp_))) {
+ llhttp_resume_after_upgrade(&htp_);
+
+ htperr = llhttp_execute(&htp_, reinterpret_cast<const char *>(rb->pos()),
+ rb->rleft());
+ }
+
+ auto nread =
+ htperr == HPE_OK
+ ? rb->rleft()
+ : reinterpret_cast<const uint8_t *>(llhttp_get_error_pos(&htp_)) -
+ rb->pos();
+ rb->drain(nread);
+ rlimit->startw();
+
+ // Well, actually header length + some body bytes
+ current_header_length_ += nread;
+
+ // Get downstream again because it may be initialized in http parser
+ // execution
+ downstream = get_downstream();
+
+ if (htperr == HPE_PAUSED) {
+ // We may pause parser in htp_msg_completecb when both side are
+ // completed. Signal write, so that we can run on_write().
+ if (downstream &&
+ downstream->get_request_state() == DownstreamState::MSG_COMPLETE &&
+ downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ handler_->signal_write();
+ }
+ return 0;
+ }
+
+ if (htperr != HPE_OK) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, this) << "HTTP parse failure: "
+ << "(" << llhttp_errno_name(htperr) << ") "
+ << llhttp_get_error_reason(&htp_);
+ }
+
+ if (downstream &&
+ downstream->get_response_state() != DownstreamState::INITIAL) {
+ handler_->set_should_close_after_write(true);
+ handler_->signal_write();
+ return 0;
+ }
+
+ unsigned int status_code;
+
+ if (htperr == HPE_INVALID_METHOD) {
+ status_code = 501;
+ } else if (downstream) {
+ status_code = downstream->response().http_status;
+ if (status_code == 0) {
+ if (downstream->get_request_state() == DownstreamState::CONNECT_FAIL) {
+ status_code = 502;
+ } else if (downstream->get_request_state() ==
+ DownstreamState::HTTP1_REQUEST_HEADER_TOO_LARGE) {
+ status_code = 431;
+ } else {
+ status_code = 400;
+ }
+ }
+ } else {
+ status_code = 400;
+ }
+
+ error_reply(status_code);
+
+ handler_->signal_write();
+
+ return 0;
+ }
+
+ // downstream can be NULL here.
+ if (downstream && downstream->request_buf_full()) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, this) << "Downstream request buffer is full";
+ }
+
+ pause_read(SHRPX_NO_BUFFER);
+
+ return 0;
+ }
+
+ return 0;
+}
+
+int HttpsUpstream::on_write() {
+ auto downstream = get_downstream();
+ if (!downstream) {
+ return 0;
+ }
+
+ auto output = downstream->get_response_buf();
+ const auto &resp = downstream->response();
+
+ if (output->rleft() > 0) {
+ return 0;
+ }
+
+ // 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() == DownstreamState::MSG_COMPLETE) {
+ if (downstream->can_detach_downstream_connection()) {
+ // Keep-alive
+ downstream->detach_downstream_connection();
+ } else {
+ // Connection close
+ downstream->pop_downstream_connection();
+ // dconn was deleted
+ }
+ // We need this if response ends before request.
+ if (downstream->get_request_state() == DownstreamState::MSG_COMPLETE) {
+ delete_downstream();
+
+ if (handler_->get_should_close_after_write()) {
+ return 0;
+ }
+
+ auto conn = handler_->get_connection();
+ auto &upstreamconf = get_config()->conn.upstream;
+
+ conn->rt.repeat = upstreamconf.timeout.idle_read;
+
+ handler_->repeat_read_timer();
+
+ return resume_read(SHRPX_NO_BUFFER, nullptr, 0);
+ } else {
+ // If the request is not complete, close the connection.
+ delete_downstream();
+
+ handler_->set_should_close_after_write(true);
+
+ return 0;
+ }
+ }
+
+ return downstream->resume_read(SHRPX_NO_BUFFER, resp.unconsumed_body_length);
+}
+
+int HttpsUpstream::on_event() { return 0; }
+
+ClientHandler *HttpsUpstream::get_client_handler() const { return handler_; }
+
+void HttpsUpstream::pause_read(IOCtrlReason reason) {
+ ioctrl_.pause_read(reason);
+}
+
+int HttpsUpstream::resume_read(IOCtrlReason reason, Downstream *downstream,
+ size_t consumed) {
+ // downstream could be nullptr
+ if (downstream && downstream->request_buf_full()) {
+ return 0;
+ }
+ if (ioctrl_.resume_read(reason)) {
+ // Process remaining data in input buffer here because these bytes
+ // are not notified by readcb until new data arrive.
+ llhttp_resume(&htp_);
+
+ auto conn = handler_->get_connection();
+ ev_feed_event(conn->loop, &conn->rev, EV_READ);
+ return 0;
+ }
+
+ return 0;
+}
+
+int HttpsUpstream::downstream_read(DownstreamConnection *dconn) {
+ auto downstream = dconn->get_downstream();
+ int rv;
+
+ rv = downstream->on_read();
+
+ if (rv == SHRPX_ERR_EOF) {
+ if (downstream->get_request_header_sent()) {
+ return downstream_eof(dconn);
+ }
+ return SHRPX_ERR_RETRY;
+ }
+
+ if (rv == SHRPX_ERR_DCONN_CANCELED) {
+ downstream->pop_downstream_connection();
+ goto end;
+ }
+
+ if (rv < 0) {
+ return downstream_error(dconn, Downstream::EVENT_ERROR);
+ }
+
+ if (downstream->get_response_state() == DownstreamState::MSG_RESET) {
+ return -1;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::MSG_BAD_HEADER) {
+ error_reply(502);
+ downstream->pop_downstream_connection();
+ goto end;
+ }
+
+ if (downstream->can_detach_downstream_connection()) {
+ // Keep-alive
+ downstream->detach_downstream_connection();
+ }
+
+end:
+ handler_->signal_write();
+
+ return 0;
+}
+
+int HttpsUpstream::downstream_write(DownstreamConnection *dconn) {
+ int rv;
+ rv = dconn->on_write();
+ if (rv == SHRPX_ERR_NETWORK) {
+ return downstream_error(dconn, Downstream::EVENT_ERROR);
+ }
+
+ if (rv != 0) {
+ return rv;
+ }
+
+ return 0;
+}
+
+int HttpsUpstream::downstream_eof(DownstreamConnection *dconn) {
+ auto downstream = dconn->get_downstream();
+
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, dconn) << "EOF";
+ }
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ goto end;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::HEADER_COMPLETE) {
+ // Server may indicate the end of the request by EOF
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, dconn) << "The end of the response body was indicated by "
+ << "EOF";
+ }
+ on_downstream_body_complete(downstream);
+ downstream->set_response_state(DownstreamState::MSG_COMPLETE);
+ downstream->pop_downstream_connection();
+ goto end;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::INITIAL) {
+ // we did not send any response headers, so we can reply error
+ // message.
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, dconn) << "Return error reply";
+ }
+ error_reply(502);
+ downstream->pop_downstream_connection();
+ goto end;
+ }
+
+ // Otherwise, we don't know how to recover from this situation. Just
+ // drop connection.
+ return -1;
+end:
+ handler_->signal_write();
+
+ return 0;
+}
+
+int HttpsUpstream::downstream_error(DownstreamConnection *dconn, int events) {
+ auto downstream = dconn->get_downstream();
+ if (LOG_ENABLED(INFO)) {
+ if (events & Downstream::EVENT_ERROR) {
+ DCLOG(INFO, dconn) << "Network error/general error";
+ } else {
+ DCLOG(INFO, dconn) << "Timeout";
+ }
+ }
+ if (downstream->get_response_state() != DownstreamState::INITIAL) {
+ return -1;
+ }
+
+ unsigned int status;
+ if (events & Downstream::EVENT_TIMEOUT) {
+ if (downstream->get_request_header_sent()) {
+ status = 504;
+ } else {
+ status = 408;
+ }
+ } else {
+ status = 502;
+ }
+ error_reply(status);
+
+ downstream->pop_downstream_connection();
+
+ handler_->signal_write();
+ return 0;
+}
+
+int HttpsUpstream::send_reply(Downstream *downstream, const uint8_t *body,
+ size_t bodylen) {
+ const auto &req = downstream->request();
+ auto &resp = downstream->response();
+ auto &balloc = downstream->get_block_allocator();
+ auto config = get_config();
+ auto &httpconf = config->http;
+
+ auto connection_close = false;
+
+ auto worker = handler_->get_worker();
+
+ if (httpconf.max_requests <= num_requests_ ||
+ worker->get_graceful_shutdown()) {
+ resp.fs.add_header_token(StringRef::from_lit("connection"),
+ StringRef::from_lit("close"), false,
+ http2::HD_CONNECTION);
+ connection_close = true;
+ } else if (req.http_major <= 0 ||
+ (req.http_major == 1 && req.http_minor == 0)) {
+ connection_close = true;
+ } else {
+ auto c = resp.fs.header(http2::HD_CONNECTION);
+ if (c && util::strieq_l("close", c->value)) {
+ connection_close = true;
+ }
+ }
+
+ if (connection_close) {
+ resp.connection_close = true;
+ handler_->set_should_close_after_write(true);
+ }
+
+ auto output = downstream->get_response_buf();
+
+ output->append("HTTP/1.1 ");
+ output->append(http2::stringify_status(balloc, resp.http_status));
+ output->append(' ');
+ output->append(http2::get_reason_phrase(resp.http_status));
+ output->append("\r\n");
+
+ for (auto &kv : resp.fs.headers()) {
+ if (kv.name.empty() || kv.name[0] == ':') {
+ continue;
+ }
+ http2::capitalize(output, kv.name);
+ output->append(": ");
+ output->append(kv.value);
+ output->append("\r\n");
+ }
+
+ if (!resp.fs.header(http2::HD_SERVER)) {
+ output->append("Server: ");
+ output->append(config->http.server_name);
+ output->append("\r\n");
+ }
+
+ for (auto &p : httpconf.add_response_headers) {
+ output->append(p.name);
+ output->append(": ");
+ output->append(p.value);
+ output->append("\r\n");
+ }
+
+ output->append("\r\n");
+
+ output->append(body, bodylen);
+
+ downstream->response_sent_body_length += bodylen;
+ downstream->set_response_state(DownstreamState::MSG_COMPLETE);
+
+ return 0;
+}
+
+void HttpsUpstream::error_reply(unsigned int status_code) {
+ auto downstream = get_downstream();
+
+ if (!downstream) {
+ attach_downstream(
+ std::make_unique<Downstream>(this, handler_->get_mcpool(), 1));
+ downstream = get_downstream();
+ }
+
+ auto &resp = downstream->response();
+ auto &balloc = downstream->get_block_allocator();
+
+ auto html = http::create_error_html(balloc, status_code);
+
+ resp.http_status = status_code;
+ // we are going to close connection for both frontend and backend in
+ // error condition. This is safest option.
+ resp.connection_close = true;
+ handler_->set_should_close_after_write(true);
+
+ auto output = downstream->get_response_buf();
+
+ output->append("HTTP/1.1 ");
+ output->append(http2::stringify_status(balloc, status_code));
+ output->append(' ');
+ output->append(http2::get_reason_phrase(status_code));
+ output->append("\r\nServer: ");
+ output->append(get_config()->http.server_name);
+ output->append("\r\nContent-Length: ");
+ std::array<uint8_t, NGHTTP2_MAX_UINT64_DIGITS> intbuf;
+ output->append(StringRef{std::begin(intbuf),
+ util::utos(std::begin(intbuf), html.size())});
+ output->append("\r\nDate: ");
+ auto lgconf = log_config();
+ lgconf->update_tstamp(std::chrono::system_clock::now());
+ output->append(lgconf->tstamp->time_http);
+ output->append("\r\nContent-Type: text/html; "
+ "charset=UTF-8\r\nConnection: close\r\n\r\n");
+ output->append(html);
+
+ downstream->response_sent_body_length += html.size();
+ downstream->set_response_state(DownstreamState::MSG_COMPLETE);
+}
+
+void HttpsUpstream::attach_downstream(std::unique_ptr<Downstream> downstream) {
+ assert(!downstream_);
+ downstream_ = std::move(downstream);
+}
+
+void HttpsUpstream::delete_downstream() {
+ if (downstream_ && downstream_->accesslog_ready()) {
+ handler_->write_accesslog(downstream_.get());
+ }
+
+ downstream_.reset();
+}
+
+Downstream *HttpsUpstream::get_downstream() const { return downstream_.get(); }
+
+std::unique_ptr<Downstream> HttpsUpstream::pop_downstream() {
+ return std::unique_ptr<Downstream>(downstream_.release());
+}
+
+int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
+ if (LOG_ENABLED(INFO)) {
+ if (downstream->get_non_final_response()) {
+ DLOG(INFO, downstream) << "HTTP non-final response header";
+ } else {
+ DLOG(INFO, downstream) << "HTTP response header completed";
+ }
+ }
+
+ const auto &req = downstream->request();
+ auto &resp = downstream->response();
+ auto &balloc = downstream->get_block_allocator();
+ auto dconn = downstream->get_downstream_connection();
+ // dconn might be nullptr if this is non-final response from mruby.
+
+ if (downstream->get_non_final_response() &&
+ !downstream->supports_non_final_response()) {
+ resp.fs.clear_headers();
+ return 0;
+ }
+
+#ifdef HAVE_MRUBY
+ if (!downstream->get_non_final_response()) {
+ assert(dconn);
+ const auto &group = dconn->get_downstream_addr_group();
+ if (group) {
+ const auto &dmruby_ctx = group->shared_addr->mruby_ctx;
+
+ if (dmruby_ctx->run_on_response_proc(downstream) != 0) {
+ error_reply(500);
+ return -1;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return -1;
+ }
+ }
+
+ auto worker = handler_->get_worker();
+ auto mruby_ctx = worker->get_mruby_context();
+
+ if (mruby_ctx->run_on_response_proc(downstream) != 0) {
+ error_reply(500);
+ return -1;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return -1;
+ }
+ }
+#endif // HAVE_MRUBY
+
+ auto connect_method = req.method == HTTP_CONNECT;
+
+ auto buf = downstream->get_response_buf();
+ buf->append("HTTP/");
+ buf->append('0' + req.http_major);
+ buf->append('.');
+ buf->append('0' + req.http_minor);
+ buf->append(' ');
+ if (req.connect_proto != ConnectProto::NONE && downstream->get_upgraded()) {
+ buf->append(http2::stringify_status(balloc, 101));
+ buf->append(' ');
+ buf->append(http2::get_reason_phrase(101));
+ } else {
+ buf->append(http2::stringify_status(balloc, resp.http_status));
+ buf->append(' ');
+ buf->append(http2::get_reason_phrase(resp.http_status));
+ }
+ buf->append("\r\n");
+
+ auto config = get_config();
+ auto &httpconf = config->http;
+
+ if (!config->http2_proxy && !httpconf.no_location_rewrite) {
+ downstream->rewrite_location_response_header(
+ get_client_handler()->get_upstream_scheme());
+ }
+
+ if (downstream->get_non_final_response()) {
+ http2::build_http1_headers_from_headers(buf, resp.fs.headers(),
+ http2::HDOP_STRIP_ALL);
+
+ buf->append("\r\n");
+
+ if (LOG_ENABLED(INFO)) {
+ log_response_headers(buf);
+ }
+
+ resp.fs.clear_headers();
+
+ return 0;
+ }
+
+ auto build_flags = (http2::HDOP_STRIP_ALL & ~http2::HDOP_STRIP_VIA) |
+ (!http2::legacy_http1(req.http_major, req.http_minor)
+ ? 0
+ : http2::HDOP_STRIP_TRANSFER_ENCODING);
+
+ http2::build_http1_headers_from_headers(buf, resp.fs.headers(), build_flags);
+
+ auto worker = handler_->get_worker();
+
+ // after graceful shutdown commenced, add connection: close header
+ // field.
+ if (httpconf.max_requests <= num_requests_ ||
+ worker->get_graceful_shutdown()) {
+ resp.connection_close = true;
+ }
+
+ // We check downstream->get_response_connection_close() in case when
+ // the Content-Length is not available.
+ if (!req.connection_close && !resp.connection_close) {
+ if (req.http_major <= 0 || req.http_minor <= 0) {
+ // We add this header for HTTP/1.0 or HTTP/0.9 clients
+ buf->append("Connection: Keep-Alive\r\n");
+ }
+ } else if (!downstream->get_upgraded()) {
+ buf->append("Connection: close\r\n");
+ }
+
+ if (!connect_method && downstream->get_upgraded()) {
+ if (req.connect_proto == ConnectProto::WEBSOCKET &&
+ resp.http_status / 100 == 2) {
+ buf->append("Upgrade: websocket\r\nConnection: Upgrade\r\n");
+ auto key = req.fs.header(http2::HD_SEC_WEBSOCKET_KEY);
+ if (!key || key->value.size() != base64::encode_length(16)) {
+ return -1;
+ }
+ std::array<uint8_t, base64::encode_length(20)> out;
+ auto accept = http2::make_websocket_accept_token(out.data(), key->value);
+ if (accept.empty()) {
+ return -1;
+ }
+ buf->append("Sec-WebSocket-Accept: ");
+ buf->append(accept);
+ buf->append("\r\n");
+ } else {
+ auto connection = resp.fs.header(http2::HD_CONNECTION);
+ if (connection) {
+ buf->append("Connection: ");
+ buf->append((*connection).value);
+ buf->append("\r\n");
+ }
+
+ auto upgrade = resp.fs.header(http2::HD_UPGRADE);
+ if (upgrade) {
+ buf->append("Upgrade: ");
+ buf->append((*upgrade).value);
+ buf->append("\r\n");
+ }
+ }
+ }
+
+ if (!resp.fs.header(http2::HD_ALT_SVC)) {
+ // We won't change or alter alt-svc from backend for now
+ if (!httpconf.altsvcs.empty()) {
+ buf->append("Alt-Svc: ");
+ buf->append(httpconf.altsvc_header_value);
+ buf->append("\r\n");
+ }
+ }
+
+ if (!config->http2_proxy && !httpconf.no_server_rewrite) {
+ buf->append("Server: ");
+ buf->append(httpconf.server_name);
+ buf->append("\r\n");
+ } else {
+ auto server = resp.fs.header(http2::HD_SERVER);
+ if (server) {
+ buf->append("Server: ");
+ buf->append((*server).value);
+ buf->append("\r\n");
+ }
+ }
+
+ if (req.method != HTTP_CONNECT || !downstream->get_upgraded()) {
+ auto affinity_cookie = downstream->get_affinity_cookie_to_send();
+ if (affinity_cookie) {
+ auto &group = dconn->get_downstream_addr_group();
+ auto &shared_addr = group->shared_addr;
+ auto &cookieconf = shared_addr->affinity.cookie;
+ auto secure =
+ http::require_cookie_secure_attribute(cookieconf.secure, req.scheme);
+ auto cookie_str = http::create_affinity_cookie(
+ balloc, cookieconf.name, affinity_cookie, cookieconf.path, secure);
+ buf->append("Set-Cookie: ");
+ buf->append(cookie_str);
+ buf->append("\r\n");
+ }
+ }
+
+ auto via = resp.fs.header(http2::HD_VIA);
+ if (httpconf.no_via) {
+ if (via) {
+ buf->append("Via: ");
+ buf->append((*via).value);
+ buf->append("\r\n");
+ }
+ } else {
+ buf->append("Via: ");
+ if (via) {
+ buf->append((*via).value);
+ buf->append(", ");
+ }
+ std::array<char, 16> viabuf;
+ auto end = http::create_via_header_value(viabuf.data(), resp.http_major,
+ resp.http_minor);
+ buf->append(viabuf.data(), end - std::begin(viabuf));
+ buf->append("\r\n");
+ }
+
+ for (auto &p : httpconf.add_response_headers) {
+ buf->append(p.name);
+ buf->append(": ");
+ buf->append(p.value);
+ buf->append("\r\n");
+ }
+
+ buf->append("\r\n");
+
+ if (LOG_ENABLED(INFO)) {
+ log_response_headers(buf);
+ }
+
+ return 0;
+}
+
+int HttpsUpstream::on_downstream_body(Downstream *downstream,
+ const uint8_t *data, size_t len,
+ bool flush) {
+ if (len == 0) {
+ return 0;
+ }
+ auto output = downstream->get_response_buf();
+ if (downstream->get_chunked_response()) {
+ output->append(util::utox(len));
+ output->append("\r\n");
+ }
+ output->append(data, len);
+
+ downstream->response_sent_body_length += len;
+
+ if (downstream->get_chunked_response()) {
+ output->append("\r\n");
+ }
+ return 0;
+}
+
+int HttpsUpstream::on_downstream_body_complete(Downstream *downstream) {
+ const auto &req = downstream->request();
+ auto &resp = downstream->response();
+
+ if (downstream->get_chunked_response()) {
+ auto output = downstream->get_response_buf();
+ const auto &trailers = resp.fs.trailers();
+ if (trailers.empty()) {
+ output->append("0\r\n\r\n");
+ } else {
+ output->append("0\r\n");
+ http2::build_http1_headers_from_headers(output, trailers,
+ http2::HDOP_STRIP_ALL);
+ output->append("\r\n");
+ }
+ }
+ if (LOG_ENABLED(INFO)) {
+ DLOG(INFO, downstream) << "HTTP response completed";
+ }
+
+ if (!downstream->validate_response_recv_body_length()) {
+ resp.connection_close = true;
+ }
+
+ if (req.connection_close || resp.connection_close ||
+ // To avoid to stall upload body
+ downstream->get_request_state() != DownstreamState::MSG_COMPLETE) {
+ auto handler = get_client_handler();
+ handler->set_should_close_after_write(true);
+ }
+ return 0;
+}
+
+int HttpsUpstream::on_downstream_abort_request(Downstream *downstream,
+ unsigned int status_code) {
+ error_reply(status_code);
+ handler_->signal_write();
+ return 0;
+}
+
+int HttpsUpstream::on_downstream_abort_request_with_https_redirect(
+ Downstream *downstream) {
+ redirect_to_https(downstream);
+ handler_->signal_write();
+ return 0;
+}
+
+int HttpsUpstream::redirect_to_https(Downstream *downstream) {
+ auto &req = downstream->request();
+ if (req.method == HTTP_CONNECT || req.scheme != "http" ||
+ req.authority.empty()) {
+ error_reply(400);
+ return 0;
+ }
+
+ auto authority = util::extract_host(req.authority);
+ if (authority.empty()) {
+ error_reply(400);
+ return 0;
+ }
+
+ auto &balloc = downstream->get_block_allocator();
+ auto config = get_config();
+ auto &httpconf = config->http;
+
+ StringRef loc;
+ if (httpconf.redirect_https_port == StringRef::from_lit("443")) {
+ loc = concat_string_ref(balloc, StringRef::from_lit("https://"), authority,
+ req.path);
+ } else {
+ loc = concat_string_ref(balloc, StringRef::from_lit("https://"), authority,
+ StringRef::from_lit(":"),
+ httpconf.redirect_https_port, req.path);
+ }
+
+ auto &resp = downstream->response();
+ resp.http_status = 308;
+ resp.fs.add_header_token(StringRef::from_lit("location"), loc, false,
+ http2::HD_LOCATION);
+ resp.fs.add_header_token(StringRef::from_lit("connection"),
+ StringRef::from_lit("close"), false,
+ http2::HD_CONNECTION);
+
+ return send_reply(downstream, nullptr, 0);
+}
+
+void HttpsUpstream::log_response_headers(DefaultMemchunks *buf) const {
+ std::string nhdrs;
+ for (auto chunk = buf->head; chunk; chunk = chunk->next) {
+ nhdrs.append(chunk->pos, chunk->last);
+ }
+ if (log_config()->errorlog_tty) {
+ nhdrs = http::colorizeHeaders(nhdrs.c_str());
+ }
+ ULOG(INFO, this) << "HTTP response headers\n" << nhdrs;
+}
+
+void HttpsUpstream::on_handler_delete() {
+ if (downstream_ && downstream_->accesslog_ready()) {
+ handler_->write_accesslog(downstream_.get());
+ }
+}
+
+int HttpsUpstream::on_downstream_reset(Downstream *downstream, bool no_retry) {
+ int rv;
+ std::unique_ptr<DownstreamConnection> dconn;
+
+ assert(downstream == downstream_.get());
+
+ downstream_->pop_downstream_connection();
+
+ if (!downstream_->request_submission_ready()) {
+ switch (downstream_->get_response_state()) {
+ case DownstreamState::MSG_COMPLETE:
+ // We have got all response body already. Send it off.
+ return 0;
+ case DownstreamState::INITIAL:
+ if (on_downstream_abort_request(downstream_.get(), 502) != 0) {
+ return -1;
+ }
+ return 0;
+ default:
+ break;
+ }
+ // Return error so that caller can delete handler
+ return -1;
+ }
+
+ downstream_->add_retry();
+
+ rv = 0;
+
+ if (no_retry || downstream_->no_more_retry()) {
+ goto fail;
+ }
+
+ for (;;) {
+ auto dconn = handler_->get_downstream_connection(rv, downstream_.get());
+ if (!dconn) {
+ goto fail;
+ }
+
+ rv = downstream_->attach_downstream_connection(std::move(dconn));
+ if (rv == 0) {
+ break;
+ }
+ }
+
+ rv = downstream_->push_request_headers();
+ if (rv != 0) {
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ if (rv == SHRPX_ERR_TLS_REQUIRED) {
+ rv = on_downstream_abort_request_with_https_redirect(downstream);
+ } else {
+ rv = on_downstream_abort_request(downstream_.get(), 502);
+ }
+ if (rv != 0) {
+ return -1;
+ }
+ downstream_->pop_downstream_connection();
+
+ return 0;
+}
+
+int HttpsUpstream::initiate_push(Downstream *downstream, const StringRef &uri) {
+ return 0;
+}
+
+int HttpsUpstream::response_riovec(struct iovec *iov, int iovcnt) const {
+ if (!downstream_) {
+ return 0;
+ }
+
+ auto buf = downstream_->get_response_buf();
+
+ return buf->riovec(iov, iovcnt);
+}
+
+void HttpsUpstream::response_drain(size_t n) {
+ if (!downstream_) {
+ return;
+ }
+
+ auto buf = downstream_->get_response_buf();
+
+ buf->drain(n);
+}
+
+bool HttpsUpstream::response_empty() const {
+ if (!downstream_) {
+ return true;
+ }
+
+ auto buf = downstream_->get_response_buf();
+
+ return buf->rleft() == 0;
+}
+
+Downstream *
+HttpsUpstream::on_downstream_push_promise(Downstream *downstream,
+ int32_t promised_stream_id) {
+ return nullptr;
+}
+
+int HttpsUpstream::on_downstream_push_promise_complete(
+ Downstream *downstream, Downstream *promised_downstream) {
+ return -1;
+}
+
+bool HttpsUpstream::push_enabled() const { return false; }
+
+void HttpsUpstream::cancel_premature_downstream(
+ Downstream *promised_downstream) {}
+
+} // namespace shrpx
diff --git a/src/shrpx_https_upstream.h b/src/shrpx_https_upstream.h
new file mode 100644
index 0000000..d85d2de
--- /dev/null
+++ b/src/shrpx_https_upstream.h
@@ -0,0 +1,113 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_HTTPS_UPSTREAM_H
+#define SHRPX_HTTPS_UPSTREAM_H
+
+#include "shrpx.h"
+
+#include <cinttypes>
+#include <memory>
+
+#include "llhttp.h"
+
+#include "shrpx_upstream.h"
+#include "memchunk.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+class ClientHandler;
+
+class HttpsUpstream : public Upstream {
+public:
+ HttpsUpstream(ClientHandler *handler);
+ virtual ~HttpsUpstream();
+ virtual int on_read();
+ virtual int on_write();
+ virtual int on_event();
+ virtual int on_downstream_abort_request(Downstream *downstream,
+ unsigned int status_code);
+ virtual int
+ on_downstream_abort_request_with_https_redirect(Downstream *downstream);
+ virtual ClientHandler *get_client_handler() const;
+
+ virtual int downstream_read(DownstreamConnection *dconn);
+ virtual int downstream_write(DownstreamConnection *dconn);
+ virtual int downstream_eof(DownstreamConnection *dconn);
+ virtual int downstream_error(DownstreamConnection *dconn, int events);
+
+ void attach_downstream(std::unique_ptr<Downstream> downstream);
+ void delete_downstream();
+ Downstream *get_downstream() const;
+ std::unique_ptr<Downstream> pop_downstream();
+ void error_reply(unsigned int status_code);
+
+ virtual void pause_read(IOCtrlReason reason);
+ virtual int resume_read(IOCtrlReason reason, Downstream *downstream,
+ size_t consumed);
+
+ virtual int on_downstream_header_complete(Downstream *downstream);
+ virtual int on_downstream_body(Downstream *downstream, const uint8_t *data,
+ size_t len, bool flush);
+ virtual int on_downstream_body_complete(Downstream *downstream);
+
+ virtual void on_handler_delete();
+ virtual int on_downstream_reset(Downstream *downstream, bool no_retry);
+ virtual int send_reply(Downstream *downstream, const uint8_t *body,
+ size_t bodylen);
+ virtual int initiate_push(Downstream *downstream, const StringRef &uri);
+ virtual int response_riovec(struct iovec *iov, int iovcnt) const;
+ virtual void response_drain(size_t n);
+ virtual bool response_empty() const;
+
+ virtual Downstream *on_downstream_push_promise(Downstream *downstream,
+ int32_t promised_stream_id);
+ virtual int
+ on_downstream_push_promise_complete(Downstream *downstream,
+ Downstream *promised_downstream);
+ virtual bool push_enabled() const;
+ virtual void cancel_premature_downstream(Downstream *promised_downstream);
+
+ void reset_current_header_length();
+ void log_response_headers(DefaultMemchunks *buf) const;
+ int redirect_to_https(Downstream *downstream);
+
+ // Called when new request has started.
+ void on_start_request();
+
+private:
+ ClientHandler *handler_;
+ llhttp_t htp_;
+ size_t current_header_length_;
+ std::unique_ptr<Downstream> downstream_;
+ IOControl ioctrl_;
+ // The number of requests seen so far.
+ size_t num_requests_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_HTTPS_UPSTREAM_H
diff --git a/src/shrpx_io_control.cc b/src/shrpx_io_control.cc
new file mode 100644
index 0000000..f43a257
--- /dev/null
+++ b/src/shrpx_io_control.cc
@@ -0,0 +1,66 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_io_control.h"
+
+#include <algorithm>
+
+#include "shrpx_rate_limit.h"
+#include "util.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+IOControl::IOControl(RateLimit *lim) : lim_(lim), rdbits_(0) {}
+
+IOControl::~IOControl() {}
+
+void IOControl::pause_read(IOCtrlReason reason) {
+ rdbits_ |= reason;
+ if (lim_) {
+ lim_->stopw();
+ }
+}
+
+bool IOControl::resume_read(IOCtrlReason reason) {
+ rdbits_ &= ~reason;
+ if (rdbits_ == 0) {
+ if (lim_) {
+ lim_->startw();
+ }
+ return true;
+ }
+
+ return false;
+}
+
+void IOControl::force_resume_read() {
+ rdbits_ = 0;
+ if (lim_) {
+ lim_->startw();
+ }
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_io_control.h b/src/shrpx_io_control.h
new file mode 100644
index 0000000..d427fcb
--- /dev/null
+++ b/src/shrpx_io_control.h
@@ -0,0 +1,58 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_IO_CONTROL_H
+#define SHRPX_IO_CONTROL_H
+
+#include "shrpx.h"
+
+#include <cinttypes>
+#include <vector>
+
+#include <ev.h>
+
+#include "shrpx_rate_limit.h"
+
+namespace shrpx {
+
+enum IOCtrlReason { SHRPX_NO_BUFFER = 1 << 0, SHRPX_MSG_BLOCK = 1 << 1 };
+
+class IOControl {
+public:
+ IOControl(RateLimit *lim);
+ ~IOControl();
+ void pause_read(IOCtrlReason reason);
+ // Returns true if read operation is enabled after this call
+ bool resume_read(IOCtrlReason reason);
+ // Clear all pause flags and enable read
+ void force_resume_read();
+
+private:
+ RateLimit *lim_;
+ uint32_t rdbits_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_IO_CONTROL_H
diff --git a/src/shrpx_live_check.cc b/src/shrpx_live_check.cc
new file mode 100644
index 0000000..9b932cd
--- /dev/null
+++ b/src/shrpx_live_check.cc
@@ -0,0 +1,792 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_live_check.h"
+#include "shrpx_worker.h"
+#include "shrpx_connect_blocker.h"
+#include "shrpx_tls.h"
+#include "shrpx_log.h"
+
+namespace shrpx {
+
+namespace {
+constexpr size_t MAX_BUFFER_SIZE = 4_k;
+} // namespace
+
+namespace {
+void readcb(struct ev_loop *loop, ev_io *w, int revents) {
+ int rv;
+ auto conn = static_cast<Connection *>(w->data);
+ auto live_check = static_cast<LiveCheck *>(conn->data);
+
+ rv = live_check->do_read();
+ if (rv != 0) {
+ live_check->on_failure();
+ return;
+ }
+}
+} // namespace
+
+namespace {
+void writecb(struct ev_loop *loop, ev_io *w, int revents) {
+ int rv;
+ auto conn = static_cast<Connection *>(w->data);
+ auto live_check = static_cast<LiveCheck *>(conn->data);
+
+ rv = live_check->do_write();
+ if (rv != 0) {
+ live_check->on_failure();
+ return;
+ }
+}
+} // namespace
+
+namespace {
+void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto conn = static_cast<Connection *>(w->data);
+ auto live_check = static_cast<LiveCheck *>(conn->data);
+
+ if (w == &conn->rt && !conn->expired_rt()) {
+ return;
+ }
+
+ live_check->on_failure();
+}
+} // namespace
+
+namespace {
+void backoff_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+ int rv;
+ auto live_check = static_cast<LiveCheck *>(w->data);
+
+ rv = live_check->initiate_connection();
+ if (rv != 0) {
+ live_check->on_failure();
+ return;
+ }
+}
+} // namespace
+
+namespace {
+void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto live_check = static_cast<LiveCheck *>(w->data);
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "SETTINGS timeout";
+ }
+
+ live_check->on_failure();
+}
+} // namespace
+
+LiveCheck::LiveCheck(struct ev_loop *loop, SSL_CTX *ssl_ctx, Worker *worker,
+ DownstreamAddr *addr, std::mt19937 &gen)
+ : conn_(loop, -1, nullptr, worker->get_mcpool(),
+ worker->get_downstream_config()->timeout.write,
+ worker->get_downstream_config()->timeout.read, {}, {}, writecb,
+ readcb, timeoutcb, this, get_config()->tls.dyn_rec.warmup_threshold,
+ get_config()->tls.dyn_rec.idle_timeout, Proto::NONE),
+ wb_(worker->get_mcpool()),
+ gen_(gen),
+ read_(&LiveCheck::noop),
+ write_(&LiveCheck::noop),
+ worker_(worker),
+ ssl_ctx_(ssl_ctx),
+ addr_(addr),
+ session_(nullptr),
+ raddr_(nullptr),
+ success_count_(0),
+ fail_count_(0),
+ settings_ack_received_(false),
+ session_closing_(false) {
+ ev_timer_init(&backoff_timer_, backoff_timeoutcb, 0., 0.);
+ backoff_timer_.data = this;
+
+ // SETTINGS ACK must be received in a short timeout. Otherwise, we
+ // assume that connection is broken.
+ ev_timer_init(&settings_timer_, settings_timeout_cb, 0., 0.);
+ settings_timer_.data = this;
+}
+
+LiveCheck::~LiveCheck() {
+ disconnect();
+
+ ev_timer_stop(conn_.loop, &backoff_timer_);
+}
+
+void LiveCheck::disconnect() {
+ if (dns_query_) {
+ auto dns_tracker = worker_->get_dns_tracker();
+
+ dns_tracker->cancel(dns_query_.get());
+ }
+
+ dns_query_.reset();
+ // We can reuse resolved_addr_
+ raddr_ = nullptr;
+
+ conn_.rlimit.stopw();
+ conn_.wlimit.stopw();
+
+ ev_timer_stop(conn_.loop, &settings_timer_);
+
+ read_ = write_ = &LiveCheck::noop;
+
+ conn_.disconnect();
+
+ nghttp2_session_del(session_);
+ session_ = nullptr;
+
+ settings_ack_received_ = false;
+ session_closing_ = false;
+
+ wb_.reset();
+}
+
+// Use the similar backoff algorithm described in
+// https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md
+namespace {
+constexpr size_t MAX_BACKOFF_EXP = 10;
+constexpr auto MULTIPLIER = 1.6;
+constexpr auto JITTER = 0.2;
+} // namespace
+
+void LiveCheck::schedule() {
+ auto base_backoff =
+ util::int_pow(MULTIPLIER, std::min(fail_count_, MAX_BACKOFF_EXP));
+ auto dist = std::uniform_real_distribution<>(-JITTER * base_backoff,
+ JITTER * base_backoff);
+
+ auto &downstreamconf = *get_config()->conn.downstream;
+
+ auto backoff =
+ std::min(downstreamconf.timeout.max_backoff, base_backoff + dist(gen_));
+
+ ev_timer_set(&backoff_timer_, backoff, 0.);
+ ev_timer_start(conn_.loop, &backoff_timer_);
+}
+
+int LiveCheck::do_read() { return read_(*this); }
+
+int LiveCheck::do_write() { return write_(*this); }
+
+int LiveCheck::initiate_connection() {
+ int rv;
+
+ auto worker_blocker = worker_->get_connect_blocker();
+ if (worker_blocker->blocked()) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Worker wide backend connection was blocked temporarily";
+ }
+ return -1;
+ }
+
+ if (!dns_query_ && addr_->tls) {
+ assert(ssl_ctx_);
+
+ auto ssl = tls::create_ssl(ssl_ctx_);
+ if (!ssl) {
+ return -1;
+ }
+
+ switch (addr_->proto) {
+ case Proto::HTTP1:
+ tls::setup_downstream_http1_alpn(ssl);
+ break;
+ case Proto::HTTP2:
+ tls::setup_downstream_http2_alpn(ssl);
+ break;
+ default:
+ assert(0);
+ }
+
+ conn_.set_ssl(ssl);
+ conn_.tls.client_session_cache = &addr_->tls_session_cache;
+ }
+
+ if (addr_->dns) {
+ if (!dns_query_) {
+ auto dns_query = std::make_unique<DNSQuery>(
+ addr_->host, [this](DNSResolverStatus status, const Address *result) {
+ int rv;
+
+ if (status == DNSResolverStatus::OK) {
+ *this->resolved_addr_ = *result;
+ }
+ rv = this->initiate_connection();
+ if (rv != 0) {
+ this->on_failure();
+ }
+ });
+ auto dns_tracker = worker_->get_dns_tracker();
+
+ if (!resolved_addr_) {
+ resolved_addr_ = std::make_unique<Address>();
+ }
+
+ switch (dns_tracker->resolve(resolved_addr_.get(), dns_query.get())) {
+ case DNSResolverStatus::ERROR:
+ return -1;
+ case DNSResolverStatus::RUNNING:
+ dns_query_ = std::move(dns_query);
+ return 0;
+ case DNSResolverStatus::OK:
+ break;
+ default:
+ assert(0);
+ }
+ } else {
+ switch (dns_query_->status) {
+ case DNSResolverStatus::ERROR:
+ dns_query_.reset();
+ return -1;
+ case DNSResolverStatus::OK:
+ dns_query_.reset();
+ break;
+ default:
+ assert(0);
+ }
+ }
+
+ util::set_port(*resolved_addr_, addr_->port);
+ raddr_ = resolved_addr_.get();
+ } else {
+ raddr_ = &addr_->addr;
+ }
+
+ conn_.fd = util::create_nonblock_socket(raddr_->su.storage.ss_family);
+
+ if (conn_.fd == -1) {
+ auto error = errno;
+ LOG(WARN) << "socket() failed; addr=" << util::to_numeric_addr(raddr_)
+ << ", errno=" << error;
+ return -1;
+ }
+
+ rv = connect(conn_.fd, &raddr_->su.sa, raddr_->len);
+ if (rv != 0 && errno != EINPROGRESS) {
+ auto error = errno;
+ LOG(WARN) << "connect() failed; addr=" << util::to_numeric_addr(raddr_)
+ << ", errno=" << error;
+
+ close(conn_.fd);
+ conn_.fd = -1;
+
+ return -1;
+ }
+
+ if (addr_->tls) {
+ auto sni_name =
+ addr_->sni.empty() ? StringRef{addr_->host} : StringRef{addr_->sni};
+ if (!util::numeric_host(sni_name.c_str())) {
+ SSL_set_tlsext_host_name(conn_.tls.ssl, sni_name.c_str());
+ }
+
+ auto session = tls::reuse_tls_session(addr_->tls_session_cache);
+ if (session) {
+ SSL_set_session(conn_.tls.ssl, session);
+ SSL_SESSION_free(session);
+ }
+
+ conn_.prepare_client_handshake();
+ }
+
+ write_ = &LiveCheck::connected;
+
+ ev_io_set(&conn_.wev, conn_.fd, EV_WRITE);
+ ev_io_set(&conn_.rev, conn_.fd, EV_READ);
+
+ conn_.wlimit.startw();
+
+ auto &downstreamconf = *get_config()->conn.downstream;
+
+ conn_.wt.repeat = downstreamconf.timeout.connect;
+ ev_timer_again(conn_.loop, &conn_.wt);
+
+ return 0;
+}
+
+int LiveCheck::connected() {
+ auto sock_error = util::get_socket_error(conn_.fd);
+ if (sock_error != 0) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Backend connect failed; addr="
+ << util::to_numeric_addr(raddr_) << ": errno=" << sock_error;
+ }
+
+ return -1;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Connection established";
+ }
+
+ auto &downstreamconf = *get_config()->conn.downstream;
+
+ // Reset timeout for write. Previously, we set timeout for connect.
+ conn_.wt.repeat = downstreamconf.timeout.write;
+ ev_timer_again(conn_.loop, &conn_.wt);
+
+ conn_.rlimit.startw();
+ conn_.again_rt();
+
+ if (conn_.tls.ssl) {
+ read_ = &LiveCheck::tls_handshake;
+ write_ = &LiveCheck::tls_handshake;
+
+ return do_write();
+ }
+
+ if (addr_->proto == Proto::HTTP2) {
+ // For HTTP/2, we try to read SETTINGS ACK from server to make
+ // sure it is really alive, and serving HTTP/2.
+ read_ = &LiveCheck::read_clear;
+ write_ = &LiveCheck::write_clear;
+
+ if (connection_made() != 0) {
+ return -1;
+ }
+
+ return 0;
+ }
+
+ on_success();
+
+ return 0;
+}
+
+int LiveCheck::tls_handshake() {
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ ERR_clear_error();
+
+ auto rv = conn_.tls_handshake();
+
+ if (rv == SHRPX_ERR_INPROGRESS) {
+ return 0;
+ }
+
+ if (rv < 0) {
+ return rv;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "SSL/TLS handshake completed";
+ }
+
+ if (!get_config()->tls.insecure &&
+ tls::check_cert(conn_.tls.ssl, addr_, raddr_) != 0) {
+ return -1;
+ }
+
+ // Check negotiated ALPN
+
+ const unsigned char *next_proto = nullptr;
+ unsigned int next_proto_len = 0;
+
+ SSL_get0_alpn_selected(conn_.tls.ssl, &next_proto, &next_proto_len);
+
+ auto proto = StringRef{next_proto, next_proto_len};
+
+ switch (addr_->proto) {
+ case Proto::HTTP1:
+ if (proto.empty() || proto == StringRef::from_lit("http/1.1")) {
+ break;
+ }
+ return -1;
+ case Proto::HTTP2:
+ if (util::check_h2_is_selected(proto)) {
+ // For HTTP/2, we try to read SETTINGS ACK from server to make
+ // sure it is really alive, and serving HTTP/2.
+ read_ = &LiveCheck::read_tls;
+ write_ = &LiveCheck::write_tls;
+
+ if (connection_made() != 0) {
+ return -1;
+ }
+
+ return 0;
+ }
+ return -1;
+ default:
+ break;
+ }
+
+ on_success();
+
+ return 0;
+}
+
+int LiveCheck::read_tls() {
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ std::array<uint8_t, 4_k> buf;
+
+ ERR_clear_error();
+
+ for (;;) {
+ auto nread = conn_.read_tls(buf.data(), buf.size());
+
+ if (nread == 0) {
+ return 0;
+ }
+
+ if (nread < 0) {
+ return nread;
+ }
+
+ if (on_read(buf.data(), nread) != 0) {
+ return -1;
+ }
+ }
+}
+
+int LiveCheck::write_tls() {
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ ERR_clear_error();
+
+ struct iovec iov;
+
+ for (;;) {
+ if (wb_.rleft() > 0) {
+ auto iovcnt = wb_.riovec(&iov, 1);
+ if (iovcnt != 1) {
+ assert(0);
+ return -1;
+ }
+ auto nwrite = conn_.write_tls(iov.iov_base, iov.iov_len);
+
+ if (nwrite == 0) {
+ return 0;
+ }
+
+ if (nwrite < 0) {
+ return nwrite;
+ }
+
+ wb_.drain(nwrite);
+
+ continue;
+ }
+
+ if (on_write() != 0) {
+ return -1;
+ }
+
+ if (wb_.rleft() == 0) {
+ conn_.start_tls_write_idle();
+ break;
+ }
+ }
+
+ conn_.wlimit.stopw();
+ ev_timer_stop(conn_.loop, &conn_.wt);
+
+ if (settings_ack_received_) {
+ on_success();
+ }
+
+ return 0;
+}
+
+int LiveCheck::read_clear() {
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ std::array<uint8_t, 4_k> buf;
+
+ for (;;) {
+ auto nread = conn_.read_clear(buf.data(), buf.size());
+
+ if (nread == 0) {
+ return 0;
+ }
+
+ if (nread < 0) {
+ return nread;
+ }
+
+ if (on_read(buf.data(), nread) != 0) {
+ return -1;
+ }
+ }
+}
+
+int LiveCheck::write_clear() {
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ struct iovec iov;
+
+ for (;;) {
+ if (wb_.rleft() > 0) {
+ auto iovcnt = wb_.riovec(&iov, 1);
+ if (iovcnt != 1) {
+ assert(0);
+ return -1;
+ }
+ auto nwrite = conn_.write_clear(iov.iov_base, iov.iov_len);
+
+ if (nwrite == 0) {
+ return 0;
+ }
+
+ if (nwrite < 0) {
+ return nwrite;
+ }
+
+ wb_.drain(nwrite);
+
+ continue;
+ }
+
+ if (on_write() != 0) {
+ return -1;
+ }
+
+ if (wb_.rleft() == 0) {
+ break;
+ }
+ }
+
+ conn_.wlimit.stopw();
+ ev_timer_stop(conn_.loop, &conn_.wt);
+
+ if (settings_ack_received_) {
+ on_success();
+ }
+
+ return 0;
+}
+
+int LiveCheck::on_read(const uint8_t *data, size_t len) {
+ ssize_t rv;
+
+ rv = nghttp2_session_mem_recv(session_, data, len);
+ if (rv < 0) {
+ LOG(ERROR) << "nghttp2_session_mem_recv() returned error: "
+ << nghttp2_strerror(rv);
+ return -1;
+ }
+
+ if (settings_ack_received_ && !session_closing_) {
+ session_closing_ = true;
+ rv = nghttp2_session_terminate_session(session_, NGHTTP2_NO_ERROR);
+ if (rv != 0) {
+ return -1;
+ }
+ }
+
+ if (nghttp2_session_want_read(session_) == 0 &&
+ nghttp2_session_want_write(session_) == 0 && wb_.rleft() == 0) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "No more read/write for this session";
+ }
+
+ // If we have SETTINGS ACK already, we treat this success.
+ if (settings_ack_received_) {
+ return 0;
+ }
+
+ return -1;
+ }
+
+ signal_write();
+
+ return 0;
+}
+
+int LiveCheck::on_write() {
+ for (;;) {
+ const uint8_t *data;
+ auto datalen = nghttp2_session_mem_send(session_, &data);
+
+ if (datalen < 0) {
+ LOG(ERROR) << "nghttp2_session_mem_send() returned error: "
+ << nghttp2_strerror(datalen);
+ return -1;
+ }
+ if (datalen == 0) {
+ break;
+ }
+ wb_.append(data, datalen);
+
+ if (wb_.rleft() >= MAX_BUFFER_SIZE) {
+ break;
+ }
+ }
+
+ if (nghttp2_session_want_read(session_) == 0 &&
+ nghttp2_session_want_write(session_) == 0 && wb_.rleft() == 0) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "No more read/write for this session";
+ }
+
+ if (settings_ack_received_) {
+ return 0;
+ }
+
+ return -1;
+ }
+
+ return 0;
+}
+
+void LiveCheck::on_failure() {
+ ++fail_count_;
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Liveness check for " << addr_->host << ":" << addr_->port
+ << " failed " << fail_count_ << " time(s) in a row";
+ }
+
+ disconnect();
+
+ schedule();
+}
+
+void LiveCheck::on_success() {
+ ++success_count_;
+ fail_count_ = 0;
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Liveness check for " << addr_->host << ":" << addr_->port
+ << " succeeded " << success_count_ << " time(s) in a row";
+ }
+
+ if (success_count_ < addr_->rise) {
+ disconnect();
+
+ schedule();
+
+ return;
+ }
+
+ LOG(NOTICE) << util::to_numeric_addr(&addr_->addr) << " is considered online";
+
+ addr_->connect_blocker->online();
+
+ success_count_ = 0;
+ fail_count_ = 0;
+
+ disconnect();
+}
+
+int LiveCheck::noop() { return 0; }
+
+void LiveCheck::start_settings_timer() {
+ auto &downstreamconf = get_config()->http2.downstream;
+
+ ev_timer_set(&settings_timer_, downstreamconf.timeout.settings, 0.);
+ ev_timer_start(conn_.loop, &settings_timer_);
+}
+
+void LiveCheck::stop_settings_timer() {
+ ev_timer_stop(conn_.loop, &settings_timer_);
+}
+
+void LiveCheck::settings_ack_received() { settings_ack_received_ = true; }
+
+namespace {
+int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame,
+ void *user_data) {
+ auto live_check = static_cast<LiveCheck *>(user_data);
+
+ if (frame->hd.type != NGHTTP2_SETTINGS ||
+ (frame->hd.flags & NGHTTP2_FLAG_ACK)) {
+ return 0;
+ }
+
+ live_check->start_settings_timer();
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame,
+ void *user_data) {
+ auto live_check = static_cast<LiveCheck *>(user_data);
+
+ if (frame->hd.type != NGHTTP2_SETTINGS ||
+ (frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) {
+ return 0;
+ }
+
+ live_check->stop_settings_timer();
+ live_check->settings_ack_received();
+
+ return 0;
+}
+} // namespace
+
+int LiveCheck::connection_made() {
+ int rv;
+
+ nghttp2_session_callbacks *callbacks;
+ rv = nghttp2_session_callbacks_new(&callbacks);
+ if (rv != 0) {
+ return -1;
+ }
+
+ nghttp2_session_callbacks_set_on_frame_send_callback(callbacks,
+ on_frame_send_callback);
+ nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
+ on_frame_recv_callback);
+
+ rv = nghttp2_session_client_new(&session_, callbacks, this);
+
+ nghttp2_session_callbacks_del(callbacks);
+
+ if (rv != 0) {
+ return -1;
+ }
+
+ rv = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, nullptr, 0);
+ if (rv != 0) {
+ return -1;
+ }
+
+ auto must_terminate =
+ addr_->tls && !nghttp2::tls::check_http2_requirement(conn_.tls.ssl);
+
+ if (must_terminate) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "TLSv1.2 was not negotiated. HTTP/2 must not be negotiated.";
+ }
+
+ rv = nghttp2_session_terminate_session(session_,
+ NGHTTP2_INADEQUATE_SECURITY);
+ if (rv != 0) {
+ return -1;
+ }
+ }
+
+ signal_write();
+
+ return 0;
+}
+
+void LiveCheck::signal_write() { conn_.wlimit.startw(); }
+
+} // namespace shrpx
diff --git a/src/shrpx_live_check.h b/src/shrpx_live_check.h
new file mode 100644
index 0000000..b65ecdc
--- /dev/null
+++ b/src/shrpx_live_check.h
@@ -0,0 +1,125 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_LIVE_CHECK_H
+#define SHRPX_LIVE_CHECK_H
+
+#include "shrpx.h"
+
+#include <functional>
+#include <random>
+
+#include <openssl/ssl.h>
+
+#include <ev.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "shrpx_connection.h"
+
+namespace shrpx {
+
+class Worker;
+struct DownstreamAddr;
+struct DNSQuery;
+
+class LiveCheck {
+public:
+ LiveCheck(struct ev_loop *loop, SSL_CTX *ssl_ctx, Worker *worker,
+ DownstreamAddr *addr, std::mt19937 &gen);
+ ~LiveCheck();
+
+ void disconnect();
+
+ void on_success();
+ void on_failure();
+
+ int initiate_connection();
+
+ // Schedules next connection attempt
+ void schedule();
+
+ // Low level I/O operation callback; they are called from do_read()
+ // or do_write().
+ int noop();
+ int connected();
+ int tls_handshake();
+ int read_tls();
+ int write_tls();
+ int read_clear();
+ int write_clear();
+
+ int do_read();
+ int do_write();
+
+ // These functions are used to feed / extract data to
+ // nghttp2_session object.
+ int on_read(const uint8_t *data, size_t len);
+ int on_write();
+
+ // Call this function when HTTP/2 connection was established. We
+ // don't call this function for HTTP/1 at the moment.
+ int connection_made();
+
+ void start_settings_timer();
+ void stop_settings_timer();
+
+ // Call this function when SETTINGS ACK was received from server.
+ void settings_ack_received();
+
+ void signal_write();
+
+private:
+ Connection conn_;
+ DefaultMemchunks wb_;
+ std::mt19937 &gen_;
+ ev_timer backoff_timer_;
+ ev_timer settings_timer_;
+ std::function<int(LiveCheck &)> read_, write_;
+ Worker *worker_;
+ // nullptr if no TLS is configured
+ SSL_CTX *ssl_ctx_;
+ // Address of remote endpoint
+ DownstreamAddr *addr_;
+ nghttp2_session *session_;
+ // Actual remote address used to contact backend. This is initially
+ // nullptr, and may point to either &addr_->addr, or
+ // resolved_addr_.get().
+ const Address *raddr_;
+ // Resolved IP address if dns parameter is used
+ std::unique_ptr<Address> resolved_addr_;
+ std::unique_ptr<DNSQuery> dns_query_;
+ // The number of successful connect attempt in a row.
+ size_t success_count_;
+ // The number of unsuccessful connect attempt in a row.
+ size_t fail_count_;
+ // true when SETTINGS ACK has been received from server.
+ bool settings_ack_received_;
+ // true when GOAWAY has been queued.
+ bool session_closing_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_LIVE_CHECK_H
diff --git a/src/shrpx_log.cc b/src/shrpx_log.cc
new file mode 100644
index 0000000..de5e09f
--- /dev/null
+++ b/src/shrpx_log.cc
@@ -0,0 +1,1008 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_log.h"
+
+#ifdef HAVE_SYSLOG_H
+# include <syslog.h>
+#endif // HAVE_SYSLOG_H
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+#ifdef HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif // HAVE_INTTYPES_H
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifdef HAVE_FCNTL_H
+# include <fcntl.h>
+#endif // HAVE_FCNTL_H
+#include <sys/wait.h>
+
+#include <cerrno>
+#include <cstdio>
+#include <cstring>
+#include <ctime>
+#include <iostream>
+#include <iomanip>
+
+#include "shrpx_config.h"
+#include "shrpx_downstream.h"
+#include "shrpx_worker.h"
+#include "util.h"
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+namespace {
+constexpr StringRef SEVERITY_STR[] = {
+ StringRef::from_lit("INFO"), StringRef::from_lit("NOTICE"),
+ StringRef::from_lit("WARN"), StringRef::from_lit("ERROR"),
+ StringRef::from_lit("FATAL")};
+} // namespace
+
+namespace {
+constexpr const char *SEVERITY_COLOR[] = {
+ "\033[1;32m", // INFO
+ "\033[1;36m", // NOTICE
+ "\033[1;33m", // WARN
+ "\033[1;31m", // ERROR
+ "\033[1;35m", // FATAL
+};
+} // namespace
+
+#ifndef NOTHREADS
+# ifdef HAVE_THREAD_LOCAL
+namespace {
+thread_local LogBuffer logbuf_;
+} // namespace
+
+namespace {
+LogBuffer *get_logbuf() { return &logbuf_; }
+} // namespace
+# else // !HAVE_THREAD_LOCAL
+namespace {
+pthread_key_t lckey;
+pthread_once_t lckey_once = PTHREAD_ONCE_INIT;
+} // namespace
+
+namespace {
+void make_key() { pthread_key_create(&lckey, nullptr); }
+} // namespace
+
+LogBuffer *get_logbuf() {
+ pthread_once(&lckey_once, make_key);
+ auto buf = static_cast<LogBuffer *>(pthread_getspecific(lckey));
+ if (!buf) {
+ buf = new LogBuffer();
+ pthread_setspecific(lckey, buf);
+ }
+ return buf;
+}
+# endif // !HAVE_THREAD_LOCAL
+#else // NOTHREADS
+namespace {
+LogBuffer *get_logbuf() {
+ static LogBuffer logbuf;
+ return &logbuf;
+}
+} // namespace
+#endif // NOTHREADS
+
+int Log::severity_thres_ = NOTICE;
+
+void Log::set_severity_level(int severity) { severity_thres_ = severity; }
+
+int Log::get_severity_level_by_name(const StringRef &name) {
+ for (size_t i = 0, max = array_size(SEVERITY_STR); i < max; ++i) {
+ if (name == SEVERITY_STR[i]) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+int severity_to_syslog_level(int severity) {
+ switch (severity) {
+ case (INFO):
+ return LOG_INFO;
+ case (NOTICE):
+ return LOG_NOTICE;
+ case (WARN):
+ return LOG_WARNING;
+ case (ERROR):
+ return LOG_ERR;
+ case (FATAL):
+ return LOG_CRIT;
+ default:
+ return -1;
+ }
+}
+
+Log::Log(int severity, const char *filename, int linenum)
+ : buf_(*get_logbuf()),
+ begin_(buf_.data()),
+ end_(begin_ + buf_.size()),
+ last_(begin_),
+ filename_(filename),
+ flags_(0),
+ severity_(severity),
+ linenum_(linenum),
+ full_(false) {}
+
+Log::~Log() {
+ int rv;
+ auto config = get_config();
+
+ if (!config) {
+ return;
+ }
+
+ auto lgconf = log_config();
+
+ auto &errorconf = config->logging.error;
+
+ if (!log_enabled(severity_) ||
+ (lgconf->errorlog_fd == -1 && !errorconf.syslog)) {
+ return;
+ }
+
+ if (errorconf.syslog) {
+ if (severity_ == NOTICE) {
+ syslog(severity_to_syslog_level(severity_), "[%s] %.*s",
+ SEVERITY_STR[severity_].c_str(), static_cast<int>(rleft()),
+ begin_);
+ } else {
+ syslog(severity_to_syslog_level(severity_), "[%s] %.*s (%s:%d)",
+ SEVERITY_STR[severity_].c_str(), static_cast<int>(rleft()), begin_,
+ filename_, linenum_);
+ }
+
+ return;
+ }
+
+ char buf[4_k];
+ auto tty = lgconf->errorlog_tty;
+
+ lgconf->update_tstamp_millis(std::chrono::system_clock::now());
+
+ // Error log format: <datetime> <main-pid> <current-pid>
+ // <thread-id> <level> (<filename>:<line>) <msg>
+ rv = snprintf(buf, sizeof(buf), "%s %d %d %s %s%s%s (%s:%d) %.*s\n",
+ lgconf->tstamp->time_iso8601.c_str(), config->pid, lgconf->pid,
+ lgconf->thread_id.c_str(), tty ? SEVERITY_COLOR[severity_] : "",
+ SEVERITY_STR[severity_].c_str(), tty ? "\033[0m" : "",
+ filename_, linenum_, static_cast<int>(rleft()), begin_);
+
+ if (rv < 0) {
+ return;
+ }
+
+ auto nwrite = std::min(static_cast<size_t>(rv), sizeof(buf) - 1);
+
+ while (write(lgconf->errorlog_fd, buf, nwrite) == -1 && errno == EINTR)
+ ;
+}
+
+Log &Log::operator<<(const std::string &s) {
+ write_seq(std::begin(s), std::end(s));
+ return *this;
+}
+
+Log &Log::operator<<(const StringRef &s) {
+ write_seq(std::begin(s), std::end(s));
+ return *this;
+}
+
+Log &Log::operator<<(const char *s) {
+ write_seq(s, s + strlen(s));
+ return *this;
+}
+
+Log &Log::operator<<(const ImmutableString &s) {
+ write_seq(std::begin(s), std::end(s));
+ return *this;
+}
+
+Log &Log::operator<<(long long n) {
+ if (n >= 0) {
+ return *this << static_cast<uint64_t>(n);
+ }
+
+ if (flags_ & fmt_hex) {
+ write_hex(n);
+ return *this;
+ }
+
+ if (full_) {
+ return *this;
+ }
+
+ n *= -1;
+
+ size_t nlen = 0;
+ for (auto t = n; t; t /= 10, ++nlen)
+ ;
+ if (wleft() < 1 /* sign */ + nlen) {
+ full_ = true;
+ return *this;
+ }
+ *last_++ = '-';
+ last_ += nlen;
+ update_full();
+
+ auto p = last_ - 1;
+ for (; n; n /= 10) {
+ *p-- = (n % 10) + '0';
+ }
+ return *this;
+}
+
+Log &Log::operator<<(unsigned long long n) {
+ if (flags_ & fmt_hex) {
+ write_hex(n);
+ return *this;
+ }
+
+ if (full_) {
+ return *this;
+ }
+
+ if (n == 0) {
+ *last_++ = '0';
+ update_full();
+ return *this;
+ }
+ size_t nlen = 0;
+ for (auto t = n; t; t /= 10, ++nlen)
+ ;
+ if (wleft() < nlen) {
+ full_ = true;
+ return *this;
+ }
+
+ last_ += nlen;
+ update_full();
+
+ auto p = last_ - 1;
+ for (; n; n /= 10) {
+ *p-- = (n % 10) + '0';
+ }
+ return *this;
+}
+
+Log &Log::operator<<(double n) {
+ if (full_) {
+ return *this;
+ }
+
+ auto left = wleft();
+ auto rv = snprintf(reinterpret_cast<char *>(last_), left, "%.9f", n);
+ if (rv > static_cast<int>(left)) {
+ full_ = true;
+ return *this;
+ }
+
+ last_ += rv;
+ update_full();
+
+ return *this;
+}
+
+Log &Log::operator<<(long double n) {
+ if (full_) {
+ return *this;
+ }
+
+ auto left = wleft();
+ auto rv = snprintf(reinterpret_cast<char *>(last_), left, "%.9Lf", n);
+ if (rv > static_cast<int>(left)) {
+ full_ = true;
+ return *this;
+ }
+
+ last_ += rv;
+ update_full();
+
+ return *this;
+}
+
+Log &Log::operator<<(bool n) {
+ if (full_) {
+ return *this;
+ }
+
+ *last_++ = n ? '1' : '0';
+ update_full();
+
+ return *this;
+}
+
+Log &Log::operator<<(const void *p) {
+ if (full_) {
+ return *this;
+ }
+
+ write_hex(reinterpret_cast<uintptr_t>(p));
+
+ return *this;
+}
+
+namespace log {
+void hex(Log &log) { log.set_flags(Log::fmt_hex); };
+
+void dec(Log &log) { log.set_flags(Log::fmt_dec); };
+} // namespace log
+
+namespace {
+template <typename OutputIterator>
+std::pair<OutputIterator, OutputIterator> copy(const char *src, size_t srclen,
+ OutputIterator d_first,
+ OutputIterator d_last) {
+ auto nwrite =
+ std::min(static_cast<size_t>(std::distance(d_first, d_last)), srclen);
+ return std::make_pair(std::copy_n(src, nwrite, d_first), d_last);
+}
+} // namespace
+
+namespace {
+template <typename OutputIterator>
+std::pair<OutputIterator, OutputIterator>
+copy(const char *src, OutputIterator d_first, OutputIterator d_last) {
+ return copy(src, strlen(src), d_first, d_last);
+}
+} // namespace
+
+namespace {
+template <typename OutputIterator>
+std::pair<OutputIterator, OutputIterator>
+copy(const StringRef &src, OutputIterator d_first, OutputIterator d_last) {
+ return copy(src.c_str(), src.size(), d_first, d_last);
+}
+} // namespace
+
+namespace {
+template <size_t N, typename OutputIterator>
+std::pair<OutputIterator, OutputIterator>
+copy_l(const char (&src)[N], OutputIterator d_first, OutputIterator d_last) {
+ return copy(src, N - 1, d_first, d_last);
+}
+} // namespace
+
+namespace {
+template <typename OutputIterator>
+std::pair<OutputIterator, OutputIterator> copy(char c, OutputIterator d_first,
+ OutputIterator d_last) {
+ if (d_first == d_last) {
+ return std::make_pair(d_last, d_last);
+ }
+ *d_first++ = c;
+ return std::make_pair(d_first, d_last);
+}
+} // namespace
+
+namespace {
+constexpr char LOWER_XDIGITS[] = "0123456789abcdef";
+} // namespace
+
+namespace {
+template <typename OutputIterator>
+std::pair<OutputIterator, OutputIterator>
+copy_hex_low(const uint8_t *src, size_t srclen, OutputIterator d_first,
+ OutputIterator d_last) {
+ auto nwrite = std::min(static_cast<size_t>(std::distance(d_first, d_last)),
+ srclen * 2) /
+ 2;
+ for (size_t i = 0; i < nwrite; ++i) {
+ *d_first++ = LOWER_XDIGITS[src[i] >> 4];
+ *d_first++ = LOWER_XDIGITS[src[i] & 0xf];
+ }
+ return std::make_pair(d_first, d_last);
+}
+} // namespace
+
+namespace {
+template <typename OutputIterator, typename T>
+std::pair<OutputIterator, OutputIterator> copy(T n, OutputIterator d_first,
+ OutputIterator d_last) {
+ if (static_cast<size_t>(std::distance(d_first, d_last)) <
+ NGHTTP2_MAX_UINT64_DIGITS) {
+ return std::make_pair(d_last, d_last);
+ }
+ return std::make_pair(util::utos(d_first, n), d_last);
+}
+} // namespace
+
+namespace {
+// 1 means that character must be escaped as "\xNN", where NN is ascii
+// code of the character in hex notation.
+constexpr uint8_t ESCAPE_TBL[] = {
+ 1 /* NUL */, 1 /* SOH */, 1 /* STX */, 1 /* ETX */, 1 /* EOT */,
+ 1 /* ENQ */, 1 /* ACK */, 1 /* BEL */, 1 /* BS */, 1 /* HT */,
+ 1 /* LF */, 1 /* VT */, 1 /* FF */, 1 /* CR */, 1 /* SO */,
+ 1 /* SI */, 1 /* DLE */, 1 /* DC1 */, 1 /* DC2 */, 1 /* DC3 */,
+ 1 /* DC4 */, 1 /* NAK */, 1 /* SYN */, 1 /* ETB */, 1 /* CAN */,
+ 1 /* EM */, 1 /* SUB */, 1 /* ESC */, 1 /* FS */, 1 /* GS */,
+ 1 /* RS */, 1 /* US */, 0 /* SPC */, 0 /* ! */, 1 /* " */,
+ 0 /* # */, 0 /* $ */, 0 /* % */, 0 /* & */, 0 /* ' */,
+ 0 /* ( */, 0 /* ) */, 0 /* * */, 0 /* + */, 0 /* , */,
+ 0 /* - */, 0 /* . */, 0 /* / */, 0 /* 0 */, 0 /* 1 */,
+ 0 /* 2 */, 0 /* 3 */, 0 /* 4 */, 0 /* 5 */, 0 /* 6 */,
+ 0 /* 7 */, 0 /* 8 */, 0 /* 9 */, 0 /* : */, 0 /* ; */,
+ 0 /* < */, 0 /* = */, 0 /* > */, 0 /* ? */, 0 /* @ */,
+ 0 /* A */, 0 /* B */, 0 /* C */, 0 /* D */, 0 /* E */,
+ 0 /* F */, 0 /* G */, 0 /* H */, 0 /* I */, 0 /* J */,
+ 0 /* K */, 0 /* L */, 0 /* M */, 0 /* N */, 0 /* O */,
+ 0 /* P */, 0 /* Q */, 0 /* R */, 0 /* S */, 0 /* T */,
+ 0 /* U */, 0 /* V */, 0 /* W */, 0 /* X */, 0 /* Y */,
+ 0 /* Z */, 0 /* [ */, 1 /* \ */, 0 /* ] */, 0 /* ^ */,
+ 0 /* _ */, 0 /* ` */, 0 /* a */, 0 /* b */, 0 /* c */,
+ 0 /* d */, 0 /* e */, 0 /* f */, 0 /* g */, 0 /* h */,
+ 0 /* i */, 0 /* j */, 0 /* k */, 0 /* l */, 0 /* m */,
+ 0 /* n */, 0 /* o */, 0 /* p */, 0 /* q */, 0 /* r */,
+ 0 /* s */, 0 /* t */, 0 /* u */, 0 /* v */, 0 /* w */,
+ 0 /* x */, 0 /* y */, 0 /* z */, 0 /* { */, 0 /* | */,
+ 0 /* } */, 0 /* ~ */, 1 /* DEL */, 1 /* 0x80 */, 1 /* 0x81 */,
+ 1 /* 0x82 */, 1 /* 0x83 */, 1 /* 0x84 */, 1 /* 0x85 */, 1 /* 0x86 */,
+ 1 /* 0x87 */, 1 /* 0x88 */, 1 /* 0x89 */, 1 /* 0x8a */, 1 /* 0x8b */,
+ 1 /* 0x8c */, 1 /* 0x8d */, 1 /* 0x8e */, 1 /* 0x8f */, 1 /* 0x90 */,
+ 1 /* 0x91 */, 1 /* 0x92 */, 1 /* 0x93 */, 1 /* 0x94 */, 1 /* 0x95 */,
+ 1 /* 0x96 */, 1 /* 0x97 */, 1 /* 0x98 */, 1 /* 0x99 */, 1 /* 0x9a */,
+ 1 /* 0x9b */, 1 /* 0x9c */, 1 /* 0x9d */, 1 /* 0x9e */, 1 /* 0x9f */,
+ 1 /* 0xa0 */, 1 /* 0xa1 */, 1 /* 0xa2 */, 1 /* 0xa3 */, 1 /* 0xa4 */,
+ 1 /* 0xa5 */, 1 /* 0xa6 */, 1 /* 0xa7 */, 1 /* 0xa8 */, 1 /* 0xa9 */,
+ 1 /* 0xaa */, 1 /* 0xab */, 1 /* 0xac */, 1 /* 0xad */, 1 /* 0xae */,
+ 1 /* 0xaf */, 1 /* 0xb0 */, 1 /* 0xb1 */, 1 /* 0xb2 */, 1 /* 0xb3 */,
+ 1 /* 0xb4 */, 1 /* 0xb5 */, 1 /* 0xb6 */, 1 /* 0xb7 */, 1 /* 0xb8 */,
+ 1 /* 0xb9 */, 1 /* 0xba */, 1 /* 0xbb */, 1 /* 0xbc */, 1 /* 0xbd */,
+ 1 /* 0xbe */, 1 /* 0xbf */, 1 /* 0xc0 */, 1 /* 0xc1 */, 1 /* 0xc2 */,
+ 1 /* 0xc3 */, 1 /* 0xc4 */, 1 /* 0xc5 */, 1 /* 0xc6 */, 1 /* 0xc7 */,
+ 1 /* 0xc8 */, 1 /* 0xc9 */, 1 /* 0xca */, 1 /* 0xcb */, 1 /* 0xcc */,
+ 1 /* 0xcd */, 1 /* 0xce */, 1 /* 0xcf */, 1 /* 0xd0 */, 1 /* 0xd1 */,
+ 1 /* 0xd2 */, 1 /* 0xd3 */, 1 /* 0xd4 */, 1 /* 0xd5 */, 1 /* 0xd6 */,
+ 1 /* 0xd7 */, 1 /* 0xd8 */, 1 /* 0xd9 */, 1 /* 0xda */, 1 /* 0xdb */,
+ 1 /* 0xdc */, 1 /* 0xdd */, 1 /* 0xde */, 1 /* 0xdf */, 1 /* 0xe0 */,
+ 1 /* 0xe1 */, 1 /* 0xe2 */, 1 /* 0xe3 */, 1 /* 0xe4 */, 1 /* 0xe5 */,
+ 1 /* 0xe6 */, 1 /* 0xe7 */, 1 /* 0xe8 */, 1 /* 0xe9 */, 1 /* 0xea */,
+ 1 /* 0xeb */, 1 /* 0xec */, 1 /* 0xed */, 1 /* 0xee */, 1 /* 0xef */,
+ 1 /* 0xf0 */, 1 /* 0xf1 */, 1 /* 0xf2 */, 1 /* 0xf3 */, 1 /* 0xf4 */,
+ 1 /* 0xf5 */, 1 /* 0xf6 */, 1 /* 0xf7 */, 1 /* 0xf8 */, 1 /* 0xf9 */,
+ 1 /* 0xfa */, 1 /* 0xfb */, 1 /* 0xfc */, 1 /* 0xfd */, 1 /* 0xfe */,
+ 1 /* 0xff */,
+};
+} // namespace
+
+namespace {
+template <typename OutputIterator>
+std::pair<OutputIterator, OutputIterator>
+copy_escape(const char *src, size_t srclen, OutputIterator d_first,
+ OutputIterator d_last) {
+ auto safe_first = src;
+ for (auto p = src; p != src + srclen && d_first != d_last; ++p) {
+ unsigned char c = *p;
+ if (!ESCAPE_TBL[c]) {
+ continue;
+ }
+
+ auto n =
+ std::min(std::distance(d_first, d_last), std::distance(safe_first, p));
+ d_first = std::copy_n(safe_first, n, d_first);
+ if (std::distance(d_first, d_last) < 4) {
+ return std::make_pair(d_first, d_last);
+ }
+ *d_first++ = '\\';
+ *d_first++ = 'x';
+ *d_first++ = LOWER_XDIGITS[c >> 4];
+ *d_first++ = LOWER_XDIGITS[c & 0xf];
+ safe_first = p + 1;
+ }
+
+ auto n = std::min(std::distance(d_first, d_last),
+ std::distance(safe_first, src + srclen));
+ return std::make_pair(std::copy_n(safe_first, n, d_first), d_last);
+}
+} // namespace
+
+namespace {
+template <typename OutputIterator>
+std::pair<OutputIterator, OutputIterator> copy_escape(const StringRef &src,
+ OutputIterator d_first,
+ OutputIterator d_last) {
+ return copy_escape(src.c_str(), src.size(), d_first, d_last);
+}
+} // namespace
+
+namespace {
+// Construct absolute request URI from |Request|, mainly to log
+// request URI for proxy request (HTTP/2 proxy or client proxy). This
+// is mostly same routine found in
+// HttpDownstreamConnection::push_request_headers(), but vastly
+// simplified since we only care about absolute URI.
+StringRef construct_absolute_request_uri(BlockAllocator &balloc,
+ const Request &req) {
+ if (req.authority.empty()) {
+ return req.path;
+ }
+
+ auto len = req.authority.size() + req.path.size();
+ if (req.scheme.empty()) {
+ len += str_size("http://");
+ } else {
+ len += req.scheme.size() + str_size("://");
+ }
+
+ auto iov = make_byte_ref(balloc, len + 1);
+ auto p = iov.base;
+
+ if (req.scheme.empty()) {
+ // We may have to log the request which lacks scheme (e.g.,
+ // http/1.1 with origin form).
+ p = util::copy_lit(p, "http://");
+ } else {
+ p = std::copy(std::begin(req.scheme), std::end(req.scheme), p);
+ p = util::copy_lit(p, "://");
+ }
+ p = std::copy(std::begin(req.authority), std::end(req.authority), p);
+ p = std::copy(std::begin(req.path), std::end(req.path), p);
+ *p = '\0';
+
+ return StringRef{iov.base, p};
+}
+} // namespace
+
+void upstream_accesslog(const std::vector<LogFragment> &lfv,
+ const LogSpec &lgsp) {
+ auto config = get_config();
+ auto lgconf = log_config();
+ auto &accessconf = get_config()->logging.access;
+
+ if (lgconf->accesslog_fd == -1 && !accessconf.syslog) {
+ return;
+ }
+
+ std::array<char, 4_k> buf;
+
+ auto downstream = lgsp.downstream;
+
+ const auto &req = downstream->request();
+ const auto &resp = downstream->response();
+ const auto &tstamp = req.tstamp;
+ auto &balloc = downstream->get_block_allocator();
+
+ auto downstream_addr = downstream->get_addr();
+ auto method = req.method == -1 ? StringRef::from_lit("<unknown>")
+ : http2::to_method_string(req.method);
+ auto path =
+ req.method == HTTP_CONNECT ? req.authority
+ : config->http2_proxy ? construct_absolute_request_uri(balloc, req)
+ : req.path.empty() ? req.method == HTTP_OPTIONS ? StringRef::from_lit("*")
+ : StringRef::from_lit("-")
+ : req.path;
+ auto path_without_query =
+ req.method == HTTP_CONNECT
+ ? path
+ : StringRef{std::begin(path),
+ std::find(std::begin(path), std::end(path), '?')};
+
+ auto p = std::begin(buf);
+ auto last = std::end(buf) - 2;
+
+ for (auto &lf : lfv) {
+ switch (lf.type) {
+ case LogFragmentType::LITERAL:
+ std::tie(p, last) = copy(lf.value, p, last);
+ break;
+ case LogFragmentType::REMOTE_ADDR:
+ std::tie(p, last) = copy(lgsp.remote_addr, p, last);
+ break;
+ case LogFragmentType::TIME_LOCAL:
+ std::tie(p, last) = copy(tstamp->time_local, p, last);
+ break;
+ case LogFragmentType::TIME_ISO8601:
+ std::tie(p, last) = copy(tstamp->time_iso8601, p, last);
+ break;
+ case LogFragmentType::REQUEST:
+ std::tie(p, last) = copy(method, p, last);
+ std::tie(p, last) = copy(' ', p, last);
+ std::tie(p, last) = copy_escape(path, p, last);
+ std::tie(p, last) = copy_l(" HTTP/", p, last);
+ std::tie(p, last) = copy(req.http_major, p, last);
+ if (req.http_major < 2) {
+ std::tie(p, last) = copy('.', p, last);
+ std::tie(p, last) = copy(req.http_minor, p, last);
+ }
+ break;
+ case LogFragmentType::METHOD:
+ std::tie(p, last) = copy(method, p, last);
+ break;
+ case LogFragmentType::PATH:
+ std::tie(p, last) = copy_escape(path, p, last);
+ break;
+ case LogFragmentType::PATH_WITHOUT_QUERY:
+ std::tie(p, last) = copy_escape(path_without_query, p, last);
+ break;
+ case LogFragmentType::PROTOCOL_VERSION:
+ std::tie(p, last) = copy_l("HTTP/", p, last);
+ std::tie(p, last) = copy(req.http_major, p, last);
+ if (req.http_major < 2) {
+ std::tie(p, last) = copy('.', p, last);
+ std::tie(p, last) = copy(req.http_minor, p, last);
+ }
+ break;
+ case LogFragmentType::STATUS:
+ std::tie(p, last) = copy(resp.http_status, p, last);
+ break;
+ case LogFragmentType::BODY_BYTES_SENT:
+ std::tie(p, last) = copy(downstream->response_sent_body_length, p, last);
+ break;
+ case LogFragmentType::HTTP: {
+ auto hd = req.fs.header(lf.value);
+ if (hd) {
+ std::tie(p, last) = copy_escape((*hd).value, p, last);
+ break;
+ }
+
+ std::tie(p, last) = copy('-', p, last);
+
+ break;
+ }
+ case LogFragmentType::AUTHORITY:
+ if (!req.authority.empty()) {
+ std::tie(p, last) = copy(req.authority, p, last);
+ break;
+ }
+
+ std::tie(p, last) = copy('-', p, last);
+
+ break;
+ case LogFragmentType::REMOTE_PORT:
+ std::tie(p, last) = copy(lgsp.remote_port, p, last);
+ break;
+ case LogFragmentType::SERVER_PORT:
+ std::tie(p, last) = copy(lgsp.server_port, p, last);
+ break;
+ case LogFragmentType::REQUEST_TIME: {
+ auto t = std::chrono::duration_cast<std::chrono::milliseconds>(
+ lgsp.request_end_time - downstream->get_request_start_time())
+ .count();
+ std::tie(p, last) = copy(t / 1000, p, last);
+ std::tie(p, last) = copy('.', p, last);
+ auto frac = t % 1000;
+ if (frac < 100) {
+ auto n = frac < 10 ? 2 : 1;
+ std::tie(p, last) = copy("000", n, p, last);
+ }
+ std::tie(p, last) = copy(frac, p, last);
+ break;
+ }
+ case LogFragmentType::PID:
+ std::tie(p, last) = copy(lgsp.pid, p, last);
+ break;
+ case LogFragmentType::ALPN:
+ std::tie(p, last) = copy_escape(lgsp.alpn, p, last);
+ break;
+ case LogFragmentType::TLS_CIPHER:
+ if (!lgsp.ssl) {
+ std::tie(p, last) = copy('-', p, last);
+ break;
+ }
+ std::tie(p, last) = copy(SSL_get_cipher_name(lgsp.ssl), p, last);
+ break;
+ case LogFragmentType::TLS_PROTOCOL:
+ if (!lgsp.ssl) {
+ std::tie(p, last) = copy('-', p, last);
+ break;
+ }
+ std::tie(p, last) =
+ copy(nghttp2::tls::get_tls_protocol(lgsp.ssl), p, last);
+ break;
+ case LogFragmentType::TLS_SESSION_ID: {
+ auto session = SSL_get_session(lgsp.ssl);
+ if (!session) {
+ std::tie(p, last) = copy('-', p, last);
+ break;
+ }
+ unsigned int session_id_length = 0;
+ auto session_id = SSL_SESSION_get_id(session, &session_id_length);
+ if (session_id_length == 0) {
+ std::tie(p, last) = copy('-', p, last);
+ break;
+ }
+ std::tie(p, last) = copy_hex_low(session_id, session_id_length, p, last);
+ break;
+ }
+ case LogFragmentType::TLS_SESSION_REUSED:
+ if (!lgsp.ssl) {
+ std::tie(p, last) = copy('-', p, last);
+ break;
+ }
+ std::tie(p, last) =
+ copy(SSL_session_reused(lgsp.ssl) ? 'r' : '.', p, last);
+ break;
+ case LogFragmentType::TLS_SNI:
+ if (lgsp.sni.empty()) {
+ std::tie(p, last) = copy('-', p, last);
+ break;
+ }
+ std::tie(p, last) = copy_escape(lgsp.sni, p, last);
+ break;
+ case LogFragmentType::TLS_CLIENT_FINGERPRINT_SHA1:
+ case LogFragmentType::TLS_CLIENT_FINGERPRINT_SHA256: {
+ if (!lgsp.ssl) {
+ std::tie(p, last) = copy('-', p, last);
+ break;
+ }
+#if OPENSSL_3_0_0_API
+ auto x = SSL_get0_peer_certificate(lgsp.ssl);
+#else // !OPENSSL_3_0_0_API
+ auto x = SSL_get_peer_certificate(lgsp.ssl);
+#endif // !OPENSSL_3_0_0_API
+ if (!x) {
+ std::tie(p, last) = copy('-', p, last);
+ break;
+ }
+ std::array<uint8_t, 32> buf;
+ auto len = tls::get_x509_fingerprint(
+ buf.data(), buf.size(), x,
+ lf.type == LogFragmentType::TLS_CLIENT_FINGERPRINT_SHA256
+ ? EVP_sha256()
+ : EVP_sha1());
+#if !OPENSSL_3_0_0_API
+ X509_free(x);
+#endif // !OPENSSL_3_0_0_API
+ if (len <= 0) {
+ std::tie(p, last) = copy('-', p, last);
+ break;
+ }
+ std::tie(p, last) = copy_hex_low(buf.data(), len, p, last);
+ break;
+ }
+ case LogFragmentType::TLS_CLIENT_ISSUER_NAME:
+ case LogFragmentType::TLS_CLIENT_SUBJECT_NAME: {
+ if (!lgsp.ssl) {
+ std::tie(p, last) = copy('-', p, last);
+ break;
+ }
+#if OPENSSL_3_0_0_API
+ auto x = SSL_get0_peer_certificate(lgsp.ssl);
+#else // !OPENSSL_3_0_0_API
+ auto x = SSL_get_peer_certificate(lgsp.ssl);
+#endif // !OPENSSL_3_0_0_API
+ if (!x) {
+ std::tie(p, last) = copy('-', p, last);
+ break;
+ }
+ auto name = lf.type == LogFragmentType::TLS_CLIENT_ISSUER_NAME
+ ? tls::get_x509_issuer_name(balloc, x)
+ : tls::get_x509_subject_name(balloc, x);
+#if !OPENSSL_3_0_0_API
+ X509_free(x);
+#endif // !OPENSSL_3_0_0_API
+ if (name.empty()) {
+ std::tie(p, last) = copy('-', p, last);
+ break;
+ }
+ std::tie(p, last) = copy(name, p, last);
+ break;
+ }
+ case LogFragmentType::TLS_CLIENT_SERIAL: {
+ if (!lgsp.ssl) {
+ std::tie(p, last) = copy('-', p, last);
+ break;
+ }
+#if OPENSSL_3_0_0_API
+ auto x = SSL_get0_peer_certificate(lgsp.ssl);
+#else // !OPENSSL_3_0_0_API
+ auto x = SSL_get_peer_certificate(lgsp.ssl);
+#endif // !OPENSSL_3_0_0_API
+ if (!x) {
+ std::tie(p, last) = copy('-', p, last);
+ break;
+ }
+ auto sn = tls::get_x509_serial(balloc, x);
+#if !OPENSSL_3_0_0_API
+ X509_free(x);
+#endif // !OPENSSL_3_0_0_API
+ if (sn.empty()) {
+ std::tie(p, last) = copy('-', p, last);
+ break;
+ }
+ std::tie(p, last) = copy(sn, p, last);
+ break;
+ }
+ case LogFragmentType::BACKEND_HOST:
+ if (!downstream_addr) {
+ std::tie(p, last) = copy('-', p, last);
+ break;
+ }
+ std::tie(p, last) = copy(downstream_addr->host, p, last);
+ break;
+ case LogFragmentType::BACKEND_PORT:
+ if (!downstream_addr) {
+ std::tie(p, last) = copy('-', p, last);
+ break;
+ }
+ std::tie(p, last) = copy(downstream_addr->port, p, last);
+ break;
+ case LogFragmentType::NONE:
+ break;
+ default:
+ break;
+ }
+ }
+
+ *p = '\0';
+
+ if (accessconf.syslog) {
+ syslog(LOG_INFO, "%s", buf.data());
+
+ return;
+ }
+
+ *p++ = '\n';
+
+ auto nwrite = std::distance(std::begin(buf), p);
+ while (write(lgconf->accesslog_fd, buf.data(), nwrite) == -1 &&
+ errno == EINTR)
+ ;
+}
+
+int reopen_log_files(const LoggingConfig &loggingconf) {
+ int res = 0;
+ int new_accesslog_fd = -1;
+ int new_errorlog_fd = -1;
+
+ auto lgconf = log_config();
+ auto &accessconf = loggingconf.access;
+ auto &errorconf = loggingconf.error;
+
+ if (!accessconf.syslog && !accessconf.file.empty()) {
+ new_accesslog_fd = open_log_file(accessconf.file.c_str());
+
+ if (new_accesslog_fd == -1) {
+ LOG(ERROR) << "Failed to open accesslog file " << accessconf.file;
+ res = -1;
+ }
+ }
+
+ if (!errorconf.syslog && !errorconf.file.empty()) {
+ new_errorlog_fd = open_log_file(errorconf.file.c_str());
+
+ if (new_errorlog_fd == -1) {
+ if (lgconf->errorlog_fd != -1) {
+ LOG(ERROR) << "Failed to open errorlog file " << errorconf.file;
+ } else {
+ std::cerr << "Failed to open errorlog file " << errorconf.file
+ << std::endl;
+ }
+
+ res = -1;
+ }
+ }
+
+ close_log_file(lgconf->accesslog_fd);
+ close_log_file(lgconf->errorlog_fd);
+
+ lgconf->accesslog_fd = new_accesslog_fd;
+ lgconf->errorlog_fd = new_errorlog_fd;
+ lgconf->errorlog_tty =
+ (new_errorlog_fd == -1) ? false : isatty(new_errorlog_fd);
+
+ return res;
+}
+
+void log_chld(pid_t pid, int rstatus, const char *msg) {
+ std::string signalstr;
+ if (WIFSIGNALED(rstatus)) {
+ signalstr += "; signal ";
+ auto sig = WTERMSIG(rstatus);
+ auto s = strsignal(sig);
+ if (s) {
+ signalstr += s;
+ signalstr += '(';
+ } else {
+ signalstr += "UNKNOWN(";
+ }
+ signalstr += util::utos(sig);
+ signalstr += ')';
+ }
+
+ LOG(NOTICE) << msg << ": [" << pid << "] exited "
+ << (WIFEXITED(rstatus) ? "normally" : "abnormally")
+ << " with status " << log::hex << rstatus << log::dec
+ << "; exit status "
+ << (WIFEXITED(rstatus) ? WEXITSTATUS(rstatus) : 0)
+ << (signalstr.empty() ? "" : signalstr.c_str());
+}
+
+void redirect_stderr_to_errorlog(const LoggingConfig &loggingconf) {
+ auto lgconf = log_config();
+ auto &errorconf = loggingconf.error;
+
+ if (errorconf.syslog || lgconf->errorlog_fd == -1) {
+ return;
+ }
+
+ dup2(lgconf->errorlog_fd, STDERR_FILENO);
+}
+
+namespace {
+int STDERR_COPY = -1;
+int STDOUT_COPY = -1;
+} // namespace
+
+void store_original_fds() {
+ // consider dup'ing stdout too
+ STDERR_COPY = dup(STDERR_FILENO);
+ STDOUT_COPY = STDOUT_FILENO;
+ // no race here, since it is called early
+ util::make_socket_closeonexec(STDERR_COPY);
+}
+
+void restore_original_fds() { dup2(STDERR_COPY, STDERR_FILENO); }
+
+void close_log_file(int &fd) {
+ if (fd != STDERR_COPY && fd != STDOUT_COPY && fd != -1) {
+ close(fd);
+ }
+ fd = -1;
+}
+
+int open_log_file(const char *path) {
+
+ if (strcmp(path, "/dev/stdout") == 0 ||
+ strcmp(path, "/proc/self/fd/1") == 0) {
+ return STDOUT_COPY;
+ }
+
+ if (strcmp(path, "/dev/stderr") == 0 ||
+ strcmp(path, "/proc/self/fd/2") == 0) {
+ return STDERR_COPY;
+ }
+#ifdef O_CLOEXEC
+
+ auto fd = open(path, O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC,
+ S_IRUSR | S_IWUSR | S_IRGRP);
+#else // !O_CLOEXEC
+
+ auto fd =
+ open(path, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP);
+
+ // We get race condition if execve is called at the same time.
+ if (fd != -1) {
+ util::make_socket_closeonexec(fd);
+ }
+
+#endif // !O_CLOEXEC
+
+ if (fd == -1) {
+ return -1;
+ }
+
+ return fd;
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_log.h b/src/shrpx_log.h
new file mode 100644
index 0000000..bc30097
--- /dev/null
+++ b/src/shrpx_log.h
@@ -0,0 +1,318 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_LOG_H
+#define SHRPX_LOG_H
+
+#include "shrpx.h"
+
+#include <sys/types.h>
+
+#include <memory>
+#include <vector>
+#include <chrono>
+
+#include "shrpx_config.h"
+#include "shrpx_log_config.h"
+#include "tls.h"
+#include "template.h"
+#include "util.h"
+
+using namespace nghttp2;
+
+#define ENABLE_LOG 1
+
+#define LOG_ENABLED(SEVERITY) (ENABLE_LOG && shrpx::Log::log_enabled(SEVERITY))
+
+#ifdef __FILE_NAME__
+# define NGHTTP2_FILE_NAME __FILE_NAME__
+#else // !__FILE_NAME__
+# define NGHTTP2_FILE_NAME __FILE__
+#endif // !__FILE_NAME__
+
+#define LOG(SEVERITY) shrpx::Log(SEVERITY, NGHTTP2_FILE_NAME, __LINE__)
+
+// Listener log
+#define LLOG(SEVERITY, LISTEN) \
+ (shrpx::Log(SEVERITY, NGHTTP2_FILE_NAME, __LINE__) \
+ << "[LISTEN:" << LISTEN << "] ")
+
+// Worker log
+#define WLOG(SEVERITY, WORKER) \
+ (shrpx::Log(SEVERITY, NGHTTP2_FILE_NAME, __LINE__) \
+ << "[WORKER:" << WORKER << "] ")
+
+// ClientHandler log
+#define CLOG(SEVERITY, CLIENT_HANDLER) \
+ (shrpx::Log(SEVERITY, NGHTTP2_FILE_NAME, __LINE__) \
+ << "[CLIENT_HANDLER:" << CLIENT_HANDLER << "] ")
+
+// Upstream log
+#define ULOG(SEVERITY, UPSTREAM) \
+ (shrpx::Log(SEVERITY, NGHTTP2_FILE_NAME, __LINE__) \
+ << "[UPSTREAM:" << UPSTREAM << "] ")
+
+// Downstream log
+#define DLOG(SEVERITY, DOWNSTREAM) \
+ (shrpx::Log(SEVERITY, NGHTTP2_FILE_NAME, __LINE__) \
+ << "[DOWNSTREAM:" << DOWNSTREAM << "] ")
+
+// Downstream connection log
+#define DCLOG(SEVERITY, DCONN) \
+ (shrpx::Log(SEVERITY, NGHTTP2_FILE_NAME, __LINE__) \
+ << "[DCONN:" << DCONN << "] ")
+
+// Downstream HTTP2 session log
+#define SSLOG(SEVERITY, HTTP2) \
+ (shrpx::Log(SEVERITY, NGHTTP2_FILE_NAME, __LINE__) \
+ << "[DHTTP2:" << HTTP2 << "] ")
+
+// Memcached connection log
+#define MCLOG(SEVERITY, MCONN) \
+ (shrpx::Log(SEVERITY, NGHTTP2_FILE_NAME, __LINE__) \
+ << "[MCONN:" << MCONN << "] ")
+
+namespace shrpx {
+
+class Downstream;
+struct DownstreamAddr;
+
+enum SeverityLevel { INFO, NOTICE, WARN, ERROR, FATAL };
+
+using LogBuffer = std::array<uint8_t, 4_k>;
+
+class Log {
+public:
+ Log(int severity, const char *filename, int linenum);
+ ~Log();
+ Log &operator<<(const std::string &s);
+ Log &operator<<(const char *s);
+ Log &operator<<(const StringRef &s);
+ Log &operator<<(const ImmutableString &s);
+ Log &operator<<(short n) { return *this << static_cast<long long>(n); }
+ Log &operator<<(int n) { return *this << static_cast<long long>(n); }
+ Log &operator<<(long n) { return *this << static_cast<long long>(n); }
+ Log &operator<<(long long n);
+ Log &operator<<(unsigned short n) {
+ return *this << static_cast<unsigned long long>(n);
+ }
+ Log &operator<<(unsigned int n) {
+ return *this << static_cast<unsigned long long>(n);
+ }
+ Log &operator<<(unsigned long n) {
+ return *this << static_cast<unsigned long long>(n);
+ }
+ Log &operator<<(unsigned long long n);
+ Log &operator<<(float n) { return *this << static_cast<double>(n); }
+ Log &operator<<(double n);
+ Log &operator<<(long double n);
+ Log &operator<<(bool n);
+ Log &operator<<(const void *p);
+ template <typename T> Log &operator<<(const std::shared_ptr<T> &ptr) {
+ return *this << ptr.get();
+ }
+ Log &operator<<(void (*func)(Log &log)) {
+ func(*this);
+ return *this;
+ }
+ template <typename InputIt> void write_seq(InputIt first, InputIt last) {
+ if (full_) {
+ return;
+ }
+
+ auto d = std::distance(first, last);
+ auto n = std::min(wleft(), static_cast<size_t>(d));
+ last_ = std::copy(first, first + n, last_);
+ update_full();
+ }
+
+ template <typename T> void write_hex(T n) {
+ if (full_) {
+ return;
+ }
+
+ if (n == 0) {
+ if (wleft() < 4 /* for "0x00" */) {
+ full_ = true;
+ return;
+ }
+ *last_++ = '0';
+ *last_++ = 'x';
+ *last_++ = '0';
+ *last_++ = '0';
+ update_full();
+ return;
+ }
+
+ size_t nlen = 0;
+ for (auto t = n; t; t >>= 8, ++nlen)
+ ;
+
+ nlen *= 2;
+
+ if (wleft() < 2 /* for "0x" */ + nlen) {
+ full_ = true;
+ return;
+ }
+
+ *last_++ = '0';
+ *last_++ = 'x';
+
+ last_ += nlen;
+ update_full();
+
+ auto p = last_ - 1;
+ for (; n; n >>= 8) {
+ uint8_t b = n & 0xff;
+ *p-- = util::LOWER_XDIGITS[b & 0xf];
+ *p-- = util::LOWER_XDIGITS[b >> 4];
+ }
+ }
+ static void set_severity_level(int severity);
+ // Returns the severity level by |name|. Returns -1 if |name| is
+ // unknown.
+ static int get_severity_level_by_name(const StringRef &name);
+ static bool log_enabled(int severity) { return severity >= severity_thres_; }
+
+ enum {
+ fmt_dec = 0x00,
+ fmt_hex = 0x01,
+ };
+
+ void set_flags(int flags) { flags_ = flags; }
+
+private:
+ size_t rleft() { return last_ - begin_; }
+ size_t wleft() { return end_ - last_; }
+ void update_full() { full_ = last_ == end_; }
+
+ LogBuffer &buf_;
+ uint8_t *begin_;
+ uint8_t *end_;
+ uint8_t *last_;
+ const char *filename_;
+ uint32_t flags_;
+ int severity_;
+ int linenum_;
+ bool full_;
+ static int severity_thres_;
+};
+
+namespace log {
+void hex(Log &log);
+void dec(Log &log);
+} // namespace log
+
+#define TTY_HTTP_HD (log_config()->errorlog_tty ? "\033[1;34m" : "")
+#define TTY_RST (log_config()->errorlog_tty ? "\033[0m" : "")
+
+enum class LogFragmentType {
+ NONE,
+ LITERAL,
+ REMOTE_ADDR,
+ TIME_LOCAL,
+ TIME_ISO8601,
+ REQUEST,
+ STATUS,
+ BODY_BYTES_SENT,
+ HTTP,
+ AUTHORITY,
+ REMOTE_PORT,
+ SERVER_PORT,
+ REQUEST_TIME,
+ PID,
+ ALPN,
+ TLS_CIPHER,
+ SSL_CIPHER = TLS_CIPHER,
+ TLS_PROTOCOL,
+ SSL_PROTOCOL = TLS_PROTOCOL,
+ TLS_SESSION_ID,
+ SSL_SESSION_ID = TLS_SESSION_ID,
+ TLS_SESSION_REUSED,
+ SSL_SESSION_REUSED = TLS_SESSION_REUSED,
+ TLS_SNI,
+ TLS_CLIENT_FINGERPRINT_SHA1,
+ TLS_CLIENT_FINGERPRINT_SHA256,
+ TLS_CLIENT_ISSUER_NAME,
+ TLS_CLIENT_SERIAL,
+ TLS_CLIENT_SUBJECT_NAME,
+ BACKEND_HOST,
+ BACKEND_PORT,
+ METHOD,
+ PATH,
+ PATH_WITHOUT_QUERY,
+ PROTOCOL_VERSION,
+};
+
+struct LogFragment {
+ LogFragment(LogFragmentType type, StringRef value = StringRef::from_lit(""))
+ : type(type), value(std::move(value)) {}
+ LogFragmentType type;
+ StringRef value;
+};
+
+struct LogSpec {
+ Downstream *downstream;
+ StringRef remote_addr;
+ StringRef alpn;
+ StringRef sni;
+ SSL *ssl;
+ std::chrono::high_resolution_clock::time_point request_end_time;
+ StringRef remote_port;
+ uint16_t server_port;
+ pid_t pid;
+};
+
+void upstream_accesslog(const std::vector<LogFragment> &lf,
+ const LogSpec &lgsp);
+
+int reopen_log_files(const LoggingConfig &loggingconf);
+
+// Logs message when process whose pid is |pid| and exist status is
+// |rstatus| exited. The |msg| is prepended to the log message.
+void log_chld(pid_t pid, int rstatus, const char *msg);
+
+void redirect_stderr_to_errorlog(const LoggingConfig &loggingconf);
+
+// Makes internal copy of stderr (and possibly stdout in the future),
+// which is then used as pointer to /dev/stderr or /proc/self/fd/2
+void store_original_fds();
+
+// Restores the original stderr that was stored with copy_original_fds
+// Used just before execv
+void restore_original_fds();
+
+// Closes |fd| which was returned by open_log_file (see below)
+// and sets it to -1. In the case that |fd| points to stdout or
+// stderr, or is -1, the descriptor is not closed (but still set to -1).
+void close_log_file(int &fd);
+
+// Opens |path| with O_APPEND enabled. If file does not exist, it is
+// created first. This function returns file descriptor referring the
+// opened file if it succeeds, or -1.
+int open_log_file(const char *path);
+
+} // namespace shrpx
+
+#endif // SHRPX_LOG_H
diff --git a/src/shrpx_log_config.cc b/src/shrpx_log_config.cc
new file mode 100644
index 0000000..92eb055
--- /dev/null
+++ b/src/shrpx_log_config.cc
@@ -0,0 +1,127 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_log_config.h"
+
+#include <unistd.h>
+
+#include <thread>
+#include <sstream>
+
+#include "util.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+Timestamp::Timestamp(const std::chrono::system_clock::time_point &tp) {
+ time_local = util::format_common_log(time_local_buf.data(), tp);
+ time_iso8601 = util::format_iso8601(time_iso8601_buf.data(), tp);
+ time_http = util::format_http_date(time_http_buf.data(), tp);
+}
+
+LogConfig::LogConfig()
+ : time_str_updated(std::chrono::system_clock::now()),
+ tstamp(std::make_shared<Timestamp>(time_str_updated)),
+ pid(getpid()),
+ accesslog_fd(-1),
+ errorlog_fd(-1),
+ errorlog_tty(false) {
+ auto tid = std::this_thread::get_id();
+ auto tid_hash =
+ util::hash32(StringRef{reinterpret_cast<uint8_t *>(&tid),
+ reinterpret_cast<uint8_t *>(&tid) + sizeof(tid)});
+ thread_id = util::format_hex(reinterpret_cast<uint8_t *>(&tid_hash),
+ sizeof(tid_hash));
+}
+
+#ifndef NOTHREADS
+# ifdef HAVE_THREAD_LOCAL
+namespace {
+thread_local std::unique_ptr<LogConfig> config = std::make_unique<LogConfig>();
+} // namespace
+
+LogConfig *log_config() { return config.get(); }
+void delete_log_config() {}
+# else // !HAVE_THREAD_LOCAL
+namespace {
+pthread_key_t lckey;
+pthread_once_t lckey_once = PTHREAD_ONCE_INIT;
+} // namespace
+
+namespace {
+void make_key() { pthread_key_create(&lckey, nullptr); }
+} // namespace
+
+LogConfig *log_config() {
+ pthread_once(&lckey_once, make_key);
+ LogConfig *config = (LogConfig *)pthread_getspecific(lckey);
+ if (!config) {
+ config = new LogConfig();
+ pthread_setspecific(lckey, config);
+ }
+ return config;
+}
+
+void delete_log_config() { delete log_config(); }
+# endif // !HAVE_THREAD_LOCAL
+#else // NOTHREADS
+namespace {
+std::unique_ptr<LogConfig> config = std::make_unique<LogConfig>();
+} // namespace
+
+LogConfig *log_config() { return config.get(); }
+
+void delete_log_config() {}
+#endif // NOTHREADS
+
+void LogConfig::update_tstamp_millis(
+ const std::chrono::system_clock::time_point &now) {
+ if (std::chrono::duration_cast<std::chrono::milliseconds>(
+ now.time_since_epoch()) ==
+ std::chrono::duration_cast<std::chrono::milliseconds>(
+ time_str_updated.time_since_epoch())) {
+ return;
+ }
+
+ time_str_updated = now;
+
+ tstamp = std::make_shared<Timestamp>(now);
+}
+
+void LogConfig::update_tstamp(
+ const std::chrono::system_clock::time_point &now) {
+ if (std::chrono::duration_cast<std::chrono::seconds>(
+ now.time_since_epoch()) ==
+ std::chrono::duration_cast<std::chrono::seconds>(
+ time_str_updated.time_since_epoch())) {
+ return;
+ }
+
+ time_str_updated = now;
+
+ tstamp = std::make_shared<Timestamp>(now);
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_log_config.h b/src/shrpx_log_config.h
new file mode 100644
index 0000000..76fa78b
--- /dev/null
+++ b/src/shrpx_log_config.h
@@ -0,0 +1,79 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_LOG_CONFIG_H
+#define SHRPX_LOG_CONFIG_H
+
+#include "shrpx.h"
+
+#include <sys/types.h>
+
+#include <chrono>
+
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+struct Timestamp {
+ Timestamp(const std::chrono::system_clock::time_point &tp);
+
+ std::array<char, sizeof("03/Jul/2014:00:19:38 +0900")> time_local_buf;
+ std::array<char, sizeof("2014-11-15T12:58:24.741+09:00")> time_iso8601_buf;
+ std::array<char, sizeof("Mon, 10 Oct 2016 10:25:58 GMT")> time_http_buf;
+ StringRef time_local;
+ StringRef time_iso8601;
+ StringRef time_http;
+};
+
+struct LogConfig {
+ std::chrono::system_clock::time_point time_str_updated;
+ std::shared_ptr<Timestamp> tstamp;
+ std::string thread_id;
+ pid_t pid;
+ int accesslog_fd;
+ int errorlog_fd;
+ // true if errorlog_fd is referring to a terminal.
+ bool errorlog_tty;
+
+ LogConfig();
+ // Updates time stamp if difference between time_str_updated and now
+ // is 1 or more milliseconds.
+ void update_tstamp_millis(const std::chrono::system_clock::time_point &now);
+ // Updates time stamp if difference between time_str_updated and
+ // now, converted to time_t, is 1 or more seconds.
+ void update_tstamp(const std::chrono::system_clock::time_point &now);
+};
+
+// We need LogConfig per thread to avoid data race around opening file
+// descriptor for log files.
+LogConfig *log_config();
+
+// Deletes log_config
+void delete_log_config();
+
+} // namespace shrpx
+
+#endif // SHRPX_LOG_CONFIG_H
diff --git a/src/shrpx_memcached_connection.cc b/src/shrpx_memcached_connection.cc
new file mode 100644
index 0000000..f72cb11
--- /dev/null
+++ b/src/shrpx_memcached_connection.cc
@@ -0,0 +1,777 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_memcached_connection.h"
+
+#include <limits.h>
+#include <sys/uio.h>
+
+#include <cerrno>
+
+#include "shrpx_memcached_request.h"
+#include "shrpx_memcached_result.h"
+#include "shrpx_config.h"
+#include "shrpx_tls.h"
+#include "shrpx_log.h"
+#include "util.h"
+
+namespace shrpx {
+
+namespace {
+void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto conn = static_cast<Connection *>(w->data);
+ auto mconn = static_cast<MemcachedConnection *>(conn->data);
+
+ if (w == &conn->rt && !conn->expired_rt()) {
+ return;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ MCLOG(INFO, mconn) << "Time out";
+ }
+
+ mconn->disconnect();
+}
+} // namespace
+
+namespace {
+void readcb(struct ev_loop *loop, ev_io *w, int revents) {
+ auto conn = static_cast<Connection *>(w->data);
+ auto mconn = static_cast<MemcachedConnection *>(conn->data);
+
+ if (mconn->on_read() != 0) {
+ mconn->reconnect_or_fail();
+ return;
+ }
+}
+} // namespace
+
+namespace {
+void writecb(struct ev_loop *loop, ev_io *w, int revents) {
+ auto conn = static_cast<Connection *>(w->data);
+ auto mconn = static_cast<MemcachedConnection *>(conn->data);
+
+ if (mconn->on_write() != 0) {
+ mconn->reconnect_or_fail();
+ return;
+ }
+}
+} // namespace
+
+namespace {
+void connectcb(struct ev_loop *loop, ev_io *w, int revents) {
+ auto conn = static_cast<Connection *>(w->data);
+ auto mconn = static_cast<MemcachedConnection *>(conn->data);
+
+ if (mconn->connected() != 0) {
+ mconn->disconnect();
+ return;
+ }
+
+ writecb(loop, w, revents);
+}
+} // namespace
+
+constexpr auto write_timeout = 10_s;
+constexpr auto read_timeout = 10_s;
+
+MemcachedConnection::MemcachedConnection(const Address *addr,
+ struct ev_loop *loop, SSL_CTX *ssl_ctx,
+ const StringRef &sni_name,
+ MemchunkPool *mcpool,
+ std::mt19937 &gen)
+ : conn_(loop, -1, nullptr, mcpool, write_timeout, read_timeout, {}, {},
+ connectcb, readcb, timeoutcb, this, 0, 0., Proto::MEMCACHED),
+ do_read_(&MemcachedConnection::noop),
+ do_write_(&MemcachedConnection::noop),
+ sni_name_(sni_name),
+ connect_blocker_(
+ gen, loop, [] {}, [] {}),
+ parse_state_{},
+ addr_(addr),
+ ssl_ctx_(ssl_ctx),
+ sendsum_(0),
+ try_count_(0),
+ connected_(false) {}
+
+MemcachedConnection::~MemcachedConnection() { conn_.disconnect(); }
+
+namespace {
+void clear_request(std::deque<std::unique_ptr<MemcachedRequest>> &q) {
+ for (auto &req : q) {
+ if (req->cb) {
+ req->cb(req.get(),
+ MemcachedResult(MemcachedStatusCode::EXT_NETWORK_ERROR));
+ }
+ }
+ q.clear();
+}
+} // namespace
+
+void MemcachedConnection::disconnect() {
+ clear_request(recvq_);
+ clear_request(sendq_);
+
+ sendbufv_.clear();
+ sendsum_ = 0;
+
+ parse_state_ = {};
+
+ connected_ = false;
+
+ conn_.disconnect();
+
+ assert(recvbuf_.rleft() == 0);
+ recvbuf_.reset();
+
+ do_read_ = do_write_ = &MemcachedConnection::noop;
+}
+
+int MemcachedConnection::initiate_connection() {
+ assert(conn_.fd == -1);
+
+ if (ssl_ctx_) {
+ auto ssl = tls::create_ssl(ssl_ctx_);
+ if (!ssl) {
+ return -1;
+ }
+ conn_.set_ssl(ssl);
+ conn_.tls.client_session_cache = &tls_session_cache_;
+ }
+
+ conn_.fd = util::create_nonblock_socket(addr_->su.storage.ss_family);
+
+ if (conn_.fd == -1) {
+ auto error = errno;
+ MCLOG(WARN, this) << "socket() failed; errno=" << error;
+
+ return -1;
+ }
+
+ int rv;
+ rv = connect(conn_.fd, &addr_->su.sa, addr_->len);
+ if (rv != 0 && errno != EINPROGRESS) {
+ auto error = errno;
+ MCLOG(WARN, this) << "connect() failed; errno=" << error;
+
+ close(conn_.fd);
+ conn_.fd = -1;
+
+ return -1;
+ }
+
+ if (ssl_ctx_) {
+ if (!util::numeric_host(sni_name_.c_str())) {
+ SSL_set_tlsext_host_name(conn_.tls.ssl, sni_name_.c_str());
+ }
+
+ auto session = tls::reuse_tls_session(tls_session_cache_);
+ if (session) {
+ SSL_set_session(conn_.tls.ssl, session);
+ SSL_SESSION_free(session);
+ }
+
+ conn_.prepare_client_handshake();
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ MCLOG(INFO, this) << "Connecting to memcached server";
+ }
+
+ ev_io_set(&conn_.wev, conn_.fd, EV_WRITE);
+ ev_io_set(&conn_.rev, conn_.fd, EV_READ);
+
+ ev_set_cb(&conn_.wev, connectcb);
+
+ conn_.wlimit.startw();
+ ev_timer_again(conn_.loop, &conn_.wt);
+
+ return 0;
+}
+
+int MemcachedConnection::connected() {
+ auto sock_error = util::get_socket_error(conn_.fd);
+ if (sock_error != 0) {
+ MCLOG(WARN, this) << "memcached connect failed; addr="
+ << util::to_numeric_addr(addr_)
+ << ": errno=" << sock_error;
+
+ connect_blocker_.on_failure();
+
+ conn_.wlimit.stopw();
+
+ return -1;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ MCLOG(INFO, this) << "connected to memcached server";
+ }
+
+ conn_.rlimit.startw();
+
+ ev_set_cb(&conn_.wev, writecb);
+
+ if (conn_.tls.ssl) {
+ conn_.again_rt();
+
+ do_read_ = &MemcachedConnection::tls_handshake;
+ do_write_ = &MemcachedConnection::tls_handshake;
+
+ return 0;
+ }
+
+ ev_timer_stop(conn_.loop, &conn_.wt);
+
+ connected_ = true;
+
+ connect_blocker_.on_success();
+
+ do_read_ = &MemcachedConnection::read_clear;
+ do_write_ = &MemcachedConnection::write_clear;
+
+ return 0;
+}
+
+int MemcachedConnection::on_write() { return do_write_(*this); }
+int MemcachedConnection::on_read() { return do_read_(*this); }
+
+int MemcachedConnection::tls_handshake() {
+ ERR_clear_error();
+
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ auto rv = conn_.tls_handshake();
+ if (rv == SHRPX_ERR_INPROGRESS) {
+ return 0;
+ }
+
+ if (rv < 0) {
+ connect_blocker_.on_failure();
+ return rv;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "SSL/TLS handshake completed";
+ }
+
+ auto &tlsconf = get_config()->tls;
+
+ if (!tlsconf.insecure &&
+ tls::check_cert(conn_.tls.ssl, addr_, sni_name_) != 0) {
+ connect_blocker_.on_failure();
+ return -1;
+ }
+
+ ev_timer_stop(conn_.loop, &conn_.rt);
+ ev_timer_stop(conn_.loop, &conn_.wt);
+
+ connected_ = true;
+
+ connect_blocker_.on_success();
+
+ do_read_ = &MemcachedConnection::read_tls;
+ do_write_ = &MemcachedConnection::write_tls;
+
+ return on_write();
+}
+
+int MemcachedConnection::write_tls() {
+ if (!connected_) {
+ return 0;
+ }
+
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ std::array<struct iovec, MAX_WR_IOVCNT> iov;
+ std::array<uint8_t, 16_k> buf;
+
+ for (; !sendq_.empty();) {
+ auto iovcnt = fill_request_buffer(iov.data(), iov.size());
+ auto p = std::begin(buf);
+ for (size_t i = 0; i < iovcnt; ++i) {
+ auto &v = iov[i];
+ auto n = std::min(static_cast<size_t>(std::end(buf) - p), v.iov_len);
+ p = std::copy_n(static_cast<uint8_t *>(v.iov_base), n, p);
+ if (p == std::end(buf)) {
+ break;
+ }
+ }
+
+ auto nwrite = conn_.write_tls(buf.data(), p - std::begin(buf));
+ if (nwrite < 0) {
+ return -1;
+ }
+ if (nwrite == 0) {
+ return 0;
+ }
+
+ drain_send_queue(nwrite);
+ }
+
+ conn_.wlimit.stopw();
+ ev_timer_stop(conn_.loop, &conn_.wt);
+
+ return 0;
+}
+
+int MemcachedConnection::read_tls() {
+ if (!connected_) {
+ return 0;
+ }
+
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ for (;;) {
+ auto nread = conn_.read_tls(recvbuf_.last, recvbuf_.wleft());
+
+ if (nread == 0) {
+ return 0;
+ }
+
+ if (nread < 0) {
+ return -1;
+ }
+
+ recvbuf_.write(nread);
+
+ if (parse_packet() != 0) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int MemcachedConnection::write_clear() {
+ if (!connected_) {
+ return 0;
+ }
+
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ std::array<struct iovec, MAX_WR_IOVCNT> iov;
+
+ for (; !sendq_.empty();) {
+ auto iovcnt = fill_request_buffer(iov.data(), iov.size());
+ auto nwrite = conn_.writev_clear(iov.data(), iovcnt);
+ if (nwrite < 0) {
+ return -1;
+ }
+ if (nwrite == 0) {
+ return 0;
+ }
+
+ drain_send_queue(nwrite);
+ }
+
+ conn_.wlimit.stopw();
+ ev_timer_stop(conn_.loop, &conn_.wt);
+
+ return 0;
+}
+
+int MemcachedConnection::read_clear() {
+ if (!connected_) {
+ return 0;
+ }
+
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ for (;;) {
+ auto nread = conn_.read_clear(recvbuf_.last, recvbuf_.wleft());
+
+ if (nread == 0) {
+ return 0;
+ }
+
+ if (nread < 0) {
+ return -1;
+ }
+
+ recvbuf_.write(nread);
+
+ if (parse_packet() != 0) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int MemcachedConnection::parse_packet() {
+ auto in = recvbuf_.pos;
+
+ for (;;) {
+ auto busy = false;
+
+ switch (parse_state_.state) {
+ case MemcachedParseState::HEADER24: {
+ if (recvbuf_.last - in < 24) {
+ recvbuf_.drain_reset(in - recvbuf_.pos);
+ return 0;
+ }
+
+ if (recvq_.empty()) {
+ MCLOG(WARN, this)
+ << "Response received, but there is no in-flight request.";
+ return -1;
+ }
+
+ auto &req = recvq_.front();
+
+ if (*in != MEMCACHED_RES_MAGIC) {
+ MCLOG(WARN, this) << "Response has bad magic: "
+ << static_cast<uint32_t>(*in);
+ return -1;
+ }
+ ++in;
+
+ parse_state_.op = static_cast<MemcachedOp>(*in++);
+ parse_state_.keylen = util::get_uint16(in);
+ in += 2;
+ parse_state_.extralen = *in++;
+ // skip 1 byte reserved data type
+ ++in;
+ parse_state_.status_code =
+ static_cast<MemcachedStatusCode>(util::get_uint16(in));
+ in += 2;
+ parse_state_.totalbody = util::get_uint32(in);
+ in += 4;
+ // skip 4 bytes opaque
+ in += 4;
+ parse_state_.cas = util::get_uint64(in);
+ in += 8;
+
+ if (req->op != parse_state_.op) {
+ MCLOG(WARN, this)
+ << "opcode in response does not match to the request: want "
+ << static_cast<uint32_t>(req->op) << ", got "
+ << static_cast<uint32_t>(parse_state_.op);
+ return -1;
+ }
+
+ if (parse_state_.keylen != 0) {
+ MCLOG(WARN, this) << "zero length keylen expected: got "
+ << parse_state_.keylen;
+ return -1;
+ }
+
+ if (parse_state_.totalbody > 16_k) {
+ MCLOG(WARN, this) << "totalbody is too large: got "
+ << parse_state_.totalbody;
+ return -1;
+ }
+
+ if (parse_state_.op == MemcachedOp::GET &&
+ parse_state_.status_code == MemcachedStatusCode::NO_ERROR &&
+ parse_state_.extralen == 0) {
+ MCLOG(WARN, this) << "response for GET does not have extra";
+ return -1;
+ }
+
+ if (parse_state_.totalbody <
+ parse_state_.keylen + parse_state_.extralen) {
+ MCLOG(WARN, this) << "totalbody is too short: totalbody "
+ << parse_state_.totalbody << ", want min "
+ << parse_state_.keylen + parse_state_.extralen;
+ return -1;
+ }
+
+ if (parse_state_.extralen) {
+ parse_state_.state = MemcachedParseState::EXTRA;
+ parse_state_.read_left = parse_state_.extralen;
+ } else {
+ parse_state_.state = MemcachedParseState::VALUE;
+ parse_state_.read_left = parse_state_.totalbody - parse_state_.keylen -
+ parse_state_.extralen;
+ }
+ busy = true;
+ break;
+ }
+ case MemcachedParseState::EXTRA: {
+ // We don't use extra for now. Just read and forget.
+ auto n = std::min(static_cast<size_t>(recvbuf_.last - in),
+ parse_state_.read_left);
+
+ parse_state_.read_left -= n;
+ in += n;
+ if (parse_state_.read_left) {
+ recvbuf_.reset();
+ return 0;
+ }
+ parse_state_.state = MemcachedParseState::VALUE;
+ // since we require keylen == 0, totalbody - extralen ==
+ // valuelen
+ parse_state_.read_left =
+ parse_state_.totalbody - parse_state_.keylen - parse_state_.extralen;
+ busy = true;
+ break;
+ }
+ case MemcachedParseState::VALUE: {
+ auto n = std::min(static_cast<size_t>(recvbuf_.last - in),
+ parse_state_.read_left);
+
+ parse_state_.value.insert(std::end(parse_state_.value), in, in + n);
+
+ parse_state_.read_left -= n;
+ in += n;
+ if (parse_state_.read_left) {
+ recvbuf_.reset();
+ return 0;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ if (parse_state_.status_code != MemcachedStatusCode::NO_ERROR) {
+ MCLOG(INFO, this) << "response returned error status: "
+ << static_cast<uint16_t>(parse_state_.status_code);
+ }
+ }
+
+ // We require at least one complete response to clear try count.
+ try_count_ = 0;
+
+ auto req = std::move(recvq_.front());
+ recvq_.pop_front();
+
+ if (sendq_.empty() && recvq_.empty()) {
+ ev_timer_stop(conn_.loop, &conn_.rt);
+ }
+
+ if (!req->canceled && req->cb) {
+ req->cb(req.get(), MemcachedResult(parse_state_.status_code,
+ std::move(parse_state_.value)));
+ }
+
+ parse_state_ = {};
+ break;
+ }
+ }
+
+ if (!busy && in == recvbuf_.last) {
+ break;
+ }
+ }
+
+ assert(in == recvbuf_.last);
+ recvbuf_.reset();
+
+ return 0;
+}
+
+#undef DEFAULT_WR_IOVCNT
+#define DEFAULT_WR_IOVCNT 128
+
+#if defined(IOV_MAX) && IOV_MAX < DEFAULT_WR_IOVCNT
+# define MAX_WR_IOVCNT IOV_MAX
+#else // !defined(IOV_MAX) || IOV_MAX >= DEFAULT_WR_IOVCNT
+# define MAX_WR_IOVCNT DEFAULT_WR_IOVCNT
+#endif // !defined(IOV_MAX) || IOV_MAX >= DEFAULT_WR_IOVCNT
+
+size_t MemcachedConnection::fill_request_buffer(struct iovec *iov,
+ size_t iovlen) {
+ if (sendsum_ == 0) {
+ for (auto &req : sendq_) {
+ if (req->canceled) {
+ continue;
+ }
+ if (serialized_size(req.get()) + sendsum_ > 1300) {
+ break;
+ }
+ sendbufv_.emplace_back();
+ sendbufv_.back().req = req.get();
+ make_request(&sendbufv_.back(), req.get());
+ sendsum_ += sendbufv_.back().left();
+ }
+
+ if (sendsum_ == 0) {
+ sendq_.clear();
+ return 0;
+ }
+ }
+
+ size_t iovcnt = 0;
+ for (auto &buf : sendbufv_) {
+ if (iovcnt + 2 > iovlen) {
+ break;
+ }
+
+ auto req = buf.req;
+ if (buf.headbuf.rleft()) {
+ iov[iovcnt++] = {buf.headbuf.pos, buf.headbuf.rleft()};
+ }
+ if (buf.send_value_left) {
+ iov[iovcnt++] = {req->value.data() + req->value.size() -
+ buf.send_value_left,
+ buf.send_value_left};
+ }
+ }
+
+ return iovcnt;
+}
+
+void MemcachedConnection::drain_send_queue(size_t nwrite) {
+ sendsum_ -= nwrite;
+
+ while (nwrite > 0) {
+ auto &buf = sendbufv_.front();
+ auto &req = sendq_.front();
+ if (req->canceled) {
+ sendq_.pop_front();
+ continue;
+ }
+ assert(buf.req == req.get());
+ auto n = std::min(static_cast<size_t>(nwrite), buf.headbuf.rleft());
+ buf.headbuf.drain(n);
+ nwrite -= n;
+ n = std::min(static_cast<size_t>(nwrite), buf.send_value_left);
+ buf.send_value_left -= n;
+ nwrite -= n;
+
+ if (buf.headbuf.rleft() || buf.send_value_left) {
+ break;
+ }
+ sendbufv_.pop_front();
+ recvq_.push_back(std::move(sendq_.front()));
+ sendq_.pop_front();
+ }
+
+ // start read timer only when we wait for responses.
+ if (recvq_.empty()) {
+ ev_timer_stop(conn_.loop, &conn_.rt);
+ } else if (!ev_is_active(&conn_.rt)) {
+ conn_.again_rt();
+ }
+}
+
+size_t MemcachedConnection::serialized_size(MemcachedRequest *req) {
+ switch (req->op) {
+ case MemcachedOp::GET:
+ return 24 + req->key.size();
+ case MemcachedOp::ADD:
+ default:
+ return 24 + 8 + req->key.size() + req->value.size();
+ }
+}
+
+void MemcachedConnection::make_request(MemcachedSendbuf *sendbuf,
+ MemcachedRequest *req) {
+ auto &headbuf = sendbuf->headbuf;
+
+ std::fill(std::begin(headbuf.buf), std::end(headbuf.buf), 0);
+
+ headbuf[0] = MEMCACHED_REQ_MAGIC;
+ headbuf[1] = static_cast<uint8_t>(req->op);
+ switch (req->op) {
+ case MemcachedOp::GET:
+ util::put_uint16be(&headbuf[2], req->key.size());
+ util::put_uint32be(&headbuf[8], req->key.size());
+ headbuf.write(24);
+ break;
+ case MemcachedOp::ADD:
+ util::put_uint16be(&headbuf[2], req->key.size());
+ headbuf[4] = 8;
+ util::put_uint32be(&headbuf[8], 8 + req->key.size() + req->value.size());
+ util::put_uint32be(&headbuf[28], req->expiry);
+ headbuf.write(32);
+ break;
+ }
+
+ headbuf.write(req->key.c_str(), req->key.size());
+
+ sendbuf->send_value_left = req->value.size();
+}
+
+int MemcachedConnection::add_request(std::unique_ptr<MemcachedRequest> req) {
+ if (connect_blocker_.blocked()) {
+ return -1;
+ }
+
+ sendq_.push_back(std::move(req));
+
+ if (connected_) {
+ signal_write();
+ return 0;
+ }
+
+ if (conn_.fd == -1 && initiate_connection() != 0) {
+ connect_blocker_.on_failure();
+ disconnect();
+ return -1;
+ }
+
+ return 0;
+}
+
+// TODO should we start write timer too?
+void MemcachedConnection::signal_write() { conn_.wlimit.startw(); }
+
+int MemcachedConnection::noop() { return 0; }
+
+void MemcachedConnection::reconnect_or_fail() {
+ if (!connected_ || (recvq_.empty() && sendq_.empty())) {
+ disconnect();
+ return;
+ }
+
+ constexpr size_t MAX_TRY_COUNT = 3;
+
+ if (++try_count_ >= MAX_TRY_COUNT) {
+ if (LOG_ENABLED(INFO)) {
+ MCLOG(INFO, this) << "Tried " << MAX_TRY_COUNT
+ << " times, and all failed. Aborting";
+ }
+ try_count_ = 0;
+ disconnect();
+ return;
+ }
+
+ std::vector<std::unique_ptr<MemcachedRequest>> q;
+ q.reserve(recvq_.size() + sendq_.size());
+
+ if (LOG_ENABLED(INFO)) {
+ MCLOG(INFO, this) << "Retry connection, enqueue "
+ << recvq_.size() + sendq_.size() << " request(s) again";
+ }
+
+ q.insert(std::end(q), std::make_move_iterator(std::begin(recvq_)),
+ std::make_move_iterator(std::end(recvq_)));
+ q.insert(std::end(q), std::make_move_iterator(std::begin(sendq_)),
+ std::make_move_iterator(std::end(sendq_)));
+
+ recvq_.clear();
+ sendq_.clear();
+
+ disconnect();
+
+ sendq_.insert(std::end(sendq_), std::make_move_iterator(std::begin(q)),
+ std::make_move_iterator(std::end(q)));
+
+ if (initiate_connection() != 0) {
+ connect_blocker_.on_failure();
+ disconnect();
+ return;
+ }
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_memcached_connection.h b/src/shrpx_memcached_connection.h
new file mode 100644
index 0000000..2e097e1
--- /dev/null
+++ b/src/shrpx_memcached_connection.h
@@ -0,0 +1,155 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_MEMCACHED_CONNECTION_H
+#define SHRPX_MEMCACHED_CONNECTION_H
+
+#include "shrpx.h"
+
+#include <memory>
+#include <deque>
+
+#include <ev.h>
+
+#include "shrpx_connection.h"
+#include "shrpx_tls.h"
+#include "shrpx_connect_blocker.h"
+#include "buffer.h"
+#include "network.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+struct MemcachedRequest;
+enum class MemcachedOp : uint8_t;
+enum class MemcachedStatusCode : uint16_t;
+
+enum class MemcachedParseState {
+ HEADER24,
+ EXTRA,
+ VALUE,
+};
+
+// Stores state when parsing response from memcached server
+struct MemcachedParseContext {
+ // Buffer for value, dynamically allocated.
+ std::vector<uint8_t> value;
+ // cas in response
+ uint64_t cas;
+ // keylen in response
+ size_t keylen;
+ // extralen in response
+ size_t extralen;
+ // totalbody in response. The length of value is totalbody -
+ // extralen - keylen.
+ size_t totalbody;
+ // Number of bytes left to read variable length field.
+ size_t read_left;
+ // Parser state; see enum above
+ MemcachedParseState state;
+ // status_code in response
+ MemcachedStatusCode status_code;
+ // op in response
+ MemcachedOp op;
+};
+
+struct MemcachedSendbuf {
+ // Buffer for header + extra + key
+ Buffer<512> headbuf;
+ // MemcachedRequest associated to this object
+ MemcachedRequest *req;
+ // Number of bytes left when sending value
+ size_t send_value_left;
+ // Returns the number of bytes this object transmits.
+ size_t left() const { return headbuf.rleft() + send_value_left; }
+};
+
+constexpr uint8_t MEMCACHED_REQ_MAGIC = 0x80;
+constexpr uint8_t MEMCACHED_RES_MAGIC = 0x81;
+
+// MemcachedConnection implements part of memcached binary protocol.
+// This is not full brown implementation. Just the part we need is
+// implemented. We only use GET and ADD.
+//
+// https://github.com/memcached/memcached/blob/master/doc/protocol-binary.xml
+// https://code.google.com/p/memcached/wiki/MemcacheBinaryProtocol
+class MemcachedConnection {
+public:
+ MemcachedConnection(const Address *addr, struct ev_loop *loop,
+ SSL_CTX *ssl_ctx, const StringRef &sni_name,
+ MemchunkPool *mcpool, std::mt19937 &gen);
+ ~MemcachedConnection();
+
+ void disconnect();
+
+ int add_request(std::unique_ptr<MemcachedRequest> req);
+ int initiate_connection();
+
+ int connected();
+ int on_write();
+ int on_read();
+
+ int write_clear();
+ int read_clear();
+
+ int tls_handshake();
+ int write_tls();
+ int read_tls();
+
+ size_t fill_request_buffer(struct iovec *iov, size_t iovlen);
+ void drain_send_queue(size_t nwrite);
+
+ void make_request(MemcachedSendbuf *sendbuf, MemcachedRequest *req);
+ int parse_packet();
+ size_t serialized_size(MemcachedRequest *req);
+
+ void signal_write();
+
+ int noop();
+
+ void reconnect_or_fail();
+
+private:
+ Connection conn_;
+ std::deque<std::unique_ptr<MemcachedRequest>> recvq_;
+ std::deque<std::unique_ptr<MemcachedRequest>> sendq_;
+ std::deque<MemcachedSendbuf> sendbufv_;
+ std::function<int(MemcachedConnection &)> do_read_, do_write_;
+ StringRef sni_name_;
+ tls::TLSSessionCache tls_session_cache_;
+ ConnectBlocker connect_blocker_;
+ MemcachedParseContext parse_state_;
+ const Address *addr_;
+ SSL_CTX *ssl_ctx_;
+ // Sum of the bytes to be transmitted in sendbufv_.
+ size_t sendsum_;
+ size_t try_count_;
+ bool connected_;
+ Buffer<8_k> recvbuf_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_MEMCACHED_CONNECTION_H
diff --git a/src/shrpx_memcached_dispatcher.cc b/src/shrpx_memcached_dispatcher.cc
new file mode 100644
index 0000000..024bd5a
--- /dev/null
+++ b/src/shrpx_memcached_dispatcher.cc
@@ -0,0 +1,53 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_memcached_dispatcher.h"
+
+#include "shrpx_memcached_request.h"
+#include "shrpx_memcached_connection.h"
+#include "shrpx_config.h"
+#include "shrpx_log.h"
+
+namespace shrpx {
+
+MemcachedDispatcher::MemcachedDispatcher(const Address *addr,
+ struct ev_loop *loop, SSL_CTX *ssl_ctx,
+ const StringRef &sni_name,
+ MemchunkPool *mcpool,
+ std::mt19937 &gen)
+ : loop_(loop),
+ mconn_(std::make_unique<MemcachedConnection>(addr, loop_, ssl_ctx,
+ sni_name, mcpool, gen)) {}
+
+MemcachedDispatcher::~MemcachedDispatcher() {}
+
+int MemcachedDispatcher::add_request(std::unique_ptr<MemcachedRequest> req) {
+ if (mconn_->add_request(std::move(req)) != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_memcached_dispatcher.h b/src/shrpx_memcached_dispatcher.h
new file mode 100644
index 0000000..de1af80
--- /dev/null
+++ b/src/shrpx_memcached_dispatcher.h
@@ -0,0 +1,63 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_MEMCACHED_DISPATCHER_H
+#define SHRPX_MEMCACHED_DISPATCHER_H
+
+#include "shrpx.h"
+
+#include <memory>
+#include <random>
+
+#include <ev.h>
+
+#include <openssl/ssl.h>
+
+#include "memchunk.h"
+#include "network.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+struct MemcachedRequest;
+class MemcachedConnection;
+
+class MemcachedDispatcher {
+public:
+ MemcachedDispatcher(const Address *addr, struct ev_loop *loop,
+ SSL_CTX *ssl_ctx, const StringRef &sni_name,
+ MemchunkPool *mcpool, std::mt19937 &gen);
+ ~MemcachedDispatcher();
+
+ int add_request(std::unique_ptr<MemcachedRequest> req);
+
+private:
+ struct ev_loop *loop_;
+ std::unique_ptr<MemcachedConnection> mconn_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_MEMCACHED_DISPATCHER_H
diff --git a/src/shrpx_memcached_request.h b/src/shrpx_memcached_request.h
new file mode 100644
index 0000000..3696983
--- /dev/null
+++ b/src/shrpx_memcached_request.h
@@ -0,0 +1,59 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_MEMCACHED_REQUEST_H
+#define SHRPX_MEMCACHED_REQUEST_H
+
+#include "shrpx.h"
+
+#include <string>
+#include <vector>
+#include <memory>
+
+#include "shrpx_memcached_result.h"
+
+namespace shrpx {
+
+enum class MemcachedOp : uint8_t {
+ GET = 0x00,
+ ADD = 0x02,
+};
+
+struct MemcachedRequest;
+
+using MemcachedResultCallback =
+ std::function<void(MemcachedRequest *req, MemcachedResult res)>;
+
+struct MemcachedRequest {
+ std::string key;
+ std::vector<uint8_t> value;
+ MemcachedResultCallback cb;
+ uint32_t expiry;
+ MemcachedOp op;
+ bool canceled;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_MEMCACHED_REQUEST_H
diff --git a/src/shrpx_memcached_result.h b/src/shrpx_memcached_result.h
new file mode 100644
index 0000000..60a87af
--- /dev/null
+++ b/src/shrpx_memcached_result.h
@@ -0,0 +1,50 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_MEMCACHED_RESULT_H
+#define SHRPX_MEMCACHED_RESULT_H
+
+#include "shrpx.h"
+
+#include <vector>
+
+namespace shrpx {
+
+enum class MemcachedStatusCode : uint16_t {
+ NO_ERROR,
+ EXT_NETWORK_ERROR = 0x1001,
+};
+
+struct MemcachedResult {
+ MemcachedResult(MemcachedStatusCode status_code) : status_code(status_code) {}
+ MemcachedResult(MemcachedStatusCode status_code, std::vector<uint8_t> value)
+ : value(std::move(value)), status_code(status_code) {}
+
+ std::vector<uint8_t> value;
+ MemcachedStatusCode status_code;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_MEMCACHED_RESULT_H
diff --git a/src/shrpx_mruby.cc b/src/shrpx_mruby.cc
new file mode 100644
index 0000000..b5c6ed3
--- /dev/null
+++ b/src/shrpx_mruby.cc
@@ -0,0 +1,238 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_mruby.h"
+
+#include <mruby/compile.h>
+#include <mruby/string.h>
+
+#include "shrpx_downstream.h"
+#include "shrpx_config.h"
+#include "shrpx_mruby_module.h"
+#include "shrpx_downstream_connection.h"
+#include "shrpx_log.h"
+
+namespace shrpx {
+
+namespace mruby {
+
+MRubyContext::MRubyContext(mrb_state *mrb, mrb_value app, mrb_value env)
+ : mrb_(mrb), app_(std::move(app)), env_(std::move(env)) {}
+
+MRubyContext::~MRubyContext() {
+ if (mrb_) {
+ mrb_close(mrb_);
+ }
+}
+
+int MRubyContext::run_app(Downstream *downstream, int phase) {
+ if (!mrb_) {
+ return 0;
+ }
+
+ MRubyAssocData data{downstream, phase};
+
+ mrb_->ud = &data;
+
+ int rv = 0;
+ auto ai = mrb_gc_arena_save(mrb_);
+ auto ai_d = defer([ai, this]() { mrb_gc_arena_restore(mrb_, ai); });
+
+ const char *method;
+ switch (phase) {
+ case PHASE_REQUEST:
+ if (!mrb_respond_to(mrb_, app_, mrb_intern_lit(mrb_, "on_req"))) {
+ return 0;
+ }
+ method = "on_req";
+ break;
+ case PHASE_RESPONSE:
+ if (!mrb_respond_to(mrb_, app_, mrb_intern_lit(mrb_, "on_resp"))) {
+ return 0;
+ }
+ method = "on_resp";
+ break;
+ default:
+ assert(0);
+ abort();
+ }
+
+ auto res = mrb_funcall(mrb_, app_, method, 1, env_);
+ (void)res;
+
+ if (mrb_->exc) {
+ // If response has been committed, ignore error
+ if (downstream->get_response_state() != DownstreamState::MSG_COMPLETE) {
+ rv = -1;
+ }
+
+ auto exc = mrb_obj_value(mrb_->exc);
+ auto inspect = mrb_inspect(mrb_, exc);
+
+ LOG(ERROR) << "Exception caught while executing mruby code: "
+ << mrb_str_to_cstr(mrb_, inspect);
+ }
+
+ mrb_->ud = nullptr;
+
+ return rv;
+}
+
+int MRubyContext::run_on_request_proc(Downstream *downstream) {
+ return run_app(downstream, PHASE_REQUEST);
+}
+
+int MRubyContext::run_on_response_proc(Downstream *downstream) {
+ return run_app(downstream, PHASE_RESPONSE);
+}
+
+void MRubyContext::delete_downstream(Downstream *downstream) {
+ if (!mrb_) {
+ return;
+ }
+ delete_downstream_from_module(mrb_, downstream);
+}
+
+namespace {
+mrb_value instantiate_app(mrb_state *mrb, RProc *proc) {
+ mrb->ud = nullptr;
+
+ auto res = mrb_top_run(mrb, proc, mrb_top_self(mrb), 0);
+
+ if (mrb->exc) {
+ auto exc = mrb_obj_value(mrb->exc);
+ auto inspect = mrb_inspect(mrb, exc);
+
+ LOG(ERROR) << "Exception caught while executing mruby code: "
+ << mrb_str_to_cstr(mrb, inspect);
+
+ return mrb_nil_value();
+ }
+
+ return res;
+}
+} // namespace
+
+// Based on
+// https://github.com/h2o/h2o/blob/master/lib/handler/mruby.c. It is
+// very hard to write these kind of code because mruby has almost no
+// documentation about compiling or generating code, at least at the
+// time of this writing.
+RProc *compile(mrb_state *mrb, const StringRef &filename) {
+ if (filename.empty()) {
+ return nullptr;
+ }
+
+ auto infile = fopen(filename.c_str(), "rb");
+ if (infile == nullptr) {
+ LOG(ERROR) << "Could not open mruby file " << filename;
+ return nullptr;
+ }
+ auto infile_d = defer(fclose, infile);
+
+ auto mrbc = mrbc_context_new(mrb);
+ if (mrbc == nullptr) {
+ LOG(ERROR) << "mrb_context_new failed";
+ return nullptr;
+ }
+ auto mrbc_d = defer(mrbc_context_free, mrb, mrbc);
+
+ auto parser = mrb_parse_file(mrb, infile, nullptr);
+ if (parser == nullptr) {
+ LOG(ERROR) << "mrb_parse_nstring failed";
+ return nullptr;
+ }
+ auto parser_d = defer(mrb_parser_free, parser);
+
+ if (parser->nerr != 0) {
+ LOG(ERROR) << "mruby parser detected parse error";
+ return nullptr;
+ }
+
+ auto proc = mrb_generate_code(mrb, parser);
+ if (proc == nullptr) {
+ LOG(ERROR) << "mrb_generate_code failed";
+ return nullptr;
+ }
+
+ return proc;
+}
+
+std::unique_ptr<MRubyContext> create_mruby_context(const StringRef &filename) {
+ if (filename.empty()) {
+ return std::make_unique<MRubyContext>(nullptr, mrb_nil_value(),
+ mrb_nil_value());
+ }
+
+ auto mrb = mrb_open();
+ if (mrb == nullptr) {
+ LOG(ERROR) << "mrb_open failed";
+ return nullptr;
+ }
+
+ auto ai = mrb_gc_arena_save(mrb);
+
+ auto req_proc = compile(mrb, filename);
+
+ if (!req_proc) {
+ mrb_gc_arena_restore(mrb, ai);
+ LOG(ERROR) << "Could not compile mruby code " << filename;
+ mrb_close(mrb);
+ return nullptr;
+ }
+
+ auto env = init_module(mrb);
+
+ auto app = instantiate_app(mrb, req_proc);
+ if (mrb_nil_p(app)) {
+ mrb_gc_arena_restore(mrb, ai);
+ LOG(ERROR) << "Could not instantiate mruby app from " << filename;
+ mrb_close(mrb);
+ return nullptr;
+ }
+
+ mrb_gc_arena_restore(mrb, ai);
+
+ // TODO These are not necessary, because we retain app and env?
+ mrb_gc_protect(mrb, env);
+ mrb_gc_protect(mrb, app);
+
+ return std::make_unique<MRubyContext>(mrb, std::move(app), std::move(env));
+}
+
+mrb_sym intern_ptr(mrb_state *mrb, void *ptr) {
+ auto p = reinterpret_cast<uintptr_t>(ptr);
+
+ return mrb_intern(mrb, reinterpret_cast<const char *>(&p), sizeof(p));
+}
+
+void check_phase(mrb_state *mrb, int phase, int phase_mask) {
+ if ((phase & phase_mask) == 0) {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "operation was not allowed in this phase");
+ }
+}
+
+} // namespace mruby
+
+} // namespace shrpx
diff --git a/src/shrpx_mruby.h b/src/shrpx_mruby.h
new file mode 100644
index 0000000..518063d
--- /dev/null
+++ b/src/shrpx_mruby.h
@@ -0,0 +1,89 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_MRUBY_H
+#define SHRPX_MRUBY_H
+
+#include "shrpx.h"
+
+#include <memory>
+
+#include <mruby.h>
+#include <mruby/proc.h>
+
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+class Downstream;
+
+namespace mruby {
+
+class MRubyContext {
+public:
+ MRubyContext(mrb_state *mrb, mrb_value app, mrb_value env);
+ ~MRubyContext();
+
+ int run_on_request_proc(Downstream *downstream);
+ int run_on_response_proc(Downstream *downstream);
+
+ int run_app(Downstream *downstream, int phase);
+
+ void delete_downstream(Downstream *downstream);
+
+private:
+ mrb_state *mrb_;
+ mrb_value app_;
+ mrb_value env_;
+};
+
+enum {
+ PHASE_NONE = 0,
+ PHASE_REQUEST = 1,
+ PHASE_RESPONSE = 1 << 1,
+};
+
+struct MRubyAssocData {
+ Downstream *downstream;
+ int phase;
+};
+
+RProc *compile(mrb_state *mrb, const StringRef &filename);
+
+std::unique_ptr<MRubyContext> create_mruby_context(const StringRef &filename);
+
+// Return interned |ptr|.
+mrb_sym intern_ptr(mrb_state *mrb, void *ptr);
+
+// Checks that |phase| is set in |phase_mask|. If not set, raise
+// exception.
+void check_phase(mrb_state *mrb, int phase, int phase_mask);
+
+} // namespace mruby
+
+} // namespace shrpx
+
+#endif // SHRPX_MRUBY_H
diff --git a/src/shrpx_mruby_module.cc b/src/shrpx_mruby_module.cc
new file mode 100644
index 0000000..27b7769
--- /dev/null
+++ b/src/shrpx_mruby_module.cc
@@ -0,0 +1,113 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_mruby_module.h"
+
+#include <array>
+
+#include <mruby/variable.h>
+#include <mruby/string.h>
+#include <mruby/hash.h>
+#include <mruby/array.h>
+
+#include "shrpx_mruby.h"
+#include "shrpx_mruby_module_env.h"
+#include "shrpx_mruby_module_request.h"
+#include "shrpx_mruby_module_response.h"
+
+namespace shrpx {
+
+namespace mruby {
+
+namespace {
+mrb_value create_env(mrb_state *mrb) {
+ auto module = mrb_module_get(mrb, "Nghttpx");
+
+ auto env_class = mrb_class_get_under(mrb, module, "Env");
+ auto request_class = mrb_class_get_under(mrb, module, "Request");
+ auto response_class = mrb_class_get_under(mrb, module, "Response");
+
+ auto env = mrb_obj_new(mrb, env_class, 0, nullptr);
+ auto req = mrb_obj_new(mrb, request_class, 0, nullptr);
+ auto resp = mrb_obj_new(mrb, response_class, 0, nullptr);
+
+ mrb_iv_set(mrb, env, mrb_intern_lit(mrb, "req"), req);
+ mrb_iv_set(mrb, env, mrb_intern_lit(mrb, "resp"), resp);
+
+ return env;
+}
+} // namespace
+
+void delete_downstream_from_module(mrb_state *mrb, Downstream *downstream) {
+ auto module = mrb_module_get(mrb, "Nghttpx");
+ auto env = mrb_obj_iv_get(mrb, reinterpret_cast<RObject *>(module),
+ mrb_intern_lit(mrb, "env"));
+ if (mrb_nil_p(env)) {
+ return;
+ }
+
+ mrb_iv_remove(mrb, env, intern_ptr(mrb, downstream));
+}
+
+mrb_value init_module(mrb_state *mrb) {
+ auto module = mrb_define_module(mrb, "Nghttpx");
+
+ mrb_define_const(mrb, module, "REQUEST_PHASE",
+ mrb_fixnum_value(PHASE_REQUEST));
+ mrb_define_const(mrb, module, "RESPONSE_PHASE",
+ mrb_fixnum_value(PHASE_RESPONSE));
+
+ init_env_class(mrb, module);
+ init_request_class(mrb, module);
+ init_response_class(mrb, module);
+
+ return create_env(mrb);
+}
+
+mrb_value create_headers_hash(mrb_state *mrb, const HeaderRefs &headers) {
+ auto hash = mrb_hash_new(mrb);
+
+ for (auto &hd : headers) {
+ if (hd.name.empty() || hd.name[0] == ':') {
+ continue;
+ }
+ auto ai = mrb_gc_arena_save(mrb);
+
+ auto key = mrb_str_new(mrb, hd.name.c_str(), hd.name.size());
+ auto ary = mrb_hash_get(mrb, hash, key);
+ if (mrb_nil_p(ary)) {
+ ary = mrb_ary_new(mrb);
+ mrb_hash_set(mrb, hash, key, ary);
+ }
+ mrb_ary_push(mrb, ary, mrb_str_new(mrb, hd.value.c_str(), hd.value.size()));
+
+ mrb_gc_arena_restore(mrb, ai);
+ }
+
+ return hash;
+}
+
+} // namespace mruby
+
+} // namespace shrpx
diff --git a/src/shrpx_mruby_module.h b/src/shrpx_mruby_module.h
new file mode 100644
index 0000000..a426bea
--- /dev/null
+++ b/src/shrpx_mruby_module.h
@@ -0,0 +1,52 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_MRUBY_MODULE_H
+#define SHRPX_MRUBY_MODULE_H
+
+#include "shrpx.h"
+
+#include <mruby.h>
+
+#include "http2.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+class Downstream;
+
+namespace mruby {
+
+mrb_value init_module(mrb_state *mrb);
+
+void delete_downstream_from_module(mrb_state *mrb, Downstream *downstream);
+
+mrb_value create_headers_hash(mrb_state *mrb, const HeaderRefs &headers);
+
+} // namespace mruby
+
+} // namespace shrpx
+
+#endif // SHRPX_MRUBY_MODULE_H
diff --git a/src/shrpx_mruby_module_env.cc b/src/shrpx_mruby_module_env.cc
new file mode 100644
index 0000000..5ebd9c0
--- /dev/null
+++ b/src/shrpx_mruby_module_env.cc
@@ -0,0 +1,500 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_mruby_module_env.h"
+
+#include <mruby/variable.h>
+#include <mruby/string.h>
+#include <mruby/hash.h>
+
+#include "shrpx_downstream.h"
+#include "shrpx_upstream.h"
+#include "shrpx_client_handler.h"
+#include "shrpx_mruby.h"
+#include "shrpx_mruby_module.h"
+#include "shrpx_log.h"
+#include "shrpx_tls.h"
+
+namespace shrpx {
+
+namespace mruby {
+
+namespace {
+mrb_value env_init(mrb_state *mrb, mrb_value self) { return self; }
+} // namespace
+
+namespace {
+mrb_value env_get_req(mrb_state *mrb, mrb_value self) {
+ return mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "req"));
+}
+} // namespace
+
+namespace {
+mrb_value env_get_resp(mrb_state *mrb, mrb_value self) {
+ return mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "resp"));
+}
+} // namespace
+
+namespace {
+mrb_value env_get_ctx(mrb_state *mrb, mrb_value self) {
+ auto data = reinterpret_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+
+ auto dsym = intern_ptr(mrb, downstream);
+
+ auto ctx = mrb_iv_get(mrb, self, dsym);
+ if (mrb_nil_p(ctx)) {
+ ctx = mrb_hash_new(mrb);
+ mrb_iv_set(mrb, self, dsym, ctx);
+ }
+
+ return ctx;
+}
+} // namespace
+
+namespace {
+mrb_value env_get_phase(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+
+ return mrb_fixnum_value(data->phase);
+}
+} // namespace
+
+namespace {
+mrb_value env_get_remote_addr(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+
+ auto &ipaddr = handler->get_ipaddr();
+
+ return mrb_str_new(mrb, ipaddr.c_str(), ipaddr.size());
+}
+} // namespace
+
+namespace {
+mrb_value env_get_server_port(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+ auto faddr = handler->get_upstream_addr();
+
+ return mrb_fixnum_value(faddr->port);
+}
+} // namespace
+
+namespace {
+mrb_value env_get_server_addr(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+ auto faddr = handler->get_upstream_addr();
+
+ return mrb_str_new(mrb, faddr->host.c_str(), faddr->host.size());
+}
+} // namespace
+
+namespace {
+mrb_value env_get_tls_used(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+
+ return handler->get_ssl() ? mrb_true_value() : mrb_false_value();
+}
+} // namespace
+
+namespace {
+mrb_value env_get_tls_sni(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+ auto sni = handler->get_tls_sni();
+
+ return mrb_str_new(mrb, sni.c_str(), sni.size());
+}
+} // namespace
+
+namespace {
+mrb_value env_get_tls_client_fingerprint_md(mrb_state *mrb, const EVP_MD *md) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+ auto ssl = handler->get_ssl();
+
+ if (!ssl) {
+ return mrb_str_new_static(mrb, "", 0);
+ }
+
+#if OPENSSL_3_0_0_API
+ auto x = SSL_get0_peer_certificate(ssl);
+#else // !OPENSSL_3_0_0_API
+ auto x = SSL_get_peer_certificate(ssl);
+#endif // !OPENSSL_3_0_0_API
+ if (!x) {
+ return mrb_str_new_static(mrb, "", 0);
+ }
+
+ // Currently the largest hash value is SHA-256, which is 32 bytes.
+ std::array<uint8_t, 32> buf;
+ auto slen = tls::get_x509_fingerprint(buf.data(), buf.size(), x, md);
+#if !OPENSSL_3_0_0_API
+ X509_free(x);
+#endif // !OPENSSL_3_0_0_API
+ if (slen == -1) {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "could not compute client fingerprint");
+ }
+
+ // TODO Use template version of format_hex
+ auto &balloc = downstream->get_block_allocator();
+ auto f = util::format_hex(balloc,
+ StringRef{std::begin(buf), std::begin(buf) + slen});
+ return mrb_str_new(mrb, f.c_str(), f.size());
+}
+} // namespace
+
+namespace {
+mrb_value env_get_tls_client_fingerprint_sha256(mrb_state *mrb,
+ mrb_value self) {
+ return env_get_tls_client_fingerprint_md(mrb, EVP_sha256());
+}
+} // namespace
+
+namespace {
+mrb_value env_get_tls_client_fingerprint_sha1(mrb_state *mrb, mrb_value self) {
+ return env_get_tls_client_fingerprint_md(mrb, EVP_sha1());
+}
+} // namespace
+
+namespace {
+mrb_value env_get_tls_client_subject_name(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+ auto ssl = handler->get_ssl();
+
+ if (!ssl) {
+ return mrb_str_new_static(mrb, "", 0);
+ }
+
+#if OPENSSL_3_0_0_API
+ auto x = SSL_get0_peer_certificate(ssl);
+#else // !OPENSSL_3_0_0_API
+ auto x = SSL_get_peer_certificate(ssl);
+#endif // !OPENSSL_3_0_0_API
+ if (!x) {
+ return mrb_str_new_static(mrb, "", 0);
+ }
+
+ auto &balloc = downstream->get_block_allocator();
+ auto name = tls::get_x509_subject_name(balloc, x);
+#if !OPENSSL_3_0_0_API
+ X509_free(x);
+#endif // !OPENSSL_3_0_0_API
+ return mrb_str_new(mrb, name.c_str(), name.size());
+}
+} // namespace
+
+namespace {
+mrb_value env_get_tls_client_issuer_name(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+ auto ssl = handler->get_ssl();
+
+ if (!ssl) {
+ return mrb_str_new_static(mrb, "", 0);
+ }
+
+#if OPENSSL_3_0_0_API
+ auto x = SSL_get0_peer_certificate(ssl);
+#else // !OPENSSL_3_0_0_API
+ auto x = SSL_get_peer_certificate(ssl);
+#endif // !OPENSSL_3_0_0_API
+ if (!x) {
+ return mrb_str_new_static(mrb, "", 0);
+ }
+
+ auto &balloc = downstream->get_block_allocator();
+ auto name = tls::get_x509_issuer_name(balloc, x);
+#if !OPENSSL_3_0_0_API
+ X509_free(x);
+#endif // !OPENSSL_3_0_0_API
+ return mrb_str_new(mrb, name.c_str(), name.size());
+}
+} // namespace
+
+namespace {
+mrb_value env_get_tls_client_serial(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+ auto ssl = handler->get_ssl();
+
+ if (!ssl) {
+ return mrb_str_new_static(mrb, "", 0);
+ }
+
+#if OPENSSL_3_0_0_API
+ auto x = SSL_get0_peer_certificate(ssl);
+#else // !OPENSSL_3_0_0_API
+ auto x = SSL_get_peer_certificate(ssl);
+#endif // !OPENSSL_3_0_0_API
+ if (!x) {
+ return mrb_str_new_static(mrb, "", 0);
+ }
+
+ auto &balloc = downstream->get_block_allocator();
+ auto sn = tls::get_x509_serial(balloc, x);
+#if !OPENSSL_3_0_0_API
+ X509_free(x);
+#endif // !OPENSSL_3_0_0_API
+ return mrb_str_new(mrb, sn.c_str(), sn.size());
+}
+} // namespace
+
+namespace {
+mrb_value env_get_tls_client_not_before(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+ auto ssl = handler->get_ssl();
+
+ if (!ssl) {
+ return mrb_fixnum_value(0);
+ }
+
+#if OPENSSL_3_0_0_API
+ auto x = SSL_get0_peer_certificate(ssl);
+#else // !OPENSSL_3_0_0_API
+ auto x = SSL_get_peer_certificate(ssl);
+#endif // !OPENSSL_3_0_0_API
+ if (!x) {
+ return mrb_fixnum_value(0);
+ }
+
+ time_t t;
+ if (tls::get_x509_not_before(t, x) != 0) {
+ t = 0;
+ }
+
+#if !OPENSSL_3_0_0_API
+ X509_free(x);
+#endif // !OPENSSL_3_0_0_API
+
+ return mrb_fixnum_value(t);
+}
+} // namespace
+
+namespace {
+mrb_value env_get_tls_client_not_after(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+ auto ssl = handler->get_ssl();
+
+ if (!ssl) {
+ return mrb_fixnum_value(0);
+ }
+
+#if OPENSSL_3_0_0_API
+ auto x = SSL_get0_peer_certificate(ssl);
+#else // !OPENSSL_3_0_0_API
+ auto x = SSL_get_peer_certificate(ssl);
+#endif // !OPENSSL_3_0_0_API
+ if (!x) {
+ return mrb_fixnum_value(0);
+ }
+
+ time_t t;
+ if (tls::get_x509_not_after(t, x) != 0) {
+ t = 0;
+ }
+
+#if !OPENSSL_3_0_0_API
+ X509_free(x);
+#endif // !OPENSSL_3_0_0_API
+
+ return mrb_fixnum_value(t);
+}
+} // namespace
+
+namespace {
+mrb_value env_get_tls_cipher(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+ auto ssl = handler->get_ssl();
+
+ if (!ssl) {
+ return mrb_str_new_static(mrb, "", 0);
+ }
+
+ return mrb_str_new_cstr(mrb, SSL_get_cipher_name(ssl));
+}
+} // namespace
+
+namespace {
+mrb_value env_get_tls_protocol(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+ auto ssl = handler->get_ssl();
+
+ if (!ssl) {
+ return mrb_str_new_static(mrb, "", 0);
+ }
+
+ return mrb_str_new_cstr(mrb, nghttp2::tls::get_tls_protocol(ssl));
+}
+} // namespace
+
+namespace {
+mrb_value env_get_tls_session_id(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+ auto ssl = handler->get_ssl();
+
+ if (!ssl) {
+ return mrb_str_new_static(mrb, "", 0);
+ }
+
+ auto session = SSL_get_session(ssl);
+ if (!session) {
+ return mrb_str_new_static(mrb, "", 0);
+ }
+
+ unsigned int session_id_length = 0;
+ auto session_id = SSL_SESSION_get_id(session, &session_id_length);
+
+ // TODO Use template version of util::format_hex.
+ auto &balloc = downstream->get_block_allocator();
+ auto id = util::format_hex(balloc, StringRef{session_id, session_id_length});
+ return mrb_str_new(mrb, id.c_str(), id.size());
+}
+} // namespace
+
+namespace {
+mrb_value env_get_tls_session_reused(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+ auto ssl = handler->get_ssl();
+
+ if (!ssl) {
+ return mrb_false_value();
+ }
+
+ return SSL_session_reused(ssl) ? mrb_true_value() : mrb_false_value();
+}
+} // namespace
+
+namespace {
+mrb_value env_get_alpn(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+ auto alpn = handler->get_alpn();
+ return mrb_str_new(mrb, alpn.c_str(), alpn.size());
+}
+} // namespace
+
+namespace {
+mrb_value env_get_tls_handshake_finished(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+ auto conn = handler->get_connection();
+ return SSL_is_init_finished(conn->tls.ssl) ? mrb_true_value()
+ : mrb_false_value();
+}
+} // namespace
+
+void init_env_class(mrb_state *mrb, RClass *module) {
+ auto env_class =
+ mrb_define_class_under(mrb, module, "Env", mrb->object_class);
+
+ mrb_define_method(mrb, env_class, "initialize", env_init, MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "req", env_get_req, MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "resp", env_get_resp, MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "ctx", env_get_ctx, MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "phase", env_get_phase, MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "remote_addr", env_get_remote_addr,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "server_addr", env_get_server_addr,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "server_port", env_get_server_port,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "tls_used", env_get_tls_used,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "tls_sni", env_get_tls_sni,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "tls_client_fingerprint_sha256",
+ env_get_tls_client_fingerprint_sha256, MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "tls_client_fingerprint_sha1",
+ env_get_tls_client_fingerprint_sha1, MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "tls_client_issuer_name",
+ env_get_tls_client_issuer_name, MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "tls_client_subject_name",
+ env_get_tls_client_subject_name, MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "tls_client_serial",
+ env_get_tls_client_serial, MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "tls_client_not_before",
+ env_get_tls_client_not_before, MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "tls_client_not_after",
+ env_get_tls_client_not_after, MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "tls_cipher", env_get_tls_cipher,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "tls_protocol", env_get_tls_protocol,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "tls_session_id", env_get_tls_session_id,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "tls_session_reused",
+ env_get_tls_session_reused, MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "alpn", env_get_alpn, MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "tls_handshake_finished",
+ env_get_tls_handshake_finished, MRB_ARGS_NONE());
+}
+
+} // namespace mruby
+
+} // namespace shrpx
diff --git a/src/shrpx_mruby_module_env.h b/src/shrpx_mruby_module_env.h
new file mode 100644
index 0000000..0884678
--- /dev/null
+++ b/src/shrpx_mruby_module_env.h
@@ -0,0 +1,42 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_MRUBY_MODULE_ENV_H
+#define SHRPX_MRUBY_MODULE_ENV_H
+
+#include "shrpx.h"
+
+#include <mruby.h>
+
+namespace shrpx {
+
+namespace mruby {
+
+void init_env_class(mrb_state *mrb, RClass *module);
+
+} // namespace mruby
+
+} // namespace shrpx
+
+#endif // SHRPX_MRUBY_MODULE_ENV_H
diff --git a/src/shrpx_mruby_module_request.cc b/src/shrpx_mruby_module_request.cc
new file mode 100644
index 0000000..1ed3aa9
--- /dev/null
+++ b/src/shrpx_mruby_module_request.cc
@@ -0,0 +1,367 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_mruby_module_request.h"
+
+#include <mruby/variable.h>
+#include <mruby/string.h>
+#include <mruby/hash.h>
+#include <mruby/array.h>
+
+#include "shrpx_downstream.h"
+#include "shrpx_upstream.h"
+#include "shrpx_client_handler.h"
+#include "shrpx_mruby.h"
+#include "shrpx_mruby_module.h"
+#include "shrpx_log.h"
+#include "util.h"
+#include "http2.h"
+
+namespace shrpx {
+
+namespace mruby {
+
+namespace {
+mrb_value request_init(mrb_state *mrb, mrb_value self) { return self; }
+} // namespace
+
+namespace {
+mrb_value request_get_http_version_major(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ const auto &req = downstream->request();
+ return mrb_fixnum_value(req.http_major);
+}
+} // namespace
+
+namespace {
+mrb_value request_get_http_version_minor(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ const auto &req = downstream->request();
+ return mrb_fixnum_value(req.http_minor);
+}
+} // namespace
+
+namespace {
+mrb_value request_get_method(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ const auto &req = downstream->request();
+ auto method = http2::to_method_string(req.method);
+
+ return mrb_str_new(mrb, method.c_str(), method.size());
+}
+} // namespace
+
+namespace {
+mrb_value request_set_method(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto &req = downstream->request();
+
+ check_phase(mrb, data->phase, PHASE_REQUEST);
+
+ const char *method;
+ mrb_int n;
+ mrb_get_args(mrb, "s", &method, &n);
+ if (n == 0) {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "method must not be empty string");
+ }
+ auto token =
+ http2::lookup_method_token(reinterpret_cast<const uint8_t *>(method), n);
+ if (token == -1) {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "method not supported");
+ }
+
+ req.method = token;
+
+ return self;
+}
+} // namespace
+
+namespace {
+mrb_value request_get_authority(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ const auto &req = downstream->request();
+
+ return mrb_str_new(mrb, req.authority.c_str(), req.authority.size());
+}
+} // namespace
+
+namespace {
+mrb_value request_set_authority(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto &req = downstream->request();
+
+ auto &balloc = downstream->get_block_allocator();
+
+ check_phase(mrb, data->phase, PHASE_REQUEST);
+
+ const char *authority;
+ mrb_int n;
+ mrb_get_args(mrb, "s", &authority, &n);
+ if (n == 0) {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "authority must not be empty string");
+ }
+
+ req.authority =
+ make_string_ref(balloc, StringRef{authority, static_cast<size_t>(n)});
+
+ return self;
+}
+} // namespace
+
+namespace {
+mrb_value request_get_scheme(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ const auto &req = downstream->request();
+
+ return mrb_str_new(mrb, req.scheme.c_str(), req.scheme.size());
+}
+} // namespace
+
+namespace {
+mrb_value request_set_scheme(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto &req = downstream->request();
+
+ auto &balloc = downstream->get_block_allocator();
+
+ check_phase(mrb, data->phase, PHASE_REQUEST);
+
+ const char *scheme;
+ mrb_int n;
+ mrb_get_args(mrb, "s", &scheme, &n);
+ if (n == 0) {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "scheme must not be empty string");
+ }
+
+ req.scheme =
+ make_string_ref(balloc, StringRef{scheme, static_cast<size_t>(n)});
+
+ return self;
+}
+} // namespace
+
+namespace {
+mrb_value request_get_path(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ const auto &req = downstream->request();
+
+ return mrb_str_new(mrb, req.path.c_str(), req.path.size());
+}
+} // namespace
+
+namespace {
+mrb_value request_set_path(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto &req = downstream->request();
+
+ auto &balloc = downstream->get_block_allocator();
+
+ check_phase(mrb, data->phase, PHASE_REQUEST);
+
+ const char *path;
+ mrb_int pathlen;
+ mrb_get_args(mrb, "s", &path, &pathlen);
+
+ req.path =
+ make_string_ref(balloc, StringRef{path, static_cast<size_t>(pathlen)});
+
+ return self;
+}
+} // namespace
+
+namespace {
+mrb_value request_get_headers(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ const auto &req = downstream->request();
+ return create_headers_hash(mrb, req.fs.headers());
+}
+} // namespace
+
+namespace {
+mrb_value request_mod_header(mrb_state *mrb, mrb_value self, bool repl) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto &req = downstream->request();
+ auto &balloc = downstream->get_block_allocator();
+
+ check_phase(mrb, data->phase, PHASE_REQUEST);
+
+ mrb_value key, values;
+ mrb_get_args(mrb, "So", &key, &values);
+
+ if (RSTRING_LEN(key) == 0) {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "empty key is not allowed");
+ }
+
+ auto ai = mrb_gc_arena_save(mrb);
+
+ key = mrb_funcall(mrb, key, "downcase", 0);
+
+ auto keyref =
+ make_string_ref(balloc, StringRef{RSTRING_PTR(key),
+ static_cast<size_t>(RSTRING_LEN(key))});
+
+ mrb_gc_arena_restore(mrb, ai);
+
+ auto token = http2::lookup_token(keyref.byte(), keyref.size());
+
+ if (repl) {
+ size_t p = 0;
+ auto &headers = req.fs.headers();
+ for (size_t i = 0; i < headers.size(); ++i) {
+ auto &kv = headers[i];
+ if (kv.name == keyref) {
+ continue;
+ }
+ if (i != p) {
+ headers[p] = std::move(kv);
+ }
+ ++p;
+ }
+ headers.resize(p);
+ }
+
+ if (mrb_array_p(values)) {
+ auto n = RARRAY_LEN(values);
+ for (int i = 0; i < n; ++i) {
+ auto value = mrb_ary_ref(mrb, values, i);
+ if (!mrb_string_p(value)) {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "value must be string");
+ }
+
+ req.fs.add_header_token(
+ keyref,
+ make_string_ref(balloc,
+ StringRef{RSTRING_PTR(value),
+ static_cast<size_t>(RSTRING_LEN(value))}),
+ false, token);
+ }
+ } else if (mrb_string_p(values)) {
+ req.fs.add_header_token(
+ keyref,
+ make_string_ref(balloc,
+ StringRef{RSTRING_PTR(values),
+ static_cast<size_t>(RSTRING_LEN(values))}),
+ false, token);
+ } else {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "value must be string");
+ }
+
+ return mrb_nil_value();
+}
+} // namespace
+
+namespace {
+mrb_value request_set_header(mrb_state *mrb, mrb_value self) {
+ return request_mod_header(mrb, self, true);
+}
+} // namespace
+
+namespace {
+mrb_value request_add_header(mrb_state *mrb, mrb_value self) {
+ return request_mod_header(mrb, self, false);
+}
+} // namespace
+
+namespace {
+mrb_value request_clear_headers(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto &req = downstream->request();
+
+ check_phase(mrb, data->phase, PHASE_REQUEST);
+
+ req.fs.clear_headers();
+
+ return mrb_nil_value();
+}
+} // namespace
+
+namespace {
+mrb_value request_push(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+
+ const char *uri;
+ mrb_int len;
+ mrb_get_args(mrb, "s", &uri, &len);
+
+ upstream->initiate_push(downstream, StringRef{uri, static_cast<size_t>(len)});
+
+ return mrb_nil_value();
+}
+} // namespace
+
+void init_request_class(mrb_state *mrb, RClass *module) {
+ auto request_class =
+ mrb_define_class_under(mrb, module, "Request", mrb->object_class);
+
+ mrb_define_method(mrb, request_class, "initialize", request_init,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, request_class, "http_version_major",
+ request_get_http_version_major, MRB_ARGS_NONE());
+ mrb_define_method(mrb, request_class, "http_version_minor",
+ request_get_http_version_minor, MRB_ARGS_NONE());
+ mrb_define_method(mrb, request_class, "method", request_get_method,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, request_class, "method=", request_set_method,
+ MRB_ARGS_REQ(1));
+ mrb_define_method(mrb, request_class, "authority", request_get_authority,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, request_class, "authority=", request_set_authority,
+ MRB_ARGS_REQ(1));
+ mrb_define_method(mrb, request_class, "scheme", request_get_scheme,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, request_class, "scheme=", request_set_scheme,
+ MRB_ARGS_REQ(1));
+ mrb_define_method(mrb, request_class, "path", request_get_path,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, request_class, "path=", request_set_path,
+ MRB_ARGS_REQ(1));
+ mrb_define_method(mrb, request_class, "headers", request_get_headers,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, request_class, "add_header", request_add_header,
+ MRB_ARGS_REQ(2));
+ mrb_define_method(mrb, request_class, "set_header", request_set_header,
+ MRB_ARGS_REQ(2));
+ mrb_define_method(mrb, request_class, "clear_headers", request_clear_headers,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, request_class, "push", request_push, MRB_ARGS_REQ(1));
+}
+
+} // namespace mruby
+
+} // namespace shrpx
diff --git a/src/shrpx_mruby_module_request.h b/src/shrpx_mruby_module_request.h
new file mode 100644
index 0000000..e74f335
--- /dev/null
+++ b/src/shrpx_mruby_module_request.h
@@ -0,0 +1,42 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_MRUBY_MODULE_REQUEST_H
+#define SHRPX_MRUBY_MODULE_REQUEST_H
+
+#include "shrpx.h"
+
+#include <mruby.h>
+
+namespace shrpx {
+
+namespace mruby {
+
+void init_request_class(mrb_state *mrb, RClass *module);
+
+} // namespace mruby
+
+} // namespace shrpx
+
+#endif // SHRPX_MRUBY_MODULE_REQUEST_H
diff --git a/src/shrpx_mruby_module_response.cc b/src/shrpx_mruby_module_response.cc
new file mode 100644
index 0000000..1de1d5f
--- /dev/null
+++ b/src/shrpx_mruby_module_response.cc
@@ -0,0 +1,398 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_mruby_module_response.h"
+
+#include <mruby/variable.h>
+#include <mruby/string.h>
+#include <mruby/hash.h>
+#include <mruby/array.h>
+
+#include "shrpx_downstream.h"
+#include "shrpx_upstream.h"
+#include "shrpx_client_handler.h"
+#include "shrpx_mruby.h"
+#include "shrpx_mruby_module.h"
+#include "shrpx_log.h"
+#include "util.h"
+#include "http2.h"
+
+namespace shrpx {
+
+namespace mruby {
+
+namespace {
+mrb_value response_init(mrb_state *mrb, mrb_value self) { return self; }
+} // namespace
+
+namespace {
+mrb_value response_get_http_version_major(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ const auto &resp = downstream->response();
+ return mrb_fixnum_value(resp.http_major);
+}
+} // namespace
+
+namespace {
+mrb_value response_get_http_version_minor(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ const auto &resp = downstream->response();
+ return mrb_fixnum_value(resp.http_minor);
+}
+} // namespace
+
+namespace {
+mrb_value response_get_status(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ const auto &resp = downstream->response();
+ return mrb_fixnum_value(resp.http_status);
+}
+} // namespace
+
+namespace {
+mrb_value response_set_status(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto &resp = downstream->response();
+
+ mrb_int status;
+ mrb_get_args(mrb, "i", &status);
+ // We don't support 1xx status code for mruby scripting yet.
+ if (status < 200 || status > 999) {
+ mrb_raise(mrb, E_RUNTIME_ERROR,
+ "invalid status; it should be [200, 999], inclusive");
+ }
+
+ resp.http_status = status;
+
+ return self;
+}
+} // namespace
+
+namespace {
+mrb_value response_get_headers(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ const auto &resp = downstream->response();
+
+ return create_headers_hash(mrb, resp.fs.headers());
+}
+} // namespace
+
+namespace {
+mrb_value response_mod_header(mrb_state *mrb, mrb_value self, bool repl) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto &resp = downstream->response();
+ auto &balloc = downstream->get_block_allocator();
+
+ mrb_value key, values;
+ mrb_get_args(mrb, "So", &key, &values);
+
+ if (RSTRING_LEN(key) == 0) {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "empty key is not allowed");
+ }
+
+ auto ai = mrb_gc_arena_save(mrb);
+
+ key = mrb_funcall(mrb, key, "downcase", 0);
+
+ auto keyref =
+ make_string_ref(balloc, StringRef{RSTRING_PTR(key),
+ static_cast<size_t>(RSTRING_LEN(key))});
+
+ mrb_gc_arena_restore(mrb, ai);
+
+ auto token = http2::lookup_token(keyref.byte(), keyref.size());
+
+ if (repl) {
+ size_t p = 0;
+ auto &headers = resp.fs.headers();
+ for (size_t i = 0; i < headers.size(); ++i) {
+ auto &kv = headers[i];
+ if (kv.name == keyref) {
+ continue;
+ }
+ if (i != p) {
+ headers[p] = std::move(kv);
+ }
+ ++p;
+ }
+ headers.resize(p);
+ }
+
+ if (mrb_array_p(values)) {
+ auto n = RARRAY_LEN(values);
+ for (int i = 0; i < n; ++i) {
+ auto value = mrb_ary_ref(mrb, values, i);
+ if (!mrb_string_p(value)) {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "value must be string");
+ }
+
+ resp.fs.add_header_token(
+ keyref,
+ make_string_ref(balloc,
+ StringRef{RSTRING_PTR(value),
+ static_cast<size_t>(RSTRING_LEN(value))}),
+ false, token);
+ }
+ } else if (mrb_string_p(values)) {
+ resp.fs.add_header_token(
+ keyref,
+ make_string_ref(balloc,
+ StringRef{RSTRING_PTR(values),
+ static_cast<size_t>(RSTRING_LEN(values))}),
+ false, token);
+ } else {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "value must be string");
+ }
+
+ return mrb_nil_value();
+}
+} // namespace
+
+namespace {
+mrb_value response_set_header(mrb_state *mrb, mrb_value self) {
+ return response_mod_header(mrb, self, true);
+}
+} // namespace
+
+namespace {
+mrb_value response_add_header(mrb_state *mrb, mrb_value self) {
+ return response_mod_header(mrb, self, false);
+}
+} // namespace
+
+namespace {
+mrb_value response_clear_headers(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto &resp = downstream->response();
+
+ resp.fs.clear_headers();
+
+ return mrb_nil_value();
+}
+} // namespace
+
+namespace {
+mrb_value response_return(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto &req = downstream->request();
+ auto &resp = downstream->response();
+ int rv;
+
+ auto &balloc = downstream->get_block_allocator();
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "response has already been committed");
+ }
+
+ const char *val;
+ mrb_int vallen;
+ mrb_get_args(mrb, "|s", &val, &vallen);
+
+ const uint8_t *body = nullptr;
+ size_t bodylen = 0;
+
+ if (resp.http_status == 0) {
+ resp.http_status = 200;
+ }
+
+ if (downstream->expect_response_body() && vallen > 0) {
+ body = reinterpret_cast<const uint8_t *>(val);
+ bodylen = vallen;
+ }
+
+ auto cl = resp.fs.header(http2::HD_CONTENT_LENGTH);
+
+ if (resp.http_status == 204 ||
+ (resp.http_status == 200 && req.method == HTTP_CONNECT)) {
+ if (cl) {
+ // Delete content-length here
+ http2::erase_header(cl);
+ }
+
+ resp.fs.content_length = -1;
+ } else {
+ auto content_length = util::make_string_ref_uint(balloc, vallen);
+
+ if (cl) {
+ cl->value = content_length;
+ } else {
+ resp.fs.add_header_token(StringRef::from_lit("content-length"),
+ content_length, false, http2::HD_CONTENT_LENGTH);
+ }
+
+ resp.fs.content_length = vallen;
+ }
+
+ auto date = resp.fs.header(http2::HD_DATE);
+ if (!date) {
+ auto lgconf = log_config();
+ lgconf->update_tstamp(std::chrono::system_clock::now());
+ resp.fs.add_header_token(StringRef::from_lit("date"),
+ make_string_ref(balloc, lgconf->tstamp->time_http),
+ false, http2::HD_DATE);
+ }
+
+ auto upstream = downstream->get_upstream();
+
+ rv = upstream->send_reply(downstream, body, bodylen);
+ if (rv != 0) {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "could not send response");
+ }
+
+ auto handler = upstream->get_client_handler();
+
+ handler->signal_write();
+
+ return self;
+}
+} // namespace
+
+namespace {
+mrb_value response_send_info(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto &resp = downstream->response();
+ int rv;
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "response has already been committed");
+ }
+
+ mrb_int http_status;
+ mrb_value hash;
+ mrb_get_args(mrb, "iH", &http_status, &hash);
+
+ if (http_status / 100 != 1) {
+ mrb_raise(mrb, E_RUNTIME_ERROR,
+ "status_code must be in range [100, 199], inclusive");
+ }
+
+ auto &balloc = downstream->get_block_allocator();
+
+ auto keys = mrb_hash_keys(mrb, hash);
+ auto keyslen = RARRAY_LEN(keys);
+
+ for (int i = 0; i < keyslen; ++i) {
+ auto key = mrb_ary_ref(mrb, keys, i);
+ if (!mrb_string_p(key)) {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "key must be string");
+ }
+
+ auto values = mrb_hash_get(mrb, hash, key);
+
+ auto ai = mrb_gc_arena_save(mrb);
+
+ key = mrb_funcall(mrb, key, "downcase", 0);
+
+ auto keyref = make_string_ref(
+ balloc,
+ StringRef{RSTRING_PTR(key), static_cast<size_t>(RSTRING_LEN(key))});
+
+ mrb_gc_arena_restore(mrb, ai);
+
+ auto token = http2::lookup_token(keyref.byte(), keyref.size());
+
+ if (mrb_array_p(values)) {
+ auto n = RARRAY_LEN(values);
+ for (int i = 0; i < n; ++i) {
+ auto value = mrb_ary_ref(mrb, values, i);
+ if (!mrb_string_p(value)) {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "value must be string");
+ }
+
+ resp.fs.add_header_token(
+ keyref,
+ make_string_ref(balloc,
+ StringRef{RSTRING_PTR(value),
+ static_cast<size_t>(RSTRING_LEN(value))}),
+ false, token);
+ }
+ } else if (mrb_string_p(values)) {
+ resp.fs.add_header_token(
+ keyref,
+ make_string_ref(balloc,
+ StringRef{RSTRING_PTR(values),
+ static_cast<size_t>(RSTRING_LEN(values))}),
+ false, token);
+ } else {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "value must be string");
+ }
+ }
+
+ resp.http_status = http_status;
+
+ auto upstream = downstream->get_upstream();
+
+ rv = upstream->on_downstream_header_complete(downstream);
+ if (rv != 0) {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "could not send non-final response");
+ }
+
+ auto handler = upstream->get_client_handler();
+
+ handler->signal_write();
+
+ return self;
+}
+} // namespace
+
+void init_response_class(mrb_state *mrb, RClass *module) {
+ auto response_class =
+ mrb_define_class_under(mrb, module, "Response", mrb->object_class);
+
+ mrb_define_method(mrb, response_class, "initialize", response_init,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, response_class, "http_version_major",
+ response_get_http_version_major, MRB_ARGS_NONE());
+ mrb_define_method(mrb, response_class, "http_version_minor",
+ response_get_http_version_minor, MRB_ARGS_NONE());
+ mrb_define_method(mrb, response_class, "status", response_get_status,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, response_class, "status=", response_set_status,
+ MRB_ARGS_REQ(1));
+ mrb_define_method(mrb, response_class, "headers", response_get_headers,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, response_class, "add_header", response_add_header,
+ MRB_ARGS_REQ(2));
+ mrb_define_method(mrb, response_class, "set_header", response_set_header,
+ MRB_ARGS_REQ(2));
+ mrb_define_method(mrb, response_class, "clear_headers",
+ response_clear_headers, MRB_ARGS_NONE());
+ mrb_define_method(mrb, response_class, "return", response_return,
+ MRB_ARGS_OPT(1));
+ mrb_define_method(mrb, response_class, "send_info", response_send_info,
+ MRB_ARGS_REQ(2));
+}
+
+} // namespace mruby
+
+} // namespace shrpx
diff --git a/src/shrpx_mruby_module_response.h b/src/shrpx_mruby_module_response.h
new file mode 100644
index 0000000..a35b42b
--- /dev/null
+++ b/src/shrpx_mruby_module_response.h
@@ -0,0 +1,42 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_MRUBY_MODULE_RESPONSE_H
+#define SHRPX_MRUBY_MODULE_RESPONSE_H
+
+#include "shrpx.h"
+
+#include <mruby.h>
+
+namespace shrpx {
+
+namespace mruby {
+
+void init_response_class(mrb_state *mrb, RClass *module);
+
+} // namespace mruby
+
+} // namespace shrpx
+
+#endif // SHRPX_MRUBY_MODULE_RESPONSE_H
diff --git a/src/shrpx_null_downstream_connection.cc b/src/shrpx_null_downstream_connection.cc
new file mode 100644
index 0000000..cd81c8a
--- /dev/null
+++ b/src/shrpx_null_downstream_connection.cc
@@ -0,0 +1,88 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2021 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_null_downstream_connection.h"
+#include "shrpx_upstream.h"
+#include "shrpx_downstream.h"
+#include "shrpx_log.h"
+
+namespace shrpx {
+
+NullDownstreamConnection::NullDownstreamConnection(
+ const std::shared_ptr<DownstreamAddrGroup> &group)
+ : group_(group) {}
+
+NullDownstreamConnection::~NullDownstreamConnection() {}
+
+int NullDownstreamConnection::attach_downstream(Downstream *downstream) {
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "Attaching to DOWNSTREAM:" << downstream;
+ }
+
+ downstream_ = downstream;
+
+ return 0;
+}
+
+void NullDownstreamConnection::detach_downstream(Downstream *downstream) {
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "Detaching from DOWNSTREAM:" << downstream;
+ }
+ downstream_ = nullptr;
+}
+
+int NullDownstreamConnection::push_request_headers() { return 0; }
+
+int NullDownstreamConnection::push_upload_data_chunk(const uint8_t *data,
+ size_t datalen) {
+ return 0;
+}
+
+int NullDownstreamConnection::end_upload_data() { return 0; }
+
+void NullDownstreamConnection::pause_read(IOCtrlReason reason) {}
+
+int NullDownstreamConnection::resume_read(IOCtrlReason reason,
+ size_t consumed) {
+ return 0;
+}
+
+void NullDownstreamConnection::force_resume_read() {}
+
+int NullDownstreamConnection::on_read() { return 0; }
+
+int NullDownstreamConnection::on_write() { return 0; }
+
+void NullDownstreamConnection::on_upstream_change(Upstream *upstream) {}
+
+bool NullDownstreamConnection::poolable() const { return false; }
+
+const std::shared_ptr<DownstreamAddrGroup> &
+NullDownstreamConnection::get_downstream_addr_group() const {
+ return group_;
+}
+
+DownstreamAddr *NullDownstreamConnection::get_addr() const { return nullptr; }
+
+} // namespace shrpx
diff --git a/src/shrpx_null_downstream_connection.h b/src/shrpx_null_downstream_connection.h
new file mode 100644
index 0000000..7defcc3
--- /dev/null
+++ b/src/shrpx_null_downstream_connection.h
@@ -0,0 +1,68 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2021 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_NULL_DOWNSTREAM_CONNECTION_H
+#define SHRPX_NULL_DOWNSTREAM_CONNECTION_H
+
+#include "shrpx_downstream_connection.h"
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+class NullDownstreamConnection : public DownstreamConnection {
+public:
+ NullDownstreamConnection(const std::shared_ptr<DownstreamAddrGroup> &group);
+ virtual ~NullDownstreamConnection();
+ virtual int attach_downstream(Downstream *downstream);
+ virtual void detach_downstream(Downstream *downstream);
+
+ virtual int push_request_headers();
+ virtual int push_upload_data_chunk(const uint8_t *data, size_t datalen);
+ virtual int end_upload_data();
+
+ virtual void pause_read(IOCtrlReason reason);
+ virtual int resume_read(IOCtrlReason reason, size_t consumed);
+ virtual void force_resume_read();
+
+ virtual int on_read();
+ virtual int on_write();
+
+ virtual void on_upstream_change(Upstream *upstream);
+
+ // true if this object is poolable.
+ virtual bool poolable() const;
+
+ virtual const std::shared_ptr<DownstreamAddrGroup> &
+ get_downstream_addr_group() const;
+ virtual DownstreamAddr *get_addr() const;
+
+private:
+ std::shared_ptr<DownstreamAddrGroup> group_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_NULL_DOWNSTREAM_CONNECTION_H
diff --git a/src/shrpx_process.h b/src/shrpx_process.h
new file mode 100644
index 0000000..d35461b
--- /dev/null
+++ b/src/shrpx_process.h
@@ -0,0 +1,37 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_PROCESS_H
+#define SHRPX_PROCESS_H
+
+#include "shrpx.h"
+
+namespace shrpx {
+
+constexpr uint8_t SHRPX_IPC_REOPEN_LOG = 1;
+constexpr uint8_t SHRPX_IPC_GRACEFUL_SHUTDOWN = 2;
+
+} // namespace shrpx
+
+#endif // SHRPX_PROCESS_H
diff --git a/src/shrpx_quic.cc b/src/shrpx_quic.cc
new file mode 100644
index 0000000..2d4de59
--- /dev/null
+++ b/src/shrpx_quic.cc
@@ -0,0 +1,393 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2021 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_quic.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <netinet/udp.h>
+
+#include <array>
+#include <chrono>
+
+#include <ngtcp2/ngtcp2_crypto.h>
+
+#include <nghttp3/nghttp3.h>
+
+#include <openssl/rand.h>
+
+#include "shrpx_config.h"
+#include "shrpx_log.h"
+#include "util.h"
+#include "xsi_strerror.h"
+
+bool operator==(const ngtcp2_cid &lhs, const ngtcp2_cid &rhs) {
+ return ngtcp2_cid_eq(&lhs, &rhs);
+}
+
+namespace shrpx {
+
+ngtcp2_tstamp quic_timestamp() {
+ return std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::steady_clock::now().time_since_epoch())
+ .count();
+}
+
+int quic_send_packet(const UpstreamAddr *faddr, const sockaddr *remote_sa,
+ size_t remote_salen, const sockaddr *local_sa,
+ size_t local_salen, const ngtcp2_pkt_info &pi,
+ const uint8_t *data, size_t datalen, size_t gso_size) {
+ iovec msg_iov = {const_cast<uint8_t *>(data), datalen};
+ msghdr msg{};
+ msg.msg_name = const_cast<sockaddr *>(remote_sa);
+ msg.msg_namelen = remote_salen;
+ msg.msg_iov = &msg_iov;
+ msg.msg_iovlen = 1;
+
+ uint8_t msg_ctrl[CMSG_SPACE(sizeof(int)) +
+#ifdef UDP_SEGMENT
+ CMSG_SPACE(sizeof(uint16_t)) +
+#endif // UDP_SEGMENT
+ CMSG_SPACE(sizeof(in6_pktinfo))];
+
+ memset(msg_ctrl, 0, sizeof(msg_ctrl));
+
+ msg.msg_control = msg_ctrl;
+ msg.msg_controllen = sizeof(msg_ctrl);
+
+ size_t controllen = 0;
+
+ auto cm = CMSG_FIRSTHDR(&msg);
+
+ switch (local_sa->sa_family) {
+ case AF_INET: {
+ controllen += CMSG_SPACE(sizeof(in_pktinfo));
+ cm->cmsg_level = IPPROTO_IP;
+ cm->cmsg_type = IP_PKTINFO;
+ cm->cmsg_len = CMSG_LEN(sizeof(in_pktinfo));
+ in_pktinfo pktinfo{};
+ auto addrin =
+ reinterpret_cast<sockaddr_in *>(const_cast<sockaddr *>(local_sa));
+ pktinfo.ipi_spec_dst = addrin->sin_addr;
+ memcpy(CMSG_DATA(cm), &pktinfo, sizeof(pktinfo));
+
+ break;
+ }
+ case AF_INET6: {
+ controllen += CMSG_SPACE(sizeof(in6_pktinfo));
+ cm->cmsg_level = IPPROTO_IPV6;
+ cm->cmsg_type = IPV6_PKTINFO;
+ cm->cmsg_len = CMSG_LEN(sizeof(in6_pktinfo));
+ in6_pktinfo pktinfo{};
+ auto addrin =
+ reinterpret_cast<sockaddr_in6 *>(const_cast<sockaddr *>(local_sa));
+ pktinfo.ipi6_addr = addrin->sin6_addr;
+ memcpy(CMSG_DATA(cm), &pktinfo, sizeof(pktinfo));
+
+ break;
+ }
+ default:
+ assert(0);
+ }
+
+#ifdef UDP_SEGMENT
+ if (gso_size && datalen > gso_size) {
+ controllen += CMSG_SPACE(sizeof(uint16_t));
+ cm = CMSG_NXTHDR(&msg, cm);
+ cm->cmsg_level = SOL_UDP;
+ cm->cmsg_type = UDP_SEGMENT;
+ cm->cmsg_len = CMSG_LEN(sizeof(uint16_t));
+ uint16_t n = gso_size;
+ memcpy(CMSG_DATA(cm), &n, sizeof(n));
+ }
+#endif // UDP_SEGMENT
+
+ controllen += CMSG_SPACE(sizeof(int));
+ cm = CMSG_NXTHDR(&msg, cm);
+ cm->cmsg_len = CMSG_LEN(sizeof(int));
+ unsigned int tos = pi.ecn;
+ memcpy(CMSG_DATA(cm), &tos, sizeof(tos));
+
+ switch (local_sa->sa_family) {
+ case AF_INET:
+ cm->cmsg_level = IPPROTO_IP;
+ cm->cmsg_type = IP_TOS;
+
+ break;
+ case AF_INET6:
+ cm->cmsg_level = IPPROTO_IPV6;
+ cm->cmsg_type = IPV6_TCLASS;
+
+ break;
+ default:
+ assert(0);
+ }
+
+ msg.msg_controllen = controllen;
+
+ ssize_t nwrite;
+
+ do {
+ nwrite = sendmsg(faddr->fd, &msg, 0);
+ } while (nwrite == -1 && errno == EINTR);
+
+ if (nwrite == -1) {
+ if (LOG_ENABLED(INFO)) {
+ auto error = errno;
+ LOG(INFO) << "sendmsg failed: errno=" << error;
+ }
+
+ return -errno;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "QUIC sent packet: local="
+ << util::to_numeric_addr(local_sa, local_salen)
+ << " remote=" << util::to_numeric_addr(remote_sa, remote_salen)
+ << " ecn=" << log::hex << pi.ecn << log::dec << " " << nwrite
+ << " bytes";
+ }
+
+ return 0;
+}
+
+int generate_quic_retry_connection_id(ngtcp2_cid &cid, size_t cidlen,
+ const uint8_t *server_id, uint8_t km_id,
+ const uint8_t *key) {
+ assert(cidlen == SHRPX_QUIC_SCIDLEN);
+
+ if (RAND_bytes(cid.data, cidlen) != 1) {
+ return -1;
+ }
+
+ cid.datalen = cidlen;
+
+ cid.data[0] = (cid.data[0] & 0x3f) | km_id;
+
+ auto p = cid.data + SHRPX_QUIC_CID_PREFIX_OFFSET;
+
+ std::copy_n(server_id, SHRPX_QUIC_SERVER_IDLEN, p);
+
+ return encrypt_quic_connection_id(p, p, key);
+}
+
+int generate_quic_connection_id(ngtcp2_cid &cid, size_t cidlen,
+ const uint8_t *cid_prefix, uint8_t km_id,
+ const uint8_t *key) {
+ assert(cidlen == SHRPX_QUIC_SCIDLEN);
+
+ if (RAND_bytes(cid.data, cidlen) != 1) {
+ return -1;
+ }
+
+ cid.datalen = cidlen;
+
+ cid.data[0] = (cid.data[0] & 0x3f) | km_id;
+
+ auto p = cid.data + SHRPX_QUIC_CID_PREFIX_OFFSET;
+
+ std::copy_n(cid_prefix, SHRPX_QUIC_CID_PREFIXLEN, p);
+
+ return encrypt_quic_connection_id(p, p, key);
+}
+
+int encrypt_quic_connection_id(uint8_t *dest, const uint8_t *src,
+ const uint8_t *key) {
+ auto ctx = EVP_CIPHER_CTX_new();
+ auto d = defer(EVP_CIPHER_CTX_free, ctx);
+
+ if (!EVP_EncryptInit_ex(ctx, EVP_aes_128_ecb(), nullptr, key, nullptr)) {
+ return -1;
+ }
+
+ EVP_CIPHER_CTX_set_padding(ctx, 0);
+
+ int len;
+
+ if (!EVP_EncryptUpdate(ctx, dest, &len, src, SHRPX_QUIC_DECRYPTED_DCIDLEN) ||
+ !EVP_EncryptFinal_ex(ctx, dest + len, &len)) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int decrypt_quic_connection_id(uint8_t *dest, const uint8_t *src,
+ const uint8_t *key) {
+ auto ctx = EVP_CIPHER_CTX_new();
+ auto d = defer(EVP_CIPHER_CTX_free, ctx);
+
+ if (!EVP_DecryptInit_ex(ctx, EVP_aes_128_ecb(), nullptr, key, nullptr)) {
+ return -1;
+ }
+
+ EVP_CIPHER_CTX_set_padding(ctx, 0);
+
+ int len;
+
+ if (!EVP_DecryptUpdate(ctx, dest, &len, src, SHRPX_QUIC_DECRYPTED_DCIDLEN) ||
+ !EVP_DecryptFinal_ex(ctx, dest + len, &len)) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int generate_quic_hashed_connection_id(ngtcp2_cid &dest,
+ const Address &remote_addr,
+ const Address &local_addr,
+ const ngtcp2_cid &cid) {
+ auto ctx = EVP_MD_CTX_new();
+ auto d = defer(EVP_MD_CTX_free, ctx);
+
+ std::array<uint8_t, 32> h;
+ unsigned int hlen = EVP_MD_size(EVP_sha256());
+
+ if (!EVP_DigestInit_ex(ctx, EVP_sha256(), nullptr) ||
+ !EVP_DigestUpdate(ctx, &remote_addr.su.sa, remote_addr.len) ||
+ !EVP_DigestUpdate(ctx, &local_addr.su.sa, local_addr.len) ||
+ !EVP_DigestUpdate(ctx, cid.data, cid.datalen) ||
+ !EVP_DigestFinal_ex(ctx, h.data(), &hlen)) {
+ return -1;
+ }
+
+ assert(hlen == h.size());
+
+ std::copy_n(std::begin(h), sizeof(dest.data), std::begin(dest.data));
+ dest.datalen = sizeof(dest.data);
+
+ return 0;
+}
+
+int generate_quic_stateless_reset_token(uint8_t *token, const ngtcp2_cid &cid,
+ const uint8_t *secret,
+ size_t secretlen) {
+ if (ngtcp2_crypto_generate_stateless_reset_token(token, secret, secretlen,
+ &cid) != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int generate_retry_token(uint8_t *token, size_t &tokenlen, uint32_t version,
+ const sockaddr *sa, socklen_t salen,
+ const ngtcp2_cid &retry_scid, const ngtcp2_cid &odcid,
+ const uint8_t *secret, size_t secretlen) {
+ auto t = std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count();
+
+ auto stokenlen = ngtcp2_crypto_generate_retry_token(
+ token, secret, secretlen, version, sa, salen, &retry_scid, &odcid, t);
+ if (stokenlen < 0) {
+ return -1;
+ }
+
+ tokenlen = stokenlen;
+
+ return 0;
+}
+
+int verify_retry_token(ngtcp2_cid &odcid, const uint8_t *token, size_t tokenlen,
+ uint32_t version, const ngtcp2_cid &dcid,
+ const sockaddr *sa, socklen_t salen,
+ const uint8_t *secret, size_t secretlen) {
+
+ auto t = std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count();
+
+ if (ngtcp2_crypto_verify_retry_token(&odcid, token, tokenlen, secret,
+ secretlen, version, sa, salen, &dcid,
+ 10 * NGTCP2_SECONDS, t) != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int generate_token(uint8_t *token, size_t &tokenlen, const sockaddr *sa,
+ size_t salen, const uint8_t *secret, size_t secretlen) {
+ auto t = std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count();
+
+ auto stokenlen = ngtcp2_crypto_generate_regular_token(
+ token, secret, secretlen, sa, salen, t);
+ if (stokenlen < 0) {
+ return -1;
+ }
+
+ tokenlen = stokenlen;
+
+ return 0;
+}
+
+int verify_token(const uint8_t *token, size_t tokenlen, const sockaddr *sa,
+ socklen_t salen, const uint8_t *secret, size_t secretlen) {
+ auto t = std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count();
+
+ if (ngtcp2_crypto_verify_regular_token(token, tokenlen, secret, secretlen, sa,
+ salen, 3600 * NGTCP2_SECONDS,
+ t) != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int generate_quic_connection_id_encryption_key(uint8_t *key, size_t keylen,
+ const uint8_t *secret,
+ size_t secretlen,
+ const uint8_t *salt,
+ size_t saltlen) {
+ constexpr uint8_t info[] = "connection id encryption key";
+ ngtcp2_crypto_md sha256;
+ ngtcp2_crypto_md_init(
+ &sha256, reinterpret_cast<void *>(const_cast<EVP_MD *>(EVP_sha256())));
+
+ if (ngtcp2_crypto_hkdf(key, keylen, &sha256, secret, secretlen, salt, saltlen,
+ info, str_size(info)) != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+const QUICKeyingMaterial *
+select_quic_keying_material(const QUICKeyingMaterials &qkms, uint8_t km_id) {
+ for (auto &qkm : qkms.keying_materials) {
+ if (km_id == qkm.id) {
+ return &qkm;
+ }
+ }
+
+ return &qkms.keying_materials.front();
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_quic.h b/src/shrpx_quic.h
new file mode 100644
index 0000000..b2f6087
--- /dev/null
+++ b/src/shrpx_quic.h
@@ -0,0 +1,138 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2021 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_QUIC_H
+#define SHRPX_QUIC_H
+
+#include "shrpx.h"
+
+#include <stdint.h>
+
+#include <functional>
+
+#include <ngtcp2/ngtcp2.h>
+
+#include "network.h"
+
+using namespace nghttp2;
+
+namespace std {
+template <> struct hash<ngtcp2_cid> {
+ std::size_t operator()(const ngtcp2_cid &cid) const noexcept {
+ // FNV-1a 64bits variant
+ constexpr uint64_t basis = 0xCBF29CE484222325ULL;
+ const uint8_t *p = cid.data, *end = cid.data + cid.datalen;
+ uint64_t h = basis;
+
+ for (; p != end;) {
+ h ^= *p++;
+ h *= basis;
+ }
+
+ return static_cast<size_t>(h);
+ }
+};
+} // namespace std
+
+bool operator==(const ngtcp2_cid &lhs, const ngtcp2_cid &rhs);
+
+namespace shrpx {
+
+struct UpstreamAddr;
+struct QUICKeyingMaterials;
+struct QUICKeyingMaterial;
+
+constexpr size_t SHRPX_QUIC_SCIDLEN = 20;
+constexpr size_t SHRPX_QUIC_SERVER_IDLEN = 4;
+// SHRPX_QUIC_CID_PREFIXLEN includes SHRPX_QUIC_SERVER_IDLEN.
+constexpr size_t SHRPX_QUIC_CID_PREFIXLEN = 8;
+constexpr size_t SHRPX_QUIC_CID_PREFIX_OFFSET = 1;
+constexpr size_t SHRPX_QUIC_DECRYPTED_DCIDLEN = 16;
+constexpr size_t SHRPX_QUIC_CID_ENCRYPTION_KEYLEN = 16;
+constexpr size_t SHRPX_QUIC_MAX_UDP_PAYLOAD_SIZE = 1472;
+constexpr size_t SHRPX_QUIC_CONN_CLOSE_PKTLEN = 256;
+constexpr size_t SHRPX_QUIC_STATELESS_RESET_BURST = 100;
+constexpr size_t SHRPX_QUIC_SECRET_RESERVEDLEN = 4;
+constexpr size_t SHRPX_QUIC_SECRETLEN = 32;
+constexpr size_t SHRPX_QUIC_SALTLEN = 32;
+constexpr uint8_t SHRPX_QUIC_DCID_KM_ID_MASK = 0xc0;
+
+ngtcp2_tstamp quic_timestamp();
+
+int quic_send_packet(const UpstreamAddr *faddr, const sockaddr *remote_sa,
+ size_t remote_salen, const sockaddr *local_sa,
+ size_t local_salen, const ngtcp2_pkt_info &pi,
+ const uint8_t *data, size_t datalen, size_t gso_size);
+
+int generate_quic_retry_connection_id(ngtcp2_cid &cid, size_t cidlen,
+ const uint8_t *server_id, uint8_t km_id,
+ const uint8_t *key);
+
+int generate_quic_connection_id(ngtcp2_cid &cid, size_t cidlen,
+ const uint8_t *cid_prefix, uint8_t km_id,
+ const uint8_t *key);
+
+int encrypt_quic_connection_id(uint8_t *dest, const uint8_t *src,
+ const uint8_t *key);
+
+int decrypt_quic_connection_id(uint8_t *dest, const uint8_t *src,
+ const uint8_t *key);
+
+int generate_quic_hashed_connection_id(ngtcp2_cid &dest,
+ const Address &remote_addr,
+ const Address &local_addr,
+ const ngtcp2_cid &cid);
+
+int generate_quic_stateless_reset_token(uint8_t *token, const ngtcp2_cid &cid,
+ const uint8_t *secret,
+ size_t secretlen);
+
+int generate_retry_token(uint8_t *token, size_t &tokenlen, uint32_t version,
+ const sockaddr *sa, socklen_t salen,
+ const ngtcp2_cid &retry_scid, const ngtcp2_cid &odcid,
+ const uint8_t *secret, size_t secretlen);
+
+int verify_retry_token(ngtcp2_cid &odcid, const uint8_t *token, size_t tokenlen,
+ uint32_t version, const ngtcp2_cid &dcid,
+ const sockaddr *sa, socklen_t salen,
+ const uint8_t *secret, size_t secretlen);
+
+int generate_token(uint8_t *token, size_t &tokenlen, const sockaddr *sa,
+ size_t salen, const uint8_t *secret, size_t secretlen);
+
+int verify_token(const uint8_t *token, size_t tokenlen, const sockaddr *sa,
+ socklen_t salen, const uint8_t *secret, size_t secretlen);
+
+int generate_quic_connection_id_encryption_key(uint8_t *key, size_t keylen,
+ const uint8_t *secret,
+ size_t secretlen,
+ const uint8_t *salt,
+ size_t saltlen);
+
+const QUICKeyingMaterial *
+select_quic_keying_material(const QUICKeyingMaterials &qkms, uint8_t km_id);
+
+} // namespace shrpx
+
+#endif // SHRPX_QUIC_H
diff --git a/src/shrpx_quic_connection_handler.cc b/src/shrpx_quic_connection_handler.cc
new file mode 100644
index 0000000..6287971
--- /dev/null
+++ b/src/shrpx_quic_connection_handler.cc
@@ -0,0 +1,761 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2021 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_quic_connection_handler.h"
+
+#include <openssl/rand.h>
+
+#include <ngtcp2/ngtcp2.h>
+#include <ngtcp2/ngtcp2_crypto.h>
+
+#include "shrpx_worker.h"
+#include "shrpx_client_handler.h"
+#include "shrpx_log.h"
+#include "shrpx_http3_upstream.h"
+#include "shrpx_connection_handler.h"
+#include "ssl_compat.h"
+
+namespace shrpx {
+
+namespace {
+void stateless_reset_bucket_regen_timercb(struct ev_loop *loop, ev_timer *w,
+ int revents) {
+ auto quic_conn_handler = static_cast<QUICConnectionHandler *>(w->data);
+
+ quic_conn_handler->on_stateless_reset_bucket_regen();
+}
+} // namespace
+
+QUICConnectionHandler::QUICConnectionHandler(Worker *worker)
+ : worker_{worker},
+ stateless_reset_bucket_{SHRPX_QUIC_STATELESS_RESET_BURST} {
+ ev_timer_init(&stateless_reset_bucket_regen_timer_,
+ stateless_reset_bucket_regen_timercb, 0., 1.);
+ stateless_reset_bucket_regen_timer_.data = this;
+}
+
+QUICConnectionHandler::~QUICConnectionHandler() {
+ ev_timer_stop(worker_->get_loop(), &stateless_reset_bucket_regen_timer_);
+}
+
+int QUICConnectionHandler::handle_packet(const UpstreamAddr *faddr,
+ const Address &remote_addr,
+ const Address &local_addr,
+ const ngtcp2_pkt_info &pi,
+ const uint8_t *data, size_t datalen) {
+ int rv;
+ ngtcp2_version_cid vc;
+
+ rv = ngtcp2_pkt_decode_version_cid(&vc, data, datalen, SHRPX_QUIC_SCIDLEN);
+ switch (rv) {
+ case 0:
+ break;
+ case NGTCP2_ERR_VERSION_NEGOTIATION:
+ send_version_negotiation(faddr, vc.version, vc.dcid, vc.dcidlen, vc.scid,
+ vc.scidlen, remote_addr, local_addr);
+
+ return 0;
+ default:
+ return 0;
+ }
+
+ auto config = get_config();
+
+ ngtcp2_cid dcid_key;
+ ngtcp2_cid_init(&dcid_key, vc.dcid, vc.dcidlen);
+
+ auto conn_handler = worker_->get_connection_handler();
+
+ ClientHandler *handler;
+
+ auto &quicconf = config->quic;
+
+ auto it = connections_.find(dcid_key);
+ if (it == std::end(connections_)) {
+ auto cwit = close_waits_.find(dcid_key);
+ if (cwit != std::end(close_waits_)) {
+ auto cw = (*cwit).second;
+
+ cw->handle_packet(faddr, remote_addr, local_addr, pi, data, datalen);
+
+ return 0;
+ }
+
+ if (data[0] & 0x80) {
+ if (generate_quic_hashed_connection_id(dcid_key, remote_addr, local_addr,
+ dcid_key) != 0) {
+ return 0;
+ }
+
+ it = connections_.find(dcid_key);
+ if (it == std::end(connections_)) {
+ auto cwit = close_waits_.find(dcid_key);
+ if (cwit != std::end(close_waits_)) {
+ auto cw = (*cwit).second;
+
+ cw->handle_packet(faddr, remote_addr, local_addr, pi, data, datalen);
+
+ return 0;
+ }
+ }
+ }
+ }
+
+ if (it == std::end(connections_)) {
+ std::array<uint8_t, SHRPX_QUIC_DECRYPTED_DCIDLEN> decrypted_dcid;
+
+ auto &qkms = conn_handler->get_quic_keying_materials();
+ const QUICKeyingMaterial *qkm = nullptr;
+
+ if (vc.dcidlen == SHRPX_QUIC_SCIDLEN) {
+ qkm = select_quic_keying_material(
+ *qkms.get(), vc.dcid[0] & SHRPX_QUIC_DCID_KM_ID_MASK);
+
+ if (decrypt_quic_connection_id(decrypted_dcid.data(),
+ vc.dcid + SHRPX_QUIC_CID_PREFIX_OFFSET,
+ qkm->cid_encryption_key.data()) != 0) {
+ return 0;
+ }
+
+ if (qkm != &qkms->keying_materials.front() ||
+ !std::equal(std::begin(decrypted_dcid),
+ std::begin(decrypted_dcid) + SHRPX_QUIC_CID_PREFIXLEN,
+ worker_->get_cid_prefix())) {
+ auto quic_lwp =
+ conn_handler->match_quic_lingering_worker_process_cid_prefix(
+ decrypted_dcid.data(), decrypted_dcid.size());
+ if (quic_lwp) {
+ if (conn_handler->forward_quic_packet_to_lingering_worker_process(
+ quic_lwp, remote_addr, local_addr, pi, data, datalen) == 0) {
+ return 0;
+ }
+
+ return 0;
+ }
+ }
+ }
+
+ // new connection
+
+ auto &upstreamconf = config->conn.upstream;
+ if (worker_->get_worker_stat()->num_connections >=
+ upstreamconf.worker_connections) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Too many connections >="
+ << upstreamconf.worker_connections;
+ }
+
+ return 0;
+ }
+
+ ngtcp2_pkt_hd hd;
+ ngtcp2_cid odcid, *podcid = nullptr;
+ const uint8_t *token = nullptr;
+ size_t tokenlen = 0;
+ ngtcp2_token_type token_type = NGTCP2_TOKEN_TYPE_UNKNOWN;
+
+ switch (ngtcp2_accept(&hd, data, datalen)) {
+ case 0: {
+ // If we get Initial and it has the CID prefix of this worker,
+ // it is likely that client is intentionally use the prefix.
+ // Just drop it.
+ if (vc.dcidlen == SHRPX_QUIC_SCIDLEN) {
+ if (qkm != &qkms->keying_materials.front()) {
+ qkm = &qkms->keying_materials.front();
+
+ if (decrypt_quic_connection_id(decrypted_dcid.data(),
+ vc.dcid + SHRPX_QUIC_CID_PREFIX_OFFSET,
+ qkm->cid_encryption_key.data()) != 0) {
+ return 0;
+ }
+ }
+
+ if (std::equal(std::begin(decrypted_dcid),
+ std::begin(decrypted_dcid) + SHRPX_QUIC_CID_PREFIXLEN,
+ worker_->get_cid_prefix())) {
+ return 0;
+ }
+ }
+
+ if (worker_->get_graceful_shutdown()) {
+ send_connection_close(faddr, hd.version, hd.dcid, hd.scid, remote_addr,
+ local_addr, NGTCP2_CONNECTION_REFUSED,
+ datalen * 3);
+ return 0;
+ }
+
+ if (hd.tokenlen == 0) {
+ if (quicconf.upstream.require_token) {
+ send_retry(faddr, vc.version, vc.dcid, vc.dcidlen, vc.scid,
+ vc.scidlen, remote_addr, local_addr, datalen * 3);
+
+ return 0;
+ }
+
+ break;
+ }
+
+ switch (hd.token[0]) {
+ case NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY: {
+ if (vc.dcidlen != SHRPX_QUIC_SCIDLEN) {
+ // Initial packets with Retry token must have DCID chosen by
+ // server.
+ return 0;
+ }
+
+ auto qkm = select_quic_keying_material(
+ *qkms.get(), vc.dcid[0] & SHRPX_QUIC_DCID_KM_ID_MASK);
+
+ if (verify_retry_token(odcid, hd.token, hd.tokenlen, hd.version,
+ hd.dcid, &remote_addr.su.sa, remote_addr.len,
+ qkm->secret.data(), qkm->secret.size()) != 0) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Failed to validate Retry token from remote="
+ << util::to_numeric_addr(&remote_addr);
+ }
+
+ // 2nd Retry packet is not allowed, so send CONNECTION_CLOSE
+ // with INVALID_TOKEN.
+ send_connection_close(faddr, hd.version, hd.dcid, hd.scid,
+ remote_addr, local_addr, NGTCP2_INVALID_TOKEN,
+ datalen * 3);
+ return 0;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Successfully validated Retry token from remote="
+ << util::to_numeric_addr(&remote_addr);
+ }
+
+ podcid = &odcid;
+ token = hd.token;
+ tokenlen = hd.tokenlen;
+ token_type = NGTCP2_TOKEN_TYPE_RETRY;
+
+ break;
+ }
+ case NGTCP2_CRYPTO_TOKEN_MAGIC_REGULAR: {
+ // If a token is a regular token, it must be at least
+ // NGTCP2_MIN_INITIAL_DCIDLEN bytes long.
+ if (vc.dcidlen < NGTCP2_MIN_INITIAL_DCIDLEN) {
+ return 0;
+ }
+
+ if (hd.tokenlen != NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN + 1) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Failed to validate token from remote="
+ << util::to_numeric_addr(&remote_addr);
+ }
+
+ if (quicconf.upstream.require_token) {
+ send_retry(faddr, vc.version, vc.dcid, vc.dcidlen, vc.scid,
+ vc.scidlen, remote_addr, local_addr, datalen * 3);
+
+ return 0;
+ }
+
+ break;
+ }
+
+ auto qkm = select_quic_keying_material(
+ *qkms.get(), hd.token[NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN]);
+
+ if (verify_token(hd.token, hd.tokenlen - 1, &remote_addr.su.sa,
+ remote_addr.len, qkm->secret.data(),
+ qkm->secret.size()) != 0) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Failed to validate token from remote="
+ << util::to_numeric_addr(&remote_addr);
+ }
+
+ if (quicconf.upstream.require_token) {
+ send_retry(faddr, vc.version, vc.dcid, vc.dcidlen, vc.scid,
+ vc.scidlen, remote_addr, local_addr, datalen * 3);
+
+ return 0;
+ }
+
+ break;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Successfully validated token from remote="
+ << util::to_numeric_addr(&remote_addr);
+ }
+
+ token = hd.token;
+ tokenlen = hd.tokenlen;
+ token_type = NGTCP2_TOKEN_TYPE_NEW_TOKEN;
+
+ break;
+ }
+ default:
+ if (quicconf.upstream.require_token) {
+ send_retry(faddr, vc.version, vc.dcid, vc.dcidlen, vc.scid,
+ vc.scidlen, remote_addr, local_addr, datalen * 3);
+
+ return 0;
+ }
+
+ break;
+ }
+
+ break;
+ }
+ default:
+ if (!config->single_thread && !(data[0] & 0x80) &&
+ vc.dcidlen == SHRPX_QUIC_SCIDLEN &&
+ !std::equal(std::begin(decrypted_dcid),
+ std::begin(decrypted_dcid) + SHRPX_QUIC_CID_PREFIXLEN,
+ worker_->get_cid_prefix())) {
+ if (conn_handler->forward_quic_packet(faddr, remote_addr, local_addr,
+ pi, decrypted_dcid.data(), data,
+ datalen) == 0) {
+ return 0;
+ }
+ }
+
+ if (!(data[0] & 0x80)) {
+ // TODO Must be rate limited
+ send_stateless_reset(faddr, vc.dcid, vc.dcidlen, remote_addr,
+ local_addr);
+ }
+
+ return 0;
+ }
+
+ handler = handle_new_connection(faddr, remote_addr, local_addr, hd, podcid,
+ token, tokenlen, token_type);
+ if (handler == nullptr) {
+ return 0;
+ }
+ } else {
+ handler = (*it).second;
+ }
+
+ if (handler->read_quic(faddr, remote_addr, local_addr, pi, data, datalen) !=
+ 0) {
+ delete handler;
+ return 0;
+ }
+
+ handler->signal_write();
+
+ return 0;
+}
+
+ClientHandler *QUICConnectionHandler::handle_new_connection(
+ const UpstreamAddr *faddr, const Address &remote_addr,
+ const Address &local_addr, const ngtcp2_pkt_hd &hd, const ngtcp2_cid *odcid,
+ const uint8_t *token, size_t tokenlen, ngtcp2_token_type token_type) {
+ std::array<char, NI_MAXHOST> host;
+ std::array<char, NI_MAXSERV> service;
+ int rv;
+
+ rv = getnameinfo(&remote_addr.su.sa, remote_addr.len, host.data(),
+ host.size(), service.data(), service.size(),
+ NI_NUMERICHOST | NI_NUMERICSERV);
+ if (rv != 0) {
+ LOG(ERROR) << "getnameinfo() failed: " << gai_strerror(rv);
+
+ return nullptr;
+ }
+
+ auto ssl_ctx = worker_->get_quic_sv_ssl_ctx();
+
+ assert(ssl_ctx);
+
+ auto ssl = tls::create_ssl(ssl_ctx);
+ if (ssl == nullptr) {
+ return nullptr;
+ }
+
+#ifdef NGHTTP2_GENUINE_OPENSSL
+ assert(SSL_is_quic(ssl));
+#endif // NGHTTP2_GENUINE_OPENSSL
+
+ SSL_set_accept_state(ssl);
+
+ auto config = get_config();
+ auto &quicconf = config->quic;
+
+ if (quicconf.upstream.early_data) {
+#ifdef NGHTTP2_GENUINE_OPENSSL
+ SSL_set_quic_early_data_enabled(ssl, 1);
+#elif defined(NGHTTP2_OPENSSL_IS_BORINGSSL)
+ SSL_set_early_data_enabled(ssl, 1);
+#endif // NGHTTP2_OPENSSL_IS_BORINGSSL
+ }
+
+ // Disable TLS session ticket if we don't have working ticket
+ // keys.
+ if (!worker_->get_ticket_keys()) {
+ SSL_set_options(ssl, SSL_OP_NO_TICKET);
+ }
+
+ auto handler = std::make_unique<ClientHandler>(
+ worker_, faddr->fd, ssl, StringRef{host.data()},
+ StringRef{service.data()}, remote_addr.su.sa.sa_family, faddr);
+
+ auto upstream = std::make_unique<Http3Upstream>(handler.get());
+ if (upstream->init(faddr, remote_addr, local_addr, hd, odcid, token, tokenlen,
+ token_type) != 0) {
+ return nullptr;
+ }
+
+ handler->setup_http3_upstream(std::move(upstream));
+
+ return handler.release();
+}
+
+namespace {
+uint32_t generate_reserved_version(const Address &addr, uint32_t version) {
+ uint32_t h = 0x811C9DC5u;
+ const uint8_t *p = reinterpret_cast<const uint8_t *>(&addr.su.sa);
+ const uint8_t *ep = p + addr.len;
+
+ for (; p != ep; ++p) {
+ h ^= *p;
+ h *= 0x01000193u;
+ }
+
+ version = htonl(version);
+ p = (const uint8_t *)&version;
+ ep = p + sizeof(version);
+
+ for (; p != ep; ++p) {
+ h ^= *p;
+ h *= 0x01000193u;
+ }
+
+ h &= 0xf0f0f0f0u;
+ h |= 0x0a0a0a0au;
+
+ return h;
+}
+} // namespace
+
+int QUICConnectionHandler::send_retry(
+ const UpstreamAddr *faddr, uint32_t version, const uint8_t *ini_dcid,
+ size_t ini_dcidlen, const uint8_t *ini_scid, size_t ini_scidlen,
+ const Address &remote_addr, const Address &local_addr, size_t max_pktlen) {
+ std::array<char, NI_MAXHOST> host;
+ std::array<char, NI_MAXSERV> port;
+
+ if (getnameinfo(&remote_addr.su.sa, remote_addr.len, host.data(), host.size(),
+ port.data(), port.size(),
+ NI_NUMERICHOST | NI_NUMERICSERV) != 0) {
+ return -1;
+ }
+
+ auto config = get_config();
+ auto &quicconf = config->quic;
+
+ auto conn_handler = worker_->get_connection_handler();
+ auto &qkms = conn_handler->get_quic_keying_materials();
+ auto &qkm = qkms->keying_materials.front();
+
+ ngtcp2_cid retry_scid;
+
+ if (generate_quic_retry_connection_id(retry_scid, SHRPX_QUIC_SCIDLEN,
+ quicconf.server_id.data(), qkm.id,
+ qkm.cid_encryption_key.data()) != 0) {
+ return -1;
+ }
+
+ std::array<uint8_t, NGTCP2_CRYPTO_MAX_RETRY_TOKENLEN> token;
+ size_t tokenlen;
+
+ ngtcp2_cid idcid, iscid;
+ ngtcp2_cid_init(&idcid, ini_dcid, ini_dcidlen);
+ ngtcp2_cid_init(&iscid, ini_scid, ini_scidlen);
+
+ if (generate_retry_token(token.data(), tokenlen, version, &remote_addr.su.sa,
+ remote_addr.len, retry_scid, idcid,
+ qkm.secret.data(), qkm.secret.size()) != 0) {
+ return -1;
+ }
+
+ std::vector<uint8_t> buf;
+ buf.resize(std::min(max_pktlen, static_cast<size_t>(256)));
+
+ auto nwrite =
+ ngtcp2_crypto_write_retry(buf.data(), buf.size(), version, &iscid,
+ &retry_scid, &idcid, token.data(), tokenlen);
+ if (nwrite < 0) {
+ LOG(ERROR) << "ngtcp2_crypto_write_retry: " << ngtcp2_strerror(nwrite);
+ return -1;
+ }
+
+ buf.resize(nwrite);
+
+ quic_send_packet(faddr, &remote_addr.su.sa, remote_addr.len,
+ &local_addr.su.sa, local_addr.len, ngtcp2_pkt_info{},
+ buf.data(), buf.size(), 0);
+
+ if (generate_quic_hashed_connection_id(idcid, remote_addr, local_addr,
+ idcid) != 0) {
+ return -1;
+ }
+
+ auto d =
+ static_cast<ev_tstamp>(NGTCP2_DEFAULT_INITIAL_RTT * 3) / NGTCP2_SECONDS;
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Enter close-wait period " << d << "s with " << buf.size()
+ << " bytes sentinel packet";
+ }
+
+ auto cw = std::make_unique<CloseWait>(worker_, std::vector<ngtcp2_cid>{idcid},
+ std::move(buf), d);
+
+ add_close_wait(cw.release());
+
+ return 0;
+}
+
+int QUICConnectionHandler::send_version_negotiation(
+ const UpstreamAddr *faddr, uint32_t version, const uint8_t *ini_dcid,
+ size_t ini_dcidlen, const uint8_t *ini_scid, size_t ini_scidlen,
+ const Address &remote_addr, const Address &local_addr) {
+ std::array<uint32_t, 2> sv{
+ generate_reserved_version(remote_addr, version),
+ NGTCP2_PROTO_VER_V1,
+ };
+
+ std::array<uint8_t, NGTCP2_MAX_UDP_PAYLOAD_SIZE> buf;
+
+ uint8_t rand_byte;
+ util::random_bytes(&rand_byte, &rand_byte + 1, worker_->get_randgen());
+
+ auto nwrite = ngtcp2_pkt_write_version_negotiation(
+ buf.data(), buf.size(), rand_byte, ini_scid, ini_scidlen, ini_dcid,
+ ini_dcidlen, sv.data(), sv.size());
+ if (nwrite < 0) {
+ LOG(ERROR) << "ngtcp2_pkt_write_version_negotiation: "
+ << ngtcp2_strerror(nwrite);
+ return -1;
+ }
+
+ return quic_send_packet(faddr, &remote_addr.su.sa, remote_addr.len,
+ &local_addr.su.sa, local_addr.len, ngtcp2_pkt_info{},
+ buf.data(), nwrite, 0);
+}
+
+int QUICConnectionHandler::send_stateless_reset(const UpstreamAddr *faddr,
+ const uint8_t *dcid,
+ size_t dcidlen,
+ const Address &remote_addr,
+ const Address &local_addr) {
+ if (stateless_reset_bucket_ == 0) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Stateless Reset bucket has been depleted";
+ }
+
+ return 0;
+ }
+
+ --stateless_reset_bucket_;
+
+ if (!ev_is_active(&stateless_reset_bucket_regen_timer_)) {
+ ev_timer_again(worker_->get_loop(), &stateless_reset_bucket_regen_timer_);
+ }
+
+ int rv;
+ std::array<uint8_t, NGTCP2_STATELESS_RESET_TOKENLEN> token;
+ ngtcp2_cid cid;
+
+ ngtcp2_cid_init(&cid, dcid, dcidlen);
+
+ auto conn_handler = worker_->get_connection_handler();
+ auto &qkms = conn_handler->get_quic_keying_materials();
+ auto &qkm = qkms->keying_materials.front();
+
+ rv = generate_quic_stateless_reset_token(token.data(), cid, qkm.secret.data(),
+ qkm.secret.size());
+ if (rv != 0) {
+ return -1;
+ }
+
+ std::array<uint8_t, NGTCP2_MIN_STATELESS_RESET_RANDLEN> rand_bytes;
+
+ if (RAND_bytes(rand_bytes.data(), rand_bytes.size()) != 1) {
+ return -1;
+ }
+
+ std::array<uint8_t, NGTCP2_MAX_UDP_PAYLOAD_SIZE> buf;
+
+ auto nwrite =
+ ngtcp2_pkt_write_stateless_reset(buf.data(), buf.size(), token.data(),
+ rand_bytes.data(), rand_bytes.size());
+ if (nwrite < 0) {
+ LOG(ERROR) << "ngtcp2_pkt_write_stateless_reset: "
+ << ngtcp2_strerror(nwrite);
+ return -1;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Send stateless_reset to remote="
+ << util::to_numeric_addr(&remote_addr)
+ << " dcid=" << util::format_hex(dcid, dcidlen);
+ }
+
+ return quic_send_packet(faddr, &remote_addr.su.sa, remote_addr.len,
+ &local_addr.su.sa, local_addr.len, ngtcp2_pkt_info{},
+ buf.data(), nwrite, 0);
+}
+
+int QUICConnectionHandler::send_connection_close(
+ const UpstreamAddr *faddr, uint32_t version, const ngtcp2_cid &ini_dcid,
+ const ngtcp2_cid &ini_scid, const Address &remote_addr,
+ const Address &local_addr, uint64_t error_code, size_t max_pktlen) {
+ std::array<uint8_t, NGTCP2_MAX_UDP_PAYLOAD_SIZE> buf;
+
+ max_pktlen = std::min(max_pktlen, buf.size());
+
+ auto nwrite = ngtcp2_crypto_write_connection_close(
+ buf.data(), max_pktlen, version, &ini_scid, &ini_dcid, error_code,
+ nullptr, 0);
+ if (nwrite < 0) {
+ LOG(ERROR) << "ngtcp2_crypto_write_connection_close failed";
+ return -1;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Send Initial CONNECTION_CLOSE with error_code=" << log::hex
+ << error_code << log::dec
+ << " to remote=" << util::to_numeric_addr(&remote_addr)
+ << " dcid=" << util::format_hex(ini_scid.data, ini_scid.datalen)
+ << " scid=" << util::format_hex(ini_dcid.data, ini_dcid.datalen);
+ }
+
+ return quic_send_packet(faddr, &remote_addr.su.sa, remote_addr.len,
+ &local_addr.su.sa, local_addr.len, ngtcp2_pkt_info{},
+ buf.data(), nwrite, 0);
+}
+
+void QUICConnectionHandler::add_connection_id(const ngtcp2_cid &cid,
+ ClientHandler *handler) {
+ connections_.emplace(cid, handler);
+}
+
+void QUICConnectionHandler::remove_connection_id(const ngtcp2_cid &cid) {
+ connections_.erase(cid);
+}
+
+void QUICConnectionHandler::add_close_wait(CloseWait *cw) {
+ for (auto &cid : cw->scids) {
+ close_waits_.emplace(cid, cw);
+ }
+}
+
+void QUICConnectionHandler::remove_close_wait(const CloseWait *cw) {
+ for (auto &cid : cw->scids) {
+ close_waits_.erase(cid);
+ }
+}
+
+void QUICConnectionHandler::on_stateless_reset_bucket_regen() {
+ assert(stateless_reset_bucket_ < SHRPX_QUIC_STATELESS_RESET_BURST);
+
+ if (++stateless_reset_bucket_ == SHRPX_QUIC_STATELESS_RESET_BURST) {
+ ev_timer_stop(worker_->get_loop(), &stateless_reset_bucket_regen_timer_);
+ }
+}
+
+static void close_wait_timeoutcb(struct ev_loop *loop, ev_timer *w,
+ int revents) {
+ auto cw = static_cast<CloseWait *>(w->data);
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "close-wait period finished";
+ }
+
+ auto quic_conn_handler = cw->worker->get_quic_connection_handler();
+ quic_conn_handler->remove_close_wait(cw);
+
+ delete cw;
+}
+
+CloseWait::CloseWait(Worker *worker, std::vector<ngtcp2_cid> scids,
+ std::vector<uint8_t> pkt, ev_tstamp period)
+ : worker{worker},
+ scids{std::move(scids)},
+ pkt{std::move(pkt)},
+ bytes_recv{0},
+ bytes_sent{0},
+ num_pkts_recv{0},
+ next_pkts_recv{1} {
+ ++worker->get_worker_stat()->num_close_waits;
+
+ ev_timer_init(&timer, close_wait_timeoutcb, period, 0.);
+ timer.data = this;
+
+ ev_timer_start(worker->get_loop(), &timer);
+}
+
+CloseWait::~CloseWait() {
+ auto loop = worker->get_loop();
+
+ ev_timer_stop(loop, &timer);
+
+ auto worker_stat = worker->get_worker_stat();
+ --worker_stat->num_close_waits;
+
+ if (worker->get_graceful_shutdown() && worker_stat->num_connections == 0 &&
+ worker_stat->num_close_waits == 0) {
+ ev_break(loop);
+ }
+}
+
+int CloseWait::handle_packet(const UpstreamAddr *faddr,
+ const Address &remote_addr,
+ const Address &local_addr,
+ const ngtcp2_pkt_info &pi, const uint8_t *data,
+ size_t datalen) {
+ if (pkt.empty()) {
+ return 0;
+ }
+
+ ++num_pkts_recv;
+ bytes_recv += datalen;
+
+ if (bytes_sent + pkt.size() > 3 * bytes_recv ||
+ next_pkts_recv > num_pkts_recv) {
+ return 0;
+ }
+
+ if (quic_send_packet(faddr, &remote_addr.su.sa, remote_addr.len,
+ &local_addr.su.sa, local_addr.len, ngtcp2_pkt_info{},
+ pkt.data(), pkt.size(), 0) != 0) {
+ return -1;
+ }
+
+ next_pkts_recv *= 2;
+ bytes_sent += pkt.size();
+
+ return 0;
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_quic_connection_handler.h b/src/shrpx_quic_connection_handler.h
new file mode 100644
index 0000000..29e73a4
--- /dev/null
+++ b/src/shrpx_quic_connection_handler.h
@@ -0,0 +1,142 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2021 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_QUIC_CONNECTION_HANDLER_H
+#define SHRPX_QUIC_CONNECTION_HANDLER_H
+
+#include "shrpx.h"
+
+#include <memory>
+#include <unordered_map>
+#include <string>
+#include <vector>
+
+#include <ngtcp2/ngtcp2.h>
+
+#include <ev.h>
+
+#include "shrpx_quic.h"
+#include "network.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+struct UpstreamAddr;
+class ClientHandler;
+class Worker;
+
+// CloseWait handles packets received in close-wait (draining or
+// closing period).
+struct CloseWait {
+ CloseWait(Worker *worker, std::vector<ngtcp2_cid> scids,
+ std::vector<uint8_t> pkt, ev_tstamp period);
+ ~CloseWait();
+
+ int handle_packet(const UpstreamAddr *faddr, const Address &remote_addr,
+ const Address &local_addr, const ngtcp2_pkt_info &pi,
+ const uint8_t *data, size_t datalen);
+
+ Worker *worker;
+ // Source Connection IDs of the connection.
+ std::vector<ngtcp2_cid> scids;
+ // QUIC packet which is sent in response to the incoming packet. It
+ // might be empty.
+ std::vector<uint8_t> pkt;
+ // Close-wait (draining or closing period) timer.
+ ev_timer timer;
+ // The number of bytes received during close-wait period.
+ size_t bytes_recv;
+ // The number of bytes sent during close-wait period.
+ size_t bytes_sent;
+ // The number of packets received during close-wait period.
+ size_t num_pkts_recv;
+ // If the number of packets received reaches this number, send a
+ // QUIC packet.
+ size_t next_pkts_recv;
+};
+
+class QUICConnectionHandler {
+public:
+ QUICConnectionHandler(Worker *worker);
+ ~QUICConnectionHandler();
+ int handle_packet(const UpstreamAddr *faddr, const Address &remote_addr,
+ const Address &local_addr, const ngtcp2_pkt_info &pi,
+ const uint8_t *data, size_t datalen);
+ // Send Retry packet. |ini_dcid| is the destination Connection ID
+ // which appeared in Client Initial packet and its length is
+ // |dcidlen|. |ini_scid| is the source Connection ID which appeared
+ // in Client Initial packet and its length is |scidlen|.
+ int send_retry(const UpstreamAddr *faddr, uint32_t version,
+ const uint8_t *ini_dcid, size_t ini_dcidlen,
+ const uint8_t *ini_scid, size_t ini_scidlen,
+ const Address &remote_addr, const Address &local_addr,
+ size_t max_pktlen);
+ // Send Version Negotiation packet. |ini_dcid| is the destination
+ // Connection ID which appeared in Client Initial packet and its
+ // length is |dcidlen|. |ini_scid| is the source Connection ID
+ // which appeared in Client Initial packet and its length is
+ // |scidlen|.
+ int send_version_negotiation(const UpstreamAddr *faddr, uint32_t version,
+ const uint8_t *ini_dcid, size_t ini_dcidlen,
+ const uint8_t *ini_scid, size_t ini_scidlen,
+ const Address &remote_addr,
+ const Address &local_addr);
+ int send_stateless_reset(const UpstreamAddr *faddr, const uint8_t *dcid,
+ size_t dcidlen, const Address &remote_addr,
+ const Address &local_addr);
+ // Send Initial CONNECTION_CLOSE. |ini_dcid| is the destination
+ // Connection ID which appeared in Client Initial packet.
+ // |ini_scid| is the source Connection ID which appeared in Client
+ // Initial packet.
+ int send_connection_close(const UpstreamAddr *faddr, uint32_t version,
+ const ngtcp2_cid &ini_dcid,
+ const ngtcp2_cid &ini_scid,
+ const Address &remote_addr,
+ const Address &local_addr, uint64_t error_code,
+ size_t max_pktlen);
+ ClientHandler *
+ handle_new_connection(const UpstreamAddr *faddr, const Address &remote_addr,
+ const Address &local_addr, const ngtcp2_pkt_hd &hd,
+ const ngtcp2_cid *odcid, const uint8_t *token,
+ size_t tokenlen, ngtcp2_token_type token_type);
+ void add_connection_id(const ngtcp2_cid &cid, ClientHandler *handler);
+ void remove_connection_id(const ngtcp2_cid &cid);
+
+ void add_close_wait(CloseWait *cw);
+ void remove_close_wait(const CloseWait *cw);
+
+ void on_stateless_reset_bucket_regen();
+
+private:
+ Worker *worker_;
+ std::unordered_map<ngtcp2_cid, ClientHandler *> connections_;
+ std::unordered_map<ngtcp2_cid, CloseWait *> close_waits_;
+ ev_timer stateless_reset_bucket_regen_timer_;
+ size_t stateless_reset_bucket_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_QUIC_CONNECTION_HANDLER_H
diff --git a/src/shrpx_quic_listener.cc b/src/shrpx_quic_listener.cc
new file mode 100644
index 0000000..9b9f120
--- /dev/null
+++ b/src/shrpx_quic_listener.cc
@@ -0,0 +1,132 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2021 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_quic_listener.h"
+#include "shrpx_worker.h"
+#include "shrpx_config.h"
+#include "shrpx_log.h"
+
+namespace shrpx {
+
+namespace {
+void readcb(struct ev_loop *loop, ev_io *w, int revent) {
+ auto l = static_cast<QUICListener *>(w->data);
+ l->on_read();
+}
+} // namespace
+
+QUICListener::QUICListener(const UpstreamAddr *faddr, Worker *worker)
+ : faddr_{faddr}, worker_{worker} {
+ ev_io_init(&rev_, readcb, faddr_->fd, EV_READ);
+ rev_.data = this;
+ ev_io_start(worker_->get_loop(), &rev_);
+}
+
+QUICListener::~QUICListener() {
+ ev_io_stop(worker_->get_loop(), &rev_);
+ close(faddr_->fd);
+}
+
+void QUICListener::on_read() {
+ sockaddr_union su;
+ std::array<uint8_t, 64_k> buf;
+ size_t pktcnt = 0;
+ iovec msg_iov{buf.data(), buf.size()};
+
+ msghdr msg{};
+ msg.msg_name = &su;
+ msg.msg_iov = &msg_iov;
+ msg.msg_iovlen = 1;
+
+ uint8_t msg_ctrl[CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(in6_pktinfo)) +
+ CMSG_SPACE(sizeof(uint16_t))];
+ msg.msg_control = msg_ctrl;
+
+ auto quic_conn_handler = worker_->get_quic_connection_handler();
+
+ for (; pktcnt < 10;) {
+ msg.msg_namelen = sizeof(su);
+ msg.msg_controllen = sizeof(msg_ctrl);
+
+ auto nread = recvmsg(faddr_->fd, &msg, 0);
+ if (nread == -1) {
+ return;
+ }
+
+ Address local_addr{};
+ if (util::msghdr_get_local_addr(local_addr, &msg, su.storage.ss_family) !=
+ 0) {
+ ++pktcnt;
+
+ continue;
+ }
+
+ util::set_port(local_addr, faddr_->port);
+
+ ngtcp2_pkt_info pi{
+ .ecn = util::msghdr_get_ecn(&msg, su.storage.ss_family),
+ };
+
+ auto gso_size = util::msghdr_get_udp_gro(&msg);
+ if (gso_size == 0) {
+ gso_size = static_cast<size_t>(nread);
+ }
+
+ auto data = buf.data();
+
+ for (;;) {
+ auto datalen = std::min(static_cast<size_t>(nread), gso_size);
+
+ ++pktcnt;
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "QUIC received packet: local="
+ << util::to_numeric_addr(&local_addr) << " remote="
+ << util::to_numeric_addr(&su.sa, msg.msg_namelen)
+ << " ecn=" << log::hex << pi.ecn << log::dec << " " << datalen
+ << " bytes";
+ }
+
+ if (datalen == 0) {
+ break;
+ }
+
+ Address remote_addr;
+ remote_addr.su = su;
+ remote_addr.len = msg.msg_namelen;
+
+ quic_conn_handler->handle_packet(faddr_, remote_addr, local_addr, pi,
+ data, datalen);
+
+ nread -= datalen;
+ if (nread == 0) {
+ break;
+ }
+
+ data += datalen;
+ }
+ }
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_quic_listener.h b/src/shrpx_quic_listener.h
new file mode 100644
index 0000000..3d70921
--- /dev/null
+++ b/src/shrpx_quic_listener.h
@@ -0,0 +1,51 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2021 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_QUIC_LISTENER_H
+#define SHRPX_QUIC_LISTENER_H
+
+#include "shrpx.h"
+
+#include <ev.h>
+
+namespace shrpx {
+
+struct UpstreamAddr;
+class Worker;
+
+class QUICListener {
+public:
+ QUICListener(const UpstreamAddr *faddr, Worker *worker);
+ ~QUICListener();
+ void on_read();
+
+private:
+ const UpstreamAddr *faddr_;
+ Worker *worker_;
+ ev_io rev_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_QUIC_LISTENER_H
diff --git a/src/shrpx_rate_limit.cc b/src/shrpx_rate_limit.cc
new file mode 100644
index 0000000..0d4f921
--- /dev/null
+++ b/src/shrpx_rate_limit.cc
@@ -0,0 +1,123 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_rate_limit.h"
+
+#include <limits>
+
+#include "shrpx_connection.h"
+#include "shrpx_log.h"
+
+namespace shrpx {
+
+namespace {
+void regencb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto r = static_cast<RateLimit *>(w->data);
+ r->regen();
+}
+} // namespace
+
+RateLimit::RateLimit(struct ev_loop *loop, ev_io *w, size_t rate, size_t burst,
+ Connection *conn)
+ : w_(w),
+ loop_(loop),
+ conn_(conn),
+ rate_(rate),
+ burst_(burst),
+ avail_(burst),
+ startw_req_(false) {
+ ev_timer_init(&t_, regencb, 0., 1.);
+ t_.data = this;
+ if (rate_ > 0) {
+ ev_timer_again(loop_, &t_);
+ }
+}
+
+RateLimit::~RateLimit() { ev_timer_stop(loop_, &t_); }
+
+size_t RateLimit::avail() const {
+ if (rate_ == 0) {
+ return std::numeric_limits<ssize_t>::max();
+ }
+ return avail_;
+}
+
+void RateLimit::drain(size_t n) {
+ if (rate_ == 0) {
+ return;
+ }
+ n = std::min(avail_, n);
+ avail_ -= n;
+ if (avail_ == 0) {
+ ev_io_stop(loop_, w_);
+ }
+}
+
+void RateLimit::regen() {
+ if (rate_ == 0) {
+ return;
+ }
+ if (avail_ + rate_ > burst_) {
+ avail_ = burst_;
+ } else {
+ avail_ += rate_;
+ }
+
+ if (w_->fd >= 0 && avail_ > 0 && startw_req_) {
+ ev_io_start(loop_, w_);
+ handle_tls_pending_read();
+ }
+}
+
+void RateLimit::startw() {
+ if (w_->fd < 0) {
+ return;
+ }
+ startw_req_ = true;
+ if (rate_ == 0 || avail_ > 0) {
+ ev_io_start(loop_, w_);
+ handle_tls_pending_read();
+ return;
+ }
+}
+
+void RateLimit::stopw() {
+ startw_req_ = false;
+ ev_io_stop(loop_, w_);
+}
+
+void RateLimit::handle_tls_pending_read() {
+ if (!conn_ || !conn_->tls.ssl ||
+ (SSL_pending(conn_->tls.ssl) == 0 && conn_->tls.rbuf.rleft() == 0 &&
+ (!conn_->tls.initial_handshake_done ||
+ conn_->tls.earlybuf.rleft() == 0))) {
+ return;
+ }
+
+ // Note that ev_feed_event works without starting watcher, but we
+ // only call this function if watcher is active.
+ ev_feed_event(loop_, w_, EV_READ);
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_rate_limit.h b/src/shrpx_rate_limit.h
new file mode 100644
index 0000000..7502a27
--- /dev/null
+++ b/src/shrpx_rate_limit.h
@@ -0,0 +1,68 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_RATE_LIMIT_H
+#define SHRPX_RATE_LIMIT_H
+
+#include "shrpx.h"
+
+#include <ev.h>
+
+#include <openssl/ssl.h>
+
+namespace shrpx {
+
+struct Connection;
+
+class RateLimit {
+public:
+ // We need |conn| object to check that it has unread bytes for TLS
+ // connection.
+ RateLimit(struct ev_loop *loop, ev_io *w, size_t rate, size_t burst,
+ Connection *conn = nullptr);
+ ~RateLimit();
+ size_t avail() const;
+ void drain(size_t n);
+ void regen();
+ void startw();
+ void stopw();
+ // Feeds event if conn_->tls object has unread bytes. This is
+ // required since it is buffered in conn_->tls object, io event is
+ // not generated unless new incoming data is received.
+ void handle_tls_pending_read();
+
+private:
+ ev_timer t_;
+ ev_io *w_;
+ struct ev_loop *loop_;
+ Connection *conn_;
+ size_t rate_;
+ size_t burst_;
+ size_t avail_;
+ bool startw_req_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_RATE_LIMIT_H
diff --git a/src/shrpx_router.cc b/src/shrpx_router.cc
new file mode 100644
index 0000000..d3565db
--- /dev/null
+++ b/src/shrpx_router.cc
@@ -0,0 +1,420 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_router.h"
+
+#include <algorithm>
+
+#include "shrpx_config.h"
+#include "shrpx_log.h"
+
+namespace shrpx {
+
+RNode::RNode() : s(nullptr), len(0), index(-1), wildcard_index(-1) {}
+
+RNode::RNode(const char *s, size_t len, ssize_t index, ssize_t wildcard_index)
+ : s(s), len(len), index(index), wildcard_index(wildcard_index) {}
+
+Router::Router() : balloc_(1024, 1024), root_{} {}
+
+Router::~Router() {}
+
+namespace {
+RNode *find_next_node(const RNode *node, char c) {
+ auto itr = std::lower_bound(std::begin(node->next), std::end(node->next), c,
+ [](const std::unique_ptr<RNode> &lhs,
+ const char c) { return lhs->s[0] < c; });
+ if (itr == std::end(node->next) || (*itr)->s[0] != c) {
+ return nullptr;
+ }
+
+ return (*itr).get();
+}
+} // namespace
+
+namespace {
+void add_next_node(RNode *node, std::unique_ptr<RNode> new_node) {
+ auto itr = std::lower_bound(std::begin(node->next), std::end(node->next),
+ new_node->s[0],
+ [](const std::unique_ptr<RNode> &lhs,
+ const char c) { return lhs->s[0] < c; });
+ node->next.insert(itr, std::move(new_node));
+}
+} // namespace
+
+void Router::add_node(RNode *node, const char *pattern, size_t patlen,
+ ssize_t index, ssize_t wildcard_index) {
+ auto pat = make_string_ref(balloc_, StringRef{pattern, patlen});
+ auto new_node =
+ std::make_unique<RNode>(pat.c_str(), pat.size(), index, wildcard_index);
+ add_next_node(node, std::move(new_node));
+}
+
+size_t Router::add_route(const StringRef &pattern, size_t idx, bool wildcard) {
+ ssize_t index = -1, wildcard_index = -1;
+ if (wildcard) {
+ wildcard_index = idx;
+ } else {
+ index = idx;
+ }
+
+ auto node = &root_;
+ size_t i = 0;
+
+ for (;;) {
+ auto next_node = find_next_node(node, pattern[i]);
+ if (next_node == nullptr) {
+ add_node(node, pattern.c_str() + i, pattern.size() - i, index,
+ wildcard_index);
+ return idx;
+ }
+
+ node = next_node;
+
+ auto slen = pattern.size() - i;
+ auto s = pattern.c_str() + i;
+ auto n = std::min(node->len, slen);
+ size_t j;
+ for (j = 0; j < n && node->s[j] == s[j]; ++j)
+ ;
+ if (j == n) {
+ // The common prefix was matched
+ if (slen == node->len) {
+ // Complete match
+ if (index != -1) {
+ if (node->index != -1) {
+ // Return the existing index for duplicates.
+ return node->index;
+ }
+ node->index = index;
+ return idx;
+ }
+
+ assert(wildcard_index != -1);
+
+ if (node->wildcard_index != -1) {
+ return node->wildcard_index;
+ }
+ node->wildcard_index = wildcard_index;
+ return idx;
+ }
+
+ if (slen > node->len) {
+ // We still have pattern to add
+ i += j;
+
+ continue;
+ }
+ }
+
+ if (node->len > j) {
+ // node must be split into 2 nodes. new_node is now the child
+ // of node.
+ auto new_node = std::make_unique<RNode>(
+ &node->s[j], node->len - j, node->index, node->wildcard_index);
+ std::swap(node->next, new_node->next);
+
+ node->len = j;
+ node->index = -1;
+ node->wildcard_index = -1;
+
+ add_next_node(node, std::move(new_node));
+
+ if (slen == j) {
+ node->index = index;
+ node->wildcard_index = wildcard_index;
+ return idx;
+ }
+ }
+
+ i += j;
+
+ assert(pattern.size() > i);
+ add_node(node, pattern.c_str() + i, pattern.size() - i, index,
+ wildcard_index);
+
+ return idx;
+ }
+}
+
+namespace {
+const RNode *match_complete(size_t *offset, const RNode *node,
+ const char *first, const char *last) {
+ *offset = 0;
+
+ if (first == last) {
+ return node;
+ }
+
+ auto p = first;
+
+ for (;;) {
+ auto next_node = find_next_node(node, *p);
+ if (next_node == nullptr) {
+ return nullptr;
+ }
+
+ node = next_node;
+
+ auto n = std::min(node->len, static_cast<size_t>(last - p));
+ if (memcmp(node->s, p, n) != 0) {
+ return nullptr;
+ }
+ p += n;
+ if (p == last) {
+ *offset = n;
+ return node;
+ }
+ }
+}
+} // namespace
+
+namespace {
+const RNode *match_partial(bool *pattern_is_wildcard, const RNode *node,
+ size_t offset, const char *first, const char *last) {
+ *pattern_is_wildcard = false;
+
+ if (first == last) {
+ if (node->len == offset) {
+ return node;
+ }
+ return nullptr;
+ }
+
+ auto p = first;
+
+ const RNode *found_node = nullptr;
+
+ if (offset > 0) {
+ auto n = std::min(node->len - offset, static_cast<size_t>(last - first));
+ if (memcmp(node->s + offset, first, n) != 0) {
+ return nullptr;
+ }
+
+ p += n;
+
+ if (p == last) {
+ if (node->len == offset + n) {
+ if (node->index != -1) {
+ return node;
+ }
+
+ // The last '/' handling, see below.
+ node = find_next_node(node, '/');
+ if (node != nullptr && node->index != -1 && node->len == 1) {
+ return node;
+ }
+
+ return nullptr;
+ }
+
+ // The last '/' handling, see below.
+ if (node->index != -1 && offset + n + 1 == node->len &&
+ node->s[node->len - 1] == '/') {
+ return node;
+ }
+
+ return nullptr;
+ }
+
+ if (node->wildcard_index != -1) {
+ found_node = node;
+ *pattern_is_wildcard = true;
+ } else if (node->index != -1 && node->s[node->len - 1] == '/') {
+ found_node = node;
+ *pattern_is_wildcard = false;
+ }
+
+ assert(node->len == offset + n);
+ }
+
+ for (;;) {
+ auto next_node = find_next_node(node, *p);
+ if (next_node == nullptr) {
+ return found_node;
+ }
+
+ node = next_node;
+
+ auto n = std::min(node->len, static_cast<size_t>(last - p));
+ if (memcmp(node->s, p, n) != 0) {
+ return found_node;
+ }
+
+ p += n;
+
+ if (p == last) {
+ if (node->len == n) {
+ // Complete match with this node
+ if (node->index != -1) {
+ *pattern_is_wildcard = false;
+ return node;
+ }
+
+ // The last '/' handling, see below.
+ node = find_next_node(node, '/');
+ if (node != nullptr && node->index != -1 && node->len == 1) {
+ *pattern_is_wildcard = false;
+ return node;
+ }
+
+ return found_node;
+ }
+
+ // We allow match without trailing "/" at the end of pattern.
+ // So, if pattern ends with '/', and pattern and path matches
+ // without that slash, we consider they match to deal with
+ // request to the directory without trailing slash. That is if
+ // pattern is "/foo/" and path is "/foo", we consider they
+ // match.
+ if (node->index != -1 && n + 1 == node->len && node->s[n] == '/') {
+ *pattern_is_wildcard = false;
+ return node;
+ }
+
+ return found_node;
+ }
+
+ if (node->wildcard_index != -1) {
+ found_node = node;
+ *pattern_is_wildcard = true;
+ } else if (node->index != -1 && node->s[node->len - 1] == '/') {
+ // This is the case when pattern which ends with "/" is included
+ // in query.
+ found_node = node;
+ *pattern_is_wildcard = false;
+ }
+
+ assert(node->len == n);
+ }
+}
+} // namespace
+
+ssize_t Router::match(const StringRef &host, const StringRef &path) const {
+ const RNode *node;
+ size_t offset;
+
+ node = match_complete(&offset, &root_, std::begin(host), std::end(host));
+ if (node == nullptr) {
+ return -1;
+ }
+
+ bool pattern_is_wildcard;
+ node = match_partial(&pattern_is_wildcard, node, offset, std::begin(path),
+ std::end(path));
+ if (node == nullptr || node == &root_) {
+ return -1;
+ }
+
+ return pattern_is_wildcard ? node->wildcard_index : node->index;
+}
+
+ssize_t Router::match(const StringRef &s) const {
+ const RNode *node;
+ size_t offset;
+
+ node = match_complete(&offset, &root_, std::begin(s), std::end(s));
+ if (node == nullptr) {
+ return -1;
+ }
+
+ if (node->len != offset) {
+ return -1;
+ }
+
+ return node->index;
+}
+
+namespace {
+const RNode *match_prefix(size_t *nread, const RNode *node, const char *first,
+ const char *last) {
+ if (first == last) {
+ return nullptr;
+ }
+
+ auto p = first;
+
+ for (;;) {
+ auto next_node = find_next_node(node, *p);
+ if (next_node == nullptr) {
+ return nullptr;
+ }
+
+ node = next_node;
+
+ auto n = std::min(node->len, static_cast<size_t>(last - p));
+ if (memcmp(node->s, p, n) != 0) {
+ return nullptr;
+ }
+
+ p += n;
+
+ if (p != last) {
+ if (node->index != -1) {
+ *nread = p - first;
+ return node;
+ }
+ continue;
+ }
+
+ if (node->len == n) {
+ *nread = p - first;
+ return node;
+ }
+
+ return nullptr;
+ }
+}
+} // namespace
+
+ssize_t Router::match_prefix(size_t *nread, const RNode **last_node,
+ const StringRef &s) const {
+ if (*last_node == nullptr) {
+ *last_node = &root_;
+ }
+
+ auto node =
+ ::shrpx::match_prefix(nread, *last_node, std::begin(s), std::end(s));
+ if (node == nullptr) {
+ return -1;
+ }
+
+ *last_node = node;
+
+ return node->index;
+}
+
+namespace {
+void dump_node(const RNode *node, int depth) {
+ fprintf(stderr, "%*ss='%.*s', len=%zu, index=%zd\n", depth, "",
+ (int)node->len, node->s, node->len, node->index);
+ for (auto &nd : node->next) {
+ dump_node(nd.get(), depth + 4);
+ }
+}
+} // namespace
+
+void Router::dump() const { dump_node(&root_, 0); }
+
+} // namespace shrpx
diff --git a/src/shrpx_router.h b/src/shrpx_router.h
new file mode 100644
index 0000000..295db7e
--- /dev/null
+++ b/src/shrpx_router.h
@@ -0,0 +1,110 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_ROUTER_H
+#define SHRPX_ROUTER_H
+
+#include "shrpx.h"
+
+#include <vector>
+#include <memory>
+
+#include "allocator.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+struct RNode {
+ RNode();
+ RNode(const char *s, size_t len, ssize_t index, ssize_t wildcard_index);
+ RNode(RNode &&) = default;
+ RNode(const RNode &) = delete;
+ RNode &operator=(RNode &&) = default;
+ RNode &operator=(const RNode &) = delete;
+
+ // Next RNode, sorted by s[0].
+ std::vector<std::unique_ptr<RNode>> next;
+ // Stores pointer to the string this node represents. Not
+ // NULL-terminated.
+ const char *s;
+ // Length of |s|
+ size_t len;
+ // Index of pattern if match ends in this node. Note that we don't
+ // store duplicated pattern.
+ ssize_t index;
+ // Index of wildcard pattern if query includes this node as prefix
+ // and it still has suffix to match. Note that we don't store
+ // duplicated pattern.
+ ssize_t wildcard_index;
+};
+
+class Router {
+public:
+ Router();
+ ~Router();
+ Router(Router &&) = default;
+ Router(const Router &) = delete;
+ Router &operator=(Router &&) = default;
+ Router &operator=(const Router &) = delete;
+
+ // Adds route |pattern| with its |index|. If same pattern has
+ // already been added, the existing index is returned. If
+ // |wildcard| is true, |pattern| is considered as wildcard pattern,
+ // and all paths which have the |pattern| as prefix and are strictly
+ // longer than |pattern| match. The wildcard pattern only works
+ // with match(const StringRef&, const StringRef&).
+ size_t add_route(const StringRef &pattern, size_t index,
+ bool wildcard = false);
+ // Returns the matched index of pattern. -1 if there is no match.
+ ssize_t match(const StringRef &host, const StringRef &path) const;
+ // Returns the matched index of pattern |s|. -1 if there is no
+ // match.
+ ssize_t match(const StringRef &s) const;
+ // Returns the matched index of pattern if a pattern is a suffix of
+ // |s|, otherwise -1. If |*last_node| is not nullptr, it specifies
+ // the first node to start matching. If it is nullptr, match will
+ // start from scratch. When the match was found (the return value
+ // is not -1), |*nread| has the number of bytes matched in |s|, and
+ // |*last_node| has the last matched node. One can continue to
+ // match the longer pattern using the returned |*last_node| to the
+ // another invocation of this function until it returns -1.
+ ssize_t match_prefix(size_t *nread, const RNode **last_node,
+ const StringRef &s) const;
+
+ void add_node(RNode *node, const char *pattern, size_t patlen, ssize_t index,
+ ssize_t wildcard_index);
+
+ void dump() const;
+
+private:
+ BlockAllocator balloc_;
+ // The root node of Patricia tree. This is special node and its s
+ // field is nulptr, and len field is 0.
+ RNode root_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_ROUTER_H
diff --git a/src/shrpx_router_test.cc b/src/shrpx_router_test.cc
new file mode 100644
index 0000000..21c2f51
--- /dev/null
+++ b/src/shrpx_router_test.cc
@@ -0,0 +1,184 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_router_test.h"
+
+#include <CUnit/CUnit.h>
+
+#include "shrpx_router.h"
+
+namespace shrpx {
+
+struct Pattern {
+ StringRef pattern;
+ size_t idx;
+ bool wildcard;
+};
+
+void test_shrpx_router_match(void) {
+ auto patterns = std::vector<Pattern>{
+ {StringRef::from_lit("nghttp2.org/"), 0},
+ {StringRef::from_lit("nghttp2.org/alpha"), 1},
+ {StringRef::from_lit("nghttp2.org/alpha/"), 2},
+ {StringRef::from_lit("nghttp2.org/alpha/bravo/"), 3},
+ {StringRef::from_lit("www.nghttp2.org/alpha/"), 4},
+ {StringRef::from_lit("/alpha"), 5},
+ {StringRef::from_lit("example.com/alpha/"), 6},
+ {StringRef::from_lit("nghttp2.org/alpha/bravo2/"), 7},
+ {StringRef::from_lit("www2.nghttp2.org/alpha/"), 8},
+ {StringRef::from_lit("www2.nghttp2.org/alpha2/"), 9},
+ };
+
+ Router router;
+
+ for (auto &p : patterns) {
+ router.add_route(p.pattern, p.idx);
+ }
+
+ ssize_t idx;
+
+ idx = router.match(StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/"));
+
+ CU_ASSERT(0 == idx);
+
+ idx = router.match(StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/alpha"));
+
+ CU_ASSERT(1 == idx);
+
+ idx = router.match(StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/alpha/"));
+
+ CU_ASSERT(2 == idx);
+
+ idx = router.match(StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/alpha/charlie"));
+
+ CU_ASSERT(2 == idx);
+
+ idx = router.match(StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/alpha/bravo/"));
+
+ CU_ASSERT(3 == idx);
+
+ // matches pattern when last '/' is missing in path
+ idx = router.match(StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/alpha/bravo"));
+
+ CU_ASSERT(3 == idx);
+
+ idx = router.match(StringRef::from_lit("www2.nghttp2.org"),
+ StringRef::from_lit("/alpha"));
+
+ CU_ASSERT(8 == idx);
+
+ idx = router.match(StringRef{}, StringRef::from_lit("/alpha"));
+
+ CU_ASSERT(5 == idx);
+}
+
+void test_shrpx_router_match_wildcard(void) {
+ constexpr auto patterns = std::array<Pattern, 6>{{
+ {StringRef::from_lit("nghttp2.org/"), 0},
+ {StringRef::from_lit("nghttp2.org/"), 1, true},
+ {StringRef::from_lit("nghttp2.org/alpha/"), 2},
+ {StringRef::from_lit("nghttp2.org/alpha/"), 3, true},
+ {StringRef::from_lit("nghttp2.org/bravo"), 4},
+ {StringRef::from_lit("nghttp2.org/bravo"), 5, true},
+ }};
+
+ Router router;
+
+ for (auto &p : patterns) {
+ router.add_route(p.pattern, p.idx, p.wildcard);
+ }
+
+ CU_ASSERT(0 == router.match(StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/")));
+
+ CU_ASSERT(1 == router.match(StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/a")));
+
+ CU_ASSERT(1 == router.match(StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/charlie")));
+
+ CU_ASSERT(2 == router.match(StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/alpha")));
+
+ CU_ASSERT(2 == router.match(StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/alpha/")));
+
+ CU_ASSERT(3 == router.match(StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/alpha/b")));
+
+ CU_ASSERT(4 == router.match(StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/bravo")));
+
+ CU_ASSERT(5 == router.match(StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/bravocharlie")));
+
+ CU_ASSERT(5 == router.match(StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/bravo/")));
+}
+
+void test_shrpx_router_match_prefix(void) {
+ auto patterns = std::vector<Pattern>{
+ {StringRef::from_lit("gro.2ptthgn."), 0},
+ {StringRef::from_lit("gro.2ptthgn.www."), 1},
+ {StringRef::from_lit("gro.2ptthgn.gmi."), 2},
+ {StringRef::from_lit("gro.2ptthgn.gmi.ahpla."), 3},
+ };
+
+ Router router;
+
+ for (auto &p : patterns) {
+ router.add_route(p.pattern, p.idx);
+ }
+
+ ssize_t idx;
+ const RNode *node;
+ size_t nread;
+
+ node = nullptr;
+
+ idx = router.match_prefix(&nread, &node,
+ StringRef::from_lit("gro.2ptthgn.gmi.ahpla.ovarb"));
+
+ CU_ASSERT(0 == idx);
+ CU_ASSERT(12 == nread);
+
+ idx = router.match_prefix(&nread, &node,
+ StringRef::from_lit("gmi.ahpla.ovarb"));
+
+ CU_ASSERT(2 == idx);
+ CU_ASSERT(4 == nread);
+
+ idx = router.match_prefix(&nread, &node, StringRef::from_lit("ahpla.ovarb"));
+
+ CU_ASSERT(3 == idx);
+ CU_ASSERT(6 == nread);
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_router_test.h b/src/shrpx_router_test.h
new file mode 100644
index 0000000..d39cb87
--- /dev/null
+++ b/src/shrpx_router_test.h
@@ -0,0 +1,40 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_ROUTER_TEST_H
+#define SHRPX_ROUTER_TEST_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+namespace shrpx {
+
+void test_shrpx_router_match(void);
+void test_shrpx_router_match_wildcard(void);
+void test_shrpx_router_match_prefix(void);
+
+} // namespace shrpx
+
+#endif // SHRPX_ROUTER_TEST_H
diff --git a/src/shrpx_signal.cc b/src/shrpx_signal.cc
new file mode 100644
index 0000000..63fcc07
--- /dev/null
+++ b/src/shrpx_signal.cc
@@ -0,0 +1,138 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_signal.h"
+
+#include <cerrno>
+
+#include "shrpx_log.h"
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+int shrpx_signal_block_all(sigset_t *oldset) {
+ sigset_t newset;
+
+ sigfillset(&newset);
+
+#ifndef NOTHREADS
+ int rv;
+
+ rv = pthread_sigmask(SIG_SETMASK, &newset, oldset);
+
+ if (rv != 0) {
+ errno = rv;
+ return -1;
+ }
+
+ return 0;
+#else // NOTHREADS
+ return sigprocmask(SIG_SETMASK, &newset, oldset);
+#endif // NOTHREADS
+}
+
+int shrpx_signal_unblock_all() {
+ sigset_t newset;
+
+ sigemptyset(&newset);
+
+#ifndef NOTHREADS
+ int rv;
+
+ rv = pthread_sigmask(SIG_SETMASK, &newset, nullptr);
+
+ if (rv != 0) {
+ errno = rv;
+ return -1;
+ }
+
+ return 0;
+#else // NOTHREADS
+ return sigprocmask(SIG_SETMASK, &newset, nullptr);
+#endif // NOTHREADS
+}
+
+int shrpx_signal_set(sigset_t *set) {
+#ifndef NOTHREADS
+ int rv;
+
+ rv = pthread_sigmask(SIG_SETMASK, set, nullptr);
+
+ if (rv != 0) {
+ errno = rv;
+ return -1;
+ }
+
+ return 0;
+#else // NOTHREADS
+ return sigprocmask(SIG_SETMASK, set, nullptr);
+#endif // NOTHREADS
+}
+
+namespace {
+template <typename Signals>
+int signal_set_handler(void (*handler)(int), Signals &&sigs) {
+ struct sigaction act {};
+ act.sa_handler = handler;
+ sigemptyset(&act.sa_mask);
+ int rv;
+ for (auto sig : sigs) {
+ rv = sigaction(sig, &act, nullptr);
+ if (rv != 0) {
+ return -1;
+ }
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+constexpr auto main_proc_ign_signals = std::array<int, 1>{SIGPIPE};
+} // namespace
+
+namespace {
+constexpr auto worker_proc_ign_signals =
+ std::array<int, 5>{REOPEN_LOG_SIGNAL, EXEC_BINARY_SIGNAL,
+ GRACEFUL_SHUTDOWN_SIGNAL, RELOAD_SIGNAL, SIGPIPE};
+} // namespace
+
+int shrpx_signal_set_main_proc_ign_handler() {
+ return signal_set_handler(SIG_IGN, main_proc_ign_signals);
+}
+
+int shrpx_signal_unset_main_proc_ign_handler() {
+ return signal_set_handler(SIG_DFL, main_proc_ign_signals);
+}
+
+int shrpx_signal_set_worker_proc_ign_handler() {
+ return signal_set_handler(SIG_IGN, worker_proc_ign_signals);
+}
+
+int shrpx_signal_unset_worker_proc_ign_handler() {
+ return signal_set_handler(SIG_DFL, worker_proc_ign_signals);
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_signal.h b/src/shrpx_signal.h
new file mode 100644
index 0000000..152ca36
--- /dev/null
+++ b/src/shrpx_signal.h
@@ -0,0 +1,60 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_SIGNAL_H
+#define SHRPX_SIGNAL_H
+
+#include "shrpx.h"
+
+#include <signal.h>
+
+namespace shrpx {
+
+constexpr int REOPEN_LOG_SIGNAL = SIGUSR1;
+constexpr int EXEC_BINARY_SIGNAL = SIGUSR2;
+constexpr int GRACEFUL_SHUTDOWN_SIGNAL = SIGQUIT;
+constexpr int RELOAD_SIGNAL = SIGHUP;
+
+// Blocks all signals. The previous signal mask is stored into
+// |oldset| if it is not nullptr. This function returns 0 if it
+// succeeds, or -1. The errno will indicate the error.
+int shrpx_signal_block_all(sigset_t *oldset);
+
+// Unblocks all signals. This function returns 0 if it succeeds, or
+// -1. The errno will indicate the error.
+int shrpx_signal_unblock_all();
+
+// Sets signal mask |set|. This function returns 0 if it succeeds, or
+// -1. The errno will indicate the error.
+int shrpx_signal_set(sigset_t *set);
+
+int shrpx_signal_set_main_proc_ign_handler();
+int shrpx_signal_unset_main_proc_ign_handler();
+
+int shrpx_signal_set_worker_proc_ign_handler();
+int shrpx_signal_unset_worker_proc_ign_handler();
+
+} // namespace shrpx
+
+#endif // SHRPX_SIGNAL_H
diff --git a/src/shrpx_tls.cc b/src/shrpx_tls.cc
new file mode 100644
index 0000000..aa0c9f2
--- /dev/null
+++ b/src/shrpx_tls.cc
@@ -0,0 +1,2465 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_tls.h"
+
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif // HAVE_SYS_SOCKET_H
+#ifdef HAVE_NETDB_H
+# include <netdb.h>
+#endif // HAVE_NETDB_H
+#include <netinet/tcp.h>
+#include <pthread.h>
+#include <sys/types.h>
+
+#include <vector>
+#include <string>
+#include <iomanip>
+
+#include <iostream>
+
+#include "ssl_compat.h"
+
+#include <openssl/crypto.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+#include <openssl/rand.h>
+#include <openssl/dh.h>
+#ifndef OPENSSL_NO_OCSP
+# include <openssl/ocsp.h>
+#endif // OPENSSL_NO_OCSP
+#if OPENSSL_3_0_0_API
+# include <openssl/params.h>
+# include <openssl/core_names.h>
+# include <openssl/decoder.h>
+#endif // OPENSSL_3_0_0_API
+#ifdef NGHTTP2_OPENSSL_IS_BORINGSSL
+# include <openssl/hmac.h>
+#endif // NGHTTP2_OPENSSL_IS_BORINGSSL
+
+#include <nghttp2/nghttp2.h>
+
+#ifdef ENABLE_HTTP3
+# include <ngtcp2/ngtcp2.h>
+# include <ngtcp2/ngtcp2_crypto.h>
+# ifdef HAVE_LIBNGTCP2_CRYPTO_QUICTLS
+# include <ngtcp2/ngtcp2_crypto_quictls.h>
+# endif // HAVE_LIBNGTCP2_CRYPTO_QUICTLS
+# ifdef HAVE_LIBNGTCP2_CRYPTO_BORINGSSL
+# include <ngtcp2/ngtcp2_crypto_boringssl.h>
+# endif // HAVE_LIBNGTCP2_CRYPTO_BORINGSSL
+#endif // ENABLE_HTTP3
+
+#include "shrpx_log.h"
+#include "shrpx_client_handler.h"
+#include "shrpx_config.h"
+#include "shrpx_worker.h"
+#include "shrpx_downstream_connection_pool.h"
+#include "shrpx_http2_session.h"
+#include "shrpx_memcached_request.h"
+#include "shrpx_memcached_dispatcher.h"
+#include "shrpx_connection_handler.h"
+#ifdef ENABLE_HTTP3
+# include "shrpx_http3_upstream.h"
+#endif // ENABLE_HTTP3
+#include "util.h"
+#include "tls.h"
+#include "template.h"
+#include "timegm.h"
+
+using namespace nghttp2;
+using namespace std::chrono_literals;
+
+namespace shrpx {
+
+namespace tls {
+
+namespace {
+int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) {
+ if (!preverify_ok) {
+ int err = X509_STORE_CTX_get_error(ctx);
+ int depth = X509_STORE_CTX_get_error_depth(ctx);
+ if (err == X509_V_ERR_CERT_HAS_EXPIRED && depth == 0 &&
+ get_config()->tls.client_verify.tolerate_expired) {
+ LOG(INFO) << "The client certificate has expired, but is accepted by "
+ "configuration";
+ return 1;
+ }
+ LOG(ERROR) << "client certificate verify error:num=" << err << ":"
+ << X509_verify_cert_error_string(err) << ":depth=" << depth;
+ }
+ return preverify_ok;
+}
+} // namespace
+
+int set_alpn_prefs(std::vector<unsigned char> &out,
+ const std::vector<StringRef> &protos) {
+ size_t len = 0;
+
+ for (const auto &proto : protos) {
+ if (proto.size() > 255) {
+ LOG(FATAL) << "Too long ALPN identifier: " << proto.size();
+ return -1;
+ }
+
+ len += 1 + proto.size();
+ }
+
+ if (len > (1 << 16) - 1) {
+ LOG(FATAL) << "Too long ALPN identifier list: " << len;
+ return -1;
+ }
+
+ out.resize(len);
+ auto ptr = out.data();
+
+ for (const auto &proto : protos) {
+ *ptr++ = proto.size();
+ ptr = std::copy(std::begin(proto), std::end(proto), ptr);
+ }
+
+ return 0;
+}
+
+namespace {
+int ssl_pem_passwd_cb(char *buf, int size, int rwflag, void *user_data) {
+ auto config = static_cast<Config *>(user_data);
+ auto len = static_cast<int>(config->tls.private_key_passwd.size());
+ if (size < len + 1) {
+ LOG(ERROR) << "ssl_pem_passwd_cb: buf is too small " << size;
+ return 0;
+ }
+ // Copy string including last '\0'.
+ memcpy(buf, config->tls.private_key_passwd.c_str(), len + 1);
+ return len;
+}
+} // namespace
+
+namespace {
+// *al is set to SSL_AD_UNRECOGNIZED_NAME by openssl, so we don't have
+// to set it explicitly.
+int servername_callback(SSL *ssl, int *al, void *arg) {
+ auto conn = static_cast<Connection *>(SSL_get_app_data(ssl));
+ auto handler = static_cast<ClientHandler *>(conn->data);
+ auto worker = handler->get_worker();
+
+ auto rawhost = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
+ if (rawhost == nullptr) {
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+
+ auto len = strlen(rawhost);
+ // NI_MAXHOST includes terminal NULL.
+ if (len == 0 || len + 1 > NI_MAXHOST) {
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+
+ std::array<uint8_t, NI_MAXHOST> buf;
+
+ auto end_buf = std::copy_n(rawhost, len, std::begin(buf));
+
+ util::inp_strlower(std::begin(buf), end_buf);
+
+ auto hostname = StringRef{std::begin(buf), end_buf};
+
+#ifdef ENABLE_HTTP3
+ auto cert_tree = conn->proto == Proto::HTTP3
+ ? worker->get_quic_cert_lookup_tree()
+ : worker->get_cert_lookup_tree();
+#else // !ENABLE_HTTP3
+ auto cert_tree = worker->get_cert_lookup_tree();
+#endif // !ENABLE_HTTP3
+
+ auto idx = cert_tree->lookup(hostname);
+ if (idx == -1) {
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+
+ handler->set_tls_sni(hostname);
+
+ auto conn_handler = worker->get_connection_handler();
+
+#ifdef ENABLE_HTTP3
+ const auto &ssl_ctx_list = conn->proto == Proto::HTTP3
+ ? conn_handler->get_quic_indexed_ssl_ctx(idx)
+ : conn_handler->get_indexed_ssl_ctx(idx);
+#else // !ENABLE_HTTP3
+ const auto &ssl_ctx_list = conn_handler->get_indexed_ssl_ctx(idx);
+#endif // !ENABLE_HTTP3
+
+ assert(!ssl_ctx_list.empty());
+
+#ifdef NGHTTP2_GENUINE_OPENSSL
+ auto num_sigalgs =
+ SSL_get_sigalgs(ssl, 0, nullptr, nullptr, nullptr, nullptr, nullptr);
+
+ for (idx = 0; idx < num_sigalgs; ++idx) {
+ int signhash;
+
+ SSL_get_sigalgs(ssl, idx, nullptr, nullptr, &signhash, nullptr, nullptr);
+ switch (signhash) {
+ case NID_ecdsa_with_SHA256:
+ case NID_ecdsa_with_SHA384:
+ case NID_ecdsa_with_SHA512:
+ break;
+ default:
+ continue;
+ }
+
+ break;
+ }
+
+ if (idx == num_sigalgs) {
+ SSL_set_SSL_CTX(ssl, ssl_ctx_list[0]);
+
+ return SSL_TLSEXT_ERR_OK;
+ }
+
+ auto num_shared_curves = SSL_get_shared_curve(ssl, -1);
+
+ for (auto i = 0; i < num_shared_curves; ++i) {
+ auto shared_curve = SSL_get_shared_curve(ssl, i);
+# if OPENSSL_3_0_0_API
+ // It looks like only short name is defined in OpenSSL. No idea
+ // which one to use because it is unknown that which one
+ // EVP_PKEY_get_utf8_string_param("group") returns.
+ auto shared_curve_name = OBJ_nid2sn(shared_curve);
+ if (shared_curve_name == nullptr) {
+ continue;
+ }
+# endif // OPENSSL_3_0_0_API
+
+ for (auto ssl_ctx : ssl_ctx_list) {
+ auto cert = SSL_CTX_get0_certificate(ssl_ctx);
+ auto pubkey = X509_get0_pubkey(cert);
+
+ if (EVP_PKEY_base_id(pubkey) != EVP_PKEY_EC) {
+ continue;
+ }
+
+# if OPENSSL_3_0_0_API
+ std::array<char, 64> curve_name;
+ if (!EVP_PKEY_get_utf8_string_param(pubkey, "group", curve_name.data(),
+ curve_name.size(), nullptr)) {
+ continue;
+ }
+
+ if (strcmp(shared_curve_name, curve_name.data()) == 0) {
+ SSL_set_SSL_CTX(ssl, ssl_ctx);
+ return SSL_TLSEXT_ERR_OK;
+ }
+# else // !OPENSSL_3_0_0_API
+ auto eckey = EVP_PKEY_get0_EC_KEY(pubkey);
+ if (eckey == nullptr) {
+ continue;
+ }
+
+ auto ecgroup = EC_KEY_get0_group(eckey);
+ auto cert_curve = EC_GROUP_get_curve_name(ecgroup);
+
+ if (shared_curve == cert_curve) {
+ SSL_set_SSL_CTX(ssl, ssl_ctx);
+ return SSL_TLSEXT_ERR_OK;
+ }
+# endif // !OPENSSL_3_0_0_API
+ }
+ }
+#endif // NGHTTP2_GENUINE_OPENSSL
+
+ SSL_set_SSL_CTX(ssl, ssl_ctx_list[0]);
+
+ return SSL_TLSEXT_ERR_OK;
+}
+} // namespace
+
+#ifndef NGHTTP2_OPENSSL_IS_BORINGSSL
+namespace {
+std::shared_ptr<std::vector<uint8_t>>
+get_ocsp_data(TLSContextData *tls_ctx_data) {
+# ifdef HAVE_ATOMIC_STD_SHARED_PTR
+ return std::atomic_load_explicit(&tls_ctx_data->ocsp_data,
+ std::memory_order_acquire);
+# else // !HAVE_ATOMIC_STD_SHARED_PTR
+ std::lock_guard<std::mutex> g(tls_ctx_data->mu);
+ return tls_ctx_data->ocsp_data;
+# endif // !HAVE_ATOMIC_STD_SHARED_PTR
+}
+} // namespace
+
+namespace {
+int ocsp_resp_cb(SSL *ssl, void *arg) {
+ auto ssl_ctx = SSL_get_SSL_CTX(ssl);
+ auto tls_ctx_data =
+ static_cast<TLSContextData *>(SSL_CTX_get_app_data(ssl_ctx));
+
+ auto data = get_ocsp_data(tls_ctx_data);
+
+ if (!data) {
+ return SSL_TLSEXT_ERR_OK;
+ }
+
+ auto buf = static_cast<uint8_t *>(
+ CRYPTO_malloc(data->size(), NGHTTP2_FILE_NAME, __LINE__));
+
+ if (!buf) {
+ return SSL_TLSEXT_ERR_OK;
+ }
+
+ std::copy(std::begin(*data), std::end(*data), buf);
+
+ SSL_set_tlsext_status_ocsp_resp(ssl, buf, data->size());
+
+ return SSL_TLSEXT_ERR_OK;
+}
+} // namespace
+#endif // NGHTTP2_OPENSSL_IS_BORINGSSL
+
+constexpr auto MEMCACHED_SESSION_CACHE_KEY_PREFIX =
+ StringRef::from_lit("nghttpx:tls-session-cache:");
+
+namespace {
+int tls_session_client_new_cb(SSL *ssl, SSL_SESSION *session) {
+ auto conn = static_cast<Connection *>(SSL_get_app_data(ssl));
+ if (conn->tls.client_session_cache == nullptr) {
+ return 0;
+ }
+
+ try_cache_tls_session(conn->tls.client_session_cache, session,
+ std::chrono::steady_clock::now());
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int tls_session_new_cb(SSL *ssl, SSL_SESSION *session) {
+ auto conn = static_cast<Connection *>(SSL_get_app_data(ssl));
+ auto handler = static_cast<ClientHandler *>(conn->data);
+ auto worker = handler->get_worker();
+ auto dispatcher = worker->get_session_cache_memcached_dispatcher();
+ auto &balloc = handler->get_block_allocator();
+
+#ifdef TLS1_3_VERSION
+ if (SSL_version(ssl) == TLS1_3_VERSION) {
+ return 0;
+ }
+#endif // TLS1_3_VERSION
+
+ const unsigned char *id;
+ unsigned int idlen;
+
+ id = SSL_SESSION_get_id(session, &idlen);
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Memcached: cache session, id=" << util::format_hex(id, idlen);
+ }
+
+ auto req = std::make_unique<MemcachedRequest>();
+ req->op = MemcachedOp::ADD;
+ req->key = MEMCACHED_SESSION_CACHE_KEY_PREFIX.str();
+ req->key +=
+ util::format_hex(balloc, StringRef{id, static_cast<size_t>(idlen)});
+
+ auto sessionlen = i2d_SSL_SESSION(session, nullptr);
+ req->value.resize(sessionlen);
+ auto buf = &req->value[0];
+ i2d_SSL_SESSION(session, &buf);
+ req->expiry = 12_h;
+ req->cb = [](MemcachedRequest *req, MemcachedResult res) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Memcached: session cache done. key=" << req->key
+ << ", status_code=" << static_cast<uint16_t>(res.status_code)
+ << ", value="
+ << std::string(std::begin(res.value), std::end(res.value));
+ }
+ if (res.status_code != MemcachedStatusCode::NO_ERROR) {
+ LOG(WARN) << "Memcached: failed to cache session key=" << req->key
+ << ", status_code=" << static_cast<uint16_t>(res.status_code)
+ << ", value="
+ << std::string(std::begin(res.value), std::end(res.value));
+ }
+ };
+ assert(!req->canceled);
+
+ dispatcher->add_request(std::move(req));
+
+ return 0;
+}
+} // namespace
+
+namespace {
+SSL_SESSION *tls_session_get_cb(SSL *ssl, const unsigned char *id, int idlen,
+ int *copy) {
+ auto conn = static_cast<Connection *>(SSL_get_app_data(ssl));
+ auto handler = static_cast<ClientHandler *>(conn->data);
+ auto worker = handler->get_worker();
+ auto dispatcher = worker->get_session_cache_memcached_dispatcher();
+ auto &balloc = handler->get_block_allocator();
+
+ if (idlen == 0) {
+ return nullptr;
+ }
+
+ if (conn->tls.cached_session) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Memcached: found cached session, id="
+ << util::format_hex(id, idlen);
+ }
+
+ // This is required, without this, memory leak occurs.
+ *copy = 0;
+
+ auto session = conn->tls.cached_session;
+ conn->tls.cached_session = nullptr;
+ return session;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Memcached: get cached session, id="
+ << util::format_hex(id, idlen);
+ }
+
+ auto req = std::make_unique<MemcachedRequest>();
+ req->op = MemcachedOp::GET;
+ req->key = MEMCACHED_SESSION_CACHE_KEY_PREFIX.str();
+ req->key +=
+ util::format_hex(balloc, StringRef{id, static_cast<size_t>(idlen)});
+ req->cb = [conn](MemcachedRequest *, MemcachedResult res) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Memcached: returned status code "
+ << static_cast<uint16_t>(res.status_code);
+ }
+
+ // We might stop reading, so start it again
+ conn->rlimit.startw();
+ ev_timer_again(conn->loop, &conn->rt);
+
+ conn->wlimit.startw();
+ ev_timer_again(conn->loop, &conn->wt);
+
+ conn->tls.cached_session_lookup_req = nullptr;
+ if (res.status_code != MemcachedStatusCode::NO_ERROR) {
+ conn->tls.handshake_state = TLSHandshakeState::CANCEL_SESSION_CACHE;
+ return;
+ }
+
+ const uint8_t *p = res.value.data();
+
+ auto session = d2i_SSL_SESSION(nullptr, &p, res.value.size());
+ if (!session) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "cannot materialize session";
+ }
+ conn->tls.handshake_state = TLSHandshakeState::CANCEL_SESSION_CACHE;
+ return;
+ }
+
+ conn->tls.cached_session = session;
+ conn->tls.handshake_state = TLSHandshakeState::GOT_SESSION_CACHE;
+ };
+
+ conn->tls.handshake_state = TLSHandshakeState::WAIT_FOR_SESSION_CACHE;
+ conn->tls.cached_session_lookup_req = req.get();
+
+ dispatcher->add_request(std::move(req));
+
+ return nullptr;
+}
+} // namespace
+
+namespace {
+int ticket_key_cb(SSL *ssl, unsigned char *key_name, unsigned char *iv,
+ EVP_CIPHER_CTX *ctx,
+#if OPENSSL_3_0_0_API
+ EVP_MAC_CTX *hctx,
+#else // !OPENSSL_3_0_0_API
+ HMAC_CTX *hctx,
+#endif // !OPENSSL_3_0_0_API
+ int enc) {
+ auto conn = static_cast<Connection *>(SSL_get_app_data(ssl));
+ auto handler = static_cast<ClientHandler *>(conn->data);
+ auto worker = handler->get_worker();
+ auto ticket_keys = worker->get_ticket_keys();
+
+ if (!ticket_keys) {
+ // No ticket keys available.
+ return -1;
+ }
+
+ auto &keys = ticket_keys->keys;
+ assert(!keys.empty());
+
+ if (enc) {
+ if (RAND_bytes(iv, EVP_MAX_IV_LENGTH) == 0) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, handler) << "session ticket key: RAND_bytes failed";
+ }
+ return -1;
+ }
+
+ auto &key = keys[0];
+
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, handler) << "encrypt session ticket key: "
+ << util::format_hex(key.data.name);
+ }
+
+ std::copy(std::begin(key.data.name), std::end(key.data.name), key_name);
+
+ EVP_EncryptInit_ex(ctx, get_config()->tls.ticket.cipher, nullptr,
+ key.data.enc_key.data(), iv);
+#if OPENSSL_3_0_0_API
+ std::array<OSSL_PARAM, 3> params{
+ OSSL_PARAM_construct_octet_string(
+ OSSL_MAC_PARAM_KEY, key.data.hmac_key.data(), key.hmac_keylen),
+ OSSL_PARAM_construct_utf8_string(
+ OSSL_MAC_PARAM_DIGEST,
+ const_cast<char *>(EVP_MD_get0_name(key.hmac)), 0),
+ OSSL_PARAM_construct_end(),
+ };
+ if (!EVP_MAC_CTX_set_params(hctx, params.data())) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, handler) << "EVP_MAC_CTX_set_params failed";
+ }
+ return -1;
+ }
+#else // !OPENSSL_3_0_0_API
+ HMAC_Init_ex(hctx, key.data.hmac_key.data(), key.hmac_keylen, key.hmac,
+ nullptr);
+#endif // !OPENSSL_3_0_0_API
+ return 1;
+ }
+
+ size_t i;
+ for (i = 0; i < keys.size(); ++i) {
+ auto &key = keys[i];
+ if (std::equal(std::begin(key.data.name), std::end(key.data.name),
+ key_name)) {
+ break;
+ }
+ }
+
+ if (i == keys.size()) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, handler) << "session ticket key "
+ << util::format_hex(key_name, 16) << " not found";
+ }
+ return 0;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, handler) << "decrypt session ticket key: "
+ << util::format_hex(key_name, 16);
+ }
+
+ auto &key = keys[i];
+#if OPENSSL_3_0_0_API
+ std::array<OSSL_PARAM, 3> params{
+ OSSL_PARAM_construct_octet_string(
+ OSSL_MAC_PARAM_KEY, key.data.hmac_key.data(), key.hmac_keylen),
+ OSSL_PARAM_construct_utf8_string(
+ OSSL_MAC_PARAM_DIGEST, const_cast<char *>(EVP_MD_get0_name(key.hmac)),
+ 0),
+ OSSL_PARAM_construct_end(),
+ };
+ if (!EVP_MAC_CTX_set_params(hctx, params.data())) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, handler) << "EVP_MAC_CTX_set_params failed";
+ }
+ return -1;
+ }
+#else // !OPENSSL_3_0_0_API
+ HMAC_Init_ex(hctx, key.data.hmac_key.data(), key.hmac_keylen, key.hmac,
+ nullptr);
+#endif // !OPENSSL_3_0_0_API
+ EVP_DecryptInit_ex(ctx, key.cipher, nullptr, key.data.enc_key.data(), iv);
+
+#ifdef TLS1_3_VERSION
+ // If ticket_key_cb is not set, OpenSSL always renew ticket for
+ // TLSv1.3.
+ if (SSL_version(ssl) == TLS1_3_VERSION) {
+ return 2;
+ }
+#endif // TLS1_3_VERSION
+
+ return i == 0 ? 1 : 2;
+}
+} // namespace
+
+namespace {
+void info_callback(const SSL *ssl, int where, int ret) {
+#ifdef TLS1_3_VERSION
+ // TLSv1.3 has no renegotiation.
+ if (SSL_version(ssl) == TLS1_3_VERSION) {
+ return;
+ }
+#endif // TLS1_3_VERSION
+
+ // To mitigate possible DOS attack using lots of renegotiations, we
+ // disable renegotiation. Since OpenSSL does not provide an easy way
+ // to disable it, we check that renegotiation is started in this
+ // callback.
+ if (where & SSL_CB_HANDSHAKE_START) {
+ auto conn = static_cast<Connection *>(SSL_get_app_data(ssl));
+ if (conn && conn->tls.initial_handshake_done) {
+ auto handler = static_cast<ClientHandler *>(conn->data);
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, handler) << "TLS renegotiation started";
+ }
+ handler->start_immediate_shutdown();
+ }
+ }
+}
+} // namespace
+
+namespace {
+int alpn_select_proto_cb(SSL *ssl, const unsigned char **out,
+ unsigned char *outlen, const unsigned char *in,
+ unsigned int inlen, void *arg) {
+ // We assume that get_config()->alpn_list contains ALPN protocol
+ // identifier sorted by preference order. So we just break when we
+ // found the first overlap.
+ for (const auto &target_proto_id : get_config()->tls.alpn_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 &&
+ util::streq(target_proto_id, StringRef{proto_id, proto_len})) {
+
+ *out = reinterpret_cast<const unsigned char *>(proto_id);
+ *outlen = proto_len;
+
+ return SSL_TLSEXT_ERR_OK;
+ }
+
+ p += 1 + proto_len;
+ }
+ }
+
+ return SSL_TLSEXT_ERR_NOACK;
+}
+} // namespace
+
+#ifdef ENABLE_HTTP3
+namespace {
+int quic_alpn_select_proto_cb(SSL *ssl, const unsigned char **out,
+ unsigned char *outlen, const unsigned char *in,
+ unsigned int inlen, void *arg) {
+ constexpr StringRef alpnlist[] = {
+ StringRef::from_lit("h3"),
+ StringRef::from_lit("h3-29"),
+ };
+
+ for (auto &alpn : alpnlist) {
+ for (auto p = in, end = in + inlen; p < end;) {
+ auto proto_id = p + 1;
+ auto proto_len = *p;
+
+ if (alpn.size() == proto_len &&
+ memcmp(alpn.byte(), proto_id, alpn.size()) == 0) {
+ *out = proto_id;
+ *outlen = proto_len;
+
+ return SSL_TLSEXT_ERR_OK;
+ }
+
+ p += 1 + proto_len;
+ }
+ }
+
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+}
+} // namespace
+#endif // ENABLE_HTTP3
+
+#ifdef NGHTTP2_GENUINE_OPENSSL
+namespace {
+int sct_add_cb(SSL *ssl, unsigned int ext_type, unsigned int context,
+ const unsigned char **out, size_t *outlen, X509 *x,
+ size_t chainidx, int *al, void *add_arg) {
+ assert(ext_type == TLSEXT_TYPE_signed_certificate_timestamp);
+
+ auto conn = static_cast<Connection *>(SSL_get_app_data(ssl));
+ if (!conn->tls.sct_requested) {
+ return 0;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "sct_add_cb is called, chainidx=" << chainidx << ", x=" << x
+ << ", context=" << log::hex << context;
+ }
+
+ // We only have SCTs for leaf certificate.
+ if (chainidx != 0) {
+ return 0;
+ }
+
+ auto ssl_ctx = SSL_get_SSL_CTX(ssl);
+ auto tls_ctx_data =
+ static_cast<TLSContextData *>(SSL_CTX_get_app_data(ssl_ctx));
+
+ *out = tls_ctx_data->sct_data.data();
+ *outlen = tls_ctx_data->sct_data.size();
+
+ return 1;
+}
+} // namespace
+
+namespace {
+void sct_free_cb(SSL *ssl, unsigned int ext_type, unsigned int context,
+ const unsigned char *out, void *add_arg) {
+ assert(ext_type == TLSEXT_TYPE_signed_certificate_timestamp);
+}
+} // namespace
+
+namespace {
+int sct_parse_cb(SSL *ssl, unsigned int ext_type, unsigned int context,
+ const unsigned char *in, size_t inlen, X509 *x,
+ size_t chainidx, int *al, void *parse_arg) {
+ assert(ext_type == TLSEXT_TYPE_signed_certificate_timestamp);
+ // client SHOULD send 0 length extension_data, but it is still
+ // SHOULD, and not MUST.
+
+ // For TLSv1.3 Certificate message, sct_add_cb is called even if
+ // client has not sent signed_certificate_timestamp extension in its
+ // ClientHello. Explicitly remember that client has included it
+ // here.
+ auto conn = static_cast<Connection *>(SSL_get_app_data(ssl));
+ conn->tls.sct_requested = true;
+
+ return 1;
+}
+} // namespace
+
+#endif // NGHTTP2_GENUINE_OPENSSL
+
+#ifndef OPENSSL_NO_PSK
+namespace {
+unsigned int psk_server_cb(SSL *ssl, const char *identity, unsigned char *psk,
+ unsigned int max_psk_len) {
+ auto config = get_config();
+ auto &tlsconf = config->tls;
+
+ auto it = tlsconf.psk_secrets.find(StringRef{identity});
+ if (it == std::end(tlsconf.psk_secrets)) {
+ return 0;
+ }
+
+ auto &secret = (*it).second;
+ if (secret.size() > max_psk_len) {
+ LOG(ERROR) << "The size of PSK secret is " << secret.size()
+ << ", but the acceptable maximum size is" << max_psk_len;
+ return 0;
+ }
+
+ std::copy(std::begin(secret), std::end(secret), psk);
+
+ return static_cast<unsigned int>(secret.size());
+}
+} // namespace
+#endif // !OPENSSL_NO_PSK
+
+#ifndef OPENSSL_NO_PSK
+namespace {
+unsigned int psk_client_cb(SSL *ssl, const char *hint, char *identity_out,
+ unsigned int max_identity_len, unsigned char *psk,
+ unsigned int max_psk_len) {
+ auto config = get_config();
+ auto &tlsconf = config->tls;
+
+ auto &identity = tlsconf.client.psk.identity;
+ auto &secret = tlsconf.client.psk.secret;
+
+ if (identity.empty()) {
+ return 0;
+ }
+
+ if (identity.size() + 1 > max_identity_len) {
+ LOG(ERROR) << "The size of PSK identity is " << identity.size()
+ << ", but the acceptable maximum size is " << max_identity_len;
+ return 0;
+ }
+
+ if (secret.size() > max_psk_len) {
+ LOG(ERROR) << "The size of PSK secret is " << secret.size()
+ << ", but the acceptable maximum size is " << max_psk_len;
+ return 0;
+ }
+
+ *std::copy(std::begin(identity), std::end(identity), identity_out) = '\0';
+ std::copy(std::begin(secret), std::end(secret), psk);
+
+ return static_cast<unsigned int>(secret.size());
+}
+} // namespace
+#endif // !OPENSSL_NO_PSK
+
+struct TLSProtocol {
+ StringRef name;
+ long int mask;
+};
+
+constexpr TLSProtocol TLS_PROTOS[] = {
+ TLSProtocol{StringRef::from_lit("TLSv1.2"), SSL_OP_NO_TLSv1_2},
+ TLSProtocol{StringRef::from_lit("TLSv1.1"), SSL_OP_NO_TLSv1_1},
+ TLSProtocol{StringRef::from_lit("TLSv1.0"), SSL_OP_NO_TLSv1}};
+
+long int create_tls_proto_mask(const std::vector<StringRef> &tls_proto_list) {
+ long int res = 0;
+
+ for (auto &supported : TLS_PROTOS) {
+ auto ok = false;
+ for (auto &name : tls_proto_list) {
+ if (util::strieq(supported.name, name)) {
+ ok = true;
+ break;
+ }
+ }
+ if (!ok) {
+ res |= supported.mask;
+ }
+ }
+ return res;
+}
+
+SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file,
+ const std::vector<uint8_t> &sct_data
+#ifdef HAVE_NEVERBLEED
+ ,
+ neverbleed_t *nb
+#endif // HAVE_NEVERBLEED
+) {
+ auto ssl_ctx = SSL_CTX_new(TLS_server_method());
+ if (!ssl_ctx) {
+ LOG(FATAL) << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+
+ auto ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) |
+ SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION |
+ SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION |
+ SSL_OP_SINGLE_ECDH_USE | SSL_OP_SINGLE_DH_USE |
+ SSL_OP_CIPHER_SERVER_PREFERENCE
+#ifdef NGHTTP2_GENUINE_OPENSSL
+ // The reason for disabling built-in anti-replay in
+ // OpenSSL is that it only works if client gets back
+ // to the same server. The freshness check
+ // described in
+ // https://tools.ietf.org/html/rfc8446#section-8.3
+ // is still performed.
+ | SSL_OP_NO_ANTI_REPLAY
+#endif // NGHTTP2_GENUINE_OPENSSL
+ ;
+
+ auto config = mod_config();
+ auto &tlsconf = config->tls;
+
+#ifdef SSL_OP_ENABLE_KTLS
+ if (tlsconf.ktls) {
+ ssl_opts |= SSL_OP_ENABLE_KTLS;
+ }
+#endif // SSL_OP_ENABLE_KTLS
+
+ SSL_CTX_set_options(ssl_ctx, ssl_opts | tlsconf.tls_proto_mask);
+
+ if (nghttp2::tls::ssl_ctx_set_proto_versions(
+ ssl_ctx, tlsconf.min_proto_version, tlsconf.max_proto_version) != 0) {
+ LOG(FATAL) << "Could not set TLS protocol version";
+ DIE();
+ }
+
+ 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);
+
+ if (!tlsconf.session_cache.memcached.host.empty()) {
+ SSL_CTX_sess_set_new_cb(ssl_ctx, tls_session_new_cb);
+ SSL_CTX_sess_set_get_cb(ssl_ctx, tls_session_get_cb);
+ }
+
+ SSL_CTX_set_timeout(ssl_ctx, tlsconf.session_timeout.count());
+
+ if (SSL_CTX_set_cipher_list(ssl_ctx, tlsconf.ciphers.c_str()) == 0) {
+ LOG(FATAL) << "SSL_CTX_set_cipher_list " << tlsconf.ciphers
+ << " failed: " << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+
+#if defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_LIBRESSL)
+ if (SSL_CTX_set_ciphersuites(ssl_ctx, tlsconf.tls13_ciphers.c_str()) == 0) {
+ LOG(FATAL) << "SSL_CTX_set_ciphersuites " << tlsconf.tls13_ciphers
+ << " failed: " << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+#endif // NGHTTP2_GENUINE_OPENSSL || NGHTTP2_OPENSSL_IS_LIBRESSL
+
+#ifndef OPENSSL_NO_EC
+ if (SSL_CTX_set1_curves_list(ssl_ctx, tlsconf.ecdh_curves.c_str()) != 1) {
+ LOG(FATAL) << "SSL_CTX_set1_curves_list " << tlsconf.ecdh_curves
+ << " failed";
+ DIE();
+ }
+#endif // OPENSSL_NO_EC
+
+ if (!tlsconf.dh_param_file.empty()) {
+ // Read DH parameters from file
+ auto bio = BIO_new_file(tlsconf.dh_param_file.c_str(), "rb");
+ if (bio == nullptr) {
+ LOG(FATAL) << "BIO_new_file() failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+#if OPENSSL_3_0_0_API
+ EVP_PKEY *dh = nullptr;
+ auto dctx = OSSL_DECODER_CTX_new_for_pkey(
+ &dh, "PEM", nullptr, "DH", OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS,
+ nullptr, nullptr);
+
+ if (!OSSL_DECODER_from_bio(dctx, bio)) {
+ LOG(FATAL) << "OSSL_DECODER_from_bio() failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+
+ if (SSL_CTX_set0_tmp_dh_pkey(ssl_ctx, dh) != 1) {
+ LOG(FATAL) << "SSL_CTX_set0_tmp_dh_pkey failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+#else // !OPENSSL_3_0_0_API
+ auto dh = PEM_read_bio_DHparams(bio, nullptr, nullptr, nullptr);
+ if (dh == nullptr) {
+ LOG(FATAL) << "PEM_read_bio_DHparams() failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+ SSL_CTX_set_tmp_dh(ssl_ctx, dh);
+ DH_free(dh);
+#endif // !OPENSSL_3_0_0_API
+ BIO_free(bio);
+ }
+
+ SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
+
+ if (SSL_CTX_set_default_verify_paths(ssl_ctx) != 1) {
+ LOG(WARN) << "Could not load system trusted ca certificates: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ }
+
+ if (!tlsconf.cacert.empty()) {
+ if (SSL_CTX_load_verify_locations(ssl_ctx, tlsconf.cacert.c_str(),
+ nullptr) != 1) {
+ LOG(FATAL) << "Could not load trusted ca certificates from "
+ << tlsconf.cacert << ": "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+ }
+
+ if (!tlsconf.private_key_passwd.empty()) {
+ SSL_CTX_set_default_passwd_cb(ssl_ctx, ssl_pem_passwd_cb);
+ SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx, config);
+ }
+
+#ifndef HAVE_NEVERBLEED
+ if (SSL_CTX_use_PrivateKey_file(ssl_ctx, private_key_file,
+ SSL_FILETYPE_PEM) != 1) {
+ LOG(FATAL) << "SSL_CTX_use_PrivateKey_file failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+#else // HAVE_NEVERBLEED
+ std::array<char, NEVERBLEED_ERRBUF_SIZE> errbuf;
+ if (neverbleed_load_private_key_file(nb, ssl_ctx, private_key_file,
+ errbuf.data()) != 1) {
+ LOG(FATAL) << "neverbleed_load_private_key_file failed: " << errbuf.data();
+ DIE();
+ }
+#endif // HAVE_NEVERBLEED
+
+ if (SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_file) != 1) {
+ LOG(FATAL) << "SSL_CTX_use_certificate_file failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+ if (SSL_CTX_check_private_key(ssl_ctx) != 1) {
+ LOG(FATAL) << "SSL_CTX_check_private_key failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+ if (tlsconf.client_verify.enabled) {
+ if (!tlsconf.client_verify.cacert.empty()) {
+ if (SSL_CTX_load_verify_locations(
+ ssl_ctx, tlsconf.client_verify.cacert.c_str(), nullptr) != 1) {
+
+ LOG(FATAL) << "Could not load trusted ca certificates from "
+ << tlsconf.client_verify.cacert << ": "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+ // It is heard that SSL_CTX_load_verify_locations() may leave
+ // error even though it returns success. See
+ // http://forum.nginx.org/read.php?29,242540
+ ERR_clear_error();
+ auto list = SSL_load_client_CA_file(tlsconf.client_verify.cacert.c_str());
+ if (!list) {
+ LOG(FATAL) << "Could not load ca certificates from "
+ << tlsconf.client_verify.cacert << ": "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+ SSL_CTX_set_client_CA_list(ssl_ctx, list);
+ }
+ SSL_CTX_set_verify(ssl_ctx,
+ SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE |
+ SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
+ verify_callback);
+ }
+ SSL_CTX_set_tlsext_servername_callback(ssl_ctx, servername_callback);
+#if OPENSSL_3_0_0_API
+ SSL_CTX_set_tlsext_ticket_key_evp_cb(ssl_ctx, ticket_key_cb);
+#else // !OPENSSL_3_0_0_API
+ SSL_CTX_set_tlsext_ticket_key_cb(ssl_ctx, ticket_key_cb);
+#endif // !OPENSSL_3_0_0_API
+#ifndef NGHTTP2_OPENSSL_IS_BORINGSSL
+ SSL_CTX_set_tlsext_status_cb(ssl_ctx, ocsp_resp_cb);
+#endif // NGHTTP2_OPENSSL_IS_BORINGSSL
+ SSL_CTX_set_info_callback(ssl_ctx, info_callback);
+
+#ifdef NGHTTP2_OPENSSL_IS_BORINGSSL
+ SSL_CTX_set_early_data_enabled(ssl_ctx, 1);
+#endif // NGHTTP2_OPENSSL_IS_BORINGSSL
+
+ // ALPN selection callback
+ SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, nullptr);
+
+ auto tls_ctx_data = new TLSContextData();
+ tls_ctx_data->cert_file = cert_file;
+ tls_ctx_data->sct_data = sct_data;
+
+ SSL_CTX_set_app_data(ssl_ctx, tls_ctx_data);
+
+#ifdef NGHTTP2_GENUINE_OPENSSL
+ // SSL_extension_supported(TLSEXT_TYPE_signed_certificate_timestamp)
+ // returns 1, which means OpenSSL internally handles it. But
+ // OpenSSL handles signed_certificate_timestamp extension specially,
+ // and it lets custom handler to process the extension.
+ if (!sct_data.empty()) {
+ // It is not entirely clear to me that SSL_EXT_CLIENT_HELLO is
+ // required here. sct_parse_cb is called without
+ // SSL_EXT_CLIENT_HELLO being set. But the passed context value
+ // is SSL_EXT_CLIENT_HELLO.
+ if (SSL_CTX_add_custom_ext(
+ ssl_ctx, TLSEXT_TYPE_signed_certificate_timestamp,
+ SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_2_SERVER_HELLO |
+ SSL_EXT_TLS1_3_CERTIFICATE | SSL_EXT_IGNORE_ON_RESUMPTION,
+ sct_add_cb, sct_free_cb, nullptr, sct_parse_cb, nullptr) != 1) {
+ LOG(FATAL) << "SSL_CTX_add_custom_ext failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+ }
+#elif defined(NGHTTP2_OPENSSL_IS_BORINGSSL)
+ if (!tls_ctx_data->sct_data.empty() &&
+ SSL_CTX_set_signed_cert_timestamp_list(
+ ssl_ctx, tls_ctx_data->sct_data.data(),
+ tls_ctx_data->sct_data.size()) != 1) {
+ LOG(FATAL) << "SSL_CTX_set_signed_cert_timestamp_list failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+#endif // NGHTTP2_OPENSSL_IS_BORINGSSL
+
+#ifdef NGHTTP2_GENUINE_OPENSSL
+ if (SSL_CTX_set_max_early_data(ssl_ctx, tlsconf.max_early_data) != 1) {
+ LOG(FATAL) << "SSL_CTX_set_max_early_data failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+ if (SSL_CTX_set_recv_max_early_data(ssl_ctx, tlsconf.max_early_data) != 1) {
+ LOG(FATAL) << "SSL_CTX_set_recv_max_early_data failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+#endif // NGHTTP2_GENUINE_OPENSSL
+
+#ifndef OPENSSL_NO_PSK
+ SSL_CTX_set_psk_server_callback(ssl_ctx, psk_server_cb);
+#endif // !LIBRESSL_NO_PSK
+
+ return ssl_ctx;
+}
+
+#ifdef ENABLE_HTTP3
+SSL_CTX *create_quic_ssl_context(const char *private_key_file,
+ const char *cert_file,
+ const std::vector<uint8_t> &sct_data
+# ifdef HAVE_NEVERBLEED
+ ,
+ neverbleed_t *nb
+# endif // HAVE_NEVERBLEED
+) {
+ auto ssl_ctx = SSL_CTX_new(TLS_server_method());
+ if (!ssl_ctx) {
+ LOG(FATAL) << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+
+ constexpr auto ssl_opts =
+ (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) |
+ SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION | SSL_OP_SINGLE_ECDH_USE |
+ SSL_OP_SINGLE_DH_USE |
+ SSL_OP_CIPHER_SERVER_PREFERENCE
+# ifdef NGHTTP2_GENUINE_OPENSSL
+ // The reason for disabling built-in anti-replay in OpenSSL is
+ // that it only works if client gets back to the same server.
+ // The freshness check described in
+ // https://tools.ietf.org/html/rfc8446#section-8.3 is still
+ // performed.
+ | SSL_OP_NO_ANTI_REPLAY
+# endif // NGHTTP2_GENUINE_OPENSSL
+ ;
+
+ auto config = mod_config();
+ auto &tlsconf = config->tls;
+
+ SSL_CTX_set_options(ssl_ctx, ssl_opts);
+
+# ifdef HAVE_LIBNGTCP2_CRYPTO_QUICTLS
+ if (ngtcp2_crypto_quictls_configure_server_context(ssl_ctx) != 0) {
+ LOG(FATAL) << "ngtcp2_crypto_quictls_configure_server_context failed";
+ DIE();
+ }
+# endif // HAVE_LIBNGTCP2_CRYPTO_QUICTLS
+# ifdef HAVE_LIBNGTCP2_CRYPTO_BORINGSSL
+ if (ngtcp2_crypto_boringssl_configure_server_context(ssl_ctx) != 0) {
+ LOG(FATAL) << "ngtcp2_crypto_boringssl_configure_server_context failed";
+ DIE();
+ }
+# endif // HAVE_LIBNGTCP2_CRYPTO_BORINGSSL
+
+ 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_OFF);
+
+ SSL_CTX_set_timeout(ssl_ctx, tlsconf.session_timeout.count());
+
+ if (SSL_CTX_set_cipher_list(ssl_ctx, tlsconf.ciphers.c_str()) == 0) {
+ LOG(FATAL) << "SSL_CTX_set_cipher_list " << tlsconf.ciphers
+ << " failed: " << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+
+# if defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_LIBRESSL)
+ if (SSL_CTX_set_ciphersuites(ssl_ctx, tlsconf.tls13_ciphers.c_str()) == 0) {
+ LOG(FATAL) << "SSL_CTX_set_ciphersuites " << tlsconf.tls13_ciphers
+ << " failed: " << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+# endif // NGHTTP2_GENUINE_OPENSSL || NGHTTP2_OPENSSL_IS_LIBRESSL
+
+# ifndef OPENSSL_NO_EC
+ if (SSL_CTX_set1_curves_list(ssl_ctx, tlsconf.ecdh_curves.c_str()) != 1) {
+ LOG(FATAL) << "SSL_CTX_set1_curves_list " << tlsconf.ecdh_curves
+ << " failed";
+ DIE();
+ }
+# endif // OPENSSL_NO_EC
+
+ if (!tlsconf.dh_param_file.empty()) {
+ // Read DH parameters from file
+ auto bio = BIO_new_file(tlsconf.dh_param_file.c_str(), "rb");
+ if (bio == nullptr) {
+ LOG(FATAL) << "BIO_new_file() failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+# if OPENSSL_3_0_0_API
+ EVP_PKEY *dh = nullptr;
+ auto dctx = OSSL_DECODER_CTX_new_for_pkey(
+ &dh, "PEM", nullptr, "DH", OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS,
+ nullptr, nullptr);
+
+ if (!OSSL_DECODER_from_bio(dctx, bio)) {
+ LOG(FATAL) << "OSSL_DECODER_from_bio() failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+
+ if (SSL_CTX_set0_tmp_dh_pkey(ssl_ctx, dh) != 1) {
+ LOG(FATAL) << "SSL_CTX_set0_tmp_dh_pkey failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+# else // !OPENSSL_3_0_0_API
+ auto dh = PEM_read_bio_DHparams(bio, nullptr, nullptr, nullptr);
+ if (dh == nullptr) {
+ LOG(FATAL) << "PEM_read_bio_DHparams() failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+ SSL_CTX_set_tmp_dh(ssl_ctx, dh);
+ DH_free(dh);
+# endif // !OPENSSL_3_0_0_API
+ BIO_free(bio);
+ }
+
+ SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
+
+ if (SSL_CTX_set_default_verify_paths(ssl_ctx) != 1) {
+ LOG(WARN) << "Could not load system trusted ca certificates: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ }
+
+ if (!tlsconf.cacert.empty()) {
+ if (SSL_CTX_load_verify_locations(ssl_ctx, tlsconf.cacert.c_str(),
+ nullptr) != 1) {
+ LOG(FATAL) << "Could not load trusted ca certificates from "
+ << tlsconf.cacert << ": "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+ }
+
+ if (!tlsconf.private_key_passwd.empty()) {
+ SSL_CTX_set_default_passwd_cb(ssl_ctx, ssl_pem_passwd_cb);
+ SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx, config);
+ }
+
+# ifndef HAVE_NEVERBLEED
+ if (SSL_CTX_use_PrivateKey_file(ssl_ctx, private_key_file,
+ SSL_FILETYPE_PEM) != 1) {
+ LOG(FATAL) << "SSL_CTX_use_PrivateKey_file failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+# else // HAVE_NEVERBLEED
+ std::array<char, NEVERBLEED_ERRBUF_SIZE> errbuf;
+ if (neverbleed_load_private_key_file(nb, ssl_ctx, private_key_file,
+ errbuf.data()) != 1) {
+ LOG(FATAL) << "neverbleed_load_private_key_file failed: " << errbuf.data();
+ DIE();
+ }
+# endif // HAVE_NEVERBLEED
+
+ if (SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_file) != 1) {
+ LOG(FATAL) << "SSL_CTX_use_certificate_file failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+ if (SSL_CTX_check_private_key(ssl_ctx) != 1) {
+ LOG(FATAL) << "SSL_CTX_check_private_key failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+ if (tlsconf.client_verify.enabled) {
+ if (!tlsconf.client_verify.cacert.empty()) {
+ if (SSL_CTX_load_verify_locations(
+ ssl_ctx, tlsconf.client_verify.cacert.c_str(), nullptr) != 1) {
+
+ LOG(FATAL) << "Could not load trusted ca certificates from "
+ << tlsconf.client_verify.cacert << ": "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+ // It is heard that SSL_CTX_load_verify_locations() may leave
+ // error even though it returns success. See
+ // http://forum.nginx.org/read.php?29,242540
+ ERR_clear_error();
+ auto list = SSL_load_client_CA_file(tlsconf.client_verify.cacert.c_str());
+ if (!list) {
+ LOG(FATAL) << "Could not load ca certificates from "
+ << tlsconf.client_verify.cacert << ": "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+ SSL_CTX_set_client_CA_list(ssl_ctx, list);
+ }
+ SSL_CTX_set_verify(ssl_ctx,
+ SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE |
+ SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
+ verify_callback);
+ }
+ SSL_CTX_set_tlsext_servername_callback(ssl_ctx, servername_callback);
+# if OPENSSL_3_0_0_API
+ SSL_CTX_set_tlsext_ticket_key_evp_cb(ssl_ctx, ticket_key_cb);
+# else // !OPENSSL_3_0_0_API
+ SSL_CTX_set_tlsext_ticket_key_cb(ssl_ctx, ticket_key_cb);
+# endif // !OPENSSL_3_0_0_API
+# ifndef NGHTTP2_OPENSSL_IS_BORINGSSL
+ SSL_CTX_set_tlsext_status_cb(ssl_ctx, ocsp_resp_cb);
+# endif // NGHTTP2_OPENSSL_IS_BORINGSSL
+
+ // ALPN selection callback
+ SSL_CTX_set_alpn_select_cb(ssl_ctx, quic_alpn_select_proto_cb, nullptr);
+
+ auto tls_ctx_data = new TLSContextData();
+ tls_ctx_data->cert_file = cert_file;
+ tls_ctx_data->sct_data = sct_data;
+
+ SSL_CTX_set_app_data(ssl_ctx, tls_ctx_data);
+
+# ifdef NGHTTP2_GENUINE_OPENSSL
+ // SSL_extension_supported(TLSEXT_TYPE_signed_certificate_timestamp)
+ // returns 1, which means OpenSSL internally handles it. But
+ // OpenSSL handles signed_certificate_timestamp extension specially,
+ // and it lets custom handler to process the extension.
+ if (!sct_data.empty()) {
+ // It is not entirely clear to me that SSL_EXT_CLIENT_HELLO is
+ // required here. sct_parse_cb is called without
+ // SSL_EXT_CLIENT_HELLO being set. But the passed context value
+ // is SSL_EXT_CLIENT_HELLO.
+ if (SSL_CTX_add_custom_ext(
+ ssl_ctx, TLSEXT_TYPE_signed_certificate_timestamp,
+ SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_2_SERVER_HELLO |
+ SSL_EXT_TLS1_3_CERTIFICATE | SSL_EXT_IGNORE_ON_RESUMPTION,
+ sct_add_cb, sct_free_cb, nullptr, sct_parse_cb, nullptr) != 1) {
+ LOG(FATAL) << "SSL_CTX_add_custom_ext failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+ }
+# elif defined(NGHTTP2_OPENSSL_IS_BORINGSSL)
+ if (!tls_ctx_data->sct_data.empty() &&
+ SSL_CTX_set_signed_cert_timestamp_list(
+ ssl_ctx, tls_ctx_data->sct_data.data(),
+ tls_ctx_data->sct_data.size()) != 1) {
+ LOG(FATAL) << "SSL_CTX_set_signed_cert_timestamp_list failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+# endif // NGHTTP2_OPENSSL_IS_BORINGSSL
+
+# ifdef NGHTTP2_GENUINE_OPENSSL
+ auto &quicconf = config->quic;
+
+ if (quicconf.upstream.early_data &&
+ SSL_CTX_set_max_early_data(ssl_ctx,
+ std::numeric_limits<uint32_t>::max()) != 1) {
+ LOG(FATAL) << "SSL_CTX_set_max_early_data failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+# endif // NGHTTP2_GENUINE_OPENSSL
+
+# ifndef OPENSSL_NO_PSK
+ SSL_CTX_set_psk_server_callback(ssl_ctx, psk_server_cb);
+# endif // !LIBRESSL_NO_PSK
+
+ return ssl_ctx;
+}
+#endif // ENABLE_HTTP3
+
+SSL_CTX *create_ssl_client_context(
+#ifdef HAVE_NEVERBLEED
+ neverbleed_t *nb,
+#endif // HAVE_NEVERBLEED
+ const StringRef &cacert, const StringRef &cert_file,
+ const StringRef &private_key_file) {
+ auto ssl_ctx = SSL_CTX_new(TLS_client_method());
+ if (!ssl_ctx) {
+ LOG(FATAL) << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+
+ auto ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) |
+ SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION |
+ SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION;
+
+ auto &tlsconf = get_config()->tls;
+
+#ifdef SSL_OP_ENABLE_KTLS
+ if (tlsconf.ktls) {
+ ssl_opts |= SSL_OP_ENABLE_KTLS;
+ }
+#endif // SSL_OP_ENABLE_KTLS
+
+ SSL_CTX_set_options(ssl_ctx, ssl_opts | tlsconf.tls_proto_mask);
+
+ SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_CLIENT |
+ SSL_SESS_CACHE_NO_INTERNAL_STORE);
+ SSL_CTX_sess_set_new_cb(ssl_ctx, tls_session_client_new_cb);
+
+ if (nghttp2::tls::ssl_ctx_set_proto_versions(
+ ssl_ctx, tlsconf.min_proto_version, tlsconf.max_proto_version) != 0) {
+ LOG(FATAL) << "Could not set TLS protocol version";
+ DIE();
+ }
+
+ if (SSL_CTX_set_cipher_list(ssl_ctx, tlsconf.client.ciphers.c_str()) == 0) {
+ LOG(FATAL) << "SSL_CTX_set_cipher_list " << tlsconf.client.ciphers
+ << " failed: " << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+
+#if defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_LIBRESSL)
+ if (SSL_CTX_set_ciphersuites(ssl_ctx, tlsconf.client.tls13_ciphers.c_str()) ==
+ 0) {
+ LOG(FATAL) << "SSL_CTX_set_ciphersuites " << tlsconf.client.tls13_ciphers
+ << " failed: " << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+#endif // NGHTTP2_GENUINE_OPENSSL || NGHTTP2_OPENSSL_IS_LIBRESSL
+
+ SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
+
+ if (SSL_CTX_set_default_verify_paths(ssl_ctx) != 1) {
+ LOG(WARN) << "Could not load system trusted ca certificates: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ }
+
+ if (!cacert.empty()) {
+ if (SSL_CTX_load_verify_locations(ssl_ctx, cacert.c_str(), nullptr) != 1) {
+
+ LOG(FATAL) << "Could not load trusted ca certificates from " << cacert
+ << ": " << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+ }
+
+ if (!tlsconf.insecure) {
+ SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, nullptr);
+ }
+
+ if (!cert_file.empty()) {
+ if (SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_file.c_str()) != 1) {
+
+ LOG(FATAL) << "Could not load client certificate from " << cert_file
+ << ": " << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+ }
+
+ if (!private_key_file.empty()) {
+#ifndef HAVE_NEVERBLEED
+ if (SSL_CTX_use_PrivateKey_file(ssl_ctx, private_key_file.c_str(),
+ SSL_FILETYPE_PEM) != 1) {
+ LOG(FATAL) << "Could not load client private key from "
+ << private_key_file << ": "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+#else // HAVE_NEVERBLEED
+ std::array<char, NEVERBLEED_ERRBUF_SIZE> errbuf;
+ if (neverbleed_load_private_key_file(nb, ssl_ctx, private_key_file.c_str(),
+ errbuf.data()) != 1) {
+ LOG(FATAL) << "neverbleed_load_private_key_file: could not load client "
+ "private key from "
+ << private_key_file << ": " << errbuf.data();
+ DIE();
+ }
+#endif // HAVE_NEVERBLEED
+ }
+
+#ifndef OPENSSL_NO_PSK
+ SSL_CTX_set_psk_client_callback(ssl_ctx, psk_client_cb);
+#endif // !OPENSSL_NO_PSK
+
+ return ssl_ctx;
+}
+
+SSL *create_ssl(SSL_CTX *ssl_ctx) {
+ auto ssl = SSL_new(ssl_ctx);
+ if (!ssl) {
+ LOG(ERROR) << "SSL_new() failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ return nullptr;
+ }
+
+ return ssl;
+}
+
+ClientHandler *accept_connection(Worker *worker, int fd, sockaddr *addr,
+ int addrlen, const UpstreamAddr *faddr) {
+ std::array<char, NI_MAXHOST> host;
+ std::array<char, NI_MAXSERV> service;
+ int rv;
+
+ if (addr->sa_family == AF_UNIX) {
+ std::copy_n("localhost", sizeof("localhost"), std::begin(host));
+ service[0] = '\0';
+ } else {
+ rv = getnameinfo(addr, addrlen, host.data(), host.size(), service.data(),
+ service.size(), NI_NUMERICHOST | NI_NUMERICSERV);
+ if (rv != 0) {
+ LOG(ERROR) << "getnameinfo() failed: " << gai_strerror(rv);
+
+ return nullptr;
+ }
+
+ rv = util::make_socket_nodelay(fd);
+ if (rv == -1) {
+ LOG(WARN) << "Setting option TCP_NODELAY failed: errno=" << errno;
+ }
+ }
+ SSL *ssl = nullptr;
+ if (faddr->tls) {
+ auto ssl_ctx = worker->get_sv_ssl_ctx();
+
+ assert(ssl_ctx);
+
+ ssl = create_ssl(ssl_ctx);
+ if (!ssl) {
+ return nullptr;
+ }
+ // Disable TLS session ticket if we don't have working ticket
+ // keys.
+ if (!worker->get_ticket_keys()) {
+ SSL_set_options(ssl, SSL_OP_NO_TICKET);
+ }
+ }
+
+ return new ClientHandler(worker, fd, ssl, StringRef{host.data()},
+ StringRef{service.data()}, addr->sa_family, faddr);
+}
+
+bool tls_hostname_match(const StringRef &pattern, const StringRef &hostname) {
+ auto ptWildcard = std::find(std::begin(pattern), std::end(pattern), '*');
+ if (ptWildcard == std::end(pattern)) {
+ return util::strieq(pattern, hostname);
+ }
+
+ auto ptLeftLabelEnd = std::find(std::begin(pattern), std::end(pattern), '.');
+ auto wildcardEnabled = true;
+ // Do case-insensitive match. At least 2 dots are required to enable
+ // wildcard match. Also wildcard must be in the left-most label.
+ // Don't attempt to match a presented identifier where the wildcard
+ // character is embedded within an A-label.
+ if (ptLeftLabelEnd == std::end(pattern) ||
+ std::find(ptLeftLabelEnd + 1, std::end(pattern), '.') ==
+ std::end(pattern) ||
+ ptLeftLabelEnd < ptWildcard || util::istarts_with_l(pattern, "xn--")) {
+ wildcardEnabled = false;
+ }
+
+ if (!wildcardEnabled) {
+ return util::strieq(pattern, hostname);
+ }
+
+ auto hnLeftLabelEnd =
+ std::find(std::begin(hostname), std::end(hostname), '.');
+ if (hnLeftLabelEnd == std::end(hostname) ||
+ !util::strieq(StringRef{ptLeftLabelEnd, std::end(pattern)},
+ StringRef{hnLeftLabelEnd, std::end(hostname)})) {
+ return false;
+ }
+ // Perform wildcard match. Here '*' must match at least one
+ // character.
+ if (hnLeftLabelEnd - std::begin(hostname) <
+ ptLeftLabelEnd - std::begin(pattern)) {
+ return false;
+ }
+ return util::istarts_with(StringRef{std::begin(hostname), hnLeftLabelEnd},
+ StringRef{std::begin(pattern), ptWildcard}) &&
+ util::iends_with(StringRef{std::begin(hostname), hnLeftLabelEnd},
+ StringRef{ptWildcard + 1, ptLeftLabelEnd});
+}
+
+namespace {
+// if return value is not empty, StringRef.c_str() must be freed using
+// OPENSSL_free().
+StringRef get_common_name(X509 *cert) {
+ auto subjectname = X509_get_subject_name(cert);
+ if (!subjectname) {
+ LOG(WARN) << "Could not get X509 name object from the certificate.";
+ return StringRef{};
+ }
+ int lastpos = -1;
+ for (;;) {
+ lastpos = X509_NAME_get_index_by_NID(subjectname, NID_commonName, lastpos);
+ if (lastpos == -1) {
+ break;
+ }
+ auto entry = X509_NAME_get_entry(subjectname, lastpos);
+
+ unsigned char *p;
+ auto plen = ASN1_STRING_to_UTF8(&p, X509_NAME_ENTRY_get_data(entry));
+ if (plen < 0) {
+ continue;
+ }
+ if (std::find(p, p + plen, '\0') != p + plen) {
+ // Embedded NULL is not permitted.
+ continue;
+ }
+ if (plen == 0) {
+ LOG(WARN) << "X509 name is empty";
+ OPENSSL_free(p);
+ continue;
+ }
+
+ return StringRef{p, static_cast<size_t>(plen)};
+ }
+ return StringRef{};
+}
+} // namespace
+
+int verify_numeric_hostname(X509 *cert, const StringRef &hostname,
+ const Address *addr) {
+ const void *saddr;
+ size_t saddrlen;
+ switch (addr->su.storage.ss_family) {
+ case AF_INET:
+ saddr = &addr->su.in.sin_addr;
+ saddrlen = sizeof(addr->su.in.sin_addr);
+ break;
+ case AF_INET6:
+ saddr = &addr->su.in6.sin6_addr;
+ saddrlen = sizeof(addr->su.in6.sin6_addr);
+ break;
+ default:
+ return -1;
+ }
+
+ auto altnames = static_cast<GENERAL_NAMES *>(
+ X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr));
+ if (altnames) {
+ auto altnames_deleter = defer(GENERAL_NAMES_free, altnames);
+ size_t n = sk_GENERAL_NAME_num(altnames);
+ auto ip_found = false;
+ for (size_t i = 0; i < n; ++i) {
+ auto altname = sk_GENERAL_NAME_value(altnames, i);
+ if (altname->type != GEN_IPADD) {
+ continue;
+ }
+
+ auto ip_addr = altname->d.iPAddress->data;
+ if (!ip_addr) {
+ continue;
+ }
+ size_t ip_addrlen = altname->d.iPAddress->length;
+
+ ip_found = true;
+ if (saddrlen == ip_addrlen && memcmp(saddr, ip_addr, ip_addrlen) == 0) {
+ return 0;
+ }
+ }
+
+ if (ip_found) {
+ return -1;
+ }
+ }
+
+ auto cn = get_common_name(cert);
+ if (cn.empty()) {
+ return -1;
+ }
+
+ // cn is not NULL terminated
+ auto rv = util::streq(hostname, cn);
+ OPENSSL_free(const_cast<char *>(cn.c_str()));
+
+ if (rv) {
+ return 0;
+ }
+
+ return -1;
+}
+
+int verify_dns_hostname(X509 *cert, const StringRef &hostname) {
+ auto altnames = static_cast<GENERAL_NAMES *>(
+ X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr));
+ if (altnames) {
+ auto dns_found = false;
+ auto altnames_deleter = defer(GENERAL_NAMES_free, altnames);
+ size_t n = sk_GENERAL_NAME_num(altnames);
+ for (size_t i = 0; i < n; ++i) {
+ auto altname = sk_GENERAL_NAME_value(altnames, i);
+ if (altname->type != GEN_DNS) {
+ continue;
+ }
+
+ auto name = ASN1_STRING_get0_data(altname->d.ia5);
+ if (!name) {
+ continue;
+ }
+
+ auto len = ASN1_STRING_length(altname->d.ia5);
+ if (len == 0) {
+ continue;
+ }
+ if (std::find(name, name + len, '\0') != name + len) {
+ // Embedded NULL is not permitted.
+ continue;
+ }
+
+ if (name[len - 1] == '.') {
+ --len;
+ if (len == 0) {
+ continue;
+ }
+ }
+
+ dns_found = true;
+
+ if (tls_hostname_match(StringRef{name, static_cast<size_t>(len)},
+ hostname)) {
+ return 0;
+ }
+ }
+
+ // RFC 6125, section 6.4.4. says that client MUST not seek a match
+ // for CN if a dns dNSName is found.
+ if (dns_found) {
+ return -1;
+ }
+ }
+
+ auto cn = get_common_name(cert);
+ if (cn.empty()) {
+ return -1;
+ }
+
+ if (cn[cn.size() - 1] == '.') {
+ if (cn.size() == 1) {
+ OPENSSL_free(const_cast<char *>(cn.c_str()));
+
+ return -1;
+ }
+ cn = StringRef{cn.c_str(), cn.size() - 1};
+ }
+
+ auto rv = tls_hostname_match(cn, hostname);
+ OPENSSL_free(const_cast<char *>(cn.c_str()));
+
+ return rv ? 0 : -1;
+}
+
+namespace {
+int verify_hostname(X509 *cert, const StringRef &hostname,
+ const Address *addr) {
+ if (util::numeric_host(hostname.c_str())) {
+ return verify_numeric_hostname(cert, hostname, addr);
+ }
+
+ return verify_dns_hostname(cert, hostname);
+}
+} // namespace
+
+int check_cert(SSL *ssl, const Address *addr, const StringRef &host) {
+#if OPENSSL_3_0_0_API
+ auto cert = SSL_get0_peer_certificate(ssl);
+#else // !OPENSSL_3_0_0_API
+ auto cert = SSL_get_peer_certificate(ssl);
+#endif // !OPENSSL_3_0_0_API
+ if (!cert) {
+ // By the protocol definition, TLS server always sends certificate
+ // if it has. If certificate cannot be retrieved, authentication
+ // without certificate is used, such as PSK.
+ return 0;
+ }
+#if !OPENSSL_3_0_0_API
+ auto cert_deleter = defer(X509_free, cert);
+#endif // !OPENSSL_3_0_0_API
+
+ if (verify_hostname(cert, host, addr) != 0) {
+ LOG(ERROR) << "Certificate verification failed: hostname does not match";
+ return -1;
+ }
+ return 0;
+}
+
+int check_cert(SSL *ssl, const DownstreamAddr *addr, const Address *raddr) {
+ auto hostname =
+ addr->sni.empty() ? StringRef{addr->host} : StringRef{addr->sni};
+ return check_cert(ssl, raddr, hostname);
+}
+
+CertLookupTree::CertLookupTree() {}
+
+ssize_t CertLookupTree::add_cert(const StringRef &hostname, size_t idx) {
+ std::array<uint8_t, NI_MAXHOST> buf;
+
+ // NI_MAXHOST includes terminal NULL byte
+ if (hostname.empty() || hostname.size() + 1 > buf.size()) {
+ return -1;
+ }
+
+ auto wildcard_it = std::find(std::begin(hostname), std::end(hostname), '*');
+ if (wildcard_it != std::end(hostname) &&
+ wildcard_it + 1 != std::end(hostname)) {
+ auto wildcard_prefix = StringRef{std::begin(hostname), wildcard_it};
+ auto wildcard_suffix = StringRef{wildcard_it + 1, std::end(hostname)};
+
+ auto rev_suffix = StringRef{std::begin(buf),
+ std::reverse_copy(std::begin(wildcard_suffix),
+ std::end(wildcard_suffix),
+ std::begin(buf))};
+
+ WildcardPattern *wpat;
+
+ if (wildcard_patterns_.size() !=
+ rev_wildcard_router_.add_route(rev_suffix, wildcard_patterns_.size())) {
+ auto wcidx = rev_wildcard_router_.match(rev_suffix);
+
+ assert(wcidx != -1);
+
+ wpat = &wildcard_patterns_[wcidx];
+ } else {
+ wildcard_patterns_.emplace_back();
+ wpat = &wildcard_patterns_.back();
+ }
+
+ auto rev_prefix = StringRef{std::begin(buf),
+ std::reverse_copy(std::begin(wildcard_prefix),
+ std::end(wildcard_prefix),
+ std::begin(buf))};
+
+ for (auto &p : wpat->rev_prefix) {
+ if (p.prefix == rev_prefix) {
+ return p.idx;
+ }
+ }
+
+ wpat->rev_prefix.emplace_back(rev_prefix, idx);
+
+ return idx;
+ }
+
+ return router_.add_route(hostname, idx);
+}
+
+ssize_t CertLookupTree::lookup(const StringRef &hostname) {
+ std::array<uint8_t, NI_MAXHOST> buf;
+
+ // NI_MAXHOST includes terminal NULL byte
+ if (hostname.empty() || hostname.size() + 1 > buf.size()) {
+ return -1;
+ }
+
+ // Always prefer exact match
+ auto idx = router_.match(hostname);
+ if (idx != -1) {
+ return idx;
+ }
+
+ if (wildcard_patterns_.empty()) {
+ return -1;
+ }
+
+ ssize_t best_idx = -1;
+ size_t best_prefixlen = 0;
+ const RNode *last_node = nullptr;
+
+ auto rev_host = StringRef{
+ std::begin(buf), std::reverse_copy(std::begin(hostname),
+ std::end(hostname), std::begin(buf))};
+
+ for (;;) {
+ size_t nread = 0;
+
+ auto wcidx =
+ rev_wildcard_router_.match_prefix(&nread, &last_node, rev_host);
+ if (wcidx == -1) {
+ return best_idx;
+ }
+
+ // '*' must match at least one byte
+ if (nread == rev_host.size()) {
+ return best_idx;
+ }
+
+ rev_host = StringRef{std::begin(rev_host) + nread, std::end(rev_host)};
+
+ auto rev_prefix = StringRef{std::begin(rev_host) + 1, std::end(rev_host)};
+
+ auto &wpat = wildcard_patterns_[wcidx];
+ for (auto &wprefix : wpat.rev_prefix) {
+ if (!util::ends_with(rev_prefix, wprefix.prefix)) {
+ continue;
+ }
+
+ auto prefixlen =
+ wprefix.prefix.size() +
+ (reinterpret_cast<const uint8_t *>(&rev_host[0]) - &buf[0]);
+
+ // Breaking a tie with longer suffix
+ if (prefixlen < best_prefixlen) {
+ continue;
+ }
+
+ best_idx = wprefix.idx;
+ best_prefixlen = prefixlen;
+ }
+ }
+}
+
+void CertLookupTree::dump() const {
+ std::cerr << "exact:" << std::endl;
+ router_.dump();
+ std::cerr << "wildcard suffix (reversed):" << std::endl;
+ rev_wildcard_router_.dump();
+}
+
+int cert_lookup_tree_add_ssl_ctx(
+ CertLookupTree *lt, std::vector<std::vector<SSL_CTX *>> &indexed_ssl_ctx,
+ SSL_CTX *ssl_ctx) {
+ std::array<uint8_t, NI_MAXHOST> buf;
+
+ auto cert = SSL_CTX_get0_certificate(ssl_ctx);
+ auto altnames = static_cast<GENERAL_NAMES *>(
+ X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr));
+ if (altnames) {
+ auto altnames_deleter = defer(GENERAL_NAMES_free, altnames);
+ size_t n = sk_GENERAL_NAME_num(altnames);
+ auto dns_found = false;
+ for (size_t i = 0; i < n; ++i) {
+ auto altname = sk_GENERAL_NAME_value(altnames, i);
+ if (altname->type != GEN_DNS) {
+ continue;
+ }
+
+ auto name = ASN1_STRING_get0_data(altname->d.ia5);
+ if (!name) {
+ continue;
+ }
+
+ auto len = ASN1_STRING_length(altname->d.ia5);
+ if (len == 0) {
+ continue;
+ }
+ if (std::find(name, name + len, '\0') != name + len) {
+ // Embedded NULL is not permitted.
+ continue;
+ }
+
+ if (name[len - 1] == '.') {
+ --len;
+ if (len == 0) {
+ continue;
+ }
+ }
+
+ dns_found = true;
+
+ if (static_cast<size_t>(len) + 1 > buf.size()) {
+ continue;
+ }
+
+ auto end_buf = std::copy_n(name, len, std::begin(buf));
+ util::inp_strlower(std::begin(buf), end_buf);
+
+ auto idx = lt->add_cert(StringRef{std::begin(buf), end_buf},
+ indexed_ssl_ctx.size());
+ if (idx == -1) {
+ continue;
+ }
+
+ if (static_cast<size_t>(idx) < indexed_ssl_ctx.size()) {
+ indexed_ssl_ctx[idx].push_back(ssl_ctx);
+ } else {
+ assert(static_cast<size_t>(idx) == indexed_ssl_ctx.size());
+ indexed_ssl_ctx.emplace_back(std::vector<SSL_CTX *>{ssl_ctx});
+ }
+ }
+
+ // Don't bother CN if we have dNSName.
+ if (dns_found) {
+ return 0;
+ }
+ }
+
+ auto cn = get_common_name(cert);
+ if (cn.empty()) {
+ return 0;
+ }
+
+ if (cn[cn.size() - 1] == '.') {
+ if (cn.size() == 1) {
+ OPENSSL_free(const_cast<char *>(cn.c_str()));
+
+ return 0;
+ }
+
+ cn = StringRef{cn.c_str(), cn.size() - 1};
+ }
+
+ auto end_buf = std::copy(std::begin(cn), std::end(cn), std::begin(buf));
+
+ OPENSSL_free(const_cast<char *>(cn.c_str()));
+
+ util::inp_strlower(std::begin(buf), end_buf);
+
+ auto idx =
+ lt->add_cert(StringRef{std::begin(buf), end_buf}, indexed_ssl_ctx.size());
+ if (idx == -1) {
+ return 0;
+ }
+
+ if (static_cast<size_t>(idx) < indexed_ssl_ctx.size()) {
+ indexed_ssl_ctx[idx].push_back(ssl_ctx);
+ } else {
+ assert(static_cast<size_t>(idx) == indexed_ssl_ctx.size());
+ indexed_ssl_ctx.emplace_back(std::vector<SSL_CTX *>{ssl_ctx});
+ }
+
+ return 0;
+}
+
+bool in_proto_list(const std::vector<StringRef> &protos,
+ const StringRef &needle) {
+ for (auto &proto : protos) {
+ if (util::streq(proto, needle)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool upstream_tls_enabled(const ConnectionConfig &connconf) {
+#ifdef ENABLE_HTTP3
+ if (connconf.quic_listener.addrs.size()) {
+ return true;
+ }
+#endif // ENABLE_HTTP3
+
+ const auto &faddrs = connconf.listener.addrs;
+ return std::any_of(std::begin(faddrs), std::end(faddrs),
+ [](const UpstreamAddr &faddr) { return faddr.tls; });
+}
+
+X509 *load_certificate(const char *filename) {
+ auto bio = BIO_new(BIO_s_file());
+ if (!bio) {
+ fprintf(stderr, "BIO_new() failed\n");
+ return nullptr;
+ }
+ auto bio_deleter = defer(BIO_vfree, bio);
+ if (!BIO_read_filename(bio, filename)) {
+ fprintf(stderr, "Could not read certificate file '%s'\n", filename);
+ return nullptr;
+ }
+ auto cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
+ if (!cert) {
+ fprintf(stderr, "Could not read X509 structure from file '%s'\n", filename);
+ return nullptr;
+ }
+
+ return cert;
+}
+
+SSL_CTX *
+setup_server_ssl_context(std::vector<SSL_CTX *> &all_ssl_ctx,
+ std::vector<std::vector<SSL_CTX *>> &indexed_ssl_ctx,
+ CertLookupTree *cert_tree
+#ifdef HAVE_NEVERBLEED
+ ,
+ neverbleed_t *nb
+#endif // HAVE_NEVERBLEED
+) {
+ auto config = get_config();
+
+ if (!upstream_tls_enabled(config->conn)) {
+ return nullptr;
+ }
+
+ auto &tlsconf = config->tls;
+
+ auto ssl_ctx = create_ssl_context(tlsconf.private_key_file.c_str(),
+ tlsconf.cert_file.c_str(), tlsconf.sct_data
+#ifdef HAVE_NEVERBLEED
+ ,
+ nb
+#endif // HAVE_NEVERBLEED
+ );
+
+ all_ssl_ctx.push_back(ssl_ctx);
+
+ assert(cert_tree);
+
+ if (cert_lookup_tree_add_ssl_ctx(cert_tree, indexed_ssl_ctx, ssl_ctx) == -1) {
+ LOG(FATAL) << "Failed to add default certificate.";
+ DIE();
+ }
+
+ for (auto &c : tlsconf.subcerts) {
+ auto ssl_ctx = create_ssl_context(c.private_key_file.c_str(),
+ c.cert_file.c_str(), c.sct_data
+#ifdef HAVE_NEVERBLEED
+ ,
+ nb
+#endif // HAVE_NEVERBLEED
+ );
+ all_ssl_ctx.push_back(ssl_ctx);
+
+ if (cert_lookup_tree_add_ssl_ctx(cert_tree, indexed_ssl_ctx, ssl_ctx) ==
+ -1) {
+ LOG(FATAL) << "Failed to add sub certificate.";
+ DIE();
+ }
+ }
+
+ return ssl_ctx;
+}
+
+#ifdef ENABLE_HTTP3
+SSL_CTX *setup_quic_server_ssl_context(
+ std::vector<SSL_CTX *> &all_ssl_ctx,
+ std::vector<std::vector<SSL_CTX *>> &indexed_ssl_ctx,
+ CertLookupTree *cert_tree
+# ifdef HAVE_NEVERBLEED
+ ,
+ neverbleed_t *nb
+# endif // HAVE_NEVERBLEED
+) {
+ auto config = get_config();
+
+ if (!upstream_tls_enabled(config->conn)) {
+ return nullptr;
+ }
+
+ auto &tlsconf = config->tls;
+
+ auto ssl_ctx =
+ create_quic_ssl_context(tlsconf.private_key_file.c_str(),
+ tlsconf.cert_file.c_str(), tlsconf.sct_data
+# ifdef HAVE_NEVERBLEED
+ ,
+ nb
+# endif // HAVE_NEVERBLEED
+ );
+
+ all_ssl_ctx.push_back(ssl_ctx);
+
+ assert(cert_tree);
+
+ if (cert_lookup_tree_add_ssl_ctx(cert_tree, indexed_ssl_ctx, ssl_ctx) == -1) {
+ LOG(FATAL) << "Failed to add default certificate.";
+ DIE();
+ }
+
+ for (auto &c : tlsconf.subcerts) {
+ auto ssl_ctx = create_quic_ssl_context(c.private_key_file.c_str(),
+ c.cert_file.c_str(), c.sct_data
+# ifdef HAVE_NEVERBLEED
+ ,
+ nb
+# endif // HAVE_NEVERBLEED
+ );
+ all_ssl_ctx.push_back(ssl_ctx);
+
+ if (cert_lookup_tree_add_ssl_ctx(cert_tree, indexed_ssl_ctx, ssl_ctx) ==
+ -1) {
+ LOG(FATAL) << "Failed to add sub certificate.";
+ DIE();
+ }
+ }
+
+ return ssl_ctx;
+}
+#endif // ENABLE_HTTP3
+
+SSL_CTX *setup_downstream_client_ssl_context(
+#ifdef HAVE_NEVERBLEED
+ neverbleed_t *nb
+#endif // HAVE_NEVERBLEED
+) {
+ auto &tlsconf = get_config()->tls;
+
+ return create_ssl_client_context(
+#ifdef HAVE_NEVERBLEED
+ nb,
+#endif // HAVE_NEVERBLEED
+ tlsconf.cacert, tlsconf.client.cert_file,
+ tlsconf.client.private_key_file);
+}
+
+void setup_downstream_http2_alpn(SSL *ssl) {
+ // ALPN advertisement
+ auto alpn = util::get_default_alpn();
+ SSL_set_alpn_protos(ssl, alpn.data(), alpn.size());
+}
+
+void setup_downstream_http1_alpn(SSL *ssl) {
+ // ALPN advertisement
+ SSL_set_alpn_protos(ssl, NGHTTP2_H1_1_ALPN.byte(), NGHTTP2_H1_1_ALPN.size());
+}
+
+std::unique_ptr<CertLookupTree> create_cert_lookup_tree() {
+ auto config = get_config();
+ if (!upstream_tls_enabled(config->conn)) {
+ return nullptr;
+ }
+ return std::make_unique<CertLookupTree>();
+}
+
+namespace {
+std::vector<uint8_t> serialize_ssl_session(SSL_SESSION *session) {
+ auto len = i2d_SSL_SESSION(session, nullptr);
+ auto buf = std::vector<uint8_t>(len);
+ auto p = buf.data();
+ i2d_SSL_SESSION(session, &p);
+
+ return buf;
+}
+} // namespace
+
+void try_cache_tls_session(TLSSessionCache *cache, SSL_SESSION *session,
+ const std::chrono::steady_clock::time_point &t) {
+ if (cache->last_updated + 1min > t) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Client session cache entry is still fresh.";
+ }
+ return;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Update client cache entry "
+ << "timestamp = " << t.time_since_epoch().count();
+ }
+
+ cache->session_data = serialize_ssl_session(session);
+ cache->last_updated = t;
+}
+
+SSL_SESSION *reuse_tls_session(const TLSSessionCache &cache) {
+ if (cache.session_data.empty()) {
+ return nullptr;
+ }
+
+ auto p = cache.session_data.data();
+ return d2i_SSL_SESSION(nullptr, &p, cache.session_data.size());
+}
+
+int proto_version_from_string(const StringRef &v) {
+#ifdef TLS1_3_VERSION
+ if (util::strieq_l("TLSv1.3", v)) {
+ return TLS1_3_VERSION;
+ }
+#endif // TLS1_3_VERSION
+ if (util::strieq_l("TLSv1.2", v)) {
+ return TLS1_2_VERSION;
+ }
+ if (util::strieq_l("TLSv1.1", v)) {
+ return TLS1_1_VERSION;
+ }
+ if (util::strieq_l("TLSv1.0", v)) {
+ return TLS1_VERSION;
+ }
+ return -1;
+}
+
+int verify_ocsp_response(SSL_CTX *ssl_ctx, const uint8_t *ocsp_resp,
+ size_t ocsp_resplen) {
+#ifndef OPENSSL_NO_OCSP
+ int rv;
+
+ STACK_OF(X509) * chain_certs;
+ SSL_CTX_get0_chain_certs(ssl_ctx, &chain_certs);
+
+ auto resp = d2i_OCSP_RESPONSE(nullptr, &ocsp_resp, ocsp_resplen);
+ if (resp == nullptr) {
+ LOG(ERROR) << "d2i_OCSP_RESPONSE failed";
+ return -1;
+ }
+ auto resp_deleter = defer(OCSP_RESPONSE_free, resp);
+
+ if (OCSP_response_status(resp) != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
+ LOG(ERROR) << "OCSP response status is not successful";
+ return -1;
+ }
+
+ ERR_clear_error();
+
+ auto bs = OCSP_response_get1_basic(resp);
+ if (bs == nullptr) {
+ LOG(ERROR) << "OCSP_response_get1_basic failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ return -1;
+ }
+ auto bs_deleter = defer(OCSP_BASICRESP_free, bs);
+
+ auto store = SSL_CTX_get_cert_store(ssl_ctx);
+
+ ERR_clear_error();
+
+ rv = OCSP_basic_verify(bs, chain_certs, store, 0);
+
+ if (rv != 1) {
+ LOG(ERROR) << "OCSP_basic_verify failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ return -1;
+ }
+
+ auto sresp = OCSP_resp_get0(bs, 0);
+ if (sresp == nullptr) {
+ LOG(ERROR) << "OCSP response verification failed: no single response";
+ return -1;
+ }
+
+ auto certid = OCSP_SINGLERESP_get0_id(sresp);
+ assert(certid != nullptr);
+
+ ASN1_INTEGER *serial;
+ rv = OCSP_id_get0_info(nullptr, nullptr, nullptr, &serial,
+ const_cast<OCSP_CERTID *>(certid));
+ if (rv != 1) {
+ LOG(ERROR) << "OCSP_id_get0_info failed";
+ return -1;
+ }
+
+ if (serial == nullptr) {
+ LOG(ERROR) << "OCSP response does not contain serial number";
+ return -1;
+ }
+
+ auto cert = SSL_CTX_get0_certificate(ssl_ctx);
+ auto cert_serial = X509_get_serialNumber(cert);
+
+ if (ASN1_INTEGER_cmp(cert_serial, serial)) {
+ LOG(ERROR) << "OCSP verification serial numbers do not match";
+ return -1;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "OCSP verification succeeded";
+ }
+#endif // !OPENSSL_NO_OCSP
+
+ return 0;
+}
+
+ssize_t get_x509_fingerprint(uint8_t *dst, size_t dstlen, const X509 *x,
+ const EVP_MD *md) {
+ unsigned int len = dstlen;
+ if (X509_digest(x, md, dst, &len) != 1) {
+ return -1;
+ }
+ return len;
+}
+
+namespace {
+StringRef get_x509_name(BlockAllocator &balloc, X509_NAME *nm) {
+ auto b = BIO_new(BIO_s_mem());
+ if (!b) {
+ return StringRef{};
+ }
+
+ auto b_deleter = defer(BIO_free, b);
+
+ // Not documented, but it seems that X509_NAME_print_ex returns the
+ // number of bytes written into b.
+ auto slen = X509_NAME_print_ex(b, nm, 0, XN_FLAG_RFC2253);
+ if (slen <= 0) {
+ return StringRef{};
+ }
+
+ auto iov = make_byte_ref(balloc, slen + 1);
+ BIO_read(b, iov.base, slen);
+ iov.base[slen] = '\0';
+ return StringRef{iov.base, static_cast<size_t>(slen)};
+}
+} // namespace
+
+StringRef get_x509_subject_name(BlockAllocator &balloc, X509 *x) {
+ return get_x509_name(balloc, X509_get_subject_name(x));
+}
+
+StringRef get_x509_issuer_name(BlockAllocator &balloc, X509 *x) {
+ return get_x509_name(balloc, X509_get_issuer_name(x));
+}
+
+StringRef get_x509_serial(BlockAllocator &balloc, X509 *x) {
+ auto sn = X509_get_serialNumber(x);
+ auto bn = BN_new();
+ auto bn_d = defer(BN_free, bn);
+ if (!ASN1_INTEGER_to_BN(sn, bn) || BN_num_bytes(bn) > 20) {
+ return StringRef{};
+ }
+
+ std::array<uint8_t, 20> b;
+ auto n = BN_bn2bin(bn, b.data());
+ assert(n <= 20);
+
+ return util::format_hex(balloc, StringRef{b.data(), static_cast<size_t>(n)});
+}
+
+namespace {
+// Performs conversion from |at| to time_t. The result is stored in
+// |t|. This function returns 0 if it succeeds, or -1.
+int time_t_from_asn1_time(time_t &t, const ASN1_TIME *at) {
+ int rv;
+
+#if defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_LIBRESSL)
+ struct tm tm;
+ rv = ASN1_TIME_to_tm(at, &tm);
+ if (rv != 1) {
+ return -1;
+ }
+
+ t = nghttp2_timegm(&tm);
+#else // !NGHTTP2_GENUINE_OPENSSL && !NGHTTP2_OPENSSL_IS_LIBRESSL
+ auto b = BIO_new(BIO_s_mem());
+ if (!b) {
+ return -1;
+ }
+
+ auto bio_deleter = defer(BIO_free, b);
+
+ rv = ASN1_TIME_print(b, at);
+ if (rv != 1) {
+ return -1;
+ }
+
+# ifdef NGHTTP2_OPENSSL_IS_BORINGSSL
+ char *s;
+# else
+ unsigned char *s;
+# endif
+ auto slen = BIO_get_mem_data(b, &s);
+ auto tt = util::parse_openssl_asn1_time_print(
+ StringRef{s, static_cast<size_t>(slen)});
+ if (tt == 0) {
+ return -1;
+ }
+
+ t = tt;
+#endif // !NGHTTP2_GENUINE_OPENSSL && !NGHTTP2_OPENSSL_IS_LIBRESSL
+
+ return 0;
+}
+} // namespace
+
+int get_x509_not_before(time_t &t, X509 *x) {
+ auto at = X509_get0_notBefore(x);
+ if (!at) {
+ return -1;
+ }
+
+ return time_t_from_asn1_time(t, at);
+}
+
+int get_x509_not_after(time_t &t, X509 *x) {
+ auto at = X509_get0_notAfter(x);
+ if (!at) {
+ return -1;
+ }
+
+ return time_t_from_asn1_time(t, at);
+}
+
+} // namespace tls
+
+} // namespace shrpx
diff --git a/src/shrpx_tls.h b/src/shrpx_tls.h
new file mode 100644
index 0000000..f740507
--- /dev/null
+++ b/src/shrpx_tls.h
@@ -0,0 +1,321 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_TLS_H
+#define SHRPX_TLS_H
+
+#include "shrpx.h"
+
+#include <vector>
+#include <mutex>
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+#include <ev.h>
+
+#ifdef HAVE_NEVERBLEED
+# include <neverbleed.h>
+#endif // HAVE_NEVERBLEED
+
+#include "network.h"
+#include "shrpx_config.h"
+#include "shrpx_router.h"
+
+namespace shrpx {
+
+class ClientHandler;
+class Worker;
+class DownstreamConnectionPool;
+struct DownstreamAddr;
+struct UpstreamAddr;
+
+namespace tls {
+
+struct TLSSessionCache {
+ // ASN1 representation of SSL_SESSION object. See
+ // i2d_SSL_SESSION(3SSL).
+ std::vector<uint8_t> session_data;
+ // The last time stamp when this cache entry is created or updated.
+ std::chrono::steady_clock::time_point last_updated;
+};
+
+// This struct stores the additional information per SSL_CTX. This is
+// attached to SSL_CTX using SSL_CTX_set_app_data().
+struct TLSContextData {
+ // SCT data formatted so that this can be directly sent as
+ // extension_data of signed_certificate_timestamp.
+ std::vector<uint8_t> sct_data;
+#ifndef HAVE_ATOMIC_STD_SHARED_PTR
+ // Protects ocsp_data;
+ std::mutex mu;
+#endif // !HAVE_ATOMIC_STD_SHARED_PTR
+ // OCSP response
+ std::shared_ptr<std::vector<uint8_t>> ocsp_data;
+
+ // Path to certificate file
+ const char *cert_file;
+};
+
+// Create server side SSL_CTX
+SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file,
+ const std::vector<uint8_t> &sct_data
+
+#ifdef HAVE_NEVERBLEED
+ ,
+ neverbleed_t *nb
+#endif // HAVE_NEVERBLEED
+);
+
+// Create client side SSL_CTX. This does not configure ALPN settings.
+SSL_CTX *create_ssl_client_context(
+#ifdef HAVE_NEVERBLEED
+ neverbleed_t *nb,
+#endif // HAVE_NEVERBLEED
+ const StringRef &cacert, const StringRef &cert_file,
+ const StringRef &private_key_file);
+
+ClientHandler *accept_connection(Worker *worker, int fd, sockaddr *addr,
+ int addrlen, const UpstreamAddr *faddr);
+
+// Check peer's certificate against given |address| and |host|.
+int check_cert(SSL *ssl, const Address *addr, const StringRef &host);
+// Check peer's certificate against given host name described in
+// |addr| and numeric address in |raddr|. Note that |raddr| might not
+// point to &addr->addr.
+int check_cert(SSL *ssl, const DownstreamAddr *addr, const Address *raddr);
+
+// Verify |cert| using numeric IP address. |hostname| and |addr|
+// should contain the same numeric IP address. This function returns
+// 0 if it succeeds, or -1.
+int verify_numeric_hostname(X509 *cert, const StringRef &hostname,
+ const Address *addr);
+
+// Verify |cert| using DNS name hostname. This function returns 0 if
+// it succeeds, or -1.
+int verify_dns_hostname(X509 *cert, const StringRef &hostname);
+
+struct WildcardRevPrefix {
+ WildcardRevPrefix(const StringRef &prefix, size_t idx)
+ : prefix(std::begin(prefix), std::end(prefix)), idx(idx) {}
+
+ // "Prefix" of wildcard pattern. It is reversed from original form.
+ // For example, if the original wildcard is "test*.nghttp2.org",
+ // prefix would be "tset".
+ ImmutableString prefix;
+ // The index of SSL_CTX. See ConnectionHandler::get_ssl_ctx().
+ size_t idx;
+};
+
+struct WildcardPattern {
+ // Wildcard host sharing only suffix is probably rare, so we just do
+ // linear search.
+ std::vector<WildcardRevPrefix> rev_prefix;
+};
+
+class CertLookupTree {
+public:
+ CertLookupTree();
+
+ // Adds hostname pattern |hostname| to the lookup tree, associating
+ // value |index|. When the queried host matches this pattern,
+ // |index| is returned. We support wildcard pattern. The left most
+ // '*' is considered as wildcard character, and it must match at
+ // least one character. If the same pattern has been already added,
+ // this function does not alter the tree, and returns the existing
+ // matching index.
+ //
+ // The caller should lower-case |hostname| since this function does
+ // do that, and lookup function performs case-sensitive match.
+ //
+ // TODO Treat wildcard pattern described as RFC 6125.
+ //
+ // This function returns the index. It returns -1 if it fails
+ // (e.g., hostname is too long). If the returned index equals to
+ // |index|, then hostname is added to the tree with the value
+ // |index|. If it is not -1, and does not equal to |index|, same
+ // hostname has already been added to the tree.
+ ssize_t add_cert(const StringRef &hostname, size_t index);
+
+ // Looks up index using the given |hostname|. The exact match takes
+ // precedence over wildcard match. For wildcard match, longest
+ // match (sum of matched suffix and prefix length in bytes) is
+ // preferred, breaking a tie with longer suffix.
+ //
+ // The caller should lower-case |hostname| since this function
+ // performs case-sensitive match.
+ ssize_t lookup(const StringRef &hostname);
+
+ // Dumps the contents of this lookup tree to stderr.
+ void dump() const;
+
+private:
+ // Exact match
+ Router router_;
+ // Wildcard reversed suffix match. The returned index is into
+ // wildcard_patterns_.
+ Router rev_wildcard_router_;
+ // Stores wildcard suffix patterns.
+ std::vector<WildcardPattern> wildcard_patterns_;
+};
+
+// Adds hostnames in certificate in |ssl_ctx| to lookup tree |lt|.
+// The subjectAltNames and commonName are considered as eligible
+// hostname. If there is at least one dNSName in subjectAltNames,
+// commonName is not considered. |ssl_ctx| is also added to
+// |indexed_ssl_ctx|. This function returns 0 if it succeeds, or -1.
+int cert_lookup_tree_add_ssl_ctx(
+ CertLookupTree *lt, std::vector<std::vector<SSL_CTX *>> &indexed_ssl_ctx,
+ SSL_CTX *ssl_ctx);
+
+// Returns true if |proto| is included in the
+// protocol list |protos|.
+bool in_proto_list(const std::vector<StringRef> &protos,
+ const StringRef &proto);
+
+// Returns true if security requirement for HTTP/2 is fulfilled.
+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<StringRef> &tls_proto_list);
+
+int set_alpn_prefs(std::vector<unsigned char> &out,
+ const std::vector<StringRef> &protos);
+
+// Setups server side SSL_CTX. This function inspects get_config()
+// and if upstream_no_tls is true, returns nullptr. Otherwise
+// construct default SSL_CTX. If subcerts are available
+// (get_config()->subcerts), caller should provide CertLookupTree
+// object as |cert_tree| parameter, otherwise SNI does not work. All
+// the created SSL_CTX is stored into |all_ssl_ctx|. They are also
+// added to |indexed_ssl_ctx|. |cert_tree| uses its index to
+// associate hostname to the SSL_CTX.
+SSL_CTX *
+setup_server_ssl_context(std::vector<SSL_CTX *> &all_ssl_ctx,
+ std::vector<std::vector<SSL_CTX *>> &indexed_ssl_ctx,
+ CertLookupTree *cert_tree
+#ifdef HAVE_NEVERBLEED
+ ,
+ neverbleed_t *nb
+#endif // HAVE_NEVERBLEED
+);
+
+#ifdef ENABLE_HTTP3
+SSL_CTX *setup_quic_server_ssl_context(
+ std::vector<SSL_CTX *> &all_ssl_ctx,
+ std::vector<std::vector<SSL_CTX *>> &indexed_ssl_ctx,
+ CertLookupTree *cert_tree
+# ifdef HAVE_NEVERBLEED
+ ,
+ neverbleed_t *nb
+# endif // HAVE_NEVERBLEED
+);
+#endif // ENABLE_HTTP3
+
+// Setups client side SSL_CTX.
+SSL_CTX *setup_downstream_client_ssl_context(
+#ifdef HAVE_NEVERBLEED
+ neverbleed_t *nb
+#endif // HAVE_NEVERBLEED
+);
+
+// Sets ALPN settings in |SSL| suitable for HTTP/2 use.
+void setup_downstream_http2_alpn(SSL *ssl);
+// Sets ALPN settings in |SSL| suitable for HTTP/1.1 use.
+void setup_downstream_http1_alpn(SSL *ssl);
+
+// Creates CertLookupTree. If frontend is configured not to use TLS,
+// this function returns nullptr.
+std::unique_ptr<CertLookupTree> create_cert_lookup_tree();
+
+SSL *create_ssl(SSL_CTX *ssl_ctx);
+
+// Returns true if SSL/TLS is enabled on upstream
+bool upstream_tls_enabled(const ConnectionConfig &connconf);
+
+// Performs TLS hostname match. |pattern| can contain wildcard
+// character '*', which matches prefix of target hostname. There are
+// several restrictions to make wildcard work. The matching algorithm
+// is based on RFC 6125.
+bool tls_hostname_match(const StringRef &pattern, const StringRef &hostname);
+
+// Caches |session|. |session| is serialized into ASN1
+// representation, and stored. |t| is used as a time stamp.
+// Depending on the existing cache's time stamp, |session| might not
+// be cached.
+void try_cache_tls_session(TLSSessionCache *cache, SSL_SESSION *session,
+ const std::chrono::steady_clock::time_point &t);
+
+// Returns cached session associated |addr|. If no cache entry is
+// found associated to |addr|, nullptr will be returned.
+SSL_SESSION *reuse_tls_session(const TLSSessionCache &addr);
+
+// Loads certificate form file |filename|. The caller should delete
+// the returned object using X509_free().
+X509 *load_certificate(const char *filename);
+
+// Returns TLS version from |v|. The returned value is defined in
+// OpenSSL header file. This function returns -1 if |v| is not valid
+// TLS version string.
+int proto_version_from_string(const StringRef &v);
+
+// Verifies OCSP response |ocsp_resp| of length |ocsp_resplen|. This
+// function returns 0 if it succeeds, or -1.
+int verify_ocsp_response(SSL_CTX *ssl_ctx, const uint8_t *ocsp_resp,
+ size_t ocsp_resplen);
+
+// Stores fingerprint of |x| in |dst| of length |dstlen|. |md|
+// specifies hash function to use, and |dstlen| must be large enough
+// to include hash value (e.g., 32 bytes for SHA-256). This function
+// returns the number of bytes written in |dst|, or -1.
+ssize_t get_x509_fingerprint(uint8_t *dst, size_t dstlen, const X509 *x,
+ const EVP_MD *md);
+
+// Returns subject name of |x|. If this function fails to get subject
+// name, it returns an empty string.
+StringRef get_x509_subject_name(BlockAllocator &balloc, X509 *x);
+
+// Returns issuer name of |x|. If this function fails to get issuer
+// name, it returns an empty string.
+StringRef get_x509_issuer_name(BlockAllocator &balloc, X509 *x);
+
+// Returns serial number of |x|. If this function fails to get serial
+// number, it returns an empty string. number
+StringRef get_x509_serial(BlockAllocator &balloc, X509 *x);
+
+// Fills NotBefore of |x| in |t|. This function returns 0 if it
+// succeeds, or -1.
+int get_x509_not_before(time_t &t, X509 *x);
+
+// Fills NotAfter of |x| in |t|. This function returns 0 if it
+// succeeds, or -1.
+int get_x509_not_after(time_t &t, X509 *x);
+
+} // namespace tls
+
+} // namespace shrpx
+
+#endif // SHRPX_TLS_H
diff --git a/src/shrpx_tls_test.cc b/src/shrpx_tls_test.cc
new file mode 100644
index 0000000..02fb168
--- /dev/null
+++ b/src/shrpx_tls_test.cc
@@ -0,0 +1,339 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_tls_test.h"
+
+#include <CUnit/CUnit.h>
+
+#include "shrpx_tls.h"
+#include "shrpx_log.h"
+#include "util.h"
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+void test_shrpx_tls_create_lookup_tree(void) {
+ auto tree = std::make_unique<tls::CertLookupTree>();
+
+ constexpr StringRef hostnames[] = {
+ StringRef::from_lit("example.com"), // 0
+ StringRef::from_lit("www.example.org"), // 1
+ StringRef::from_lit("*www.example.org"), // 2
+ StringRef::from_lit("xy*.host.domain"), // 3
+ StringRef::from_lit("*yy.host.domain"), // 4
+ StringRef::from_lit("nghttp2.sourceforge.net"), // 5
+ StringRef::from_lit("sourceforge.net"), // 6
+ StringRef::from_lit("sourceforge.net"), // 7, duplicate
+ StringRef::from_lit("*.foo.bar"), // 8, oo.bar is suffix of *.foo.bar
+ StringRef::from_lit("oo.bar") // 9
+ };
+ auto num = array_size(hostnames);
+
+ for (size_t idx = 0; idx < num; ++idx) {
+ tree->add_cert(hostnames[idx], idx);
+ }
+
+ tree->dump();
+
+ CU_ASSERT(0 == tree->lookup(hostnames[0]));
+ CU_ASSERT(1 == tree->lookup(hostnames[1]));
+ CU_ASSERT(2 == tree->lookup(StringRef::from_lit("2www.example.org")));
+ CU_ASSERT(-1 == tree->lookup(StringRef::from_lit("www2.example.org")));
+ CU_ASSERT(3 == tree->lookup(StringRef::from_lit("xy1.host.domain")));
+ // Does not match *yy.host.domain, because * must match at least 1
+ // character.
+ CU_ASSERT(-1 == tree->lookup(StringRef::from_lit("yy.host.domain")));
+ CU_ASSERT(4 == tree->lookup(StringRef::from_lit("xyy.host.domain")));
+ CU_ASSERT(-1 == tree->lookup(StringRef{}));
+ CU_ASSERT(5 == tree->lookup(hostnames[5]));
+ CU_ASSERT(6 == tree->lookup(hostnames[6]));
+ static constexpr char h6[] = "pdylay.sourceforge.net";
+ for (int i = 0; i < 7; ++i) {
+ CU_ASSERT(-1 == tree->lookup(StringRef{h6 + i, str_size(h6) - i}));
+ }
+ CU_ASSERT(8 == tree->lookup(StringRef::from_lit("x.foo.bar")));
+ CU_ASSERT(9 == tree->lookup(hostnames[9]));
+
+ constexpr StringRef names[] = {
+ StringRef::from_lit("rab"), // 1
+ StringRef::from_lit("zab"), // 2
+ StringRef::from_lit("zzub"), // 3
+ StringRef::from_lit("ab") // 4
+ };
+ num = array_size(names);
+
+ tree = std::make_unique<tls::CertLookupTree>();
+ for (size_t idx = 0; idx < num; ++idx) {
+ tree->add_cert(names[idx], idx);
+ }
+ for (size_t i = 0; i < num; ++i) {
+ CU_ASSERT((ssize_t)i == tree->lookup(names[i]));
+ }
+}
+
+// We use cfssl to generate key pairs.
+//
+// CA self-signed key pairs generation:
+//
+// $ cfssl genkey -initca ca.nghttp2.org.csr.json |
+// cfssljson -bare ca.nghttp2.org
+//
+// Create CSR:
+//
+// $ cfssl genkey test.nghttp2.org.csr.json | cfssljson -bare test.nghttp2.org
+// $ cfssl genkey test.example.com.csr.json | cfssljson -bare test.example.com
+//
+// Sign CSR:
+//
+// $ cfssl sign -ca ca.nghttp2.org.pem -ca-key ca.nghttp2.org-key.pem
+// -config=ca-config.json -profile=server test.nghttp2.org.csr |
+// cfssljson -bare test.nghttp2.org
+//
+// $ cfssl sign -ca ca.nghttp2.org.pem -ca-key ca.nghttp2.org-key.pem
+// -config=ca-config.json -profile=server test.example.com.csr |
+// cfssljson -bare test.example.com
+//
+void test_shrpx_tls_cert_lookup_tree_add_ssl_ctx(void) {
+ int rv;
+
+ static constexpr char nghttp2_certfile[] =
+ NGHTTP2_SRC_DIR "/test.nghttp2.org.pem";
+ auto nghttp2_ssl_ctx = SSL_CTX_new(TLS_server_method());
+ auto nghttp2_ssl_ctx_del = defer(SSL_CTX_free, nghttp2_ssl_ctx);
+ auto nghttp2_tls_ctx_data = std::make_unique<tls::TLSContextData>();
+ nghttp2_tls_ctx_data->cert_file = nghttp2_certfile;
+ SSL_CTX_set_app_data(nghttp2_ssl_ctx, nghttp2_tls_ctx_data.get());
+ rv = SSL_CTX_use_certificate_chain_file(nghttp2_ssl_ctx, nghttp2_certfile);
+
+ CU_ASSERT(1 == rv);
+
+ static constexpr char examples_certfile[] =
+ NGHTTP2_SRC_DIR "/test.example.com.pem";
+ auto examples_ssl_ctx = SSL_CTX_new(TLS_server_method());
+ auto examples_ssl_ctx_del = defer(SSL_CTX_free, examples_ssl_ctx);
+ auto examples_tls_ctx_data = std::make_unique<tls::TLSContextData>();
+ examples_tls_ctx_data->cert_file = examples_certfile;
+ SSL_CTX_set_app_data(examples_ssl_ctx, examples_tls_ctx_data.get());
+ rv = SSL_CTX_use_certificate_chain_file(examples_ssl_ctx, examples_certfile);
+
+ CU_ASSERT(1 == rv);
+
+ tls::CertLookupTree tree;
+ std::vector<std::vector<SSL_CTX *>> indexed_ssl_ctx;
+
+ rv = tls::cert_lookup_tree_add_ssl_ctx(&tree, indexed_ssl_ctx,
+ nghttp2_ssl_ctx);
+
+ CU_ASSERT(0 == rv);
+
+ rv = tls::cert_lookup_tree_add_ssl_ctx(&tree, indexed_ssl_ctx,
+ examples_ssl_ctx);
+
+ CU_ASSERT(0 == rv);
+
+ CU_ASSERT(-1 == tree.lookup(StringRef::from_lit("not-used.nghttp2.org")));
+ CU_ASSERT(0 == tree.lookup(StringRef::from_lit("test.nghttp2.org")));
+ CU_ASSERT(1 == tree.lookup(StringRef::from_lit("w.test.nghttp2.org")));
+ CU_ASSERT(2 == tree.lookup(StringRef::from_lit("www.test.nghttp2.org")));
+ CU_ASSERT(3 == tree.lookup(StringRef::from_lit("test.example.com")));
+}
+
+template <size_t N, size_t M>
+bool tls_hostname_match_wrapper(const char (&pattern)[N],
+ const char (&hostname)[M]) {
+ return tls::tls_hostname_match(StringRef{pattern, N}, StringRef{hostname, M});
+}
+
+void test_shrpx_tls_tls_hostname_match(void) {
+ CU_ASSERT(tls_hostname_match_wrapper("example.com", "example.com"));
+ CU_ASSERT(tls_hostname_match_wrapper("example.com", "EXAMPLE.com"));
+
+ // check wildcard
+ CU_ASSERT(tls_hostname_match_wrapper("*.example.com", "www.example.com"));
+ CU_ASSERT(tls_hostname_match_wrapper("*w.example.com", "www.example.com"));
+ CU_ASSERT(tls_hostname_match_wrapper("www*.example.com", "www1.example.com"));
+ CU_ASSERT(
+ tls_hostname_match_wrapper("www*.example.com", "WWW12.EXAMPLE.com"));
+ // at least 2 dots are required after '*'
+ CU_ASSERT(!tls_hostname_match_wrapper("*.com", "example.com"));
+ CU_ASSERT(!tls_hostname_match_wrapper("*", "example.com"));
+ // '*' must be in left most label
+ CU_ASSERT(
+ !tls_hostname_match_wrapper("blog.*.example.com", "blog.my.example.com"));
+ // prefix is wrong
+ CU_ASSERT(
+ !tls_hostname_match_wrapper("client*.example.com", "server.example.com"));
+ // '*' must match at least one character
+ CU_ASSERT(!tls_hostname_match_wrapper("www*.example.com", "www.example.com"));
+
+ CU_ASSERT(!tls_hostname_match_wrapper("example.com", "nghttp2.org"));
+ CU_ASSERT(!tls_hostname_match_wrapper("www.example.com", "example.com"));
+ CU_ASSERT(!tls_hostname_match_wrapper("example.com", "www.example.com"));
+}
+
+static X509 *load_cert(const char *path) {
+ auto f = fopen(path, "r");
+ auto cert = PEM_read_X509(f, nullptr, nullptr, nullptr);
+
+ fclose(f);
+
+ return cert;
+}
+
+static Address parse_addr(const char *ipaddr) {
+ addrinfo hints{};
+
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
+
+ addrinfo *res = nullptr;
+
+ auto rv = getaddrinfo(ipaddr, "443", &hints, &res);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(nullptr != res);
+
+ Address addr;
+ addr.len = res->ai_addrlen;
+ memcpy(&addr.su, res->ai_addr, res->ai_addrlen);
+
+ freeaddrinfo(res);
+
+ return addr;
+}
+
+void test_shrpx_tls_verify_numeric_hostname(void) {
+ {
+ // Successful IPv4 address match in SAN
+ static constexpr char ipaddr[] = "127.0.0.1";
+ auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/verify_hostname.crt");
+ auto addr = parse_addr(ipaddr);
+ auto rv =
+ tls::verify_numeric_hostname(cert, StringRef::from_lit(ipaddr), &addr);
+
+ CU_ASSERT(0 == rv);
+
+ X509_free(cert);
+ }
+
+ {
+ // Successful IPv6 address match in SAN
+ static constexpr char ipaddr[] = "::1";
+ auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/verify_hostname.crt");
+ auto addr = parse_addr(ipaddr);
+ auto rv =
+ tls::verify_numeric_hostname(cert, StringRef::from_lit(ipaddr), &addr);
+
+ CU_ASSERT(0 == rv);
+
+ X509_free(cert);
+ }
+
+ {
+ // Unsuccessful IPv4 address match in SAN
+ static constexpr char ipaddr[] = "192.168.0.127";
+ auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/verify_hostname.crt");
+ auto addr = parse_addr(ipaddr);
+ auto rv =
+ tls::verify_numeric_hostname(cert, StringRef::from_lit(ipaddr), &addr);
+
+ CU_ASSERT(-1 == rv);
+
+ X509_free(cert);
+ }
+
+ {
+ // CommonName is not used if SAN is available
+ static constexpr char ipaddr[] = "192.168.0.1";
+ auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/ipaddr.crt");
+ auto addr = parse_addr(ipaddr);
+ auto rv =
+ tls::verify_numeric_hostname(cert, StringRef::from_lit(ipaddr), &addr);
+
+ CU_ASSERT(-1 == rv);
+
+ X509_free(cert);
+ }
+
+ {
+ // Successful IPv4 address match in CommonName
+ static constexpr char ipaddr[] = "127.0.0.1";
+ auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/nosan_ip.crt");
+ auto addr = parse_addr(ipaddr);
+ auto rv =
+ tls::verify_numeric_hostname(cert, StringRef::from_lit(ipaddr), &addr);
+
+ CU_ASSERT(0 == rv);
+
+ X509_free(cert);
+ }
+}
+
+void test_shrpx_tls_verify_dns_hostname(void) {
+ {
+ // Successful exact DNS name match in SAN
+ auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/verify_hostname.crt");
+ auto rv = tls::verify_dns_hostname(
+ cert, StringRef::from_lit("nghttp2.example.com"));
+
+ CU_ASSERT(0 == rv);
+
+ X509_free(cert);
+ }
+
+ {
+ // Successful wildcard DNS name match in SAN
+ auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/verify_hostname.crt");
+ auto rv = tls::verify_dns_hostname(
+ cert, StringRef::from_lit("www.nghttp2.example.com"));
+
+ CU_ASSERT(0 == rv);
+
+ X509_free(cert);
+ }
+
+ {
+ // CommonName is not used if SAN is available.
+ auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/verify_hostname.crt");
+ auto rv = tls::verify_dns_hostname(cert, StringRef::from_lit("localhost"));
+
+ CU_ASSERT(-1 == rv);
+
+ X509_free(cert);
+ }
+
+ {
+ // Successful DNS name match in CommonName
+ auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/nosan.crt");
+ auto rv = tls::verify_dns_hostname(cert, StringRef::from_lit("localhost"));
+
+ CU_ASSERT(0 == rv);
+
+ X509_free(cert);
+ }
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_tls_test.h b/src/shrpx_tls_test.h
new file mode 100644
index 0000000..7edc742
--- /dev/null
+++ b/src/shrpx_tls_test.h
@@ -0,0 +1,42 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_TLS_TEST_H
+#define SHRPX_TLS_TEST_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+namespace shrpx {
+
+void test_shrpx_tls_create_lookup_tree(void);
+void test_shrpx_tls_cert_lookup_tree_add_ssl_ctx(void);
+void test_shrpx_tls_tls_hostname_match(void);
+void test_shrpx_tls_verify_numeric_hostname(void);
+void test_shrpx_tls_verify_dns_hostname(void);
+
+} // namespace shrpx
+
+#endif // SHRPX_TLS_TEST_H
diff --git a/src/shrpx_upstream.h b/src/shrpx_upstream.h
new file mode 100644
index 0000000..3da62c9
--- /dev/null
+++ b/src/shrpx_upstream.h
@@ -0,0 +1,112 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_UPSTREAM_H
+#define SHRPX_UPSTREAM_H
+
+#include "shrpx.h"
+#include "shrpx_io_control.h"
+#include "memchunk.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+class ClientHandler;
+class Downstream;
+class DownstreamConnection;
+
+class Upstream {
+public:
+ virtual ~Upstream() {}
+ virtual int on_read() = 0;
+ virtual int on_write() = 0;
+ virtual int on_timeout(Downstream *downstream) { return 0; };
+ virtual int on_downstream_abort_request(Downstream *downstream,
+ unsigned int status_code) = 0;
+ // Called when the current request is aborted without forwarding it
+ // to backend, and it should be redirected to https URI.
+ virtual int
+ on_downstream_abort_request_with_https_redirect(Downstream *downstream) = 0;
+ virtual int downstream_read(DownstreamConnection *dconn) = 0;
+ virtual int downstream_write(DownstreamConnection *dconn) = 0;
+ virtual int downstream_eof(DownstreamConnection *dconn) = 0;
+ virtual int downstream_error(DownstreamConnection *dconn, int events) = 0;
+ virtual ClientHandler *get_client_handler() const = 0;
+
+ virtual int on_downstream_header_complete(Downstream *downstream) = 0;
+ virtual int on_downstream_body(Downstream *downstream, const uint8_t *data,
+ size_t len, bool flush) = 0;
+ virtual int on_downstream_body_complete(Downstream *downstream) = 0;
+
+ virtual void on_handler_delete() = 0;
+ // Called when downstream connection for |downstream| is reset.
+ // Currently this is only used by Http2Session. If |no_retry| is
+ // true, another connection attempt using new DownstreamConnection
+ // is not allowed.
+ virtual int on_downstream_reset(Downstream *downstream, bool no_retry) = 0;
+
+ virtual void pause_read(IOCtrlReason reason) = 0;
+ virtual int resume_read(IOCtrlReason reason, Downstream *downstream,
+ size_t consumed) = 0;
+ virtual int send_reply(Downstream *downstream, const uint8_t *body,
+ size_t bodylen) = 0;
+
+ // Starts server push. The |downstream| is an associated stream for
+ // the pushed resource. This function returns 0 if it succeeds,
+ // otherwise -1.
+ virtual int initiate_push(Downstream *downstream, const StringRef &uri) = 0;
+
+ // Fills response data in |iov| whose capacity is |iovcnt|. Returns
+ // the number of iovs filled.
+ virtual int response_riovec(struct iovec *iov, int iovcnt) const = 0;
+ virtual void response_drain(size_t n) = 0;
+ virtual bool response_empty() const = 0;
+
+ // Called when PUSH_PROMISE was started in downstream. The
+ // associated downstream is given as |downstream|. The promised
+ // stream ID is given as |promised_stream_id|. If upstream supports
+ // server push for the corresponding upstream connection, it should
+ // return Downstream object for pushed stream. Otherwise, returns
+ // nullptr.
+ virtual Downstream *
+ on_downstream_push_promise(Downstream *downstream,
+ int32_t promised_stream_id) = 0;
+ // Called when PUSH_PROMISE frame was completely received in
+ // downstream. The associated downstream is given as |downstream|.
+ // This function returns 0 if it succeeds, or -1.
+ virtual int
+ on_downstream_push_promise_complete(Downstream *downstream,
+ Downstream *promised_downstream) = 0;
+ // Returns true if server push is enabled in upstream connection.
+ virtual bool push_enabled() const = 0;
+ // Cancels promised downstream. This function is called when
+ // PUSH_PROMISE for |promised_downstream| is not submitted to
+ // upstream session.
+ virtual void cancel_premature_downstream(Downstream *promised_downstream) = 0;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_UPSTREAM_H
diff --git a/src/shrpx_worker.cc b/src/shrpx_worker.cc
new file mode 100644
index 0000000..4c069db
--- /dev/null
+++ b/src/shrpx_worker.cc
@@ -0,0 +1,1347 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_worker.h"
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+#include <netinet/udp.h>
+
+#include <cstdio>
+#include <memory>
+
+#include <openssl/rand.h>
+
+#ifdef HAVE_LIBBPF
+# include <bpf/bpf.h>
+# include <bpf/libbpf.h>
+#endif // HAVE_LIBBPF
+
+#include "shrpx_tls.h"
+#include "shrpx_log.h"
+#include "shrpx_client_handler.h"
+#include "shrpx_http2_session.h"
+#include "shrpx_log_config.h"
+#include "shrpx_memcached_dispatcher.h"
+#ifdef HAVE_MRUBY
+# include "shrpx_mruby.h"
+#endif // HAVE_MRUBY
+#ifdef ENABLE_HTTP3
+# include "shrpx_quic_listener.h"
+#endif // ENABLE_HTTP3
+#include "shrpx_connection_handler.h"
+#include "util.h"
+#include "template.h"
+#include "xsi_strerror.h"
+
+namespace shrpx {
+
+namespace {
+void eventcb(struct ev_loop *loop, ev_async *w, int revents) {
+ auto worker = static_cast<Worker *>(w->data);
+ worker->process_events();
+}
+} // namespace
+
+namespace {
+void mcpool_clear_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto worker = static_cast<Worker *>(w->data);
+ if (worker->get_worker_stat()->num_connections != 0) {
+ return;
+ }
+ auto mcpool = worker->get_mcpool();
+ if (mcpool->freelistsize == mcpool->poolsize) {
+ worker->get_mcpool()->clear();
+ }
+}
+} // namespace
+
+namespace {
+void proc_wev_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto worker = static_cast<Worker *>(w->data);
+ worker->process_events();
+}
+} // namespace
+
+DownstreamAddrGroup::DownstreamAddrGroup() : retired{false} {}
+
+DownstreamAddrGroup::~DownstreamAddrGroup() {}
+
+// DownstreamKey is used to index SharedDownstreamAddr in order to
+// find the same configuration.
+using DownstreamKey = std::tuple<
+ std::vector<
+ std::tuple<StringRef, StringRef, StringRef, size_t, size_t, Proto,
+ uint32_t, uint32_t, uint32_t, bool, bool, bool, bool>>,
+ bool, SessionAffinity, StringRef, StringRef, SessionAffinityCookieSecure,
+ SessionAffinityCookieStickiness, int64_t, int64_t, StringRef, bool>;
+
+namespace {
+DownstreamKey
+create_downstream_key(const std::shared_ptr<SharedDownstreamAddr> &shared_addr,
+ const StringRef &mruby_file) {
+ DownstreamKey dkey;
+
+ auto &addrs = std::get<0>(dkey);
+ addrs.resize(shared_addr->addrs.size());
+ auto p = std::begin(addrs);
+ for (auto &a : shared_addr->addrs) {
+ std::get<0>(*p) = a.host;
+ std::get<1>(*p) = a.sni;
+ std::get<2>(*p) = a.group;
+ std::get<3>(*p) = a.fall;
+ std::get<4>(*p) = a.rise;
+ std::get<5>(*p) = a.proto;
+ std::get<6>(*p) = a.port;
+ std::get<7>(*p) = a.weight;
+ std::get<8>(*p) = a.group_weight;
+ std::get<9>(*p) = a.host_unix;
+ std::get<10>(*p) = a.tls;
+ std::get<11>(*p) = a.dns;
+ std::get<12>(*p) = a.upgrade_scheme;
+ ++p;
+ }
+ std::sort(std::begin(addrs), std::end(addrs));
+
+ std::get<1>(dkey) = shared_addr->redirect_if_not_tls;
+
+ auto &affinity = shared_addr->affinity;
+ std::get<2>(dkey) = affinity.type;
+ std::get<3>(dkey) = affinity.cookie.name;
+ std::get<4>(dkey) = affinity.cookie.path;
+ std::get<5>(dkey) = affinity.cookie.secure;
+ std::get<6>(dkey) = affinity.cookie.stickiness;
+ auto &timeout = shared_addr->timeout;
+ std::get<7>(dkey) = timeout.read;
+ std::get<8>(dkey) = timeout.write;
+ std::get<9>(dkey) = mruby_file;
+ std::get<10>(dkey) = shared_addr->dnf;
+
+ return dkey;
+}
+} // namespace
+
+Worker::Worker(struct ev_loop *loop, SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx,
+ SSL_CTX *tls_session_cache_memcached_ssl_ctx,
+ tls::CertLookupTree *cert_tree,
+#ifdef ENABLE_HTTP3
+ SSL_CTX *quic_sv_ssl_ctx, tls::CertLookupTree *quic_cert_tree,
+ const uint8_t *cid_prefix, size_t cid_prefixlen,
+# ifdef HAVE_LIBBPF
+ size_t index,
+# endif // HAVE_LIBBPF
+#endif // ENABLE_HTTP3
+ const std::shared_ptr<TicketKeys> &ticket_keys,
+ ConnectionHandler *conn_handler,
+ std::shared_ptr<DownstreamConfig> downstreamconf)
+ :
+#if defined(ENABLE_HTTP3) && defined(HAVE_LIBBPF)
+ index_{index},
+#endif // ENABLE_HTTP3 && HAVE_LIBBPF
+ randgen_(util::make_mt19937()),
+ worker_stat_{},
+ dns_tracker_(loop, get_config()->conn.downstream->family),
+#ifdef ENABLE_HTTP3
+ quic_upstream_addrs_{get_config()->conn.quic_listener.addrs},
+#endif // ENABLE_HTTP3
+ loop_(loop),
+ sv_ssl_ctx_(sv_ssl_ctx),
+ cl_ssl_ctx_(cl_ssl_ctx),
+ cert_tree_(cert_tree),
+ conn_handler_(conn_handler),
+#ifdef ENABLE_HTTP3
+ quic_sv_ssl_ctx_{quic_sv_ssl_ctx},
+ quic_cert_tree_{quic_cert_tree},
+ quic_conn_handler_{this},
+#endif // ENABLE_HTTP3
+ ticket_keys_(ticket_keys),
+ connect_blocker_(
+ std::make_unique<ConnectBlocker>(randgen_, loop_, nullptr, nullptr)),
+ graceful_shutdown_(false) {
+#ifdef ENABLE_HTTP3
+ std::copy_n(cid_prefix, cid_prefixlen, std::begin(cid_prefix_));
+#endif // ENABLE_HTTP3
+
+ ev_async_init(&w_, eventcb);
+ w_.data = this;
+ ev_async_start(loop_, &w_);
+
+ ev_timer_init(&mcpool_clear_timer_, mcpool_clear_cb, 0., 0.);
+ mcpool_clear_timer_.data = this;
+
+ ev_timer_init(&proc_wev_timer_, proc_wev_cb, 0., 0.);
+ proc_wev_timer_.data = this;
+
+ auto &session_cacheconf = get_config()->tls.session_cache;
+
+ if (!session_cacheconf.memcached.host.empty()) {
+ session_cache_memcached_dispatcher_ = std::make_unique<MemcachedDispatcher>(
+ &session_cacheconf.memcached.addr, loop,
+ tls_session_cache_memcached_ssl_ctx,
+ StringRef{session_cacheconf.memcached.host}, &mcpool_, randgen_);
+ }
+
+ replace_downstream_config(std::move(downstreamconf));
+}
+
+namespace {
+void ensure_enqueue_addr(
+ std::priority_queue<WeightGroupEntry, std::vector<WeightGroupEntry>,
+ WeightGroupEntryGreater> &wgpq,
+ WeightGroup *wg, DownstreamAddr *addr) {
+ uint32_t cycle;
+ if (!wg->pq.empty()) {
+ auto &top = wg->pq.top();
+ cycle = top.cycle;
+ } else {
+ cycle = 0;
+ }
+
+ addr->cycle = cycle;
+ addr->pending_penalty = 0;
+ wg->pq.push(DownstreamAddrEntry{addr, addr->seq, addr->cycle});
+ addr->queued = true;
+
+ if (!wg->queued) {
+ if (!wgpq.empty()) {
+ auto &top = wgpq.top();
+ cycle = top.cycle;
+ } else {
+ cycle = 0;
+ }
+
+ wg->cycle = cycle;
+ wg->pending_penalty = 0;
+ wgpq.push(WeightGroupEntry{wg, wg->seq, wg->cycle});
+ wg->queued = true;
+ }
+}
+} // namespace
+
+void Worker::replace_downstream_config(
+ std::shared_ptr<DownstreamConfig> downstreamconf) {
+ for (auto &g : downstream_addr_groups_) {
+ g->retired = true;
+
+ auto &shared_addr = g->shared_addr;
+ for (auto &addr : shared_addr->addrs) {
+ addr.dconn_pool->remove_all();
+ }
+ }
+
+ downstreamconf_ = downstreamconf;
+
+ // Making a copy is much faster with multiple thread on
+ // backendconfig API call.
+ auto groups = downstreamconf->addr_groups;
+
+ downstream_addr_groups_ =
+ std::vector<std::shared_ptr<DownstreamAddrGroup>>(groups.size());
+
+ std::map<DownstreamKey, size_t> addr_groups_indexer;
+#ifdef HAVE_MRUBY
+ // TODO It is a bit less efficient because
+ // mruby::create_mruby_context returns std::unique_ptr and we cannot
+ // use std::make_shared.
+ std::map<StringRef, std::shared_ptr<mruby::MRubyContext>> shared_mruby_ctxs;
+#endif // HAVE_MRUBY
+
+ for (size_t i = 0; i < groups.size(); ++i) {
+ auto &src = groups[i];
+ auto &dst = downstream_addr_groups_[i];
+
+ dst = std::make_shared<DownstreamAddrGroup>();
+ dst->pattern =
+ ImmutableString{std::begin(src.pattern), std::end(src.pattern)};
+
+ auto shared_addr = std::make_shared<SharedDownstreamAddr>();
+
+ shared_addr->addrs.resize(src.addrs.size());
+ shared_addr->affinity.type = src.affinity.type;
+ if (src.affinity.type == SessionAffinity::COOKIE) {
+ shared_addr->affinity.cookie.name =
+ make_string_ref(shared_addr->balloc, src.affinity.cookie.name);
+ if (!src.affinity.cookie.path.empty()) {
+ shared_addr->affinity.cookie.path =
+ make_string_ref(shared_addr->balloc, src.affinity.cookie.path);
+ }
+ shared_addr->affinity.cookie.secure = src.affinity.cookie.secure;
+ shared_addr->affinity.cookie.stickiness = src.affinity.cookie.stickiness;
+ }
+ shared_addr->affinity_hash = src.affinity_hash;
+ shared_addr->affinity_hash_map = src.affinity_hash_map;
+ shared_addr->redirect_if_not_tls = src.redirect_if_not_tls;
+ shared_addr->dnf = src.dnf;
+ shared_addr->timeout.read = src.timeout.read;
+ shared_addr->timeout.write = src.timeout.write;
+
+ for (size_t j = 0; j < src.addrs.size(); ++j) {
+ auto &src_addr = src.addrs[j];
+ auto &dst_addr = shared_addr->addrs[j];
+
+ dst_addr.addr = src_addr.addr;
+ dst_addr.host = make_string_ref(shared_addr->balloc, src_addr.host);
+ dst_addr.hostport =
+ make_string_ref(shared_addr->balloc, src_addr.hostport);
+ dst_addr.port = src_addr.port;
+ dst_addr.host_unix = src_addr.host_unix;
+ dst_addr.weight = src_addr.weight;
+ dst_addr.group = make_string_ref(shared_addr->balloc, src_addr.group);
+ dst_addr.group_weight = src_addr.group_weight;
+ dst_addr.affinity_hash = src_addr.affinity_hash;
+ dst_addr.proto = src_addr.proto;
+ dst_addr.tls = src_addr.tls;
+ dst_addr.sni = make_string_ref(shared_addr->balloc, src_addr.sni);
+ dst_addr.fall = src_addr.fall;
+ dst_addr.rise = src_addr.rise;
+ dst_addr.dns = src_addr.dns;
+ dst_addr.upgrade_scheme = src_addr.upgrade_scheme;
+ }
+
+#ifdef HAVE_MRUBY
+ auto mruby_ctx_it = shared_mruby_ctxs.find(src.mruby_file);
+ if (mruby_ctx_it == std::end(shared_mruby_ctxs)) {
+ shared_addr->mruby_ctx = mruby::create_mruby_context(src.mruby_file);
+ assert(shared_addr->mruby_ctx);
+ shared_mruby_ctxs.emplace(src.mruby_file, shared_addr->mruby_ctx);
+ } else {
+ shared_addr->mruby_ctx = (*mruby_ctx_it).second;
+ }
+#endif // HAVE_MRUBY
+
+ // share the connection if patterns have the same set of backend
+ // addresses.
+
+ auto dkey = create_downstream_key(shared_addr, src.mruby_file);
+ auto it = addr_groups_indexer.find(dkey);
+
+ if (it == std::end(addr_groups_indexer)) {
+ auto shared_addr_ptr = shared_addr.get();
+
+ for (auto &addr : shared_addr->addrs) {
+ addr.connect_blocker = std::make_unique<ConnectBlocker>(
+ randgen_, loop_, nullptr, [shared_addr_ptr, &addr]() {
+ if (!addr.queued) {
+ if (!addr.wg) {
+ return;
+ }
+ ensure_enqueue_addr(shared_addr_ptr->pq, addr.wg, &addr);
+ }
+ });
+
+ addr.live_check = std::make_unique<LiveCheck>(loop_, cl_ssl_ctx_, this,
+ &addr, randgen_);
+ }
+
+ size_t seq = 0;
+ for (auto &addr : shared_addr->addrs) {
+ addr.dconn_pool = std::make_unique<DownstreamConnectionPool>();
+ addr.seq = seq++;
+ }
+
+ util::shuffle(std::begin(shared_addr->addrs),
+ std::end(shared_addr->addrs), randgen_,
+ [](auto i, auto j) { std::swap((*i).seq, (*j).seq); });
+
+ if (shared_addr->affinity.type == SessionAffinity::NONE) {
+ std::map<StringRef, WeightGroup *> wgs;
+ size_t num_wgs = 0;
+ for (auto &addr : shared_addr->addrs) {
+ if (wgs.find(addr.group) == std::end(wgs)) {
+ ++num_wgs;
+ wgs.emplace(addr.group, nullptr);
+ }
+ }
+
+ shared_addr->wgs = std::vector<WeightGroup>(num_wgs);
+
+ for (auto &addr : shared_addr->addrs) {
+ auto &wg = wgs[addr.group];
+ if (wg == nullptr) {
+ wg = &shared_addr->wgs[--num_wgs];
+ wg->seq = num_wgs;
+ }
+
+ wg->weight = addr.group_weight;
+ wg->pq.push(DownstreamAddrEntry{&addr, addr.seq, addr.cycle});
+ addr.queued = true;
+ addr.wg = wg;
+ }
+
+ assert(num_wgs == 0);
+
+ for (auto &kv : wgs) {
+ shared_addr->pq.push(
+ WeightGroupEntry{kv.second, kv.second->seq, kv.second->cycle});
+ kv.second->queued = true;
+ }
+ }
+
+ dst->shared_addr = std::move(shared_addr);
+
+ addr_groups_indexer.emplace(std::move(dkey), i);
+ } else {
+ auto &g = *(std::begin(downstream_addr_groups_) + (*it).second);
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << dst->pattern << " shares the same backend group with "
+ << g->pattern;
+ }
+ dst->shared_addr = g->shared_addr;
+ }
+ }
+}
+
+Worker::~Worker() {
+ ev_async_stop(loop_, &w_);
+ ev_timer_stop(loop_, &mcpool_clear_timer_);
+ ev_timer_stop(loop_, &proc_wev_timer_);
+}
+
+void Worker::schedule_clear_mcpool() {
+ // libev manual says: "If the watcher is already active nothing will
+ // happen." Since we don't change any timeout here, we don't have
+ // to worry about querying ev_is_active.
+ ev_timer_start(loop_, &mcpool_clear_timer_);
+}
+
+void Worker::wait() {
+#ifndef NOTHREADS
+ fut_.get();
+#endif // !NOTHREADS
+}
+
+void Worker::run_async() {
+#ifndef NOTHREADS
+ fut_ = std::async(std::launch::async, [this] {
+ (void)reopen_log_files(get_config()->logging);
+ ev_run(loop_);
+ delete_log_config();
+ });
+#endif // !NOTHREADS
+}
+
+void Worker::send(WorkerEvent event) {
+ {
+ std::lock_guard<std::mutex> g(m_);
+
+ q_.emplace_back(std::move(event));
+ }
+
+ ev_async_send(loop_, &w_);
+}
+
+void Worker::process_events() {
+ WorkerEvent wev;
+ {
+ std::lock_guard<std::mutex> g(m_);
+
+ // Process event one at a time. This is important for
+ // WorkerEventType::NEW_CONNECTION event since accepting large
+ // number of new connections at once may delay time to 1st byte
+ // for existing connections.
+
+ if (q_.empty()) {
+ ev_timer_stop(loop_, &proc_wev_timer_);
+ return;
+ }
+
+ wev = std::move(q_.front());
+ q_.pop_front();
+ }
+
+ ev_timer_start(loop_, &proc_wev_timer_);
+
+ auto config = get_config();
+
+ auto worker_connections = config->conn.upstream.worker_connections;
+
+ switch (wev.type) {
+ case WorkerEventType::NEW_CONNECTION: {
+ if (LOG_ENABLED(INFO)) {
+ WLOG(INFO, this) << "WorkerEvent: client_fd=" << wev.client_fd
+ << ", addrlen=" << wev.client_addrlen;
+ }
+
+ if (worker_stat_.num_connections >= worker_connections) {
+
+ if (LOG_ENABLED(INFO)) {
+ WLOG(INFO, this) << "Too many connections >= " << worker_connections;
+ }
+
+ close(wev.client_fd);
+
+ break;
+ }
+
+ auto client_handler =
+ tls::accept_connection(this, wev.client_fd, &wev.client_addr.sa,
+ wev.client_addrlen, wev.faddr);
+ if (!client_handler) {
+ if (LOG_ENABLED(INFO)) {
+ WLOG(ERROR, this) << "ClientHandler creation failed";
+ }
+ close(wev.client_fd);
+ break;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ WLOG(INFO, this) << "CLIENT_HANDLER:" << client_handler << " created";
+ }
+
+ break;
+ }
+ case WorkerEventType::REOPEN_LOG:
+ WLOG(NOTICE, this) << "Reopening log files: worker process (thread " << this
+ << ")";
+
+ reopen_log_files(config->logging);
+
+ break;
+ case WorkerEventType::GRACEFUL_SHUTDOWN:
+ WLOG(NOTICE, this) << "Graceful shutdown commencing";
+
+ graceful_shutdown_ = true;
+
+ if (worker_stat_.num_connections == 0 &&
+ worker_stat_.num_close_waits == 0) {
+ ev_break(loop_);
+
+ return;
+ }
+
+ break;
+ case WorkerEventType::REPLACE_DOWNSTREAM:
+ WLOG(NOTICE, this) << "Replace downstream";
+
+ replace_downstream_config(wev.downstreamconf);
+
+ break;
+#ifdef ENABLE_HTTP3
+ case WorkerEventType::QUIC_PKT_FORWARD: {
+ const UpstreamAddr *faddr;
+
+ if (wev.quic_pkt->upstream_addr_index == static_cast<size_t>(-1)) {
+ faddr = find_quic_upstream_addr(wev.quic_pkt->local_addr);
+ if (faddr == nullptr) {
+ LOG(ERROR) << "No suitable upstream address found";
+
+ break;
+ }
+ } else if (quic_upstream_addrs_.size() <=
+ wev.quic_pkt->upstream_addr_index) {
+ LOG(ERROR) << "upstream_addr_index is too large";
+
+ break;
+ } else {
+ faddr = &quic_upstream_addrs_[wev.quic_pkt->upstream_addr_index];
+ }
+
+ quic_conn_handler_.handle_packet(
+ faddr, wev.quic_pkt->remote_addr, wev.quic_pkt->local_addr,
+ wev.quic_pkt->pi, wev.quic_pkt->data.data(), wev.quic_pkt->data.size());
+
+ break;
+ }
+#endif // ENABLE_HTTP3
+ default:
+ if (LOG_ENABLED(INFO)) {
+ WLOG(INFO, this) << "unknown event type " << static_cast<int>(wev.type);
+ }
+ }
+}
+
+tls::CertLookupTree *Worker::get_cert_lookup_tree() const { return cert_tree_; }
+
+#ifdef ENABLE_HTTP3
+tls::CertLookupTree *Worker::get_quic_cert_lookup_tree() const {
+ return quic_cert_tree_;
+}
+#endif // ENABLE_HTTP3
+
+std::shared_ptr<TicketKeys> Worker::get_ticket_keys() {
+#ifdef HAVE_ATOMIC_STD_SHARED_PTR
+ return std::atomic_load_explicit(&ticket_keys_, std::memory_order_acquire);
+#else // !HAVE_ATOMIC_STD_SHARED_PTR
+ std::lock_guard<std::mutex> g(ticket_keys_m_);
+ return ticket_keys_;
+#endif // !HAVE_ATOMIC_STD_SHARED_PTR
+}
+
+void Worker::set_ticket_keys(std::shared_ptr<TicketKeys> ticket_keys) {
+#ifdef HAVE_ATOMIC_STD_SHARED_PTR
+ // This is single writer
+ std::atomic_store_explicit(&ticket_keys_, std::move(ticket_keys),
+ std::memory_order_release);
+#else // !HAVE_ATOMIC_STD_SHARED_PTR
+ std::lock_guard<std::mutex> g(ticket_keys_m_);
+ ticket_keys_ = std::move(ticket_keys);
+#endif // !HAVE_ATOMIC_STD_SHARED_PTR
+}
+
+WorkerStat *Worker::get_worker_stat() { return &worker_stat_; }
+
+struct ev_loop *Worker::get_loop() const { return loop_; }
+
+SSL_CTX *Worker::get_sv_ssl_ctx() const { return sv_ssl_ctx_; }
+
+SSL_CTX *Worker::get_cl_ssl_ctx() const { return cl_ssl_ctx_; }
+
+#ifdef ENABLE_HTTP3
+SSL_CTX *Worker::get_quic_sv_ssl_ctx() const { return quic_sv_ssl_ctx_; }
+#endif // ENABLE_HTTP3
+
+void Worker::set_graceful_shutdown(bool f) { graceful_shutdown_ = f; }
+
+bool Worker::get_graceful_shutdown() const { return graceful_shutdown_; }
+
+MemchunkPool *Worker::get_mcpool() { return &mcpool_; }
+
+MemcachedDispatcher *Worker::get_session_cache_memcached_dispatcher() {
+ return session_cache_memcached_dispatcher_.get();
+}
+
+std::mt19937 &Worker::get_randgen() { return randgen_; }
+
+#ifdef HAVE_MRUBY
+int Worker::create_mruby_context() {
+ mruby_ctx_ = mruby::create_mruby_context(StringRef{get_config()->mruby_file});
+ if (!mruby_ctx_) {
+ return -1;
+ }
+
+ return 0;
+}
+
+mruby::MRubyContext *Worker::get_mruby_context() const {
+ return mruby_ctx_.get();
+}
+#endif // HAVE_MRUBY
+
+std::vector<std::shared_ptr<DownstreamAddrGroup>> &
+Worker::get_downstream_addr_groups() {
+ return downstream_addr_groups_;
+}
+
+ConnectBlocker *Worker::get_connect_blocker() const {
+ return connect_blocker_.get();
+}
+
+const DownstreamConfig *Worker::get_downstream_config() const {
+ return downstreamconf_.get();
+}
+
+ConnectionHandler *Worker::get_connection_handler() const {
+ return conn_handler_;
+}
+
+#ifdef ENABLE_HTTP3
+QUICConnectionHandler *Worker::get_quic_connection_handler() {
+ return &quic_conn_handler_;
+}
+#endif // ENABLE_HTTP3
+
+DNSTracker *Worker::get_dns_tracker() { return &dns_tracker_; }
+
+#ifdef ENABLE_HTTP3
+# ifdef HAVE_LIBBPF
+bool Worker::should_attach_bpf() const {
+ auto config = get_config();
+ auto &quicconf = config->quic;
+ auto &apiconf = config->api;
+
+ if (quicconf.bpf.disabled) {
+ return false;
+ }
+
+ if (!config->single_thread && apiconf.enabled) {
+ return index_ == 1;
+ }
+
+ return index_ == 0;
+}
+
+bool Worker::should_update_bpf_map() const {
+ auto config = get_config();
+ auto &quicconf = config->quic;
+
+ return !quicconf.bpf.disabled;
+}
+
+uint32_t Worker::compute_sk_index() const {
+ auto config = get_config();
+ auto &apiconf = config->api;
+
+ if (!config->single_thread && apiconf.enabled) {
+ return index_ - 1;
+ }
+
+ return index_;
+}
+# endif // HAVE_LIBBPF
+
+int Worker::setup_quic_server_socket() {
+ size_t n = 0;
+
+ for (auto &addr : quic_upstream_addrs_) {
+ assert(!addr.host_unix);
+ if (create_quic_server_socket(addr) != 0) {
+ return -1;
+ }
+
+ // Make sure that each endpoint has a unique address.
+ for (size_t i = 0; i < n; ++i) {
+ const auto &a = quic_upstream_addrs_[i];
+
+ if (addr.hostport == a.hostport) {
+ LOG(FATAL)
+ << "QUIC frontend endpoint must be unique: a duplicate found for "
+ << addr.hostport;
+
+ return -1;
+ }
+ }
+
+ ++n;
+
+ quic_listeners_.emplace_back(std::make_unique<QUICListener>(&addr, this));
+ }
+
+ return 0;
+}
+
+int Worker::create_quic_server_socket(UpstreamAddr &faddr) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ int fd = -1;
+ int rv;
+
+ auto service = util::utos(faddr.port);
+ addrinfo hints{};
+ hints.ai_family = faddr.family;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_flags = AI_PASSIVE;
+# ifdef AI_ADDRCONFIG
+ hints.ai_flags |= AI_ADDRCONFIG;
+# endif // AI_ADDRCONFIG
+
+ auto node =
+ faddr.host == StringRef::from_lit("*") ? nullptr : faddr.host.c_str();
+
+ addrinfo *res, *rp;
+ rv = getaddrinfo(node, service.c_str(), &hints, &res);
+# ifdef AI_ADDRCONFIG
+ if (rv != 0) {
+ // Retry without AI_ADDRCONFIG
+ hints.ai_flags &= ~AI_ADDRCONFIG;
+ rv = getaddrinfo(node, service.c_str(), &hints, &res);
+ }
+# endif // AI_ADDRCONFIG
+ if (rv != 0) {
+ LOG(FATAL) << "Unable to get IPv" << (faddr.family == AF_INET ? "4" : "6")
+ << " address for " << faddr.host << ", port " << faddr.port
+ << ": " << gai_strerror(rv);
+ return -1;
+ }
+
+ auto res_d = defer(freeaddrinfo, res);
+
+ std::array<char, NI_MAXHOST> host;
+
+ for (rp = res; rp; rp = rp->ai_next) {
+ rv = getnameinfo(rp->ai_addr, rp->ai_addrlen, host.data(), host.size(),
+ nullptr, 0, NI_NUMERICHOST);
+ if (rv != 0) {
+ LOG(WARN) << "getnameinfo() failed: " << gai_strerror(rv);
+ continue;
+ }
+
+# ifdef SOCK_NONBLOCK
+ fd = socket(rp->ai_family, rp->ai_socktype | SOCK_NONBLOCK | SOCK_CLOEXEC,
+ rp->ai_protocol);
+ if (fd == -1) {
+ auto error = errno;
+ LOG(WARN) << "socket() syscall failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ continue;
+ }
+# else // !SOCK_NONBLOCK
+ fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if (fd == -1) {
+ auto error = errno;
+ LOG(WARN) << "socket() syscall failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ continue;
+ }
+ util::make_socket_nonblocking(fd);
+ util::make_socket_closeonexec(fd);
+# endif // !SOCK_NONBLOCK
+
+ int val = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ auto error = errno;
+ LOG(WARN) << "Failed to set SO_REUSEADDR option to listener socket: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ continue;
+ }
+
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ auto error = errno;
+ LOG(WARN) << "Failed to set SO_REUSEPORT option to listener socket: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ continue;
+ }
+
+ if (faddr.family == AF_INET6) {
+# ifdef IPV6_V6ONLY
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ auto error = errno;
+ LOG(WARN) << "Failed to set IPV6_V6ONLY option to listener socket: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ continue;
+ }
+# endif // IPV6_V6ONLY
+
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ auto error = errno;
+ LOG(WARN)
+ << "Failed to set IPV6_RECVPKTINFO option to listener socket: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ continue;
+ }
+
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVTCLASS, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ auto error = errno;
+ LOG(WARN) << "Failed to set IPV6_RECVTCLASS option to listener socket: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ continue;
+ }
+
+# if defined(IPV6_MTU_DISCOVER) && defined(IPV6_PMTUDISC_DO)
+ int mtu_disc = IPV6_PMTUDISC_DO;
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &mtu_disc,
+ static_cast<socklen_t>(sizeof(mtu_disc))) == -1) {
+ auto error = errno;
+ LOG(WARN)
+ << "Failed to set IPV6_MTU_DISCOVER option to listener socket: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ continue;
+ }
+# endif // defined(IPV6_MTU_DISCOVER) && defined(IP_PMTUDISC_DO)
+ } else {
+ if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ auto error = errno;
+ LOG(WARN) << "Failed to set IP_PKTINFO option to listener socket: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ continue;
+ }
+
+ if (setsockopt(fd, IPPROTO_IP, IP_RECVTOS, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ auto error = errno;
+ LOG(WARN) << "Failed to set IP_RECVTOS option to listener socket: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ continue;
+ }
+
+# if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DO)
+ int mtu_disc = IP_PMTUDISC_DO;
+ if (setsockopt(fd, IPPROTO_IP, IP_MTU_DISCOVER, &mtu_disc,
+ static_cast<socklen_t>(sizeof(mtu_disc))) == -1) {
+ auto error = errno;
+ LOG(WARN) << "Failed to set IP_MTU_DISCOVER option to listener socket: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ continue;
+ }
+# endif // defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DO)
+ }
+
+# ifdef UDP_GRO
+ if (setsockopt(fd, IPPROTO_UDP, UDP_GRO, &val, sizeof(val)) == -1) {
+ auto error = errno;
+ LOG(WARN) << "Failed to set UDP_GRO option to listener socket: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ continue;
+ }
+# endif // UDP_GRO
+
+ if (bind(fd, rp->ai_addr, rp->ai_addrlen) == -1) {
+ auto error = errno;
+ LOG(WARN) << "bind() syscall failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ continue;
+ }
+
+# ifdef HAVE_LIBBPF
+ auto config = get_config();
+
+ auto &quic_bpf_refs = conn_handler_->get_quic_bpf_refs();
+
+ if (should_attach_bpf()) {
+ auto &bpfconf = config->quic.bpf;
+
+ auto obj = bpf_object__open_file(bpfconf.prog_file.c_str(), nullptr);
+ if (!obj) {
+ auto error = errno;
+ LOG(FATAL) << "Failed to open bpf object file: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ return -1;
+ }
+
+ rv = bpf_object__load(obj);
+ if (rv != 0) {
+ auto error = errno;
+ LOG(FATAL) << "Failed to load bpf object file: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ return -1;
+ }
+
+ auto prog = bpf_object__find_program_by_name(obj, "select_reuseport");
+ if (!prog) {
+ auto error = errno;
+ LOG(FATAL) << "Failed to find sk_reuseport program: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ return -1;
+ }
+
+ auto &ref = quic_bpf_refs[faddr.index];
+
+ ref.obj = obj;
+
+ ref.reuseport_array =
+ bpf_object__find_map_by_name(obj, "reuseport_array");
+ if (!ref.reuseport_array) {
+ auto error = errno;
+ LOG(FATAL) << "Failed to get reuseport_array: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ return -1;
+ }
+
+ ref.cid_prefix_map = bpf_object__find_map_by_name(obj, "cid_prefix_map");
+ if (!ref.cid_prefix_map) {
+ auto error = errno;
+ LOG(FATAL) << "Failed to get cid_prefix_map: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ return -1;
+ }
+
+ auto sk_info = bpf_object__find_map_by_name(obj, "sk_info");
+ if (!sk_info) {
+ auto error = errno;
+ LOG(FATAL) << "Failed to get sk_info: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ return -1;
+ }
+
+ constexpr uint32_t zero = 0;
+ uint64_t num_socks = config->num_worker;
+
+ rv = bpf_map__update_elem(sk_info, &zero, sizeof(zero), &num_socks,
+ sizeof(num_socks), BPF_ANY);
+ if (rv != 0) {
+ auto error = errno;
+ LOG(FATAL) << "Failed to update sk_info: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ return -1;
+ }
+
+ constexpr uint32_t key_high_idx = 1;
+ constexpr uint32_t key_low_idx = 2;
+
+ auto &qkms = conn_handler_->get_quic_keying_materials();
+ auto &qkm = qkms->keying_materials.front();
+
+ rv = bpf_map__update_elem(sk_info, &key_high_idx, sizeof(key_high_idx),
+ qkm.cid_encryption_key.data(),
+ qkm.cid_encryption_key.size() / 2, BPF_ANY);
+ if (rv != 0) {
+ auto error = errno;
+ LOG(FATAL) << "Failed to update key_high_idx sk_info: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ return -1;
+ }
+
+ rv = bpf_map__update_elem(sk_info, &key_low_idx, sizeof(key_low_idx),
+ qkm.cid_encryption_key.data() +
+ qkm.cid_encryption_key.size() / 2,
+ qkm.cid_encryption_key.size() / 2, BPF_ANY);
+ if (rv != 0) {
+ auto error = errno;
+ LOG(FATAL) << "Failed to update key_low_idx sk_info: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ return -1;
+ }
+
+ auto prog_fd = bpf_program__fd(prog);
+
+ if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_REUSEPORT_EBPF, &prog_fd,
+ static_cast<socklen_t>(sizeof(prog_fd))) == -1) {
+ LOG(FATAL) << "Failed to attach bpf program: "
+ << xsi_strerror(errno, errbuf.data(), errbuf.size());
+ close(fd);
+ return -1;
+ }
+ }
+
+ if (should_update_bpf_map()) {
+ const auto &ref = quic_bpf_refs[faddr.index];
+ auto sk_index = compute_sk_index();
+
+ rv = bpf_map__update_elem(ref.reuseport_array, &sk_index,
+ sizeof(sk_index), &fd, sizeof(fd), BPF_NOEXIST);
+ if (rv != 0) {
+ auto error = errno;
+ LOG(FATAL) << "Failed to update reuseport_array: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ return -1;
+ }
+
+ rv = bpf_map__update_elem(ref.cid_prefix_map, cid_prefix_.data(),
+ cid_prefix_.size(), &sk_index, sizeof(sk_index),
+ BPF_NOEXIST);
+ if (rv != 0) {
+ auto error = errno;
+ LOG(FATAL) << "Failed to update cid_prefix_map: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ return -1;
+ }
+ }
+# endif // HAVE_LIBBPF
+
+ break;
+ }
+
+ if (!rp) {
+ LOG(FATAL) << "Listening " << (faddr.family == AF_INET ? "IPv4" : "IPv6")
+ << " socket failed";
+
+ return -1;
+ }
+
+ faddr.fd = fd;
+ faddr.hostport = util::make_http_hostport(mod_config()->balloc,
+ StringRef{host.data()}, faddr.port);
+
+ LOG(NOTICE) << "Listening on " << faddr.hostport << ", quic";
+
+ return 0;
+}
+
+const uint8_t *Worker::get_cid_prefix() const { return cid_prefix_.data(); }
+
+const UpstreamAddr *Worker::find_quic_upstream_addr(const Address &local_addr) {
+ std::array<char, NI_MAXHOST> host;
+
+ auto rv = getnameinfo(&local_addr.su.sa, local_addr.len, host.data(),
+ host.size(), nullptr, 0, NI_NUMERICHOST);
+ if (rv != 0) {
+ LOG(ERROR) << "getnameinfo: " << gai_strerror(rv);
+
+ return nullptr;
+ }
+
+ uint16_t port;
+
+ switch (local_addr.su.sa.sa_family) {
+ case AF_INET:
+ port = htons(local_addr.su.in.sin_port);
+
+ break;
+ case AF_INET6:
+ port = htons(local_addr.su.in6.sin6_port);
+
+ break;
+ default:
+ assert(0);
+ abort();
+ }
+
+ std::array<char, util::max_hostport> hostport_buf;
+
+ auto hostport = util::make_http_hostport(std::begin(hostport_buf),
+ StringRef{host.data()}, port);
+ const UpstreamAddr *fallback_faddr = nullptr;
+
+ for (auto &faddr : quic_upstream_addrs_) {
+ if (faddr.hostport == hostport) {
+ return &faddr;
+ }
+
+ if (faddr.port != port || faddr.family != local_addr.su.sa.sa_family) {
+ continue;
+ }
+
+ if (faddr.port == 443 || faddr.port == 80) {
+ switch (faddr.family) {
+ case AF_INET:
+ if (util::streq(faddr.hostport, StringRef::from_lit("0.0.0.0"))) {
+ fallback_faddr = &faddr;
+ }
+
+ break;
+ case AF_INET6:
+ if (util::streq(faddr.hostport, StringRef::from_lit("[::]"))) {
+ fallback_faddr = &faddr;
+ }
+
+ break;
+ default:
+ assert(0);
+ }
+ } else {
+ switch (faddr.family) {
+ case AF_INET:
+ if (util::starts_with(faddr.hostport,
+ StringRef::from_lit("0.0.0.0:"))) {
+ fallback_faddr = &faddr;
+ }
+
+ break;
+ case AF_INET6:
+ if (util::starts_with(faddr.hostport, StringRef::from_lit("[::]:"))) {
+ fallback_faddr = &faddr;
+ }
+
+ break;
+ default:
+ assert(0);
+ }
+ }
+ }
+
+ return fallback_faddr;
+}
+#endif // ENABLE_HTTP3
+
+namespace {
+size_t match_downstream_addr_group_host(
+ const RouterConfig &routerconf, const StringRef &host,
+ const StringRef &path,
+ const std::vector<std::shared_ptr<DownstreamAddrGroup>> &groups,
+ size_t catch_all, BlockAllocator &balloc) {
+
+ const auto &router = routerconf.router;
+ const auto &rev_wildcard_router = routerconf.rev_wildcard_router;
+ const auto &wildcard_patterns = routerconf.wildcard_patterns;
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Perform mapping selection, using host=" << host
+ << ", path=" << path;
+ }
+
+ auto group = router.match(host, path);
+ if (group != -1) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Found pattern with query " << host << path
+ << ", matched pattern=" << groups[group]->pattern;
+ }
+ return group;
+ }
+
+ if (!wildcard_patterns.empty() && !host.empty()) {
+ auto rev_host_src = make_byte_ref(balloc, host.size() - 1);
+ auto ep =
+ std::copy(std::begin(host) + 1, std::end(host), rev_host_src.base);
+ std::reverse(rev_host_src.base, ep);
+ auto rev_host = StringRef{rev_host_src.base, ep};
+
+ ssize_t best_group = -1;
+ const RNode *last_node = nullptr;
+
+ for (;;) {
+ size_t nread = 0;
+ auto wcidx =
+ rev_wildcard_router.match_prefix(&nread, &last_node, rev_host);
+ if (wcidx == -1) {
+ break;
+ }
+
+ rev_host = StringRef{std::begin(rev_host) + nread, std::end(rev_host)};
+
+ auto &wc = wildcard_patterns[wcidx];
+ auto group = wc.router.match(StringRef{}, path);
+ if (group != -1) {
+ // We sorted wildcard_patterns in a way that first match is the
+ // longest host pattern.
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Found wildcard pattern with query " << host << path
+ << ", matched pattern=" << groups[group]->pattern;
+ }
+
+ best_group = group;
+ }
+ }
+
+ if (best_group != -1) {
+ return best_group;
+ }
+ }
+
+ group = router.match(StringRef::from_lit(""), path);
+ if (group != -1) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Found pattern with query " << path
+ << ", matched pattern=" << groups[group]->pattern;
+ }
+ return group;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "None match. Use catch-all pattern";
+ }
+ return catch_all;
+}
+} // namespace
+
+size_t match_downstream_addr_group(
+ const RouterConfig &routerconf, const StringRef &hostport,
+ const StringRef &raw_path,
+ const std::vector<std::shared_ptr<DownstreamAddrGroup>> &groups,
+ size_t catch_all, BlockAllocator &balloc) {
+ if (std::find(std::begin(hostport), std::end(hostport), '/') !=
+ std::end(hostport)) {
+ // We use '/' specially, and if '/' is included in host, it breaks
+ // our code. Select catch-all case.
+ return catch_all;
+ }
+
+ auto fragment = std::find(std::begin(raw_path), std::end(raw_path), '#');
+ auto query = std::find(std::begin(raw_path), fragment, '?');
+ auto path = StringRef{std::begin(raw_path), query};
+
+ if (path.empty() || path[0] != '/') {
+ path = StringRef::from_lit("/");
+ }
+
+ if (hostport.empty()) {
+ return match_downstream_addr_group_host(routerconf, hostport, path, groups,
+ catch_all, balloc);
+ }
+
+ StringRef host;
+ if (hostport[0] == '[') {
+ // assume this is IPv6 numeric address
+ auto p = std::find(std::begin(hostport), std::end(hostport), ']');
+ if (p == std::end(hostport)) {
+ return catch_all;
+ }
+ if (p + 1 < std::end(hostport) && *(p + 1) != ':') {
+ return catch_all;
+ }
+ host = StringRef{std::begin(hostport), p + 1};
+ } else {
+ auto p = std::find(std::begin(hostport), std::end(hostport), ':');
+ if (p == std::begin(hostport)) {
+ return catch_all;
+ }
+ host = StringRef{std::begin(hostport), p};
+ }
+
+ if (std::find_if(std::begin(host), std::end(host), [](char c) {
+ return 'A' <= c || c <= 'Z';
+ }) != std::end(host)) {
+ auto low_host = make_byte_ref(balloc, host.size() + 1);
+ auto ep = std::copy(std::begin(host), std::end(host), low_host.base);
+ *ep = '\0';
+ util::inp_strlower(low_host.base, ep);
+ host = StringRef{low_host.base, ep};
+ }
+ return match_downstream_addr_group_host(routerconf, host, path, groups,
+ catch_all, balloc);
+}
+
+void downstream_failure(DownstreamAddr *addr, const Address *raddr) {
+ const auto &connect_blocker = addr->connect_blocker;
+
+ if (connect_blocker->in_offline()) {
+ return;
+ }
+
+ connect_blocker->on_failure();
+
+ if (addr->fall == 0) {
+ return;
+ }
+
+ auto fail_count = connect_blocker->get_fail_count();
+
+ if (fail_count >= addr->fall) {
+ if (raddr) {
+ LOG(WARN) << "Could not connect to " << util::to_numeric_addr(raddr)
+ << " " << fail_count
+ << " times in a row; considered as offline";
+ } else {
+ LOG(WARN) << "Could not connect to " << addr->host << ":" << addr->port
+ << " " << fail_count
+ << " times in a row; considered as offline";
+ }
+
+ connect_blocker->offline();
+
+ if (addr->rise) {
+ addr->live_check->schedule();
+ }
+ }
+}
+
+#ifdef ENABLE_HTTP3
+int create_cid_prefix(uint8_t *cid_prefix, const uint8_t *server_id) {
+ auto p = std::copy_n(server_id, SHRPX_QUIC_SERVER_IDLEN, cid_prefix);
+
+ if (RAND_bytes(p, SHRPX_QUIC_CID_PREFIXLEN - SHRPX_QUIC_SERVER_IDLEN) != 1) {
+ return -1;
+ }
+
+ return 0;
+}
+#endif // ENABLE_HTTP3
+
+} // namespace shrpx
diff --git a/src/shrpx_worker.h b/src/shrpx_worker.h
new file mode 100644
index 0000000..3cc7b57
--- /dev/null
+++ b/src/shrpx_worker.h
@@ -0,0 +1,480 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_WORKER_H
+#define SHRPX_WORKER_H
+
+#include "shrpx.h"
+
+#include <mutex>
+#include <vector>
+#include <random>
+#include <unordered_map>
+#include <deque>
+#include <thread>
+#include <queue>
+#ifndef NOTHREADS
+# include <future>
+#endif // NOTHREADS
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+#include <ev.h>
+
+#include "shrpx_config.h"
+#include "shrpx_downstream_connection_pool.h"
+#include "memchunk.h"
+#include "shrpx_tls.h"
+#include "shrpx_live_check.h"
+#include "shrpx_connect_blocker.h"
+#include "shrpx_dns_tracker.h"
+#ifdef ENABLE_HTTP3
+# include "shrpx_quic_connection_handler.h"
+# include "shrpx_quic.h"
+#endif // ENABLE_HTTP3
+#include "allocator.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+class Http2Session;
+class ConnectBlocker;
+class MemcachedDispatcher;
+struct UpstreamAddr;
+class ConnectionHandler;
+#ifdef ENABLE_HTTP3
+class QUICListener;
+#endif // ENABLE_HTTP3
+
+#ifdef HAVE_MRUBY
+namespace mruby {
+
+class MRubyContext;
+
+} // namespace mruby
+#endif // HAVE_MRUBY
+
+namespace tls {
+class CertLookupTree;
+} // namespace tls
+
+struct WeightGroup;
+
+struct DownstreamAddr {
+ Address addr;
+ // backend address. If |host_unix| is true, this is UNIX domain
+ // socket path.
+ StringRef host;
+ StringRef hostport;
+ // backend port. 0 if |host_unix| is true.
+ uint16_t port;
+ // true if |host| contains UNIX domain socket path.
+ bool host_unix;
+
+ // sni field to send remote server if TLS is enabled.
+ StringRef sni;
+
+ std::unique_ptr<ConnectBlocker> connect_blocker;
+ std::unique_ptr<LiveCheck> live_check;
+ // Connection pool for this particular address if session affinity
+ // is enabled
+ std::unique_ptr<DownstreamConnectionPool> dconn_pool;
+ size_t fall;
+ size_t rise;
+ // Client side TLS session cache
+ tls::TLSSessionCache tls_session_cache;
+ // List of Http2Session which is not fully utilized (i.e., the
+ // server advertised maximum concurrency is not reached). We will
+ // coalesce as much stream as possible in one Http2Session to fully
+ // utilize TCP connection.
+ DList<Http2Session> http2_extra_freelist;
+ WeightGroup *wg;
+ // total number of streams created in HTTP/2 connections for this
+ // address.
+ size_t num_dconn;
+ // the sequence number of this address to randomize the order access
+ // threads.
+ size_t seq;
+ // Application protocol used in this backend
+ Proto proto;
+ // cycle is used to prioritize this address. Lower value takes
+ // higher priority.
+ uint32_t cycle;
+ // penalty which is applied to the next cycle calculation.
+ uint32_t pending_penalty;
+ // Weight of this address inside a weight group. Its range is [1,
+ // 256], inclusive.
+ uint32_t weight;
+ // name of group which this address belongs to.
+ StringRef group;
+ // Weight of the weight group which this address belongs to. Its
+ // range is [1, 256], inclusive.
+ uint32_t group_weight;
+ // affinity hash for this address. It is assigned when strict
+ // stickiness is enabled.
+ uint32_t affinity_hash;
+ // true if TLS is used in this backend
+ bool tls;
+ // true if dynamic DNS is enabled
+ bool dns;
+ // true if :scheme pseudo header field should be upgraded to secure
+ // variant (e.g., "https") when forwarding request to a backend
+ // connected by TLS connection.
+ bool upgrade_scheme;
+ // true if this address is queued.
+ bool queued;
+};
+
+constexpr uint32_t MAX_DOWNSTREAM_ADDR_WEIGHT = 256;
+
+struct DownstreamAddrEntry {
+ DownstreamAddr *addr;
+ size_t seq;
+ uint32_t cycle;
+};
+
+struct DownstreamAddrEntryGreater {
+ bool operator()(const DownstreamAddrEntry &lhs,
+ const DownstreamAddrEntry &rhs) const {
+ auto d = lhs.cycle - rhs.cycle;
+ if (d == 0) {
+ return rhs.seq < lhs.seq;
+ }
+ return d <= 2 * MAX_DOWNSTREAM_ADDR_WEIGHT - 1;
+ }
+};
+
+struct WeightGroup {
+ std::priority_queue<DownstreamAddrEntry, std::vector<DownstreamAddrEntry>,
+ DownstreamAddrEntryGreater>
+ pq;
+ size_t seq;
+ uint32_t weight;
+ uint32_t cycle;
+ uint32_t pending_penalty;
+ // true if this object is queued.
+ bool queued;
+};
+
+struct WeightGroupEntry {
+ WeightGroup *wg;
+ size_t seq;
+ uint32_t cycle;
+};
+
+struct WeightGroupEntryGreater {
+ bool operator()(const WeightGroupEntry &lhs,
+ const WeightGroupEntry &rhs) const {
+ auto d = lhs.cycle - rhs.cycle;
+ if (d == 0) {
+ return rhs.seq < lhs.seq;
+ }
+ return d <= 2 * MAX_DOWNSTREAM_ADDR_WEIGHT - 1;
+ }
+};
+
+struct SharedDownstreamAddr {
+ SharedDownstreamAddr()
+ : balloc(1024, 1024),
+ affinity{SessionAffinity::NONE},
+ redirect_if_not_tls{false},
+ dnf{false},
+ timeout{} {}
+
+ SharedDownstreamAddr(const SharedDownstreamAddr &) = delete;
+ SharedDownstreamAddr(SharedDownstreamAddr &&) = delete;
+ SharedDownstreamAddr &operator=(const SharedDownstreamAddr &) = delete;
+ SharedDownstreamAddr &operator=(SharedDownstreamAddr &&) = delete;
+
+ BlockAllocator balloc;
+ std::vector<DownstreamAddr> addrs;
+ std::vector<WeightGroup> wgs;
+ std::priority_queue<WeightGroupEntry, std::vector<WeightGroupEntry>,
+ WeightGroupEntryGreater>
+ pq;
+ // Bunch of session affinity hash. Only used if affinity ==
+ // SessionAffinity::IP.
+ std::vector<AffinityHash> affinity_hash;
+ // Maps affinity hash of each DownstreamAddr to its index in addrs.
+ // It is only assigned when strict stickiness is enabled.
+ std::unordered_map<uint32_t, size_t> affinity_hash_map;
+#ifdef HAVE_MRUBY
+ std::shared_ptr<mruby::MRubyContext> mruby_ctx;
+#endif // HAVE_MRUBY
+ // Configuration for session affinity
+ AffinityConfig affinity;
+ // Session affinity
+ // true if this group requires that client connection must be TLS,
+ // and the request must be redirected to https URI.
+ bool redirect_if_not_tls;
+ // true if a request should not be forwarded to a backend.
+ bool dnf;
+ // Timeouts for backend connection.
+ struct {
+ ev_tstamp read;
+ ev_tstamp write;
+ } timeout;
+};
+
+struct DownstreamAddrGroup {
+ DownstreamAddrGroup();
+ ~DownstreamAddrGroup();
+
+ DownstreamAddrGroup(const DownstreamAddrGroup &) = delete;
+ DownstreamAddrGroup(DownstreamAddrGroup &&) = delete;
+ DownstreamAddrGroup &operator=(const DownstreamAddrGroup &) = delete;
+ DownstreamAddrGroup &operator=(DownstreamAddrGroup &&) = delete;
+
+ ImmutableString pattern;
+ std::shared_ptr<SharedDownstreamAddr> shared_addr;
+ // true if this group is no longer used for new request. If this is
+ // true, the connection made using one of address in shared_addr
+ // must not be pooled.
+ bool retired;
+};
+
+struct WorkerStat {
+ size_t num_connections;
+ size_t num_close_waits;
+};
+
+#ifdef ENABLE_HTTP3
+struct QUICPacket {
+ QUICPacket(size_t upstream_addr_index, const Address &remote_addr,
+ const Address &local_addr, const ngtcp2_pkt_info &pi,
+ const uint8_t *data, size_t datalen)
+ : upstream_addr_index{upstream_addr_index},
+ remote_addr{remote_addr},
+ local_addr{local_addr},
+ pi{pi},
+ data{data, data + datalen} {}
+ QUICPacket() : upstream_addr_index{}, remote_addr{}, local_addr{}, pi{} {}
+ size_t upstream_addr_index;
+ Address remote_addr;
+ Address local_addr;
+ ngtcp2_pkt_info pi;
+ std::vector<uint8_t> data;
+};
+#endif // ENABLE_HTTP3
+
+enum class WorkerEventType {
+ NEW_CONNECTION = 0x01,
+ REOPEN_LOG = 0x02,
+ GRACEFUL_SHUTDOWN = 0x03,
+ REPLACE_DOWNSTREAM = 0x04,
+#ifdef ENABLE_HTTP3
+ QUIC_PKT_FORWARD = 0x05,
+#endif // ENABLE_HTTP3
+};
+
+struct WorkerEvent {
+ WorkerEventType type;
+ struct {
+ sockaddr_union client_addr;
+ size_t client_addrlen;
+ int client_fd;
+ const UpstreamAddr *faddr;
+ };
+ std::shared_ptr<TicketKeys> ticket_keys;
+ std::shared_ptr<DownstreamConfig> downstreamconf;
+#ifdef ENABLE_HTTP3
+ std::unique_ptr<QUICPacket> quic_pkt;
+#endif // ENABLE_HTTP3
+};
+
+class Worker {
+public:
+ Worker(struct ev_loop *loop, SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx,
+ SSL_CTX *tls_session_cache_memcached_ssl_ctx,
+ tls::CertLookupTree *cert_tree,
+#ifdef ENABLE_HTTP3
+ SSL_CTX *quic_sv_ssl_ctx, tls::CertLookupTree *quic_cert_tree,
+ const uint8_t *cid_prefix, size_t cid_prefixlen,
+# ifdef HAVE_LIBBPF
+ size_t index,
+# endif // HAVE_LIBBPF
+#endif // ENABLE_HTTP3
+ const std::shared_ptr<TicketKeys> &ticket_keys,
+ ConnectionHandler *conn_handler,
+ std::shared_ptr<DownstreamConfig> downstreamconf);
+ ~Worker();
+ void run_async();
+ void wait();
+ void process_events();
+ void send(WorkerEvent event);
+
+ tls::CertLookupTree *get_cert_lookup_tree() const;
+#ifdef ENABLE_HTTP3
+ tls::CertLookupTree *get_quic_cert_lookup_tree() const;
+#endif // ENABLE_HTTP3
+
+ // 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();
+ struct ev_loop *get_loop() const;
+ SSL_CTX *get_sv_ssl_ctx() const;
+ SSL_CTX *get_cl_ssl_ctx() const;
+#ifdef ENABLE_HTTP3
+ SSL_CTX *get_quic_sv_ssl_ctx() const;
+#endif // ENABLE_HTTP3
+
+ void set_graceful_shutdown(bool f);
+ bool get_graceful_shutdown() const;
+
+ MemchunkPool *get_mcpool();
+ void schedule_clear_mcpool();
+
+ MemcachedDispatcher *get_session_cache_memcached_dispatcher();
+
+ std::mt19937 &get_randgen();
+
+#ifdef HAVE_MRUBY
+ int create_mruby_context();
+
+ mruby::MRubyContext *get_mruby_context() const;
+#endif // HAVE_MRUBY
+
+ std::vector<std::shared_ptr<DownstreamAddrGroup>> &
+ get_downstream_addr_groups();
+
+ ConnectBlocker *get_connect_blocker() const;
+
+ const DownstreamConfig *get_downstream_config() const;
+
+ void
+ replace_downstream_config(std::shared_ptr<DownstreamConfig> downstreamconf);
+
+ ConnectionHandler *get_connection_handler() const;
+
+#ifdef ENABLE_HTTP3
+ QUICConnectionHandler *get_quic_connection_handler();
+
+ int setup_quic_server_socket();
+
+ const uint8_t *get_cid_prefix() const;
+
+# ifdef HAVE_LIBBPF
+ bool should_attach_bpf() const;
+
+ bool should_update_bpf_map() const;
+
+ uint32_t compute_sk_index() const;
+# endif // HAVE_LIBBPF
+
+ int create_quic_server_socket(UpstreamAddr &addr);
+
+ // Returns a pointer to UpstreamAddr which matches |local_addr|.
+ const UpstreamAddr *find_quic_upstream_addr(const Address &local_addr);
+#endif // ENABLE_HTTP3
+
+ DNSTracker *get_dns_tracker();
+
+private:
+#ifndef NOTHREADS
+ std::future<void> fut_;
+#endif // NOTHREADS
+#if defined(ENABLE_HTTP3) && defined(HAVE_LIBBPF)
+ // Unique index of this worker.
+ size_t index_;
+#endif // ENABLE_HTTP3 && HAVE_LIBBPF
+ std::mutex m_;
+ std::deque<WorkerEvent> q_;
+ std::mt19937 randgen_;
+ ev_async w_;
+ ev_timer mcpool_clear_timer_;
+ ev_timer proc_wev_timer_;
+ MemchunkPool mcpool_;
+ WorkerStat worker_stat_;
+ DNSTracker dns_tracker_;
+
+#ifdef ENABLE_HTTP3
+ std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN> cid_prefix_;
+ std::vector<UpstreamAddr> quic_upstream_addrs_;
+ std::vector<std::unique_ptr<QUICListener>> quic_listeners_;
+#endif // ENABLE_HTTP3
+
+ std::shared_ptr<DownstreamConfig> downstreamconf_;
+ std::unique_ptr<MemcachedDispatcher> session_cache_memcached_dispatcher_;
+#ifdef HAVE_MRUBY
+ std::unique_ptr<mruby::MRubyContext> mruby_ctx_;
+#endif // HAVE_MRUBY
+ struct ev_loop *loop_;
+
+ // Following fields are shared across threads if
+ // get_config()->tls_ctx_per_worker == true.
+ SSL_CTX *sv_ssl_ctx_;
+ SSL_CTX *cl_ssl_ctx_;
+ tls::CertLookupTree *cert_tree_;
+ ConnectionHandler *conn_handler_;
+#ifdef ENABLE_HTTP3
+ SSL_CTX *quic_sv_ssl_ctx_;
+ tls::CertLookupTree *quic_cert_tree_;
+
+ QUICConnectionHandler quic_conn_handler_;
+#endif // ENABLE_HTTP3
+
+#ifndef HAVE_ATOMIC_STD_SHARED_PTR
+ std::mutex ticket_keys_m_;
+#endif // !HAVE_ATOMIC_STD_SHARED_PTR
+ std::shared_ptr<TicketKeys> ticket_keys_;
+ std::vector<std::shared_ptr<DownstreamAddrGroup>> downstream_addr_groups_;
+ // Worker level blocker for downstream connection. For example,
+ // this is used when file descriptor is exhausted.
+ std::unique_ptr<ConnectBlocker> connect_blocker_;
+
+ bool graceful_shutdown_;
+};
+
+// Selects group based on request's |hostport| and |path|. |hostport|
+// is the value taken from :authority or host header field, and may
+// contain port. The |path| may contain query part. We require the
+// catch-all pattern in place, so this function always selects one
+// group. The catch-all group index is given in |catch_all|. All
+// patterns are given in |groups|.
+size_t match_downstream_addr_group(
+ const RouterConfig &routerconfig, const StringRef &hostport,
+ const StringRef &path,
+ const std::vector<std::shared_ptr<DownstreamAddrGroup>> &groups,
+ size_t catch_all, BlockAllocator &balloc);
+
+// Calls this function if connecting to backend failed. |raddr| is
+// the actual address used to connect to backend, and it could be
+// nullptr. This function may schedule live check.
+void downstream_failure(DownstreamAddr *addr, const Address *raddr);
+
+#ifdef ENABLE_HTTP3
+// Creates unpredictable SHRPX_QUIC_CID_PREFIXLEN bytes sequence which
+// is used as a prefix of QUIC Connection ID. This function returns
+// -1 on failure. |server_id| must be 2 bytes long.
+int create_cid_prefix(uint8_t *cid_prefix, const uint8_t *server_id);
+#endif // ENABLE_HTTP3
+
+} // namespace shrpx
+
+#endif // SHRPX_WORKER_H
diff --git a/src/shrpx_worker_process.cc b/src/shrpx_worker_process.cc
new file mode 100644
index 0000000..05eac02
--- /dev/null
+++ b/src/shrpx_worker_process.cc
@@ -0,0 +1,701 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_worker_process.h"
+
+#include <sys/types.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+#include <sys/resource.h>
+#include <sys/wait.h>
+#include <grp.h>
+
+#include <cinttypes>
+#include <cstdlib>
+
+#include <openssl/rand.h>
+
+#include <ev.h>
+
+#include <ares.h>
+
+#include "shrpx_config.h"
+#include "shrpx_connection_handler.h"
+#include "shrpx_log_config.h"
+#include "shrpx_worker.h"
+#include "shrpx_accept_handler.h"
+#include "shrpx_http2_upstream.h"
+#include "shrpx_http2_session.h"
+#include "shrpx_memcached_dispatcher.h"
+#include "shrpx_memcached_request.h"
+#include "shrpx_process.h"
+#include "shrpx_tls.h"
+#include "shrpx_log.h"
+#include "util.h"
+#include "app_helper.h"
+#include "template.h"
+#include "xsi_strerror.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+namespace {
+void drop_privileges(
+#ifdef HAVE_NEVERBLEED
+ neverbleed_t *nb
+#endif // HAVE_NEVERBLEED
+) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ auto config = get_config();
+
+ if (getuid() == 0 && config->uid != 0) {
+#ifdef HAVE_NEVERBLEED
+ if (nb) {
+ neverbleed_setuidgid(nb, config->user.c_str(), 1);
+ }
+#endif // HAVE_NEVERBLEED
+
+ if (initgroups(config->user.c_str(), config->gid) != 0) {
+ auto error = errno;
+ LOG(FATAL) << "Could not change supplementary groups: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ exit(EXIT_FAILURE);
+ }
+ if (setgid(config->gid) != 0) {
+ auto error = errno;
+ LOG(FATAL) << "Could not change gid: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ exit(EXIT_FAILURE);
+ }
+ if (setuid(config->uid) != 0) {
+ auto error = errno;
+ LOG(FATAL) << "Could not change uid: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ exit(EXIT_FAILURE);
+ }
+ if (setuid(0) != -1) {
+ LOG(FATAL) << "Still have root privileges?";
+ exit(EXIT_FAILURE);
+ }
+ }
+}
+} // namespace
+
+namespace {
+void graceful_shutdown(ConnectionHandler *conn_handler) {
+ if (conn_handler->get_graceful_shutdown()) {
+ return;
+ }
+
+ LOG(NOTICE) << "Graceful shutdown signal received";
+
+ conn_handler->set_graceful_shutdown(true);
+
+ // TODO What happens for the connections not established in the
+ // kernel?
+ conn_handler->accept_pending_connection();
+ conn_handler->delete_acceptor();
+
+ conn_handler->graceful_shutdown_worker();
+
+ auto single_worker = conn_handler->get_single_worker();
+ if (single_worker) {
+ auto worker_stat = single_worker->get_worker_stat();
+ if (worker_stat->num_connections == 0 &&
+ worker_stat->num_close_waits == 0) {
+ ev_break(conn_handler->get_loop());
+ }
+
+ return;
+ }
+}
+} // namespace
+
+namespace {
+void reopen_log(ConnectionHandler *conn_handler) {
+ LOG(NOTICE) << "Reopening log files: worker process (thread main)";
+
+ auto config = get_config();
+ auto &loggingconf = config->logging;
+
+ (void)reopen_log_files(loggingconf);
+ redirect_stderr_to_errorlog(loggingconf);
+
+ conn_handler->worker_reopen_log_files();
+}
+} // namespace
+
+namespace {
+void ipc_readcb(struct ev_loop *loop, ev_io *w, int revents) {
+ auto conn_handler = static_cast<ConnectionHandler *>(w->data);
+ std::array<uint8_t, 1024> buf;
+ ssize_t nread;
+ while ((nread = read(w->fd, buf.data(), buf.size())) == -1 && errno == EINTR)
+ ;
+ if (nread == -1) {
+ auto error = errno;
+ LOG(ERROR) << "Failed to read data from ipc channel: errno=" << error;
+ return;
+ }
+
+ if (nread == 0) {
+ // IPC socket closed. Perform immediate shutdown.
+ LOG(FATAL) << "IPC socket is closed. Perform immediate shutdown.";
+ nghttp2_Exit(EXIT_FAILURE);
+ }
+
+ for (ssize_t i = 0; i < nread; ++i) {
+ switch (buf[i]) {
+ case SHRPX_IPC_GRACEFUL_SHUTDOWN:
+ graceful_shutdown(conn_handler);
+ break;
+ case SHRPX_IPC_REOPEN_LOG:
+ reopen_log(conn_handler);
+ break;
+ }
+ }
+}
+} // namespace
+
+#ifdef ENABLE_HTTP3
+namespace {
+void quic_ipc_readcb(struct ev_loop *loop, ev_io *w, int revents) {
+ auto conn_handler = static_cast<ConnectionHandler *>(w->data);
+
+ if (conn_handler->quic_ipc_read() != 0) {
+ LOG(ERROR) << "Failed to read data from QUIC IPC channel";
+
+ return;
+ }
+}
+} // namespace
+#endif // ENABLE_HTTP3
+
+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)) <=
+ ticket_key.data.enc_key.size());
+ assert(ticket_key.hmac_keylen <= ticket_key.data.hmac_key.size());
+
+ 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 new 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());
+
+ 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);
+ }
+
+ auto &new_key = ticket_keys->keys[0];
+
+ if (generate_ticket_key(new_key) != 0) {
+ if (LOG_ENABLED(INFO)) {
+ 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";
+ 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->set_ticket_keys_to_worker(ticket_keys);
+}
+} // namespace
+
+namespace {
+void memcached_get_ticket_key_cb(struct ev_loop *loop, ev_timer *w,
+ int revents) {
+ auto conn_handler = static_cast<ConnectionHandler *>(w->data);
+ auto dispatcher = conn_handler->get_tls_ticket_key_memcached_dispatcher();
+
+ auto req = std::make_unique<MemcachedRequest>();
+ req->key = "nghttpx:tls-ticket-key";
+ req->op = MemcachedOp::GET;
+ req->cb = [conn_handler, w](MemcachedRequest *req, MemcachedResult res) {
+ switch (res.status_code) {
+ case MemcachedStatusCode::NO_ERROR:
+ break;
+ case MemcachedStatusCode::EXT_NETWORK_ERROR:
+ conn_handler->on_tls_ticket_key_network_error(w);
+ return;
+ default:
+ conn_handler->on_tls_ticket_key_not_found(w);
+ return;
+ }
+
+ // |version (4bytes)|len (2bytes)|key (variable length)|...
+ // (len, key) pairs are repeated as necessary.
+
+ auto &value = res.value;
+ if (value.size() < 4) {
+ LOG(WARN) << "Memcached: tls ticket key value is too small: got "
+ << value.size();
+ conn_handler->on_tls_ticket_key_not_found(w);
+ return;
+ }
+ auto p = value.data();
+ auto version = util::get_uint32(p);
+ // Currently supported version is 1.
+ if (version != 1) {
+ LOG(WARN) << "Memcached: tls ticket key version: want 1, got " << version;
+ conn_handler->on_tls_ticket_key_not_found(w);
+ return;
+ }
+
+ auto end = p + value.size();
+ p += 4;
+
+ auto &ticketconf = get_config()->tls.ticket;
+
+ size_t expectedlen;
+ size_t enc_keylen;
+ size_t hmac_keylen;
+ if (ticketconf.cipher == EVP_aes_128_cbc()) {
+ expectedlen = 48;
+ enc_keylen = 16;
+ hmac_keylen = 16;
+ } else if (ticketconf.cipher == EVP_aes_256_cbc()) {
+ expectedlen = 80;
+ enc_keylen = 32;
+ hmac_keylen = 32;
+ } else {
+ return;
+ }
+
+ auto ticket_keys = std::make_shared<TicketKeys>();
+
+ for (; p != end;) {
+ if (end - p < 2) {
+ LOG(WARN) << "Memcached: tls ticket key data is too small";
+ conn_handler->on_tls_ticket_key_not_found(w);
+ return;
+ }
+ auto len = util::get_uint16(p);
+ p += 2;
+ if (len != expectedlen) {
+ LOG(WARN) << "Memcached: wrong tls ticket key size: want "
+ << expectedlen << ", got " << len;
+ conn_handler->on_tls_ticket_key_not_found(w);
+ return;
+ }
+ if (p + len > end) {
+ LOG(WARN) << "Memcached: too short tls ticket key payload: want " << len
+ << ", got " << (end - p);
+ conn_handler->on_tls_ticket_key_not_found(w);
+ return;
+ }
+ auto key = TicketKey();
+ key.cipher = ticketconf.cipher;
+ key.hmac = EVP_sha256();
+ key.hmac_keylen = hmac_keylen;
+
+ std::copy_n(p, key.data.name.size(), std::begin(key.data.name));
+ p += key.data.name.size();
+
+ std::copy_n(p, enc_keylen, std::begin(key.data.enc_key));
+ p += enc_keylen;
+
+ std::copy_n(p, hmac_keylen, std::begin(key.data.hmac_key));
+ p += hmac_keylen;
+
+ ticket_keys->keys.push_back(std::move(key));
+ }
+
+ conn_handler->on_tls_ticket_key_get_success(ticket_keys, w);
+ };
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Memcached: tls ticket key get request sent";
+ }
+
+ dispatcher->add_request(std::move(req));
+}
+
+} // namespace
+
+#ifdef HAVE_NEVERBLEED
+namespace {
+void nb_child_cb(struct ev_loop *loop, ev_child *w, int revents) {
+ log_chld(w->rpid, w->rstatus, "neverbleed process");
+
+ ev_child_stop(loop, w);
+
+ LOG(FATAL) << "neverbleed process exited; aborting now";
+
+ nghttp2_Exit(EXIT_FAILURE);
+}
+} // namespace
+#endif // HAVE_NEVERBLEED
+
+namespace {
+int send_ready_event(int ready_ipc_fd) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ auto pid = getpid();
+ ssize_t nwrite;
+
+ while ((nwrite = write(ready_ipc_fd, &pid, sizeof(pid))) == -1 &&
+ errno == EINTR)
+ ;
+
+ if (nwrite < 0) {
+ auto error = errno;
+
+ LOG(ERROR) << "Writing PID to ready IPC channel failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+
+ return -1;
+ }
+
+ return 0;
+}
+} // namespace
+
+int worker_process_event_loop(WorkerProcessConfig *wpconf) {
+ int rv;
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ (void)errbuf;
+
+ auto config = get_config();
+
+ if (reopen_log_files(config->logging) != 0) {
+ LOG(FATAL) << "Failed to open log file";
+ return -1;
+ }
+
+ rv = ares_library_init(ARES_LIB_INIT_ALL);
+ if (rv != 0) {
+ LOG(FATAL) << "ares_library_init failed: " << ares_strerror(rv);
+ return -1;
+ }
+
+ auto loop = EV_DEFAULT;
+
+ auto gen = util::make_mt19937();
+
+#ifdef HAVE_NEVERBLEED
+ std::array<char, NEVERBLEED_ERRBUF_SIZE> nb_errbuf;
+ auto nb = std::make_unique<neverbleed_t>();
+ if (neverbleed_init(nb.get(), nb_errbuf.data()) != 0) {
+ LOG(FATAL) << "neverbleed_init failed: " << nb_errbuf.data();
+ return -1;
+ }
+
+ LOG(NOTICE) << "neverbleed process [" << nb->daemon_pid << "] spawned";
+
+ ev_child nb_childev;
+
+ ev_child_init(&nb_childev, nb_child_cb, nb->daemon_pid, 0);
+ nb_childev.data = nullptr;
+ ev_child_start(loop, &nb_childev);
+#endif // HAVE_NEVERBLEED
+
+ auto conn_handler = std::make_unique<ConnectionHandler>(loop, gen);
+
+#ifdef HAVE_NEVERBLEED
+ conn_handler->set_neverbleed(nb.get());
+#endif // HAVE_NEVERBLEED
+
+#ifdef ENABLE_HTTP3
+ conn_handler->set_quic_ipc_fd(wpconf->quic_ipc_fd);
+ conn_handler->set_quic_lingering_worker_processes(
+ wpconf->quic_lingering_worker_processes);
+#endif // ENABLE_HTTP3
+
+ for (auto &addr : config->conn.listener.addrs) {
+ conn_handler->add_acceptor(
+ std::make_unique<AcceptHandler>(&addr, conn_handler.get()));
+ }
+
+ MemchunkPool mcpool;
+
+ ev_timer renew_ticket_key_timer;
+ if (tls::upstream_tls_enabled(config->conn)) {
+ auto &ticketconf = config->tls.ticket;
+ auto &memcachedconf = ticketconf.memcached;
+
+ if (!memcachedconf.host.empty()) {
+ SSL_CTX *ssl_ctx = nullptr;
+
+ if (memcachedconf.tls) {
+ ssl_ctx = conn_handler->create_tls_ticket_key_memcached_ssl_ctx();
+ }
+
+ conn_handler->set_tls_ticket_key_memcached_dispatcher(
+ std::make_unique<MemcachedDispatcher>(
+ &ticketconf.memcached.addr, loop, ssl_ctx,
+ StringRef{memcachedconf.host}, &mcpool, gen));
+
+ ev_timer_init(&renew_ticket_key_timer, memcached_get_ticket_key_cb, 0.,
+ 0.);
+ renew_ticket_key_timer.data = conn_handler.get();
+ // Get first ticket keys.
+ memcached_get_ticket_key_cb(loop, &renew_ticket_key_timer, 0);
+ } else {
+ bool auto_tls_ticket_key = true;
+ if (!ticketconf.files.empty()) {
+ if (!ticketconf.cipher_given) {
+ LOG(WARN)
+ << "It is strongly recommended to specify "
+ "--tls-ticket-key-cipher=aes-128-cbc (or "
+ "tls-ticket-key-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-key-cipher "
+ "becomes aes-256-cbc";
+ }
+ auto ticket_keys = read_tls_ticket_key_file(
+ ticketconf.files, ticketconf.cipher, EVP_sha256());
+ if (!ticket_keys) {
+ LOG(WARN) << "Use internal session ticket key generator";
+ } else {
+ conn_handler->set_ticket_keys(std::move(ticket_keys));
+ auto_tls_ticket_key = false;
+ }
+ }
+ if (auto_tls_ticket_key) {
+ // 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);
+
+ // Generate first session ticket key before running workers.
+ renew_ticket_key_cb(loop, &renew_ticket_key_timer, 0);
+ }
+ }
+ }
+
+#ifdef ENABLE_HTTP3
+ auto &quicconf = config->quic;
+
+ std::shared_ptr<QUICKeyingMaterials> qkms;
+
+ if (!quicconf.upstream.secret_file.empty()) {
+ qkms = read_quic_secret_file(quicconf.upstream.secret_file);
+ if (!qkms) {
+ LOG(WARN) << "Use QUIC keying materials generated internally";
+ }
+ }
+
+ if (!qkms) {
+ qkms = std::make_shared<QUICKeyingMaterials>();
+ qkms->keying_materials.resize(1);
+
+ auto &qkm = qkms->keying_materials.front();
+
+ if (RAND_bytes(qkm.reserved.data(), qkm.reserved.size()) != 1) {
+ LOG(ERROR) << "Failed to generate QUIC secret reserved data";
+ return -1;
+ }
+
+ if (RAND_bytes(qkm.secret.data(), qkm.secret.size()) != 1) {
+ LOG(ERROR) << "Failed to generate QUIC secret";
+ return -1;
+ }
+
+ if (RAND_bytes(qkm.salt.data(), qkm.salt.size()) != 1) {
+ LOG(ERROR) << "Failed to generate QUIC salt";
+ return -1;
+ }
+ }
+
+ for (auto &qkm : qkms->keying_materials) {
+ if (generate_quic_connection_id_encryption_key(
+ qkm.cid_encryption_key.data(), qkm.cid_encryption_key.size(),
+ qkm.secret.data(), qkm.secret.size(), qkm.salt.data(),
+ qkm.salt.size()) != 0) {
+ LOG(ERROR) << "Failed to generate QUIC Connection ID encryption key";
+ return -1;
+ }
+ }
+
+ conn_handler->set_quic_keying_materials(std::move(qkms));
+
+ conn_handler->set_cid_prefixes(wpconf->cid_prefixes);
+ conn_handler->set_quic_lingering_worker_processes(
+ wpconf->quic_lingering_worker_processes);
+#endif // ENABLE_HTTP3
+
+ if (config->single_thread) {
+ rv = conn_handler->create_single_worker();
+ if (rv != 0) {
+ return -1;
+ }
+ } else {
+#ifndef NOTHREADS
+ sigset_t set;
+ sigemptyset(&set);
+ sigaddset(&set, SIGCHLD);
+
+ rv = pthread_sigmask(SIG_BLOCK, &set, nullptr);
+ if (rv != 0) {
+ LOG(ERROR) << "Blocking SIGCHLD failed: "
+ << xsi_strerror(rv, errbuf.data(), errbuf.size());
+ return -1;
+ }
+#endif // !NOTHREADS
+
+ rv = conn_handler->create_worker_thread(config->num_worker);
+ if (rv != 0) {
+ return -1;
+ }
+
+#ifndef NOTHREADS
+ rv = pthread_sigmask(SIG_UNBLOCK, &set, nullptr);
+ if (rv != 0) {
+ LOG(ERROR) << "Unblocking SIGCHLD failed: "
+ << xsi_strerror(rv, errbuf.data(), errbuf.size());
+ return -1;
+ }
+#endif // !NOTHREADS
+ }
+
+#if defined(ENABLE_HTTP3) && defined(HAVE_LIBBPF)
+ conn_handler->unload_bpf_objects();
+#endif // defined(ENABLE_HTTP3) && defined(HAVE_LIBBPF)
+
+ drop_privileges(
+#ifdef HAVE_NEVERBLEED
+ nb.get()
+#endif // HAVE_NEVERBLEED
+ );
+
+ ev_io ipcev;
+ ev_io_init(&ipcev, ipc_readcb, wpconf->ipc_fd, EV_READ);
+ ipcev.data = conn_handler.get();
+ ev_io_start(loop, &ipcev);
+
+#ifdef ENABLE_HTTP3
+ ev_io quic_ipcev;
+ ev_io_init(&quic_ipcev, quic_ipc_readcb, wpconf->quic_ipc_fd, EV_READ);
+ quic_ipcev.data = conn_handler.get();
+ ev_io_start(loop, &quic_ipcev);
+#endif // ENABLE_HTTP3
+
+ if (tls::upstream_tls_enabled(config->conn) && !config->tls.ocsp.disabled) {
+ if (config->tls.ocsp.startup) {
+ conn_handler->set_enable_acceptor_on_ocsp_completion(true);
+ conn_handler->disable_acceptor();
+ }
+
+ conn_handler->proceed_next_cert_ocsp();
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Entering event loop";
+ }
+
+ if (send_ready_event(wpconf->ready_ipc_fd) != 0) {
+ return -1;
+ }
+
+ ev_run(loop, 0);
+
+ conn_handler->cancel_ocsp_update();
+
+ // Destroy SSL_CTX held in conn_handler before killing neverbleed
+ // daemon. Otherwise priv_rsa_finish yields "write error" and
+ // worker process aborts.
+ conn_handler.reset();
+
+#ifdef HAVE_NEVERBLEED
+ assert(nb->daemon_pid > 0);
+
+ rv = kill(nb->daemon_pid, SIGTERM);
+ if (rv != 0) {
+ auto error = errno;
+ LOG(ERROR) << "Could not send signal to neverbleed daemon: errno=" << error;
+ }
+
+ while ((rv = waitpid(nb->daemon_pid, nullptr, 0)) == -1 && errno == EINTR)
+ ;
+ if (rv == -1) {
+ auto error = errno;
+ LOG(ERROR) << "Error occurred while we were waiting for the completion "
+ "of neverbleed process: errno="
+ << error;
+ }
+#endif // HAVE_NEVERBLEED
+
+ ares_library_cleanup();
+
+ return 0;
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_worker_process.h b/src/shrpx_worker_process.h
new file mode 100644
index 0000000..f432503
--- /dev/null
+++ b/src/shrpx_worker_process.h
@@ -0,0 +1,67 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_WORKER_PROCESS_H
+#define SHRPX_WORKER_PROCESS_H
+
+#include "shrpx.h"
+
+#include <vector>
+#include <array>
+
+#include "shrpx_connection_handler.h"
+#ifdef ENABLE_HTTP3
+# include "shrpx_quic.h"
+#endif // ENABLE_HTTP3
+
+namespace shrpx {
+
+class ConnectionHandler;
+
+struct WorkerProcessConfig {
+ // IPC socket to read event from main process
+ int ipc_fd;
+ // IPC socket to tell that a worker process is ready for service.
+ int ready_ipc_fd;
+ // IPv4 or UNIX domain socket, or -1 if not used
+ int server_fd;
+ // IPv6 socket, or -1 if not used
+ int server_fd6;
+#ifdef ENABLE_HTTP3
+ // CID prefixes for the new worker process.
+ std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>> cid_prefixes;
+ // IPC socket to read forwarded QUIC UDP datagram from the current
+ // worker process.
+ int quic_ipc_fd;
+ // Lingering worker processes which were created before this worker
+ // process to forward QUIC UDP datagram during reload.
+ std::vector<QUICLingeringWorkerProcess> quic_lingering_worker_processes;
+#endif // ENABLE_HTTP3
+};
+
+int worker_process_event_loop(WorkerProcessConfig *wpconf);
+
+} // namespace shrpx
+
+#endif // SHRPX_WORKER_PROCESS_H
diff --git a/src/shrpx_worker_test.cc b/src/shrpx_worker_test.cc
new file mode 100644
index 0000000..7c5c329
--- /dev/null
+++ b/src/shrpx_worker_test.cc
@@ -0,0 +1,247 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_worker_test.h"
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+
+#include <cstdlib>
+
+#include <CUnit/CUnit.h>
+
+#include "shrpx_worker.h"
+#include "shrpx_connect_blocker.h"
+#include "shrpx_log.h"
+
+namespace shrpx {
+
+void test_shrpx_worker_match_downstream_addr_group(void) {
+ auto groups = std::vector<std::shared_ptr<DownstreamAddrGroup>>();
+ for (auto &s : {"nghttp2.org/", "nghttp2.org/alpha/bravo/",
+ "nghttp2.org/alpha/charlie", "nghttp2.org/delta%3A",
+ "www.nghttp2.org/", "[::1]/", "nghttp2.org/alpha/bravo/delta",
+ // Check that match is done in the single node
+ "example.com/alpha/bravo", "192.168.0.1/alpha/", "/golf/"}) {
+ auto g = std::make_shared<DownstreamAddrGroup>();
+ g->pattern = ImmutableString(s);
+ groups.push_back(std::move(g));
+ }
+
+ BlockAllocator balloc(1024, 1024);
+ RouterConfig routerconf;
+
+ auto &router = routerconf.router;
+ auto &wcrouter = routerconf.rev_wildcard_router;
+ auto &wp = routerconf.wildcard_patterns;
+
+ for (size_t i = 0; i < groups.size(); ++i) {
+ auto &g = groups[i];
+ router.add_route(StringRef{g->pattern}, i);
+ }
+
+ CU_ASSERT(0 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/"), groups, 255, balloc));
+
+ // port is removed
+ CU_ASSERT(0 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("nghttp2.org:8080"),
+ StringRef::from_lit("/"), groups, 255, balloc));
+
+ // host is case-insensitive
+ CU_ASSERT(4 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("WWW.nghttp2.org"),
+ StringRef::from_lit("/alpha"), groups, 255, balloc));
+
+ CU_ASSERT(1 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/alpha/bravo/"), groups, 255,
+ balloc));
+
+ // /alpha/bravo also matches /alpha/bravo/
+ CU_ASSERT(1 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/alpha/bravo"), groups, 255, balloc));
+
+ // path part is case-sensitive
+ CU_ASSERT(0 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/Alpha/bravo"), groups, 255, balloc));
+
+ CU_ASSERT(1 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/alpha/bravo/charlie"), groups, 255,
+ balloc));
+
+ CU_ASSERT(2 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/alpha/charlie"), groups, 255,
+ balloc));
+
+ // pattern which does not end with '/' must match its entirely. So
+ // this matches to group 0, not group 2.
+ CU_ASSERT(0 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/alpha/charlie/"), groups, 255,
+ balloc));
+
+ CU_ASSERT(255 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("example.org"),
+ StringRef::from_lit("/"), groups, 255, balloc));
+
+ CU_ASSERT(255 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit(""),
+ StringRef::from_lit("/"), groups, 255, balloc));
+
+ CU_ASSERT(255 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit(""),
+ StringRef::from_lit("alpha"), groups, 255, balloc));
+
+ CU_ASSERT(255 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("foo/bar"),
+ StringRef::from_lit("/"), groups, 255, balloc));
+
+ // If path is StringRef::from_lit("*", only match with host + "/").
+ CU_ASSERT(0 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("*"), groups, 255, balloc));
+
+ CU_ASSERT(5 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("[::1]"),
+ StringRef::from_lit("/"), groups, 255, balloc));
+ CU_ASSERT(5 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("[::1]:8080"),
+ StringRef::from_lit("/"), groups, 255, balloc));
+ CU_ASSERT(255 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("[::1"),
+ StringRef::from_lit("/"), groups, 255, balloc));
+ CU_ASSERT(255 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("[::1]8000"),
+ StringRef::from_lit("/"), groups, 255, balloc));
+
+ // Check the case where adding route extends tree
+ CU_ASSERT(6 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/alpha/bravo/delta"), groups, 255,
+ balloc));
+
+ CU_ASSERT(1 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/alpha/bravo/delta/"), groups, 255,
+ balloc));
+
+ // Check the case where query is done in a single node
+ CU_ASSERT(7 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("example.com"),
+ StringRef::from_lit("/alpha/bravo"), groups, 255, balloc));
+
+ CU_ASSERT(255 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("example.com"),
+ StringRef::from_lit("/alpha/bravo/"), groups, 255,
+ balloc));
+
+ CU_ASSERT(255 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("example.com"),
+ StringRef::from_lit("/alpha"), groups, 255, balloc));
+
+ // Check the case where quey is done in a single node
+ CU_ASSERT(8 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("192.168.0.1"),
+ StringRef::from_lit("/alpha"), groups, 255, balloc));
+
+ CU_ASSERT(8 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("192.168.0.1"),
+ StringRef::from_lit("/alpha/"), groups, 255, balloc));
+
+ CU_ASSERT(8 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("192.168.0.1"),
+ StringRef::from_lit("/alpha/bravo"), groups, 255, balloc));
+
+ CU_ASSERT(255 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("192.168.0.1"),
+ StringRef::from_lit("/alph"), groups, 255, balloc));
+
+ CU_ASSERT(255 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("192.168.0.1"),
+ StringRef::from_lit("/"), groups, 255, balloc));
+
+ // Test for wildcard hosts
+ auto g1 = std::make_shared<DownstreamAddrGroup>();
+ g1->pattern = ImmutableString::from_lit("git.nghttp2.org");
+ groups.push_back(std::move(g1));
+
+ auto g2 = std::make_shared<DownstreamAddrGroup>();
+ g2->pattern = ImmutableString::from_lit(".nghttp2.org");
+ groups.push_back(std::move(g2));
+
+ auto g3 = std::make_shared<DownstreamAddrGroup>();
+ g3->pattern = ImmutableString::from_lit(".local");
+ groups.push_back(std::move(g3));
+
+ wp.emplace_back(StringRef::from_lit("git.nghttp2.org"));
+ wcrouter.add_route(StringRef::from_lit("gro.2ptthgn.tig"), 0);
+ wp.back().router.add_route(StringRef::from_lit("/echo/"), 10);
+
+ wp.emplace_back(StringRef::from_lit(".nghttp2.org"));
+ wcrouter.add_route(StringRef::from_lit("gro.2ptthgn."), 1);
+ wp.back().router.add_route(StringRef::from_lit("/echo/"), 11);
+ wp.back().router.add_route(StringRef::from_lit("/echo/foxtrot"), 12);
+
+ wp.emplace_back(StringRef::from_lit(".local"));
+ wcrouter.add_route(StringRef::from_lit("lacol."), 2);
+ wp.back().router.add_route(StringRef::from_lit("/"), 13);
+
+ CU_ASSERT(11 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("git.nghttp2.org"),
+ StringRef::from_lit("/echo"), groups, 255, balloc));
+
+ CU_ASSERT(10 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("0git.nghttp2.org"),
+ StringRef::from_lit("/echo"), groups, 255, balloc));
+
+ CU_ASSERT(11 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("it.nghttp2.org"),
+ StringRef::from_lit("/echo"), groups, 255, balloc));
+
+ CU_ASSERT(255 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit(".nghttp2.org"),
+ StringRef::from_lit("/echo/foxtrot"), groups, 255,
+ balloc));
+
+ CU_ASSERT(9 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("alpha.nghttp2.org"),
+ StringRef::from_lit("/golf"), groups, 255, balloc));
+
+ CU_ASSERT(0 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/echo"), groups, 255, balloc));
+
+ CU_ASSERT(13 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("test.local"),
+ StringRef{}, groups, 255, balloc));
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_worker_test.h b/src/shrpx_worker_test.h
new file mode 100644
index 0000000..8ffa2f1
--- /dev/null
+++ b/src/shrpx_worker_test.h
@@ -0,0 +1,38 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_WORKER_TEST_H
+#define SHRPX_WORKER_TEST_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+namespace shrpx {
+
+void test_shrpx_worker_match_downstream_addr_group(void);
+
+} // namespace shrpx
+
+#endif // SHRPX_WORKER_TEST_H
diff --git a/src/ssl_compat.h b/src/ssl_compat.h
new file mode 100644
index 0000000..f691104
--- /dev/null
+++ b/src/ssl_compat.h
@@ -0,0 +1,48 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef OPENSSL_COMPAT_H
+
+# include <openssl/opensslv.h>
+
+# ifdef LIBRESSL_VERSION_NUMBER
+# define NGHTTP2_OPENSSL_IS_LIBRESSL
+# endif // !LIBRESSL_VERSION_NUMBER
+
+# if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC)
+# define NGHTTP2_OPENSSL_IS_BORINGSSL
+# endif // OPENSSL_IS_BORINGSSL || OPENSSL_IS_AWSLC
+
+# if !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) && \
+ !defined(NGHTTP2_OPENSSL_IS_LIBRESSL)
+# define NGHTTP2_GENUINE_OPENSSL
+# endif // !NGHTTP2_OPENSSL_IS_BORINGSSL && !NGHTTP2_OPENSSL_IS_LIBRESSL
+
+# ifdef NGHTTP2_GENUINE_OPENSSL
+# define OPENSSL_3_0_0_API (OPENSSL_VERSION_NUMBER >= 0x30000000L)
+# else // !NGHTTP2_GENUINE_OPENSSL
+# define OPENSSL_3_0_0_API 0
+# endif // !NGHTTP2_GENUINE_OPENSSL
+
+#endif // OPENSSL_COMPAT_H
diff --git a/src/template.h b/src/template.h
new file mode 100644
index 0000000..530a1d1
--- /dev/null
+++ b/src/template.h
@@ -0,0 +1,548 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef TEMPLATE_H
+#define TEMPLATE_H
+
+#include "nghttp2_config.h"
+
+#include <cstring>
+#include <cstdio>
+#include <cstdlib>
+#include <memory>
+#include <array>
+#include <functional>
+#include <typeinfo>
+#include <algorithm>
+#include <ostream>
+#include <utility>
+
+namespace nghttp2 {
+
+// std::forward is constexpr since C++14
+template <typename... T>
+constexpr std::array<
+ typename std::decay<typename std::common_type<T...>::type>::type,
+ sizeof...(T)>
+make_array(T &&...t) {
+ return std::array<
+ typename std::decay<typename std::common_type<T...>::type>::type,
+ sizeof...(T)>{{std::forward<T>(t)...}};
+}
+
+template <typename T, size_t N> constexpr size_t array_size(T (&)[N]) {
+ return N;
+}
+
+template <typename T, size_t N> constexpr size_t str_size(T (&)[N]) {
+ return N - 1;
+}
+
+// inspired by <http://blog.korfuri.fr/post/go-defer-in-cpp/>, but our
+// template can take functions returning other than void.
+template <typename F, typename... T> struct Defer {
+ Defer(F &&f, T &&...t)
+ : f(std::bind(std::forward<F>(f), std::forward<T>(t)...)) {}
+ Defer(Defer &&o) noexcept : f(std::move(o.f)) {}
+ ~Defer() { f(); }
+
+ using ResultType = typename std::result_of<typename std::decay<F>::type(
+ typename std::decay<T>::type...)>::type;
+ std::function<ResultType()> f;
+};
+
+template <typename F, typename... T> Defer<F, T...> defer(F &&f, T &&...t) {
+ return Defer<F, T...>(std::forward<F>(f), std::forward<T>(t)...);
+}
+
+template <typename T, typename F> bool test_flags(T t, F flags) {
+ return (t & flags) == flags;
+}
+
+// doubly linked list of element T*. T must have field T *dlprev and
+// T *dlnext, which point to previous element and next element in the
+// list respectively.
+template <typename T> struct DList {
+ DList() : head(nullptr), tail(nullptr), len(0) {}
+
+ DList(const DList &) = delete;
+ DList &operator=(const DList &) = delete;
+
+ DList(DList &&other) noexcept
+ : head{std::exchange(other.head, nullptr)},
+ tail{std::exchange(other.tail, nullptr)},
+ len{std::exchange(other.len, 0)} {}
+
+ DList &operator=(DList &&other) noexcept {
+ if (this == &other) {
+ return *this;
+ }
+ head = std::exchange(other.head, nullptr);
+ tail = std::exchange(other.tail, nullptr);
+ len = std::exchange(other.len, 0);
+
+ return *this;
+ }
+
+ void append(T *t) {
+ ++len;
+ if (tail) {
+ tail->dlnext = t;
+ t->dlprev = tail;
+ tail = t;
+ return;
+ }
+ head = tail = t;
+ }
+
+ void remove(T *t) {
+ --len;
+ auto p = t->dlprev;
+ auto n = t->dlnext;
+ if (p) {
+ p->dlnext = n;
+ }
+ if (head == t) {
+ head = n;
+ }
+ if (n) {
+ n->dlprev = p;
+ }
+ if (tail == t) {
+ tail = p;
+ }
+ t->dlprev = t->dlnext = nullptr;
+ }
+
+ bool empty() const { return head == nullptr; }
+
+ size_t size() const { return len; }
+
+ T *head, *tail;
+ size_t len;
+};
+
+template <typename T> void dlist_delete_all(DList<T> &dl) {
+ for (auto e = dl.head; e;) {
+ auto next = e->dlnext;
+ delete e;
+ e = next;
+ }
+}
+
+// User-defined literals for K, M, and G (powers of 1024)
+
+constexpr unsigned long long operator"" _k(unsigned long long k) {
+ return k * 1024;
+}
+
+constexpr unsigned long long operator"" _m(unsigned long long m) {
+ return m * 1024 * 1024;
+}
+
+constexpr unsigned long long operator"" _g(unsigned long long g) {
+ return g * 1024 * 1024 * 1024;
+}
+
+// User-defined literals for time, converted into double in seconds
+
+// hours
+constexpr double operator"" _h(unsigned long long h) { return h * 60 * 60; }
+
+// minutes
+constexpr double operator"" _min(unsigned long long min) { return min * 60; }
+
+// seconds
+constexpr double operator"" _s(unsigned long long s) { return s; }
+
+// milliseconds
+constexpr double operator"" _ms(unsigned long long ms) { return ms / 1000.; }
+
+// Returns a copy of NULL-terminated string [first, last).
+template <typename InputIt>
+std::unique_ptr<char[]> strcopy(InputIt first, InputIt last) {
+ auto res = std::make_unique<char[]>(last - first + 1);
+ *std::copy(first, last, res.get()) = '\0';
+ return res;
+}
+
+// Returns a copy of NULL-terminated string |val|.
+inline std::unique_ptr<char[]> strcopy(const char *val) {
+ return strcopy(val, val + strlen(val));
+}
+
+inline std::unique_ptr<char[]> strcopy(const char *val, size_t n) {
+ return strcopy(val, val + n);
+}
+
+// Returns a copy of val.c_str().
+inline std::unique_ptr<char[]> strcopy(const std::string &val) {
+ return strcopy(std::begin(val), std::end(val));
+}
+
+inline std::unique_ptr<char[]> strcopy(const std::unique_ptr<char[]> &val) {
+ if (!val) {
+ return nullptr;
+ }
+ return strcopy(val.get());
+}
+
+inline std::unique_ptr<char[]> strcopy(const std::unique_ptr<char[]> &val,
+ size_t n) {
+ if (!val) {
+ return nullptr;
+ }
+ return strcopy(val.get(), val.get() + n);
+}
+
+// ImmutableString represents string that is immutable unlike
+// std::string. It has c_str() and size() functions to mimic
+// std::string. It manages buffer by itself. Just like std::string,
+// c_str() returns NULL-terminated string, but NULL character may
+// appear before the final terminal NULL.
+class ImmutableString {
+public:
+ using traits_type = std::char_traits<char>;
+ using value_type = traits_type::char_type;
+ using allocator_type = std::allocator<char>;
+ using size_type = std::allocator_traits<allocator_type>::size_type;
+ using difference_type =
+ std::allocator_traits<allocator_type>::difference_type;
+ using const_reference = const value_type &;
+ using const_pointer = const value_type *;
+ using const_iterator = const_pointer;
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+ ImmutableString() : len(0), base("") {}
+ ImmutableString(const char *s, size_t slen)
+ : len(slen), base(copystr(s, s + len)) {}
+ explicit ImmutableString(const char *s)
+ : len(strlen(s)), base(copystr(s, s + len)) {}
+ explicit ImmutableString(const std::string &s)
+ : len(s.size()), base(copystr(std::begin(s), std::end(s))) {}
+ template <typename InputIt>
+ ImmutableString(InputIt first, InputIt last)
+ : len(std::distance(first, last)), base(copystr(first, last)) {}
+ ImmutableString(const ImmutableString &other)
+ : len(other.len), base(copystr(std::begin(other), std::end(other))) {}
+ ImmutableString(ImmutableString &&other) noexcept
+ : len{std::exchange(other.len, 0)}, base{std::exchange(other.base, "")} {}
+ ~ImmutableString() {
+ if (len) {
+ delete[] base;
+ }
+ }
+
+ ImmutableString &operator=(const ImmutableString &other) {
+ if (this == &other) {
+ return *this;
+ }
+ if (len) {
+ delete[] base;
+ }
+ len = other.len;
+ base = copystr(std::begin(other), std::end(other));
+ return *this;
+ }
+ ImmutableString &operator=(ImmutableString &&other) noexcept {
+ if (this == &other) {
+ return *this;
+ }
+ if (len) {
+ delete[] base;
+ }
+ len = std::exchange(other.len, 0);
+ base = std::exchange(other.base, "");
+ return *this;
+ }
+
+ template <size_t N> static ImmutableString from_lit(const char (&s)[N]) {
+ return ImmutableString(s, N - 1);
+ }
+
+ const_iterator begin() const { return base; };
+ const_iterator cbegin() const { return base; };
+
+ const_iterator end() const { return base + len; };
+ const_iterator cend() const { return base + len; };
+
+ const_reverse_iterator rbegin() const {
+ return const_reverse_iterator{base + len};
+ }
+ const_reverse_iterator crbegin() const {
+ return const_reverse_iterator{base + len};
+ }
+
+ const_reverse_iterator rend() const { return const_reverse_iterator{base}; }
+ const_reverse_iterator crend() const { return const_reverse_iterator{base}; }
+
+ const char *c_str() const { return base; }
+ size_type size() const { return len; }
+ bool empty() const { return len == 0; }
+ const_reference operator[](size_type pos) const { return *(base + pos); }
+
+private:
+ template <typename InputIt> const char *copystr(InputIt first, InputIt last) {
+ if (first == last) {
+ return "";
+ }
+ auto res = new char[std::distance(first, last) + 1];
+ *std::copy(first, last, res) = '\0';
+ return res;
+ }
+
+ size_type len;
+ const char *base;
+};
+
+inline bool operator==(const ImmutableString &lhs, const ImmutableString &rhs) {
+ return lhs.size() == rhs.size() &&
+ std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs));
+}
+
+inline bool operator==(const ImmutableString &lhs, const std::string &rhs) {
+ return lhs.size() == rhs.size() &&
+ std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs));
+}
+
+inline bool operator==(const std::string &lhs, const ImmutableString &rhs) {
+ return rhs == lhs;
+}
+
+inline bool operator==(const ImmutableString &lhs, const char *rhs) {
+ return lhs.size() == strlen(rhs) &&
+ std::equal(std::begin(lhs), std::end(lhs), rhs);
+}
+
+inline bool operator==(const char *lhs, const ImmutableString &rhs) {
+ return rhs == lhs;
+}
+
+inline bool operator!=(const ImmutableString &lhs, const ImmutableString &rhs) {
+ return !(lhs == rhs);
+}
+
+inline bool operator!=(const ImmutableString &lhs, const std::string &rhs) {
+ return !(lhs == rhs);
+}
+
+inline bool operator!=(const std::string &lhs, const ImmutableString &rhs) {
+ return !(rhs == lhs);
+}
+
+inline bool operator!=(const ImmutableString &lhs, const char *rhs) {
+ return !(lhs == rhs);
+}
+
+inline bool operator!=(const char *lhs, const ImmutableString &rhs) {
+ return !(rhs == lhs);
+}
+
+inline std::ostream &operator<<(std::ostream &o, const ImmutableString &s) {
+ return o.write(s.c_str(), s.size());
+}
+
+inline std::string &operator+=(std::string &lhs, const ImmutableString &rhs) {
+ lhs.append(rhs.c_str(), rhs.size());
+ return lhs;
+}
+
+// StringRef is a reference to a string owned by something else. So
+// it behaves like simple string, but it does not own pointer. When
+// it is default constructed, it has empty string. You can freely
+// copy or move around this struct, but never free its pointer. str()
+// function can be used to export the content as std::string.
+class StringRef {
+public:
+ using traits_type = std::char_traits<char>;
+ using value_type = traits_type::char_type;
+ using allocator_type = std::allocator<char>;
+ using size_type = std::allocator_traits<allocator_type>::size_type;
+ using difference_type =
+ std::allocator_traits<allocator_type>::difference_type;
+ using const_reference = const value_type &;
+ using const_pointer = const value_type *;
+ using const_iterator = const_pointer;
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+ constexpr StringRef() : base(""), len(0) {}
+ explicit StringRef(const std::string &s) : base(s.c_str()), len(s.size()) {}
+ explicit StringRef(const ImmutableString &s)
+ : base(s.c_str()), len(s.size()) {}
+ explicit StringRef(const char *s) : base(s), len(strlen(s)) {}
+ constexpr StringRef(const char *s, size_t n) : base(s), len(n) {}
+ template <typename CharT>
+ constexpr StringRef(const CharT *s, size_t n)
+ : base(reinterpret_cast<const char *>(s)), len(n) {}
+ template <typename InputIt>
+ StringRef(InputIt first, InputIt last)
+ : base(reinterpret_cast<const char *>(&*first)),
+ len(std::distance(first, last)) {}
+ template <typename InputIt>
+ StringRef(InputIt *first, InputIt *last)
+ : base(reinterpret_cast<const char *>(first)),
+ len(std::distance(first, last)) {}
+ template <typename CharT, size_t N>
+ constexpr static StringRef from_lit(const CharT (&s)[N]) {
+ return StringRef{s, N - 1};
+ }
+ static StringRef from_maybe_nullptr(const char *s) {
+ if (s == nullptr) {
+ return StringRef();
+ }
+
+ return StringRef(s);
+ }
+
+ constexpr const_iterator begin() const { return base; };
+ constexpr const_iterator cbegin() const { return base; };
+
+ constexpr const_iterator end() const { return base + len; };
+ constexpr const_iterator cend() const { return base + len; };
+
+ const_reverse_iterator rbegin() const {
+ return const_reverse_iterator{base + len};
+ }
+ const_reverse_iterator crbegin() const {
+ return const_reverse_iterator{base + len};
+ }
+
+ const_reverse_iterator rend() const { return const_reverse_iterator{base}; }
+ const_reverse_iterator crend() const { return const_reverse_iterator{base}; }
+
+ constexpr const char *c_str() const { return base; }
+ constexpr size_type size() const { return len; }
+ constexpr bool empty() const { return len == 0; }
+ constexpr const_reference operator[](size_type pos) const {
+ return *(base + pos);
+ }
+
+ std::string str() const { return std::string(base, len); }
+ const uint8_t *byte() const {
+ return reinterpret_cast<const uint8_t *>(base);
+ }
+
+private:
+ const char *base;
+ size_type len;
+};
+
+inline bool operator==(const StringRef &lhs, const StringRef &rhs) {
+ return lhs.size() == rhs.size() &&
+ std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs));
+}
+
+inline bool operator==(const StringRef &lhs, const std::string &rhs) {
+ return lhs.size() == rhs.size() &&
+ std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs));
+}
+
+inline bool operator==(const std::string &lhs, const StringRef &rhs) {
+ return rhs == lhs;
+}
+
+inline bool operator==(const StringRef &lhs, const char *rhs) {
+ return lhs.size() == strlen(rhs) &&
+ std::equal(std::begin(lhs), std::end(lhs), rhs);
+}
+
+inline bool operator==(const StringRef &lhs, const ImmutableString &rhs) {
+ return lhs.size() == rhs.size() &&
+ std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs));
+}
+
+inline bool operator==(const ImmutableString &lhs, const StringRef &rhs) {
+ return rhs == lhs;
+}
+
+inline bool operator==(const char *lhs, const StringRef &rhs) {
+ return rhs == lhs;
+}
+
+inline bool operator!=(const StringRef &lhs, const StringRef &rhs) {
+ return !(lhs == rhs);
+}
+
+inline bool operator!=(const StringRef &lhs, const std::string &rhs) {
+ return !(lhs == rhs);
+}
+
+inline bool operator!=(const std::string &lhs, const StringRef &rhs) {
+ return !(rhs == lhs);
+}
+
+inline bool operator!=(const StringRef &lhs, const char *rhs) {
+ return !(lhs == rhs);
+}
+
+inline bool operator!=(const char *lhs, const StringRef &rhs) {
+ return !(rhs == lhs);
+}
+
+inline bool operator<(const StringRef &lhs, const StringRef &rhs) {
+ return std::lexicographical_compare(std::begin(lhs), std::end(lhs),
+ std::begin(rhs), std::end(rhs));
+}
+
+inline std::ostream &operator<<(std::ostream &o, const StringRef &s) {
+ return o.write(s.c_str(), s.size());
+}
+
+inline std::string &operator+=(std::string &lhs, const StringRef &rhs) {
+ lhs.append(rhs.c_str(), rhs.size());
+ return lhs;
+}
+
+inline int run_app(std::function<int(int, char **)> app, int argc,
+ char **argv) {
+ try {
+ return app(argc, argv);
+ } catch (const std::bad_alloc &) {
+ fputs("Out of memory\n", stderr);
+ } catch (const std::exception &x) {
+ fprintf(stderr, "Caught %s:\n%s\n", typeid(x).name(), x.what());
+ } catch (...) {
+ fputs("Unknown exception caught\n", stderr);
+ }
+ return EXIT_FAILURE;
+}
+
+} // namespace nghttp2
+
+namespace std {
+template <> struct hash<nghttp2::StringRef> {
+ std::size_t operator()(const nghttp2::StringRef &s) const noexcept {
+ // 32 bit FNV-1a:
+ // https://tools.ietf.org/html/draft-eastlake-fnv-16#section-6.1.1
+ uint32_t h = 2166136261u;
+ for (auto c : s) {
+ h ^= static_cast<uint8_t>(c);
+ h += (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24);
+ }
+ return h;
+ }
+};
+} // namespace std
+
+#endif // TEMPLATE_H
diff --git a/src/template_test.cc b/src/template_test.cc
new file mode 100644
index 0000000..4a77315
--- /dev/null
+++ b/src/template_test.cc
@@ -0,0 +1,204 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "template_test.h"
+
+#include <cstring>
+#include <iostream>
+#include <sstream>
+
+#include <CUnit/CUnit.h>
+
+#include "template.h"
+
+namespace nghttp2 {
+
+void test_template_immutable_string(void) {
+ ImmutableString null;
+
+ CU_ASSERT("" == null);
+ CU_ASSERT(0 == null.size());
+ CU_ASSERT(null.empty());
+
+ ImmutableString from_cstr("alpha");
+
+ CU_ASSERT(0 == strcmp("alpha", from_cstr.c_str()));
+ CU_ASSERT(5 == from_cstr.size());
+ CU_ASSERT(!from_cstr.empty());
+ CU_ASSERT("alpha" == from_cstr);
+ CU_ASSERT(from_cstr == "alpha");
+ CU_ASSERT(std::string("alpha") == from_cstr);
+ CU_ASSERT(from_cstr == std::string("alpha"));
+
+ // copy constructor
+ ImmutableString src("charlie");
+ ImmutableString copy = src;
+
+ CU_ASSERT("charlie" == copy);
+ CU_ASSERT(7 == copy.size());
+
+ // copy assignment
+ ImmutableString copy2;
+ copy2 = src;
+
+ CU_ASSERT("charlie" == copy2);
+ CU_ASSERT(7 == copy2.size());
+
+ // move constructor
+ ImmutableString move = std::move(copy);
+
+ CU_ASSERT("charlie" == move);
+ CU_ASSERT(7 == move.size());
+ CU_ASSERT("" == copy);
+ CU_ASSERT(0 == copy.size());
+
+ // move assignment
+ move = std::move(from_cstr);
+
+ CU_ASSERT("alpha" == move);
+ CU_ASSERT(5 == move.size());
+ CU_ASSERT("" == from_cstr);
+ CU_ASSERT(0 == from_cstr.size());
+
+ // from string literal
+ auto from_lit = StringRef::from_lit("bravo");
+
+ CU_ASSERT("bravo" == from_lit);
+ CU_ASSERT(5 == from_lit.size());
+
+ // equality
+ ImmutableString eq("delta");
+
+ CU_ASSERT("delta1" != eq);
+ CU_ASSERT("delt" != eq);
+ CU_ASSERT(eq != "delta1");
+ CU_ASSERT(eq != "delt");
+
+ // operator[]
+ ImmutableString br_op("foxtrot");
+
+ CU_ASSERT('f' == br_op[0]);
+ CU_ASSERT('o' == br_op[1]);
+ CU_ASSERT('t' == br_op[6]);
+ CU_ASSERT('\0' == br_op[7]);
+
+ // operator==(const ImmutableString &, const ImmutableString &)
+ {
+ ImmutableString a("foo");
+ ImmutableString b("foo");
+ ImmutableString c("fo");
+
+ CU_ASSERT(a == b);
+ CU_ASSERT(a != c);
+ CU_ASSERT(c != b);
+ }
+
+ // operator<<
+ {
+ ImmutableString a("foo");
+ std::stringstream ss;
+ ss << a;
+
+ CU_ASSERT("foo" == ss.str());
+ }
+
+ // operator +=(std::string &, const ImmutableString &)
+ {
+ std::string a = "alpha";
+ a += ImmutableString("bravo");
+
+ CU_ASSERT("alphabravo" == a);
+ }
+}
+
+void test_template_string_ref(void) {
+ StringRef empty;
+
+ CU_ASSERT("" == empty);
+ CU_ASSERT(0 == empty.size());
+
+ // from std::string
+ std::string alpha = "alpha";
+
+ StringRef ref(alpha);
+
+ CU_ASSERT("alpha" == ref);
+ CU_ASSERT(ref == "alpha");
+ CU_ASSERT(alpha == ref);
+ CU_ASSERT(ref == alpha);
+ CU_ASSERT(5 == ref.size());
+
+ // from string literal
+ auto from_lit = StringRef::from_lit("alpha");
+
+ CU_ASSERT("alpha" == from_lit);
+ CU_ASSERT(5 == from_lit.size());
+
+ // from ImmutableString
+ auto im = ImmutableString::from_lit("bravo");
+
+ StringRef imref(im);
+
+ CU_ASSERT("bravo" == imref);
+ CU_ASSERT(5 == imref.size());
+
+ // from C-string
+ StringRef cstrref("charlie");
+
+ CU_ASSERT("charlie" == cstrref);
+ CU_ASSERT(7 == cstrref.size());
+
+ // from C-string and its length
+ StringRef cstrnref("delta", 5);
+
+ CU_ASSERT("delta" == cstrnref);
+ CU_ASSERT(5 == cstrnref.size());
+
+ // operator[]
+ StringRef br_op("foxtrot");
+
+ CU_ASSERT('f' == br_op[0]);
+ CU_ASSERT('o' == br_op[1]);
+ CU_ASSERT('t' == br_op[6]);
+ CU_ASSERT('\0' == br_op[7]);
+
+ // operator<<
+ {
+ StringRef a("foo");
+ std::stringstream ss;
+ ss << a;
+
+ CU_ASSERT("foo" == ss.str());
+ }
+
+ // operator +=(std::string &, const StringRef &)
+ {
+ std::string a = "alpha";
+ a += StringRef("bravo");
+
+ CU_ASSERT("alphabravo" == a);
+ }
+}
+
+} // namespace nghttp2
diff --git a/src/template_test.h b/src/template_test.h
new file mode 100644
index 0000000..2c1448f
--- /dev/null
+++ b/src/template_test.h
@@ -0,0 +1,39 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef TEMPLATE_TEST_H
+#define TEMPLATE_TEST_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+namespace nghttp2 {
+
+void test_template_immutable_string(void);
+void test_template_string_ref(void);
+
+} // namespace nghttp2
+
+#endif // TEMPLATE_TEST_H
diff --git a/src/test.example.com-key.pem b/src/test.example.com-key.pem
new file mode 100644
index 0000000..6d5515c
--- /dev/null
+++ b/src/test.example.com-key.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEArf2UBsEh/xwd/4WZfVFf5sMyWcns/1idF2FroLDwqVUYRlxp
+U/KbrIG8X8v3w4cVP/xOXd1y9Q+W9OLK2YScAeHeE97mHZXbcpowUxvTDv/GNIHH
+XK/yvYM8R2EEnZR71qXdoXsCakv/aG2ewkkvA108eEbk0u7RxNmIsYsO0Y4iBtwB
+M/MSkAYPfWdrdHhY9z0l4M8GAyUOuZc0t6j0zw3fzkjqmVgGEJvcVzvSgZIzMLqS
+zvC89viXtj1pyzQjMLgmuDbs0l47uRHpZXgMGVyF3UuQipPzvhO7G/ZbX/4kUU5b
+PabdtvPbjju7dE5PfGrKOThM6HS93Y7QvYTUtwIDAQABAoIBAQCjUL69iFOs7muK
+CZGFe/iU1uxQM6XuGPN7mso3z15W07UxdlS3o6ZUSoLTONWcBxP/N4knulHJjZSY
+0LivbDYz3htic3t0kdGmxOxPVnLKRXN6ncbQTaeAE8tlBMAcWd/UH2Tlylz+Ac//
+6cV3gNJMShwUmhb3l4v3Rml0nZ6PO1pFc/Chk5L9REAV8G6rNtc9bzgmgoFucRO/
+8ce/uJrENt1Pu3vBvmz42DTGfG48v5RZ0OY4qEPawZJ7p+QYiTf6h3Eilss/AllW
+PPfQ0thdyB+yrZ3p6qb+ZUYphpGxgg6YlQxLfDKAikuo+EXwjPBPfeHhTO4kAj+h
+opDCroZhAoGBANyVFbagCWqwguE6nVPmnCaiNQUIh8b7L2CnkkLfdbPQr/KvyIjg
+Ua125bTJhe9Uk+ZBWsobQkjA0Baidiylx51pWYaxPVn5araVmkh2dqMluk2QE82X
+AWemBgKhAqCLLLMVXbrRYlxpKUm1Fc/lJ8Ig2R/MJSntTMpQhJtIejUbAoGBAMnt
+XMvlFABCoFbI9GMcteI/KkvNGQUy3OKEln/QCssnE4/XIu7LCxy6P+1lycbFy/mQ
+0bnp525sPEIIkMpi6LeAbSzYN2O3BRjNrjPcbx6Khz9DweNhRIo5qTFRszZ+pHbV
+N+9Oc9JVenwPw6EuW7uZRFKFhCHtsBFdUrWLJoSVAoGAQ3ytdwGBwA2fDW/UgL32
+mm9YT2DrwbpKJYU/X4xkw44ett6HOTGAa9ULtINPogi7c2AdeeZbIk0znSk5hLF3
+4DZCOM5zWdrQhmpBGNh9ta6uUFq7ZFRGDsMh5Z4DYsER/PyVf7neIS3ffviTYtbW
+kjNgmrTnzesXanK2D5heI28CgYEAhl+qjRTYhoPP53C7EOmeL/0QzHij2c3LKAJL
+lKqBREewwNvNp1L/BhL7T6OY7unZny48IpgBJn5oaxkAIW5IpzSTcnBAC99TSPo2
+ntRmLdDJx9PzRrkHv2Q3r1ZLCEymbV3eZyWx9ZpkdAKZkL0k1mZcDP5Eu79Ml4Ge
+9Kiw7TECgYEAh+nTKwrCUFGbe4RIVCj/QG7FVPbq5PdxJ3gILZ3/1XkhPcNRFKJS
+u5qPfA02tYEALz9KXATK1uRB/GlBM7Eap/g2GFiHpVxrw6wPpybLywJmNyNTwqiq
+eJxQ0FRzW9Kwwn1ThPY38LdFe/wvXZFOcNvGD8hHCLQRdlBR4zuTsBk=
+-----END RSA PRIVATE KEY-----
diff --git a/src/test.example.com.csr b/src/test.example.com.csr
new file mode 100644
index 0000000..8163935
--- /dev/null
+++ b/src/test.example.com.csr
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIICpTCCAY0CAQAwYDELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUx
+ITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEZMBcGA1UEAxMQdGVz
+dC5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK39
+lAbBIf8cHf+FmX1RX+bDMlnJ7P9YnRdha6Cw8KlVGEZcaVPym6yBvF/L98OHFT/8
+Tl3dcvUPlvTiytmEnAHh3hPe5h2V23KaMFMb0w7/xjSBx1yv8r2DPEdhBJ2Ue9al
+3aF7AmpL/2htnsJJLwNdPHhG5NLu0cTZiLGLDtGOIgbcATPzEpAGD31na3R4WPc9
+JeDPBgMlDrmXNLeo9M8N385I6plYBhCb3Fc70oGSMzC6ks7wvPb4l7Y9acs0IzC4
+Jrg27NJeO7kR6WV4DBlchd1LkIqT874Tuxv2W1/+JFFOWz2m3bbz2447u3ROT3xq
+yjk4TOh0vd2O0L2E1LcCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQBMAUqwty7R
+/YWRrC8NuvrbSsW0r7Z7FXWxny5w5ImONCgVffc2wVydtBVQ0rfd3pDZLyu0P4sx
+4bJ/KBz67t2MsOKbCMDS7SJuFwHu9AUzaYNh455HeBOVwb6LemJDNnCtMG9DgcRv
+2BpwKqekUVTGDuUQmLibjE8qwDHw/p9k4gjQBxlfJe2sIZGs6oA/JGFJUU6ZIn8Y
+M6aazrbjWexbWCnjhiXkNa8kfKiSHzU+2ct+GY5QxI221+63bXRiAi2/LK0gaY+p
++3vYu75F7+8oPZOfsGmYEyPz7c1jPqcwPgVDk+sdvl1MO1TGFRaFNIlRP1DhpHkj
+fuJ/id6oUHhj
+-----END CERTIFICATE REQUEST-----
diff --git a/src/test.example.com.csr.json b/src/test.example.com.csr.json
new file mode 100644
index 0000000..5cd3e1e
--- /dev/null
+++ b/src/test.example.com.csr.json
@@ -0,0 +1,14 @@
+{
+ "CN": "test.example.com",
+ "key": {
+ "algo": "rsa",
+ "size": 2048
+ },
+ "names": [
+ {
+ "C": "AU",
+ "ST": "Some-State",
+ "O": "Internet Widgits Pty Ltd"
+ }
+ ]
+}
diff --git a/src/test.example.com.pem b/src/test.example.com.pem
new file mode 100644
index 0000000..1c7e71e
--- /dev/null
+++ b/src/test.example.com.pem
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIIDwTCCAqmgAwIBAgIUDhKNhGRUq1TSHD6aG2k4TRR8iA0wDQYJKoZIhvcNAQEL
+BQAwXjELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoT
+GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEXMBUGA1UEAxMOY2EubmdodHRwMi5v
+cmcwHhcNMTYwNjI1MDkzNzAwWhcNMjYwNjIzMDkzNzAwWjBgMQswCQYDVQQGEwJB
+VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0
+cyBQdHkgTHRkMRkwFwYDVQQDExB0ZXN0LmV4YW1wbGUuY29tMIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEArf2UBsEh/xwd/4WZfVFf5sMyWcns/1idF2Fr
+oLDwqVUYRlxpU/KbrIG8X8v3w4cVP/xOXd1y9Q+W9OLK2YScAeHeE97mHZXbcpow
+UxvTDv/GNIHHXK/yvYM8R2EEnZR71qXdoXsCakv/aG2ewkkvA108eEbk0u7RxNmI
+sYsO0Y4iBtwBM/MSkAYPfWdrdHhY9z0l4M8GAyUOuZc0t6j0zw3fzkjqmVgGEJvc
+VzvSgZIzMLqSzvC89viXtj1pyzQjMLgmuDbs0l47uRHpZXgMGVyF3UuQipPzvhO7
+G/ZbX/4kUU5bPabdtvPbjju7dE5PfGrKOThM6HS93Y7QvYTUtwIDAQABo3UwczAO
+BgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIw
+ADAdBgNVHQ4EFgQUm8jn1FICope9qUce6ORQ0CtbmhYwHwYDVR0jBBgwFoAU0DnF
+VHVlxrFEkv1ULqnO4ua924YwDQYJKoZIhvcNAQELBQADggEBAD7RPz/5rAnS1MNP
+JfAj1TXZSBwlYgtmJL65yaFB6a1SNSTo15deAm/1Vl10LbmYdV4sVnGKeZjhKNk+
+bvVzetUSUS7Rh1fHtxlivJFkG1VrvPu9b416l2aKftBiaNyAWXbyjqXwLYli6Ehk
+uu6jZd0040Ggh7bY+KMSnDFDrp7Rar7OvGu9Iovs+sPdkc/iEbvwEiXdMjf3gwkT
+Wqx6br1VDLzhD83HAsFA9tt5fv6KTf91UgJnCmOi81Uo6fSEJG84g32T25gwwmCK
+q4U049aGF/f4u3QuWDsfYqNePycurAg3m5PC0wCoqvpY2u/q+PGbjWMi2PfZsF8U
+imgl/L0=
+-----END CERTIFICATE-----
diff --git a/src/test.nghttp2.org-key.pem b/src/test.nghttp2.org-key.pem
new file mode 100644
index 0000000..2532895
--- /dev/null
+++ b/src/test.nghttp2.org-key.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA7p6KKa3ctS+Sr/nf2uTKNtTshuDVzTsBTbaGydj8q0YDmT3n
+CnOPWXvvG1N+jJv5pcAXN2ZnV9UpGh3N5g/CaRcFTgQQ8o+NlCXYBdPIXAJ+Kkbx
+limDw3n9xIXfeL6+V2QuPNrqh6n23xwDg5boKaNkpf7X5OrjT1Ph57SEfX1op3GX
+bwkAP2+3WlxxYYs0htRq2gH97q9J4MlhHPDapi+uKGs+2b1y6Uxgf4nD5jEWdPmy
+VqeKs+fT4ja2n+3gujpdOo2lg504p50gL4zP8zhAlcqlQCmeJGL1xFzCtm2wHQo7
+6XHSWca4pJ7rxf2oIdtE7ikvgFlTVXnG1T3TEQIDAQABAoIBAQDlX/UD96MPcDmb
+e6EZ85AGgUsUpJAhBjVMlMagxTqtEVJoPj8XptoHdMD2DZ66XzztfedTU9bHcZpf
+BoNkQYXqKzzoL7Ry1leML4ymnVweRi8tSKD2bdXBVEUCYoXctc6WhzCDQxTrcBBl
+i7I9DhUB4ZTglEbIQJpdKQ8hAj/Rt55KWSxc+8X7ItSdtMrq+uz+pqg4PkysVAFS
+3aDybOqiI/2hzOvwQU4HaB48uUQwpOU6EGidt0C5nAdWOQMbS8kkCJz6UODiUfdM
+mLIyA4ygkQ45QthzrddKauUMhUd/y1SAFJOambR4ZiyA+bItomlbNq018sFx3FDr
+Uvg7nz2ZAoGBAPC6aO4W0U7vsWyL/lgC7ybFbtPJ4emj4wK/W87qx3LW1/dRgl7Q
+h6oblZTFK/oV6xA7J2/Foocz/s1ntnyIdxrtrZUYAIiBXlrWhDg9+MnnmErfXx7H
+CkRyWH6i9JbTRUeiWRNBGQ9yMkwQPc2Ckytxrh7w9M+RVCpyzUh8lX+XAoGBAP3B
+4V8cF3bVEUOk0tHshR5m2kcJ22qmUv8WUG+IdRUDb4tRpzVFC8HcKedEjk3jxkXR
+UInRSD+hLhx0HIhjZKWqJffSZI/G3U8AqoKu9+eh/xHZCah/8KW1YWNsn4rROcyZ
+5XFRiMn7psWTjLEZ17zQS4rk9g65SKc9u1wtTxeXAoGAIY3qOF2n2Tfh5D5zOnNW
+QHI+q3i1a6qzZtujgWkKWgCGY+vRn0Oz1Us5A16kbZyGgmGscpD6wZvGxXzSW/Nt
+nqxIiMKquFxH+aNzFJ/WwNXuTWlrSc/2p2nE2gn+y9MxEfYYMm3df2CskBuncbDk
+sKaM3bU6eoBIWg5cfOEYuYsCgYACB2bR59uYK6PzsoGtBAMcdx4Pq1iBxcqsF3WV
+LrYg8OIXbxOzLVYmuqfrHXU10jhnnoDSWUYGnDdOKu9/d6v6Vx3umVQMgj6Kvyqd
+2OBKjdUIQ3/8ROmbqZOZw+iSp5GavTBEc65wTv7KXZ+mWtqKu++esK32+CxIignR
+dttHCQKBgQDZUt94wj9s5p7H5LH6hxyqNr6P9JYEuYPao5l/3mOJ8N330wKuN2G+
+GUg7p/AhtQHwdoyErlsQavKZa791oCvfJiOURYa8gYU03sYsyI1tV45UexCwl40f
+oS+VQYgU16UdYo9B2petecEPNpM+mgpne3qzVUwJ5NUNURgmWpyiQw==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test.nghttp2.org.csr b/src/test.nghttp2.org.csr
new file mode 100644
index 0000000..dc4bb10
--- /dev/null
+++ b/src/test.nghttp2.org.csr
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIDATCCAekCAQAwZDELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUx
+ITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEdMBsGA1UEAxMUbm90
+LXVzZWQubmdodHRwMi5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQDunooprdy1L5Kv+d/a5Mo21OyG4NXNOwFNtobJ2PyrRgOZPecKc49Ze+8bU36M
+m/mlwBc3ZmdX1SkaHc3mD8JpFwVOBBDyj42UJdgF08hcAn4qRvGWKYPDef3Ehd94
+vr5XZC482uqHqfbfHAODlugpo2Sl/tfk6uNPU+HntIR9fWincZdvCQA/b7daXHFh
+izSG1GraAf3ur0ngyWEc8NqmL64oaz7ZvXLpTGB/icPmMRZ0+bJWp4qz59PiNraf
+7eC6Ol06jaWDnTinnSAvjM/zOECVyqVAKZ4kYvXEXMK2bbAdCjvpcdJZxriknuvF
+/agh20TuKS+AWVNVecbVPdMRAgMBAAGgWDBWBgkqhkiG9w0BCQ4xSTBHMEUGA1Ud
+EQQ+MDyCEFRFU1QuTkdIVFRQMi5PUkeCEioudGVzdC5uZ2h0dHAyLm9yZ4IUdyp3
+LnRlc3QubmdodHRwMi5vcmcwDQYJKoZIhvcNAQELBQADggEBAIAEwnoM5moRwO5U
+eaeVCuzpxw1qQsB769GyQu+ey1aa+2BYflirv/FW+8x/uzQpCWGEgHqd5w+MXyXA
+PsyucHgKh5Ia6MUW6xxlHkkOtVtmZiH7lXWv90RNtdfHHGWnBzw8iGsk5WfEaNho
+NlPiuYLiFqA7W6jR/c4kOg3zziDlwTXaH6SWLCuDzLTb7E7nGcrWkN6moYj+QlSx
+viA4GsqDBoFgXT7cSfUzS8ZwIjrqbx7C1xkzPEt5jAiCD/UBX9ot0G+lEgCv3UQj
+Q1KkY+TO3bzMkt/kQSX2Q6plKj8D77tlDfFCjd77VC2lL3Qmzaz+M6T7uF+wyl9W
+AQJvoUg=
+-----END CERTIFICATE REQUEST-----
diff --git a/src/test.nghttp2.org.csr.json b/src/test.nghttp2.org.csr.json
new file mode 100644
index 0000000..5ee3069
--- /dev/null
+++ b/src/test.nghttp2.org.csr.json
@@ -0,0 +1,19 @@
+{
+ "CN": "not-used.nghttp2.org",
+ "hosts": [
+ "TEST.NGHTTP2.ORG",
+ "*.test.nghttp2.org",
+ "w*w.test.nghttp2.org"
+ ],
+ "key": {
+ "algo": "rsa",
+ "size": 2048
+ },
+ "names": [
+ {
+ "C": "AU",
+ "ST": "Some-State",
+ "O": "Internet Widgits Pty Ltd"
+ }
+ ]
+}
diff --git a/src/test.nghttp2.org.pem b/src/test.nghttp2.org.pem
new file mode 100644
index 0000000..0c386fc
--- /dev/null
+++ b/src/test.nghttp2.org.pem
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIEDjCCAvagAwIBAgIUQBCY8Nre85JT1c7P+HbXUF9yzg8wDQYJKoZIhvcNAQEL
+BQAwXjELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoT
+GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEXMBUGA1UEAxMOY2EubmdodHRwMi5v
+cmcwHhcNMTYwNjI1MDkzMzAwWhcNMjYwNjIzMDkzMzAwWjBkMQswCQYDVQQGEwJB
+VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0
+cyBQdHkgTHRkMR0wGwYDVQQDExRub3QtdXNlZC5uZ2h0dHAyLm9yZzCCASIwDQYJ
+KoZIhvcNAQEBBQADggEPADCCAQoCggEBAO6eiimt3LUvkq/539rkyjbU7Ibg1c07
+AU22hsnY/KtGA5k95wpzj1l77xtTfoyb+aXAFzdmZ1fVKRodzeYPwmkXBU4EEPKP
+jZQl2AXTyFwCfipG8ZYpg8N5/cSF33i+vldkLjza6oep9t8cA4OW6CmjZKX+1+Tq
+409T4ee0hH19aKdxl28JAD9vt1pccWGLNIbUatoB/e6vSeDJYRzw2qYvrihrPtm9
+culMYH+Jw+YxFnT5slanirPn0+I2tp/t4Lo6XTqNpYOdOKedIC+Mz/M4QJXKpUAp
+niRi9cRcwrZtsB0KO+lx0lnGuKSe68X9qCHbRO4pL4BZU1V5xtU90xECAwEAAaOB
+vTCBujAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0T
+AQH/BAIwADAdBgNVHQ4EFgQUGlxgxowH6jrQiyyFpCbwPkCXXIYwHwYDVR0jBBgw
+FoAU0DnFVHVlxrFEkv1ULqnO4ua924YwRQYDVR0RBD4wPIIQVEVTVC5OR0hUVFAy
+Lk9SR4ISKi50ZXN0Lm5naHR0cDIub3JnghR3KncudGVzdC5uZ2h0dHAyLm9yZzAN
+BgkqhkiG9w0BAQsFAAOCAQEANCqM6ocfqOpgDEHYOOQTGFHJIptQhS3kRYAdTIo2
+G8XvGCoy+CDYe1GAUWbxE090+a1I1rsYMHcWKJnjKaCBZid7KMhyayIvrmgEsOCh
+L8iLf3bxkCoyIAmCpxJwa3LMxm2QQLtRx8AoMXWf+N8are4HY6MLNn6aP4zaTrTZ
+H+WkjKIh7WjSHtW/ro666PCXJDCCdRXljOf8v/fff3bYiLg8o70RBp7OFM0HaPtK
+wCfcLLxBeoVIncWswB6GtVUFhLeGjepDzWpuDHOdw6DtpghwSXvWFu9bRtl+x02m
+LAGfJ0kJrpYGfr9UB51NFX3aM/D3p2zxrjKwR2b59vJEcA==
+-----END CERTIFICATE-----
diff --git a/src/testdata/Makefile.am b/src/testdata/Makefile.am
new file mode 100644
index 0000000..b1cb575
--- /dev/null
+++ b/src/testdata/Makefile.am
@@ -0,0 +1,27 @@
+# nghttp2 - HTTP/2 C Library
+
+# Copyright (c) 2023 Tatsuhiro Tsujikawa
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+EXTRA_DIST = \
+ ipaddr.crt \
+ nosan.crt \
+ nosan_ip.crt \
+ verify_hostname.crt
diff --git a/src/testdata/ipaddr.crt b/src/testdata/ipaddr.crt
new file mode 100644
index 0000000..cdacdf0
--- /dev/null
+++ b/src/testdata/ipaddr.crt
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBfDCCASKgAwIBAgIBATAKBggqhkjOPQQDAjAWMRQwEgYDVQQDEwsxOTIuMTY4
+LjAuMTAeFw0yMzAzMTUxMjQ5MDBaFw0zMzAxMjExMjQ5MDBaMBYxFDASBgNVBAMT
+CzE5Mi4xNjguMC4xMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERDh3hNne6xGM
+fOrf7ln5EFnlLpk98qadBx3MKjG5gAfMYHzf/S7v19G608sH1LtabubV+Tvjllon
+K56G2Gk0+6NhMF8wDgYDVR0PAQH/BAQDAgeAME0GA1UdEQRGMESCE25naHR0cDIu
+ZXhhbXBsZS5jb22CFSoubmdodHRwMi5leGFtcGxlLmNvbYcEfwAAAYcQAAAAAAAA
+AAAAAAAAAAAAATAKBggqhkjOPQQDAgNIADBFAiEA3jZzO49MYccR5mYS08qVUCdh
+HsEAC8GhRXFwL6zvf2ACIFAJrca2zTU4QRjV6V+LGRHc2ZocE2e7wFTLobblmDfB
+-----END CERTIFICATE-----
diff --git a/src/testdata/nosan.crt b/src/testdata/nosan.crt
new file mode 100644
index 0000000..ebbb5c0
--- /dev/null
+++ b/src/testdata/nosan.crt
@@ -0,0 +1,9 @@
+-----BEGIN CERTIFICATE-----
+MIIBKDCBz6ADAgECAgEBMAoGCCqGSM49BAMCMBQxEjAQBgNVBAMTCWxvY2FsaG9z
+dDAeFw0yMzAzMTUxMjQzMzhaFw0zMzAxMjExMjQzMzhaMBQxEjAQBgNVBAMTCWxv
+Y2FsaG9zdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABIEpWYgtXtcx0uJ2oFPK
+RiII93iw5ITMrhMfBXQ0SzCfkUdvCJ0gNW+3isTBu4Jt0URpgP37eGwiJf2wPApq
+KpajEjAQMA4GA1UdDwEB/wQEAwIHgDAKBggqhkjOPQQDAgNIADBFAiEA4IYil4G4
+cMxaVkcAnMGgiSdn7/qIgdhFB0Vx5AOd+EUCIGubRPhsXAJXvG//cK25mmxi3Wax
+r7AgRKuDtWxn2bCO
+-----END CERTIFICATE-----
diff --git a/src/testdata/nosan_ip.crt b/src/testdata/nosan_ip.crt
new file mode 100644
index 0000000..717a1bd
--- /dev/null
+++ b/src/testdata/nosan_ip.crt
@@ -0,0 +1,9 @@
+-----BEGIN CERTIFICATE-----
+MIIBJzCBz6ADAgECAgEBMAoGCCqGSM49BAMCMBQxEjAQBgNVBAMTCTEyNy4wLjAu
+MTAeFw0yMzAzMTUxMjQ1MTVaFw0zMzAxMjExMjQ1MTVaMBQxEjAQBgNVBAMTCTEy
+Ny4wLjAuMTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOXGPfSzXoeD7jszmAQO
+qAhak5HQMTmj32Q/xqO9WmCnXRQ+T06701o6q1hjotrC/HdMk9kabsKHc9V7Bk4O
+zkGjEjAQMA4GA1UdDwEB/wQEAwIHgDAKBggqhkjOPQQDAgNHADBEAiAI3fKrkNTN
+IEo9qI8bd/pZ6on4d9vLcnHtqYhcuWZGTwIgW2zYMwASLUw4H1k/prBtTEEJOahJ
+bvFs3oMbJEprQ+g=
+-----END CERTIFICATE-----
diff --git a/src/testdata/verify_hostname.crt b/src/testdata/verify_hostname.crt
new file mode 100644
index 0000000..a327616
--- /dev/null
+++ b/src/testdata/verify_hostname.crt
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBeTCCAR6gAwIBAgIBATAKBggqhkjOPQQDAjAUMRIwEAYDVQQDEwlsb2NhbGhv
+c3QwHhcNMjMwMzE1MTIzNzU1WhcNMzMwMTIxMTIzNzU1WjAUMRIwEAYDVQQDEwls
+b2NhbGhvc3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATMHcWmb55fi0KHNDwM
+cYzVTAOfzJf44AqrqC+Pq2zW/ig8tPZbXf3eA/Vvp07Di+yWmuo3fGatUcY4nsx+
+Jd62o2EwXzAOBgNVHQ8BAf8EBAMCB4AwTQYDVR0RBEYwRIITbmdodHRwMi5leGFt
+cGxlLmNvbYIVKi5uZ2h0dHAyLmV4YW1wbGUuY29thwR/AAABhxAAAAAAAAAAAAAA
+AAAAAAABMAoGCCqGSM49BAMCA0kAMEYCIQDQJFRJ3Ah4cGy7bwpkzVYeTgG+NhDa
+55F4dPtJp9dS8wIhALQ9qf379lke1jVHg2t84iZLo3bL23RgICMezEYvqO3K
+-----END CERTIFICATE-----
diff --git a/src/timegm.c b/src/timegm.c
new file mode 100644
index 0000000..3a671b1
--- /dev/null
+++ b/src/timegm.c
@@ -0,0 +1,88 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "timegm.h"
+
+#include <inttypes.h>
+
+/* Counter the number of leap year in the range [0, y). The |y| is the
+ year, including century (e.g., 2012) */
+static int count_leap_year(int y) {
+ y -= 1;
+ return y / 4 - y / 100 + y / 400;
+}
+
+/* Based on the algorithm of Python 2.7 calendar.timegm. */
+time_t nghttp2_timegm(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 + tm->tm_yday;
+ 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;
+ }
+ }
+
+ 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;
+ }
+ }
+
+ return (time_t)t;
+}
diff --git a/src/timegm.h b/src/timegm.h
new file mode 100644
index 0000000..152917c
--- /dev/null
+++ b/src/timegm.h
@@ -0,0 +1,49 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef TIMEGM_H
+#define TIMEGM_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <time.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+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 */
+
+#endif /* TIMEGM_H */
diff --git a/src/tls.cc b/src/tls.cc
new file mode 100644
index 0000000..e88dc56
--- /dev/null
+++ b/src/tls.cc
@@ -0,0 +1,125 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "tls.h"
+
+#include <cassert>
+#include <vector>
+#include <mutex>
+#include <iostream>
+
+#include <openssl/crypto.h>
+#include <openssl/conf.h>
+
+#include "ssl_compat.h"
+
+namespace nghttp2 {
+
+namespace tls {
+
+const char *get_tls_protocol(SSL *ssl) {
+ switch (SSL_version(ssl)) {
+ case SSL2_VERSION:
+ return "SSLv2";
+ case SSL3_VERSION:
+ return "SSLv3";
+#ifdef TLS1_3_VERSION
+ case TLS1_3_VERSION:
+ return "TLSv1.3";
+#endif // TLS1_3_VERSION
+ case TLS1_2_VERSION:
+ return "TLSv1.2";
+ case TLS1_1_VERSION:
+ return "TLSv1.1";
+ case TLS1_VERSION:
+ return "TLSv1";
+ default:
+ return "unknown";
+ }
+}
+
+TLSSessionInfo *get_tls_session_info(TLSSessionInfo *tls_info, SSL *ssl) {
+ if (!ssl) {
+ return nullptr;
+ }
+
+ auto session = SSL_get_session(ssl);
+ if (!session) {
+ return nullptr;
+ }
+
+ tls_info->cipher = SSL_get_cipher_name(ssl);
+ tls_info->protocol = get_tls_protocol(ssl);
+ tls_info->session_reused = SSL_session_reused(ssl);
+
+ unsigned int session_id_length;
+ tls_info->session_id = SSL_SESSION_get_id(session, &session_id_length);
+ tls_info->session_id_length = session_id_length;
+
+ return tls_info;
+}
+
+/* Conditional logic w/ lookup tables to check if id is one of the
+ the block listed cipher suites for HTTP/2 described in RFC 7540.
+ https://github.com/jay/http2_blacklisted_ciphers
+*/
+#define IS_CIPHER_BANNED_METHOD2(id) \
+ ((0x0000 <= id && id <= 0x00FF && \
+ "\xFF\xFF\xFF\xCF\xFF\xFF\xFF\xFF\x7F\x00\x00\x00\x80\x3F\x00\x00" \
+ "\xF0\xFF\xFF\x3F\xF3\xF3\xFF\xFF\x3F\x00\x00\x00\x00\x00\x00\x80" \
+ [(id & 0xFF) / 8] & \
+ (1 << (id % 8))) || \
+ (0xC000 <= id && id <= 0xC0FF && \
+ "\xFE\xFF\xFF\xFF\xFF\x67\xFE\xFF\xFF\xFF\x33\xCF\xFC\xCF\xFF\xCF" \
+ "\x3C\xF3\xFC\x3F\x33\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
+ [(id & 0xFF) / 8] & \
+ (1 << (id % 8))))
+
+bool check_http2_cipher_block_list(SSL *ssl) {
+ int id = SSL_CIPHER_get_id(SSL_get_current_cipher(ssl)) & 0xFFFFFF;
+
+ return IS_CIPHER_BANNED_METHOD2(id);
+}
+
+bool check_http2_tls_version(SSL *ssl) {
+ auto tls_ver = SSL_version(ssl);
+
+ return tls_ver >= TLS1_2_VERSION;
+}
+
+bool check_http2_requirement(SSL *ssl) {
+ return check_http2_tls_version(ssl) && !check_http2_cipher_block_list(ssl);
+}
+
+int ssl_ctx_set_proto_versions(SSL_CTX *ssl_ctx, int min, int max) {
+ if (SSL_CTX_set_min_proto_version(ssl_ctx, min) != 1 ||
+ SSL_CTX_set_max_proto_version(ssl_ctx, max) != 1) {
+ return -1;
+ }
+ return 0;
+}
+
+} // namespace tls
+
+} // namespace nghttp2
diff --git a/src/tls.h b/src/tls.h
new file mode 100644
index 0000000..59e2ccd
--- /dev/null
+++ b/src/tls.h
@@ -0,0 +1,104 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef TLS_H
+#define TLS_H
+
+#include "nghttp2_config.h"
+
+#include <cinttypes>
+
+#include <openssl/ssl.h>
+
+#include "ssl_compat.h"
+
+namespace nghttp2 {
+
+namespace tls {
+
+// Recommended general purpose "Intermediate compatibility" cipher
+// suites for TLSv1.2 by mozilla.
+//
+// https://wiki.mozilla.org/Security/Server_Side_TLS
+constexpr char DEFAULT_CIPHER_LIST[] =
+ "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-"
+ "AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-"
+ "POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-"
+ "AES256-GCM-SHA384";
+
+// Recommended general purpose "Modern compatibility" cipher suites
+// for TLSv1.3 by mozilla.
+//
+// https://wiki.mozilla.org/Security/Server_Side_TLS
+constexpr char DEFAULT_TLS13_CIPHER_LIST[] =
+#if defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_LIBRESSL)
+ "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256"
+#else // !NGHTTP2_GENUINE_OPENSSL && !NGHTTP2_OPENSSL_IS_LIBRESSL
+ ""
+#endif // !NGHTTP2_GENUINE_OPENSSL && !NGHTTP2_OPENSSL_IS_LIBRESSL
+ ;
+
+constexpr auto NGHTTP2_TLS_MIN_VERSION = TLS1_VERSION;
+#ifdef TLS1_3_VERSION
+constexpr auto NGHTTP2_TLS_MAX_VERSION = TLS1_3_VERSION;
+#else // !TLS1_3_VERSION
+constexpr auto NGHTTP2_TLS_MAX_VERSION = TLS1_2_VERSION;
+#endif // !TLS1_3_VERSION
+
+const char *get_tls_protocol(SSL *ssl);
+
+struct TLSSessionInfo {
+ const char *cipher;
+ const char *protocol;
+ const uint8_t *session_id;
+ bool session_reused;
+ size_t session_id_length;
+};
+
+TLSSessionInfo *get_tls_session_info(TLSSessionInfo *tls_info, SSL *ssl);
+
+// Returns true iff the negotiated protocol is TLSv1.2.
+bool check_http2_tls_version(SSL *ssl);
+
+// Returns true iff the negotiated cipher suite is in HTTP/2 cipher
+// block list.
+bool check_http2_cipher_block_list(SSL *ssl);
+
+// Returns true if SSL/TLS requirement for HTTP/2 is fulfilled.
+// To fulfill the requirement, the following 2 terms must be hold:
+//
+// 1. The negotiated protocol must be TLSv1.2.
+// 2. The negotiated cipher cuite is not listed in the block list
+// described in RFC 7540.
+bool check_http2_requirement(SSL *ssl);
+
+// Sets TLS min and max versions to |ssl_ctx|. This function returns
+// 0 if it succeeds, or -1.
+int ssl_ctx_set_proto_versions(SSL_CTX *ssl_ctx, int min, int max);
+
+} // namespace tls
+
+} // namespace nghttp2
+
+#endif // TLS_H
diff --git a/src/util.cc b/src/util.cc
new file mode 100644
index 0000000..0996c0a
--- /dev/null
+++ b/src/util.cc
@@ -0,0 +1,1796 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "util.h"
+
+#include <sys/types.h>
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif // HAVE_SYS_SOCKET_H
+#ifdef HAVE_NETDB_H
+# include <netdb.h>
+#endif // HAVE_NETDB_H
+#include <sys/stat.h>
+#ifdef HAVE_FCNTL_H
+# include <fcntl.h>
+#endif // HAVE_FCNTL_H
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif // HAVE_NETINET_IN_H
+#ifdef HAVE_NETINET_IP_H
+# include <netinet/ip.h>
+#endif // HAVE_NETINET_IP_H
+#include <netinet/udp.h>
+#ifdef _WIN32
+# include <ws2tcpip.h>
+#else // !_WIN32
+# include <netinet/tcp.h>
+#endif // !_WIN32
+#ifdef HAVE_ARPA_INET_H
+# include <arpa/inet.h>
+#endif // HAVE_ARPA_INET_H
+
+#include <cmath>
+#include <cerrno>
+#include <cassert>
+#include <cstdio>
+#include <cstring>
+#include <ctime>
+#include <iostream>
+#include <fstream>
+#include <iomanip>
+
+#include <openssl/evp.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "ssl_compat.h"
+#include "timegm.h"
+
+namespace nghttp2 {
+
+namespace util {
+
+#ifndef _WIN32
+namespace {
+int nghttp2_inet_pton(int af, const char *src, void *dst) {
+ return inet_pton(af, src, dst);
+}
+} // namespace
+#else // _WIN32
+namespace {
+// inet_pton-wrapper for Windows
+int nghttp2_inet_pton(int af, const char *src, void *dst) {
+# if _WIN32_WINNT >= 0x0600
+ return InetPtonA(af, src, dst);
+# else
+ // the function takes a 'char*', so we need to make a copy
+ char addr[INET6_ADDRSTRLEN + 1];
+ strncpy(addr, src, sizeof(addr));
+ addr[sizeof(addr) - 1] = 0;
+
+ int size = sizeof(struct in6_addr);
+
+ if (WSAStringToAddress(addr, af, nullptr, (LPSOCKADDR)dst, &size) == 0)
+ return 1;
+ return 0;
+# endif
+}
+} // namespace
+#endif // _WIN32
+
+const char UPPER_XDIGITS[] = "0123456789ABCDEF";
+
+bool in_rfc3986_unreserved_chars(const char c) {
+ switch (c) {
+ case '-':
+ case '.':
+ case '_':
+ case '~':
+ return true;
+ }
+
+ return is_alpha(c) || is_digit(c);
+}
+
+bool in_rfc3986_sub_delims(const char c) {
+ switch (c) {
+ case '!':
+ case '$':
+ case '&':
+ case '\'':
+ case '(':
+ case ')':
+ case '*':
+ case '+':
+ case ',':
+ case ';':
+ case '=':
+ return true;
+ }
+
+ return false;
+}
+
+std::string percent_encode(const unsigned char *target, size_t len) {
+ std::string dest;
+ for (size_t i = 0; i < len; ++i) {
+ unsigned char c = target[i];
+
+ if (in_rfc3986_unreserved_chars(c)) {
+ dest += c;
+ } else {
+ dest += '%';
+ dest += UPPER_XDIGITS[c >> 4];
+ dest += UPPER_XDIGITS[(c & 0x0f)];
+ }
+ }
+ return dest;
+}
+
+std::string percent_encode(const std::string &target) {
+ return percent_encode(reinterpret_cast<const unsigned char *>(target.c_str()),
+ target.size());
+}
+
+bool in_token(char c) {
+ switch (c) {
+ case '!':
+ case '#':
+ case '$':
+ case '%':
+ case '&':
+ case '\'':
+ case '*':
+ case '+':
+ case '-':
+ case '.':
+ case '^':
+ case '_':
+ case '`':
+ case '|':
+ case '~':
+ return true;
+ }
+
+ return is_alpha(c) || is_digit(c);
+}
+
+bool in_attr_char(char c) {
+ switch (c) {
+ case '*':
+ case '\'':
+ case '%':
+ return false;
+ }
+
+ return util::in_token(c);
+}
+
+StringRef percent_encode_token(BlockAllocator &balloc,
+ const StringRef &target) {
+ auto iov = make_byte_ref(balloc, target.size() * 3 + 1);
+ auto p = percent_encode_token(iov.base, target);
+
+ *p = '\0';
+
+ return StringRef{iov.base, p};
+}
+
+size_t percent_encode_tokenlen(const StringRef &target) {
+ size_t n = 0;
+
+ for (auto first = std::begin(target); first != std::end(target); ++first) {
+ uint8_t c = *first;
+
+ if (c != '%' && in_token(c)) {
+ ++n;
+ continue;
+ }
+
+ // percent-encoded character '%ff'
+ n += 3;
+ }
+
+ return n;
+}
+
+uint32_t hex_to_uint(char c) {
+ if (c <= '9') {
+ return c - '0';
+ }
+ if (c <= 'Z') {
+ return c - 'A' + 10;
+ }
+ if (c <= 'z') {
+ return c - 'a' + 10;
+ }
+ return 256;
+}
+
+StringRef quote_string(BlockAllocator &balloc, const StringRef &target) {
+ auto cnt = std::count(std::begin(target), std::end(target), '"');
+
+ if (cnt == 0) {
+ return make_string_ref(balloc, target);
+ }
+
+ auto iov = make_byte_ref(balloc, target.size() + cnt + 1);
+ auto p = quote_string(iov.base, target);
+
+ *p = '\0';
+
+ return StringRef{iov.base, p};
+}
+
+size_t quote_stringlen(const StringRef &target) {
+ size_t n = 0;
+
+ for (auto c : target) {
+ if (c == '"') {
+ n += 2;
+ } else {
+ ++n;
+ }
+ }
+
+ return n;
+}
+
+namespace {
+template <typename Iterator>
+Iterator cpydig(Iterator d, uint32_t n, size_t len) {
+ auto p = d + len - 1;
+
+ do {
+ *p-- = (n % 10) + '0';
+ n /= 10;
+ } while (p >= d);
+
+ return d + len;
+}
+} // namespace
+
+namespace {
+constexpr const char *MONTH[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
+constexpr const char *DAY_OF_WEEK[] = {"Sun", "Mon", "Tue", "Wed",
+ "Thu", "Fri", "Sat"};
+} // namespace
+
+std::string http_date(time_t t) {
+ /* Sat, 27 Sep 2014 06:31:15 GMT */
+ std::string res(29, 0);
+ http_date(&res[0], t);
+ return res;
+}
+
+char *http_date(char *res, time_t t) {
+ struct tm tms;
+
+ if (gmtime_r(&t, &tms) == nullptr) {
+ return res;
+ }
+
+ auto p = res;
+
+ auto s = DAY_OF_WEEK[tms.tm_wday];
+ p = std::copy_n(s, 3, p);
+ *p++ = ',';
+ *p++ = ' ';
+ p = cpydig(p, tms.tm_mday, 2);
+ *p++ = ' ';
+ s = MONTH[tms.tm_mon];
+ p = std::copy_n(s, 3, p);
+ *p++ = ' ';
+ p = cpydig(p, tms.tm_year + 1900, 4);
+ *p++ = ' ';
+ p = cpydig(p, tms.tm_hour, 2);
+ *p++ = ':';
+ p = cpydig(p, tms.tm_min, 2);
+ *p++ = ':';
+ p = cpydig(p, tms.tm_sec, 2);
+ s = " GMT";
+ p = std::copy_n(s, 4, p);
+
+ return p;
+}
+
+std::string common_log_date(time_t t) {
+ // 03/Jul/2014:00:19:38 +0900
+ std::string res(26, 0);
+ common_log_date(&res[0], t);
+ return res;
+}
+
+char *common_log_date(char *res, time_t t) {
+ struct tm tms;
+
+ if (localtime_r(&t, &tms) == nullptr) {
+ return res;
+ }
+
+ auto p = res;
+
+ p = cpydig(p, tms.tm_mday, 2);
+ *p++ = '/';
+ auto s = MONTH[tms.tm_mon];
+ p = std::copy_n(s, 3, p);
+ *p++ = '/';
+ p = cpydig(p, tms.tm_year + 1900, 4);
+ *p++ = ':';
+ p = cpydig(p, tms.tm_hour, 2);
+ *p++ = ':';
+ p = cpydig(p, tms.tm_min, 2);
+ *p++ = ':';
+ p = cpydig(p, tms.tm_sec, 2);
+ *p++ = ' ';
+
+#ifdef HAVE_STRUCT_TM_TM_GMTOFF
+ auto gmtoff = tms.tm_gmtoff;
+#else // !HAVE_STRUCT_TM_TM_GMTOFF
+ auto gmtoff = nghttp2_timegm(&tms) - t;
+#endif // !HAVE_STRUCT_TM_TM_GMTOFF
+ if (gmtoff >= 0) {
+ *p++ = '+';
+ } else {
+ *p++ = '-';
+ gmtoff = -gmtoff;
+ }
+
+ p = cpydig(p, gmtoff / 3600, 2);
+ p = cpydig(p, (gmtoff % 3600) / 60, 2);
+
+ return p;
+}
+
+std::string iso8601_date(int64_t ms) {
+ // 2014-11-15T12:58:24.741Z
+ // 2014-11-15T12:58:24.741+09:00
+ std::string res(29, 0);
+ auto p = iso8601_date(&res[0], ms);
+ res.resize(p - &res[0]);
+ return res;
+}
+
+char *iso8601_date(char *res, int64_t ms) {
+ time_t sec = ms / 1000;
+
+ tm tms;
+ if (localtime_r(&sec, &tms) == nullptr) {
+ return res;
+ }
+
+ auto p = res;
+
+ p = cpydig(p, tms.tm_year + 1900, 4);
+ *p++ = '-';
+ p = cpydig(p, tms.tm_mon + 1, 2);
+ *p++ = '-';
+ p = cpydig(p, tms.tm_mday, 2);
+ *p++ = 'T';
+ p = cpydig(p, tms.tm_hour, 2);
+ *p++ = ':';
+ p = cpydig(p, tms.tm_min, 2);
+ *p++ = ':';
+ p = cpydig(p, tms.tm_sec, 2);
+ *p++ = '.';
+ p = cpydig(p, ms % 1000, 3);
+
+#ifdef HAVE_STRUCT_TM_TM_GMTOFF
+ auto gmtoff = tms.tm_gmtoff;
+#else // !HAVE_STRUCT_TM_TM_GMTOFF
+ auto gmtoff = nghttp2_timegm(&tms) - sec;
+#endif // !HAVE_STRUCT_TM_TM_GMTOFF
+ if (gmtoff == 0) {
+ *p++ = 'Z';
+ } else {
+ if (gmtoff > 0) {
+ *p++ = '+';
+ } else {
+ *p++ = '-';
+ gmtoff = -gmtoff;
+ }
+ p = cpydig(p, gmtoff / 3600, 2);
+ *p++ = ':';
+ p = cpydig(p, (gmtoff % 3600) / 60, 2);
+ }
+
+ return p;
+}
+
+char *iso8601_basic_date(char *res, int64_t ms) {
+ time_t sec = ms / 1000;
+
+ tm tms;
+ if (localtime_r(&sec, &tms) == nullptr) {
+ return res;
+ }
+
+ auto p = res;
+
+ p = cpydig(p, tms.tm_year + 1900, 4);
+ p = cpydig(p, tms.tm_mon + 1, 2);
+ p = cpydig(p, tms.tm_mday, 2);
+ *p++ = 'T';
+ p = cpydig(p, tms.tm_hour, 2);
+ p = cpydig(p, tms.tm_min, 2);
+ p = cpydig(p, tms.tm_sec, 2);
+ *p++ = '.';
+ p = cpydig(p, ms % 1000, 3);
+
+#ifdef HAVE_STRUCT_TM_TM_GMTOFF
+ auto gmtoff = tms.tm_gmtoff;
+#else // !HAVE_STRUCT_TM_TM_GMTOFF
+ auto gmtoff = nghttp2_timegm(&tms) - sec;
+#endif // !HAVE_STRUCT_TM_TM_GMTOFF
+ if (gmtoff == 0) {
+ *p++ = 'Z';
+ } else {
+ if (gmtoff > 0) {
+ *p++ = '+';
+ } else {
+ *p++ = '-';
+ gmtoff = -gmtoff;
+ }
+ p = cpydig(p, gmtoff / 3600, 2);
+ p = cpydig(p, (gmtoff % 3600) / 60, 2);
+ }
+
+ return p;
+}
+
+time_t parse_http_date(const StringRef &s) {
+ tm tm{};
+#ifdef _WIN32
+ // there is no strptime - use std::get_time
+ std::stringstream sstr(s.str());
+ sstr >> std::get_time(&tm, "%a, %d %b %Y %H:%M:%S GMT");
+ if (sstr.fail()) {
+ return 0;
+ }
+#else // !_WIN32
+ char *r = strptime(s.c_str(), "%a, %d %b %Y %H:%M:%S GMT", &tm);
+ if (r == 0) {
+ return 0;
+ }
+#endif // !_WIN32
+ return nghttp2_timegm_without_yday(&tm);
+}
+
+time_t parse_openssl_asn1_time_print(const StringRef &s) {
+ tm tm{};
+ auto r = strptime(s.c_str(), "%b %d %H:%M:%S %Y GMT", &tm);
+ if (r == nullptr) {
+ return 0;
+ }
+ return nghttp2_timegm_without_yday(&tm);
+}
+
+char upcase(char c) {
+ if ('a' <= c && c <= 'z') {
+ return c - 'a' + 'A';
+ } else {
+ return c;
+ }
+}
+
+std::string format_hex(const unsigned char *s, size_t len) {
+ std::string res;
+ res.resize(len * 2);
+
+ for (size_t i = 0; i < len; ++i) {
+ unsigned char c = s[i];
+
+ res[i * 2] = LOWER_XDIGITS[c >> 4];
+ res[i * 2 + 1] = LOWER_XDIGITS[c & 0x0f];
+ }
+ return res;
+}
+
+StringRef format_hex(BlockAllocator &balloc, const StringRef &s) {
+ auto iov = make_byte_ref(balloc, s.size() * 2 + 1);
+ auto p = iov.base;
+
+ for (auto cc : s) {
+ uint8_t c = cc;
+ *p++ = LOWER_XDIGITS[c >> 4];
+ *p++ = LOWER_XDIGITS[c & 0xf];
+ }
+
+ *p = '\0';
+
+ return StringRef{iov.base, p};
+}
+
+void to_token68(std::string &base64str) {
+ std::transform(std::begin(base64str), std::end(base64str),
+ std::begin(base64str), [](char c) {
+ switch (c) {
+ case '+':
+ return '-';
+ case '/':
+ return '_';
+ default:
+ return c;
+ }
+ });
+ base64str.erase(std::find(std::begin(base64str), std::end(base64str), '='),
+ std::end(base64str));
+}
+
+StringRef to_base64(BlockAllocator &balloc, const StringRef &token68str) {
+ // At most 3 padding '='
+ auto len = token68str.size() + 3;
+ auto iov = make_byte_ref(balloc, len + 1);
+ auto p = iov.base;
+
+ p = std::transform(std::begin(token68str), std::end(token68str), p,
+ [](char c) {
+ switch (c) {
+ case '-':
+ return '+';
+ case '_':
+ return '/';
+ default:
+ return c;
+ }
+ });
+
+ auto rem = token68str.size() & 0x3;
+ if (rem) {
+ p = std::fill_n(p, 4 - rem, '=');
+ }
+
+ *p = '\0';
+
+ return StringRef{iov.base, p};
+}
+
+namespace {
+// Calculates Damerau–Levenshtein distance between c-string a and b
+// with given costs. swapcost, subcost, addcost and delcost are cost
+// to swap 2 adjacent characters, substitute characters, add character
+// and delete character respectively.
+int levenshtein(const char *a, int alen, const char *b, int blen, int swapcost,
+ int subcost, int addcost, int delcost) {
+ auto dp = std::vector<std::vector<int>>(3, std::vector<int>(blen + 1));
+ for (int i = 0; i <= blen; ++i) {
+ dp[1][i] = i;
+ }
+ for (int i = 1; i <= alen; ++i) {
+ dp[0][0] = i;
+ for (int j = 1; j <= blen; ++j) {
+ dp[0][j] = dp[1][j - 1] + (a[i - 1] == b[j - 1] ? 0 : subcost);
+ if (i >= 2 && j >= 2 && a[i - 1] != b[j - 1] && a[i - 2] == b[j - 1] &&
+ a[i - 1] == b[j - 2]) {
+ dp[0][j] = std::min(dp[0][j], dp[2][j - 2] + swapcost);
+ }
+ dp[0][j] = std::min(dp[0][j],
+ std::min(dp[1][j] + delcost, dp[0][j - 1] + addcost));
+ }
+ std::rotate(std::begin(dp), std::begin(dp) + 2, std::end(dp));
+ }
+ return dp[1][blen];
+}
+} // namespace
+
+void show_candidates(const char *unkopt, const option *options) {
+ for (; *unkopt == '-'; ++unkopt)
+ ;
+ if (*unkopt == '\0') {
+ return;
+ }
+ auto unkoptend = unkopt;
+ for (; *unkoptend && *unkoptend != '='; ++unkoptend)
+ ;
+ auto unkoptlen = unkoptend - unkopt;
+ if (unkoptlen == 0) {
+ return;
+ }
+ int prefix_match = 0;
+ auto cands = std::vector<std::pair<int, const char *>>();
+ for (size_t i = 0; options[i].name != nullptr; ++i) {
+ auto optnamelen = strlen(options[i].name);
+ // Use cost 0 for prefix match
+ if (istarts_with(options[i].name, options[i].name + optnamelen, unkopt,
+ unkopt + unkoptlen)) {
+ if (optnamelen == static_cast<size_t>(unkoptlen)) {
+ // Exact match, then we don't show any candidates.
+ return;
+ }
+ ++prefix_match;
+ cands.emplace_back(0, options[i].name);
+ continue;
+ }
+ // Use cost 0 for suffix match, but match at least 3 characters
+ if (unkoptlen >= 3 &&
+ iends_with(options[i].name, options[i].name + optnamelen, unkopt,
+ unkopt + unkoptlen)) {
+ cands.emplace_back(0, options[i].name);
+ continue;
+ }
+ // cost values are borrowed from git, help.c.
+ int sim =
+ levenshtein(unkopt, unkoptlen, options[i].name, optnamelen, 0, 2, 1, 3);
+ cands.emplace_back(sim, options[i].name);
+ }
+ if (prefix_match == 1 || cands.empty()) {
+ return;
+ }
+ std::sort(std::begin(cands), std::end(cands));
+ int threshold = cands[0].first;
+ // threshold value is a magic value.
+ if (threshold > 6) {
+ return;
+ }
+ std::cerr << "\nDid you mean:\n";
+ for (auto &item : cands) {
+ if (item.first > threshold) {
+ break;
+ }
+ std::cerr << "\t--" << item.second << "\n";
+ }
+}
+
+bool has_uri_field(const http_parser_url &u, http_parser_url_fields field) {
+ return u.field_set & (1 << field);
+}
+
+bool fieldeq(const char *uri1, const http_parser_url &u1, const char *uri2,
+ const http_parser_url &u2, http_parser_url_fields field) {
+ if (!has_uri_field(u1, field)) {
+ if (!has_uri_field(u2, field)) {
+ return true;
+ } else {
+ return false;
+ }
+ } else if (!has_uri_field(u2, field)) {
+ return false;
+ }
+ if (u1.field_data[field].len != u2.field_data[field].len) {
+ return false;
+ }
+ return memcmp(uri1 + u1.field_data[field].off,
+ uri2 + u2.field_data[field].off, u1.field_data[field].len) == 0;
+}
+
+bool fieldeq(const char *uri, const http_parser_url &u,
+ http_parser_url_fields field, const char *t) {
+ return fieldeq(uri, u, field, StringRef{t});
+}
+
+bool fieldeq(const char *uri, const http_parser_url &u,
+ http_parser_url_fields field, const StringRef &t) {
+ if (!has_uri_field(u, field)) {
+ return t.empty();
+ }
+ auto &f = u.field_data[field];
+ return StringRef{uri + f.off, f.len} == t;
+}
+
+StringRef get_uri_field(const char *uri, const http_parser_url &u,
+ http_parser_url_fields field) {
+ if (!util::has_uri_field(u, field)) {
+ return StringRef{};
+ }
+
+ return StringRef{uri + u.field_data[field].off, u.field_data[field].len};
+}
+
+uint16_t get_default_port(const char *uri, const http_parser_url &u) {
+ if (util::fieldeq(uri, u, UF_SCHEMA, "https")) {
+ return 443;
+ } else if (util::fieldeq(uri, u, UF_SCHEMA, "http")) {
+ return 80;
+ } else {
+ return 443;
+ }
+}
+
+bool porteq(const char *uri1, const http_parser_url &u1, const char *uri2,
+ const http_parser_url &u2) {
+ uint16_t port1, port2;
+ port1 =
+ util::has_uri_field(u1, UF_PORT) ? u1.port : get_default_port(uri1, u1);
+ port2 =
+ util::has_uri_field(u2, UF_PORT) ? u2.port : get_default_port(uri2, u2);
+ return port1 == port2;
+}
+
+void write_uri_field(std::ostream &o, const char *uri, const http_parser_url &u,
+ http_parser_url_fields field) {
+ if (util::has_uri_field(u, field)) {
+ o.write(uri + u.field_data[field].off, u.field_data[field].len);
+ }
+}
+
+bool numeric_host(const char *hostname) {
+ return numeric_host(hostname, AF_INET) || numeric_host(hostname, AF_INET6);
+}
+
+bool numeric_host(const char *hostname, int family) {
+ int rv;
+ std::array<uint8_t, sizeof(struct in6_addr)> dst;
+
+ rv = nghttp2_inet_pton(family, hostname, dst.data());
+
+ return rv == 1;
+}
+
+std::string numeric_name(const struct sockaddr *sa, socklen_t salen) {
+ std::array<char, NI_MAXHOST> host;
+ auto rv = getnameinfo(sa, salen, host.data(), host.size(), nullptr, 0,
+ NI_NUMERICHOST);
+ if (rv != 0) {
+ return "unknown";
+ }
+ return host.data();
+}
+
+std::string to_numeric_addr(const Address *addr) {
+ return to_numeric_addr(&addr->su.sa, addr->len);
+}
+
+std::string to_numeric_addr(const struct sockaddr *sa, socklen_t salen) {
+ auto family = sa->sa_family;
+#ifndef _WIN32
+ if (family == AF_UNIX) {
+ return reinterpret_cast<const sockaddr_un *>(sa)->sun_path;
+ }
+#endif // !_WIN32
+
+ std::array<char, NI_MAXHOST> host;
+ std::array<char, NI_MAXSERV> serv;
+ auto rv = getnameinfo(sa, salen, host.data(), host.size(), serv.data(),
+ serv.size(), NI_NUMERICHOST | NI_NUMERICSERV);
+ if (rv != 0) {
+ return "unknown";
+ }
+
+ auto hostlen = strlen(host.data());
+ auto servlen = strlen(serv.data());
+
+ std::string s;
+ char *p;
+ if (family == AF_INET6) {
+ s.resize(hostlen + servlen + 2 + 1);
+ p = &s[0];
+ *p++ = '[';
+ p = std::copy_n(host.data(), hostlen, p);
+ *p++ = ']';
+ } else {
+ s.resize(hostlen + servlen + 1);
+ p = &s[0];
+ p = std::copy_n(host.data(), hostlen, p);
+ }
+ *p++ = ':';
+ std::copy_n(serv.data(), servlen, p);
+
+ return s;
+}
+
+void set_port(Address &addr, uint16_t port) {
+ switch (addr.su.storage.ss_family) {
+ case AF_INET:
+ addr.su.in.sin_port = htons(port);
+ break;
+ case AF_INET6:
+ addr.su.in6.sin6_port = htons(port);
+ break;
+ }
+}
+
+std::string ascii_dump(const uint8_t *data, size_t len) {
+ std::string res;
+
+ for (size_t i = 0; i < len; ++i) {
+ auto c = data[i];
+
+ if (c >= 0x20 && c < 0x7f) {
+ res += c;
+ } else {
+ res += '.';
+ }
+ }
+
+ return res;
+}
+
+char *get_exec_path(int argc, char **const argv, const char *cwd) {
+ if (argc == 0 || cwd == nullptr) {
+ return nullptr;
+ }
+
+ auto argv0 = argv[0];
+ auto len = strlen(argv0);
+
+ char *path;
+
+ if (argv0[0] == '/') {
+ path = static_cast<char *>(malloc(len + 1));
+ if (path == nullptr) {
+ return nullptr;
+ }
+ memcpy(path, argv0, len + 1);
+ } else {
+ auto cwdlen = strlen(cwd);
+ path = static_cast<char *>(malloc(len + 1 + cwdlen + 1));
+ if (path == nullptr) {
+ return nullptr;
+ }
+ memcpy(path, cwd, cwdlen);
+ path[cwdlen] = '/';
+ memcpy(path + cwdlen + 1, argv0, len + 1);
+ }
+
+ return path;
+}
+
+bool check_path(const std::string &path) {
+ // We don't like '\' in path.
+ return !path.empty() && path[0] == '/' &&
+ path.find('\\') == std::string::npos &&
+ path.find("/../") == std::string::npos &&
+ path.find("/./") == std::string::npos &&
+ !util::ends_with_l(path, "/..") && !util::ends_with_l(path, "/.");
+}
+
+int64_t to_time64(const timeval &tv) {
+ return tv.tv_sec * 1000000 + tv.tv_usec;
+}
+
+bool check_h2_is_selected(const StringRef &proto) {
+ return streq(NGHTTP2_H2, proto) || streq(NGHTTP2_H2_16, proto) ||
+ streq(NGHTTP2_H2_14, proto);
+}
+
+namespace {
+bool select_proto(const unsigned char **out, unsigned char *outlen,
+ const unsigned char *in, unsigned int inlen,
+ const StringRef &key) {
+ for (auto p = in, end = in + inlen; p + key.size() <= end; p += *p + 1) {
+ if (std::equal(std::begin(key), std::end(key), p)) {
+ *out = p + 1;
+ *outlen = *p;
+ return true;
+ }
+ }
+ return false;
+}
+} // namespace
+
+bool select_h2(const unsigned char **out, unsigned char *outlen,
+ const unsigned char *in, unsigned int inlen) {
+ return select_proto(out, outlen, in, inlen, NGHTTP2_H2_ALPN) ||
+ select_proto(out, outlen, in, inlen, NGHTTP2_H2_16_ALPN) ||
+ select_proto(out, outlen, in, inlen, NGHTTP2_H2_14_ALPN);
+}
+
+bool select_protocol(const unsigned char **out, unsigned char *outlen,
+ const unsigned char *in, unsigned int inlen,
+ std::vector<std::string> proto_list) {
+ for (const auto &proto : proto_list) {
+ if (select_proto(out, outlen, in, inlen, StringRef{proto})) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+std::vector<unsigned char> get_default_alpn() {
+ auto res = std::vector<unsigned char>(NGHTTP2_H2_ALPN.size() +
+ NGHTTP2_H2_16_ALPN.size() +
+ NGHTTP2_H2_14_ALPN.size());
+ auto p = std::begin(res);
+
+ p = std::copy_n(std::begin(NGHTTP2_H2_ALPN), NGHTTP2_H2_ALPN.size(), p);
+ p = std::copy_n(std::begin(NGHTTP2_H2_16_ALPN), NGHTTP2_H2_16_ALPN.size(), p);
+ p = std::copy_n(std::begin(NGHTTP2_H2_14_ALPN), NGHTTP2_H2_14_ALPN.size(), p);
+
+ return res;
+}
+
+std::vector<StringRef> split_str(const StringRef &s, char delim) {
+ size_t len = 1;
+ auto last = std::end(s);
+ StringRef::const_iterator d;
+ for (auto first = std::begin(s); (d = std::find(first, last, delim)) != last;
+ ++len, first = d + 1)
+ ;
+
+ auto list = std::vector<StringRef>(len);
+
+ len = 0;
+ for (auto first = std::begin(s);; ++len) {
+ auto stop = std::find(first, last, delim);
+ list[len] = StringRef{first, stop};
+ if (stop == last) {
+ break;
+ }
+ first = stop + 1;
+ }
+ return list;
+}
+
+std::vector<StringRef> split_str(const StringRef &s, char delim, size_t n) {
+ if (n == 0) {
+ return split_str(s, delim);
+ }
+
+ if (n == 1) {
+ return {s};
+ }
+
+ size_t len = 1;
+ auto last = std::end(s);
+ StringRef::const_iterator d;
+ for (auto first = std::begin(s);
+ len < n && (d = std::find(first, last, delim)) != last;
+ ++len, first = d + 1)
+ ;
+
+ auto list = std::vector<StringRef>(len);
+
+ len = 0;
+ for (auto first = std::begin(s);; ++len) {
+ if (len == n - 1) {
+ list[len] = StringRef{first, last};
+ break;
+ }
+
+ auto stop = std::find(first, last, delim);
+ list[len] = StringRef{first, stop};
+ if (stop == last) {
+ break;
+ }
+ first = stop + 1;
+ }
+ return list;
+}
+
+std::vector<std::string> parse_config_str_list(const StringRef &s, char delim) {
+ auto sublist = split_str(s, delim);
+ auto res = std::vector<std::string>();
+ res.reserve(sublist.size());
+ for (const auto &s : sublist) {
+ res.emplace_back(std::begin(s), std::end(s));
+ }
+ return res;
+}
+
+int make_socket_closeonexec(int fd) {
+#ifdef _WIN32
+ (void)fd;
+ return 0;
+#else // !_WIN32
+ int flags;
+ int rv;
+ while ((flags = fcntl(fd, F_GETFD)) == -1 && errno == EINTR)
+ ;
+ while ((rv = fcntl(fd, F_SETFD, flags | FD_CLOEXEC)) == -1 && errno == EINTR)
+ ;
+ return rv;
+#endif // !_WIN32
+}
+
+int make_socket_nonblocking(int fd) {
+ int rv;
+
+#ifdef _WIN32
+ u_long mode = 1;
+
+ rv = ioctlsocket(fd, FIONBIO, &mode);
+#else // !_WIN32
+ int flags;
+ while ((flags = fcntl(fd, F_GETFL, 0)) == -1 && errno == EINTR)
+ ;
+ while ((rv = fcntl(fd, F_SETFL, flags | O_NONBLOCK)) == -1 && errno == EINTR)
+ ;
+#endif // !_WIN32
+
+ return rv;
+}
+
+int make_socket_nodelay(int fd) {
+ int val = 1;
+ if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char *>(&val),
+ sizeof(val)) == -1) {
+ return -1;
+ }
+ return 0;
+}
+
+int create_nonblock_socket(int family) {
+#ifdef SOCK_NONBLOCK
+ auto fd = socket(family, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
+
+ if (fd == -1) {
+ return -1;
+ }
+#else // !SOCK_NONBLOCK
+ auto fd = socket(family, SOCK_STREAM, 0);
+
+ if (fd == -1) {
+ return -1;
+ }
+
+ make_socket_nonblocking(fd);
+ make_socket_closeonexec(fd);
+#endif // !SOCK_NONBLOCK
+
+ if (family == AF_INET || family == AF_INET6) {
+ make_socket_nodelay(fd);
+ }
+
+ return fd;
+}
+
+int create_nonblock_udp_socket(int family) {
+#ifdef SOCK_NONBLOCK
+ auto fd = socket(family, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
+
+ if (fd == -1) {
+ return -1;
+ }
+#else // !SOCK_NONBLOCK
+ auto fd = socket(family, SOCK_DGRAM, 0);
+
+ if (fd == -1) {
+ return -1;
+ }
+
+ make_socket_nonblocking(fd);
+ make_socket_closeonexec(fd);
+#endif // !SOCK_NONBLOCK
+
+ return fd;
+}
+
+int bind_any_addr_udp(int fd, int family) {
+ addrinfo hints{};
+ addrinfo *res, *rp;
+ int rv;
+
+ hints.ai_family = family;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_flags = AI_PASSIVE;
+
+ rv = getaddrinfo(nullptr, "0", &hints, &res);
+ if (rv != 0) {
+ return -1;
+ }
+
+ for (rp = res; rp; rp = rp->ai_next) {
+ if (bind(fd, rp->ai_addr, rp->ai_addrlen) != -1) {
+ break;
+ }
+ }
+
+ freeaddrinfo(res);
+
+ if (!rp) {
+ return -1;
+ }
+
+ return 0;
+}
+
+bool check_socket_connected(int fd) {
+ int error;
+ socklen_t len = sizeof(error);
+ if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *)&error, &len) != 0) {
+ return false;
+ }
+
+ return error == 0;
+}
+
+int get_socket_error(int fd) {
+ int error;
+ socklen_t len = sizeof(error);
+ if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *)&error, &len) != 0) {
+ return -1;
+ }
+
+ return error;
+}
+
+bool ipv6_numeric_addr(const char *host) {
+ uint8_t dst[16];
+ return nghttp2_inet_pton(AF_INET6, host, dst) == 1;
+}
+
+namespace {
+std::pair<int64_t, size_t> parse_uint_digits(const void *ss, size_t len) {
+ const uint8_t *s = static_cast<const uint8_t *>(ss);
+ int64_t n = 0;
+ size_t i;
+ if (len == 0) {
+ return {-1, 0};
+ }
+ constexpr int64_t max = std::numeric_limits<int64_t>::max();
+ for (i = 0; i < len; ++i) {
+ if ('0' <= s[i] && s[i] <= '9') {
+ if (n > max / 10) {
+ return {-1, 0};
+ }
+ n *= 10;
+ if (n > max - (s[i] - '0')) {
+ return {-1, 0};
+ }
+ n += s[i] - '0';
+ continue;
+ }
+ break;
+ }
+ if (i == 0) {
+ return {-1, 0};
+ }
+ return {n, i};
+}
+} // namespace
+
+int64_t parse_uint_with_unit(const char *s) {
+ return parse_uint_with_unit(reinterpret_cast<const uint8_t *>(s), strlen(s));
+}
+
+int64_t parse_uint_with_unit(const StringRef &s) {
+ return parse_uint_with_unit(s.byte(), s.size());
+}
+
+int64_t parse_uint_with_unit(const uint8_t *s, size_t len) {
+ int64_t n;
+ size_t i;
+ std::tie(n, i) = parse_uint_digits(s, len);
+ if (n == -1) {
+ return -1;
+ }
+ if (i == len) {
+ return n;
+ }
+ if (i + 1 != len) {
+ return -1;
+ }
+ int mul = 1;
+ switch (s[i]) {
+ case 'K':
+ case 'k':
+ mul = 1 << 10;
+ break;
+ case 'M':
+ case 'm':
+ mul = 1 << 20;
+ break;
+ case 'G':
+ case 'g':
+ mul = 1 << 30;
+ break;
+ default:
+ return -1;
+ }
+ constexpr int64_t max = std::numeric_limits<int64_t>::max();
+ if (n > max / mul) {
+ return -1;
+ }
+ return n * mul;
+}
+
+int64_t parse_uint(const char *s) {
+ return parse_uint(reinterpret_cast<const uint8_t *>(s), strlen(s));
+}
+
+int64_t parse_uint(const std::string &s) {
+ return parse_uint(reinterpret_cast<const uint8_t *>(s.c_str()), s.size());
+}
+
+int64_t parse_uint(const StringRef &s) {
+ return parse_uint(s.byte(), s.size());
+}
+
+int64_t parse_uint(const uint8_t *s, size_t len) {
+ int64_t n;
+ size_t i;
+ std::tie(n, i) = parse_uint_digits(s, len);
+ if (n == -1 || i != len) {
+ return -1;
+ }
+ return n;
+}
+
+double parse_duration_with_unit(const char *s) {
+ return parse_duration_with_unit(reinterpret_cast<const uint8_t *>(s),
+ strlen(s));
+}
+
+double parse_duration_with_unit(const StringRef &s) {
+ return parse_duration_with_unit(s.byte(), s.size());
+}
+
+double parse_duration_with_unit(const uint8_t *s, size_t len) {
+ constexpr auto max = std::numeric_limits<int64_t>::max();
+ int64_t n;
+ size_t i;
+
+ std::tie(n, i) = parse_uint_digits(s, len);
+ if (n == -1) {
+ goto fail;
+ }
+ if (i == len) {
+ return static_cast<double>(n);
+ }
+ switch (s[i]) {
+ case 'S':
+ case 's':
+ // seconds
+ if (i + 1 != len) {
+ goto fail;
+ }
+ return static_cast<double>(n);
+ case 'M':
+ case 'm':
+ if (i + 1 == len) {
+ // minutes
+ if (n > max / 60) {
+ goto fail;
+ }
+ return static_cast<double>(n) * 60;
+ }
+
+ if (i + 2 != len || (s[i + 1] != 's' && s[i + 1] != 'S')) {
+ goto fail;
+ }
+ // milliseconds
+ return static_cast<double>(n) / 1000.;
+ case 'H':
+ case 'h':
+ // hours
+ if (i + 1 != len) {
+ goto fail;
+ }
+ if (n > max / 3600) {
+ goto fail;
+ }
+ return static_cast<double>(n) * 3600;
+ }
+fail:
+ return std::numeric_limits<double>::infinity();
+}
+
+std::string duration_str(double t) {
+ if (t == 0.) {
+ return "0";
+ }
+ auto frac = static_cast<int64_t>(t * 1000) % 1000;
+ if (frac > 0) {
+ return utos(static_cast<int64_t>(t * 1000)) + "ms";
+ }
+ auto v = static_cast<int64_t>(t);
+ if (v % 60) {
+ return utos(v) + "s";
+ }
+ v /= 60;
+ if (v % 60) {
+ return utos(v) + "m";
+ }
+ v /= 60;
+ return utos(v) + "h";
+}
+
+std::string format_duration(const std::chrono::microseconds &u) {
+ const char *unit = "us";
+ int d = 0;
+ auto t = u.count();
+ if (t >= 1000000) {
+ d = 1000000;
+ unit = "s";
+ } else if (t >= 1000) {
+ d = 1000;
+ unit = "ms";
+ } else {
+ return utos(t) + unit;
+ }
+ return dtos(static_cast<double>(t) / d) + unit;
+}
+
+std::string format_duration(double t) {
+ const char *unit = "us";
+ if (t >= 1.) {
+ unit = "s";
+ } else if (t >= 0.001) {
+ t *= 1000.;
+ unit = "ms";
+ } else {
+ t *= 1000000.;
+ return utos(static_cast<int64_t>(t)) + unit;
+ }
+ return dtos(t) + unit;
+}
+
+std::string dtos(double n) {
+ auto m = llround(100. * n);
+ auto f = utos(m % 100);
+ return utos(m / 100) + "." + (f.size() == 1 ? "0" : "") + f;
+}
+
+StringRef make_http_hostport(BlockAllocator &balloc, const StringRef &host,
+ uint16_t port) {
+ auto iov = make_byte_ref(balloc, host.size() + 2 + 1 + 5 + 1);
+ return make_http_hostport(iov.base, host, port);
+}
+
+StringRef make_hostport(BlockAllocator &balloc, const StringRef &host,
+ uint16_t port) {
+ auto iov = make_byte_ref(balloc, host.size() + 2 + 1 + 5 + 1);
+ return make_hostport(iov.base, host, port);
+}
+
+namespace {
+void hexdump8(FILE *out, const uint8_t *first, const uint8_t *last) {
+ auto stop = std::min(first + 8, last);
+ for (auto k = first; k != stop; ++k) {
+ fprintf(out, "%02x ", *k);
+ }
+ // each byte needs 3 spaces (2 hex value and space)
+ for (; stop != first + 8; ++stop) {
+ fputs(" ", out);
+ }
+ // we have extra space after 8 bytes
+ fputc(' ', out);
+}
+} // namespace
+
+void hexdump(FILE *out, const uint8_t *src, size_t len) {
+ if (len == 0) {
+ return;
+ }
+ size_t buflen = 0;
+ auto repeated = false;
+ std::array<uint8_t, 16> buf{};
+ auto end = src + len;
+ auto i = src;
+ for (;;) {
+ auto nextlen =
+ std::min(static_cast<size_t>(16), static_cast<size_t>(end - i));
+ if (nextlen == buflen &&
+ std::equal(std::begin(buf), std::begin(buf) + buflen, i)) {
+ // as long as adjacent 16 bytes block are the same, we just
+ // print single '*'.
+ if (!repeated) {
+ repeated = true;
+ fputs("*\n", out);
+ }
+ i += nextlen;
+ continue;
+ }
+ repeated = false;
+ fprintf(out, "%08lx", static_cast<unsigned long>(i - src));
+ if (i == end) {
+ fputc('\n', out);
+ break;
+ }
+ fputs(" ", out);
+ hexdump8(out, i, end);
+ hexdump8(out, i + 8, std::max(i + 8, end));
+ fputc('|', out);
+ auto stop = std::min(i + 16, end);
+ buflen = stop - i;
+ auto p = buf.data();
+ for (; i != stop; ++i) {
+ *p++ = *i;
+ if (0x20 <= *i && *i <= 0x7e) {
+ fputc(*i, out);
+ } else {
+ fputc('.', out);
+ }
+ }
+ fputs("|\n", out);
+ }
+}
+
+void put_uint16be(uint8_t *buf, uint16_t n) {
+ uint16_t x = htons(n);
+ memcpy(buf, &x, sizeof(uint16_t));
+}
+
+void put_uint32be(uint8_t *buf, uint32_t n) {
+ uint32_t x = htonl(n);
+ memcpy(buf, &x, sizeof(uint32_t));
+}
+
+uint16_t get_uint16(const uint8_t *data) {
+ uint16_t n;
+ memcpy(&n, data, sizeof(uint16_t));
+ return ntohs(n);
+}
+
+uint32_t get_uint32(const uint8_t *data) {
+ uint32_t n;
+ memcpy(&n, data, sizeof(uint32_t));
+ return ntohl(n);
+}
+
+uint64_t get_uint64(const uint8_t *data) {
+ uint64_t n = 0;
+ n += static_cast<uint64_t>(data[0]) << 56;
+ n += static_cast<uint64_t>(data[1]) << 48;
+ n += static_cast<uint64_t>(data[2]) << 40;
+ n += static_cast<uint64_t>(data[3]) << 32;
+ n += static_cast<uint64_t>(data[4]) << 24;
+ n += data[5] << 16;
+ n += data[6] << 8;
+ n += data[7];
+ return n;
+}
+
+int read_mime_types(std::map<std::string, std::string> &res,
+ const char *filename) {
+ std::ifstream infile(filename);
+ if (!infile) {
+ return -1;
+ }
+
+ auto delim_pred = [](char c) { return c == ' ' || c == '\t'; };
+
+ std::string line;
+ while (std::getline(infile, line)) {
+ if (line.empty() || line[0] == '#') {
+ continue;
+ }
+
+ auto type_end = std::find_if(std::begin(line), std::end(line), delim_pred);
+ if (type_end == std::begin(line)) {
+ continue;
+ }
+
+ auto ext_end = type_end;
+ for (;;) {
+ auto ext_start = std::find_if_not(ext_end, std::end(line), delim_pred);
+ if (ext_start == std::end(line)) {
+ break;
+ }
+ ext_end = std::find_if(ext_start, std::end(line), delim_pred);
+#ifdef HAVE_STD_MAP_EMPLACE
+ res.emplace(std::string(ext_start, ext_end),
+ std::string(std::begin(line), type_end));
+#else // !HAVE_STD_MAP_EMPLACE
+ res.insert(std::make_pair(std::string(ext_start, ext_end),
+ std::string(std::begin(line), type_end)));
+#endif // !HAVE_STD_MAP_EMPLACE
+ }
+ }
+
+ return 0;
+}
+
+StringRef percent_decode(BlockAllocator &balloc, const StringRef &src) {
+ auto iov = make_byte_ref(balloc, src.size() * 3 + 1);
+ auto p = iov.base;
+ for (auto first = std::begin(src); first != std::end(src); ++first) {
+ if (*first != '%') {
+ *p++ = *first;
+ continue;
+ }
+
+ if (first + 1 != std::end(src) && first + 2 != std::end(src) &&
+ is_hex_digit(*(first + 1)) && is_hex_digit(*(first + 2))) {
+ *p++ = (hex_to_uint(*(first + 1)) << 4) + hex_to_uint(*(first + 2));
+ first += 2;
+ continue;
+ }
+
+ *p++ = *first;
+ }
+ *p = '\0';
+ return StringRef{iov.base, p};
+}
+
+// Returns x**y
+double int_pow(double x, size_t y) {
+ auto res = 1.;
+ for (; y; --y) {
+ res *= x;
+ }
+ return res;
+}
+
+uint32_t hash32(const StringRef &s) {
+ /* 32 bit FNV-1a: http://isthe.com/chongo/tech/comp/fnv/ */
+ uint32_t h = 2166136261u;
+ size_t i;
+
+ for (i = 0; i < s.size(); ++i) {
+ h ^= s[i];
+ h += (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24);
+ }
+
+ return h;
+}
+
+namespace {
+int message_digest(uint8_t *res, const EVP_MD *meth, const StringRef &s) {
+ int rv;
+
+ auto ctx = EVP_MD_CTX_new();
+ if (ctx == nullptr) {
+ return -1;
+ }
+
+ auto ctx_deleter = defer(EVP_MD_CTX_free, ctx);
+
+ rv = EVP_DigestInit_ex(ctx, meth, nullptr);
+ if (rv != 1) {
+ return -1;
+ }
+
+ rv = EVP_DigestUpdate(ctx, s.c_str(), s.size());
+ if (rv != 1) {
+ return -1;
+ }
+
+ unsigned int mdlen = EVP_MD_size(meth);
+
+ rv = EVP_DigestFinal_ex(ctx, res, &mdlen);
+ if (rv != 1) {
+ return -1;
+ }
+
+ return 0;
+}
+} // namespace
+
+int sha256(uint8_t *res, const StringRef &s) {
+ return message_digest(res, EVP_sha256(), s);
+}
+
+int sha1(uint8_t *res, const StringRef &s) {
+ return message_digest(res, EVP_sha1(), s);
+}
+
+bool is_hex_string(const StringRef &s) {
+ if (s.size() % 2) {
+ return false;
+ }
+
+ for (auto c : s) {
+ if (!is_hex_digit(c)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+StringRef decode_hex(BlockAllocator &balloc, const StringRef &s) {
+ auto iov = make_byte_ref(balloc, s.size() + 1);
+ auto p = decode_hex(iov.base, s);
+ *p = '\0';
+ return StringRef{iov.base, p};
+}
+
+StringRef extract_host(const StringRef &hostport) {
+ if (hostport[0] == '[') {
+ // assume this is IPv6 numeric address
+ auto p = std::find(std::begin(hostport), std::end(hostport), ']');
+ if (p == std::end(hostport)) {
+ return StringRef{};
+ }
+ if (p + 1 < std::end(hostport) && *(p + 1) != ':') {
+ return StringRef{};
+ }
+ return StringRef{std::begin(hostport), p + 1};
+ }
+
+ auto p = std::find(std::begin(hostport), std::end(hostport), ':');
+ if (p == std::begin(hostport)) {
+ return StringRef{};
+ }
+ return StringRef{std::begin(hostport), p};
+}
+
+std::pair<StringRef, StringRef> split_hostport(const StringRef &hostport) {
+ if (hostport.empty()) {
+ return {};
+ }
+ if (hostport[0] == '[') {
+ // assume this is IPv6 numeric address
+ auto p = std::find(std::begin(hostport), std::end(hostport), ']');
+ if (p == std::end(hostport)) {
+ return {};
+ }
+ if (p + 1 == std::end(hostport)) {
+ return {StringRef{std::begin(hostport) + 1, p}, {}};
+ }
+ if (*(p + 1) != ':' || p + 2 == std::end(hostport)) {
+ return {};
+ }
+ return {StringRef{std::begin(hostport) + 1, p},
+ StringRef{p + 2, std::end(hostport)}};
+ }
+
+ auto p = std::find(std::begin(hostport), std::end(hostport), ':');
+ if (p == std::begin(hostport)) {
+ return {};
+ }
+ if (p == std::end(hostport)) {
+ return {StringRef{std::begin(hostport), p}, {}};
+ }
+ if (p + 1 == std::end(hostport)) {
+ return {};
+ }
+
+ return {StringRef{std::begin(hostport), p},
+ StringRef{p + 1, std::end(hostport)}};
+}
+
+std::mt19937 make_mt19937() {
+ std::random_device rd;
+ return std::mt19937(rd());
+}
+
+int daemonize(int nochdir, int noclose) {
+#ifdef __APPLE__
+ pid_t pid;
+ pid = fork();
+ if (pid == -1) {
+ return -1;
+ } else if (pid > 0) {
+ _exit(EXIT_SUCCESS);
+ }
+ if (setsid() == -1) {
+ return -1;
+ }
+ pid = fork();
+ if (pid == -1) {
+ return -1;
+ } else if (pid > 0) {
+ _exit(EXIT_SUCCESS);
+ }
+ if (nochdir == 0) {
+ if (chdir("/") == -1) {
+ return -1;
+ }
+ }
+ if (noclose == 0) {
+ if (freopen("/dev/null", "r", stdin) == nullptr) {
+ return -1;
+ }
+ if (freopen("/dev/null", "w", stdout) == nullptr) {
+ return -1;
+ }
+ if (freopen("/dev/null", "w", stderr) == nullptr) {
+ return -1;
+ }
+ }
+ return 0;
+#else // !__APPLE__
+ return daemon(nochdir, noclose);
+#endif // !__APPLE__
+}
+
+StringRef rstrip(BlockAllocator &balloc, const StringRef &s) {
+ auto it = std::rbegin(s);
+ for (; it != std::rend(s) && (*it == ' ' || *it == '\t'); ++it)
+ ;
+
+ auto len = it - std::rbegin(s);
+ if (len == 0) {
+ return s;
+ }
+
+ return make_string_ref(balloc, StringRef{s.c_str(), s.size() - len});
+}
+
+#ifdef ENABLE_HTTP3
+int msghdr_get_local_addr(Address &dest, msghdr *msg, int family) {
+ switch (family) {
+ case AF_INET:
+ for (auto cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
+ if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) {
+ in_pktinfo pktinfo;
+ memcpy(&pktinfo, CMSG_DATA(cmsg), sizeof(pktinfo));
+ dest.len = sizeof(dest.su.in);
+ auto &sa = dest.su.in;
+ sa.sin_family = AF_INET;
+ sa.sin_addr = pktinfo.ipi_addr;
+
+ return 0;
+ }
+ }
+
+ return -1;
+ case AF_INET6:
+ for (auto cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
+ if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) {
+ in6_pktinfo pktinfo;
+ memcpy(&pktinfo, CMSG_DATA(cmsg), sizeof(pktinfo));
+ dest.len = sizeof(dest.su.in6);
+ auto &sa = dest.su.in6;
+ sa.sin6_family = AF_INET6;
+ sa.sin6_addr = pktinfo.ipi6_addr;
+ return 0;
+ }
+ }
+
+ return -1;
+ }
+
+ return -1;
+}
+
+uint8_t msghdr_get_ecn(msghdr *msg, int family) {
+ switch (family) {
+ case AF_INET:
+ for (auto cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
+ if (cmsg->cmsg_level == IPPROTO_IP &&
+# ifdef __APPLE__
+ cmsg->cmsg_type == IP_RECVTOS
+# else // !__APPLE__
+ cmsg->cmsg_type == IP_TOS
+# endif // !__APPLE__
+ && cmsg->cmsg_len) {
+ return *reinterpret_cast<uint8_t *>(CMSG_DATA(cmsg)) & IPTOS_ECN_MASK;
+ }
+ }
+
+ return 0;
+ case AF_INET6:
+ for (auto cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
+ if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_TCLASS &&
+ cmsg->cmsg_len) {
+ unsigned int tos;
+
+ memcpy(&tos, CMSG_DATA(cmsg), sizeof(tos));
+
+ return tos & IPTOS_ECN_MASK;
+ }
+ }
+
+ return 0;
+ }
+
+ return 0;
+}
+
+size_t msghdr_get_udp_gro(msghdr *msg) {
+ uint16_t gso_size = 0;
+
+# ifdef UDP_GRO
+ for (auto cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
+ if (cmsg->cmsg_level == SOL_UDP && cmsg->cmsg_type == UDP_GRO) {
+ memcpy(&gso_size, CMSG_DATA(cmsg), sizeof(gso_size));
+
+ break;
+ }
+ }
+# endif // UDP_GRO
+
+ return gso_size;
+}
+#endif // ENABLE_HTTP3
+
+} // namespace util
+
+} // namespace nghttp2
diff --git a/src/util.h b/src/util.h
new file mode 100644
index 0000000..d818bf2
--- /dev/null
+++ b/src/util.h
@@ -0,0 +1,971 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef UTIL_H
+#define UTIL_H
+
+#include "nghttp2_config.h"
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+#include <getopt.h>
+#ifdef HAVE_NETDB_H
+# include <netdb.h>
+#endif // HAVE_NETDB_H
+
+#include <cmath>
+#include <cstring>
+#include <cassert>
+#include <vector>
+#include <string>
+#include <algorithm>
+#include <sstream>
+#include <memory>
+#include <chrono>
+#include <map>
+#include <random>
+
+#ifdef HAVE_LIBEV
+# include <ev.h>
+#endif // HAVE_LIBEV
+
+#include "url-parser/url_parser.h"
+
+#include "template.h"
+#include "network.h"
+#include "allocator.h"
+
+namespace nghttp2 {
+
+constexpr auto NGHTTP2_H2_ALPN = StringRef::from_lit("\x2h2");
+constexpr auto NGHTTP2_H2 = StringRef::from_lit("h2");
+
+// The additional HTTP/2 protocol ALPN protocol identifier we also
+// supports for our applications to make smooth migration into final
+// h2 ALPN ID.
+constexpr auto NGHTTP2_H2_16_ALPN = StringRef::from_lit("\x5h2-16");
+constexpr auto NGHTTP2_H2_16 = StringRef::from_lit("h2-16");
+
+constexpr auto NGHTTP2_H2_14_ALPN = StringRef::from_lit("\x5h2-14");
+constexpr auto NGHTTP2_H2_14 = StringRef::from_lit("h2-14");
+
+constexpr auto NGHTTP2_H1_1_ALPN = StringRef::from_lit("\x8http/1.1");
+constexpr auto NGHTTP2_H1_1 = StringRef::from_lit("http/1.1");
+
+constexpr size_t NGHTTP2_MAX_UINT64_DIGITS = str_size("18446744073709551615");
+
+namespace util {
+
+extern const char UPPER_XDIGITS[];
+
+inline bool is_alpha(const char c) {
+ return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z');
+}
+
+inline bool is_digit(const char c) { return '0' <= c && c <= '9'; }
+
+inline bool is_hex_digit(const char c) {
+ return is_digit(c) || ('A' <= c && c <= 'F') || ('a' <= c && c <= 'f');
+}
+
+// Returns true if |s| is hex string.
+bool is_hex_string(const StringRef &s);
+
+bool in_rfc3986_unreserved_chars(const char c);
+
+bool in_rfc3986_sub_delims(const char c);
+
+// Returns true if |c| is in token (HTTP-p1, Section 3.2.6)
+bool in_token(char c);
+
+bool in_attr_char(char c);
+
+// Returns integer corresponding to hex notation |c|. If
+// is_hex_digit(c) is false, it returns 256.
+uint32_t hex_to_uint(char c);
+
+std::string percent_encode(const unsigned char *target, size_t len);
+
+std::string percent_encode(const std::string &target);
+
+template <typename InputIt>
+std::string percent_decode(InputIt first, InputIt last) {
+ std::string result;
+ result.resize(last - first);
+ auto p = std::begin(result);
+ for (; first != last; ++first) {
+ if (*first != '%') {
+ *p++ = *first;
+ continue;
+ }
+
+ if (first + 1 != last && first + 2 != last && is_hex_digit(*(first + 1)) &&
+ is_hex_digit(*(first + 2))) {
+ *p++ = (hex_to_uint(*(first + 1)) << 4) + hex_to_uint(*(first + 2));
+ first += 2;
+ continue;
+ }
+
+ *p++ = *first;
+ }
+ result.resize(p - std::begin(result));
+ return result;
+}
+
+StringRef percent_decode(BlockAllocator &balloc, const StringRef &src);
+
+// Percent encode |target| if character is not in token or '%'.
+StringRef percent_encode_token(BlockAllocator &balloc, const StringRef &target);
+
+template <typename OutputIt>
+OutputIt percent_encode_token(OutputIt it, const StringRef &target) {
+ for (auto first = std::begin(target); first != std::end(target); ++first) {
+ uint8_t c = *first;
+
+ if (c != '%' && in_token(c)) {
+ *it++ = c;
+ continue;
+ }
+
+ *it++ = '%';
+ *it++ = UPPER_XDIGITS[c >> 4];
+ *it++ = UPPER_XDIGITS[(c & 0x0f)];
+ }
+
+ return it;
+}
+
+// Returns the number of bytes written by percent_encode_token with
+// the same |target| parameter. The return value does not include a
+// terminal NUL byte.
+size_t percent_encode_tokenlen(const StringRef &target);
+
+// Returns quotedString version of |target|. Currently, this function
+// just replace '"' with '\"'.
+StringRef quote_string(BlockAllocator &balloc, const StringRef &target);
+
+template <typename OutputIt>
+OutputIt quote_string(OutputIt it, const StringRef &target) {
+ for (auto c : target) {
+ if (c == '"') {
+ *it++ = '\\';
+ *it++ = '"';
+ } else {
+ *it++ = c;
+ }
+ }
+
+ return it;
+}
+
+// Returns the number of bytes written by quote_string with the same
+// |target| parameter. The return value does not include a terminal
+// NUL byte.
+size_t quote_stringlen(const StringRef &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);
+}
+
+template <size_t N> std::string format_hex(const std::array<uint8_t, N> &s) {
+ return format_hex(s.data(), s.size());
+}
+
+StringRef format_hex(BlockAllocator &balloc, const StringRef &s);
+
+static constexpr char LOWER_XDIGITS[] = "0123456789abcdef";
+
+template <typename OutputIt>
+OutputIt format_hex(OutputIt it, const StringRef &s) {
+ for (auto cc : s) {
+ uint8_t c = cc;
+ *it++ = LOWER_XDIGITS[c >> 4];
+ *it++ = LOWER_XDIGITS[c & 0xf];
+ }
+
+ return it;
+}
+
+// decode_hex decodes hex string |s|, returns the decoded byte string.
+// This function assumes |s| is hex string, that is is_hex_string(s)
+// == true.
+StringRef decode_hex(BlockAllocator &balloc, const StringRef &s);
+
+template <typename OutputIt>
+OutputIt decode_hex(OutputIt d_first, const StringRef &s) {
+ for (auto it = std::begin(s); it != std::end(s); it += 2) {
+ *d_first++ = (hex_to_uint(*it) << 4) | hex_to_uint(*(it + 1));
+ }
+
+ return d_first;
+}
+
+// Returns given time |t| from epoch in HTTP Date format (e.g., Mon,
+// 10 Oct 2016 10:25:58 GMT).
+std::string http_date(time_t t);
+// Writes given time |t| from epoch in HTTP Date format into the
+// buffer pointed by |res|. The buffer must be at least 29 bytes
+// long. This function returns the one beyond the last position.
+char *http_date(char *res, time_t t);
+
+// Returns given time |t| from epoch in Common Log format (e.g.,
+// 03/Jul/2014:00:19:38 +0900)
+std::string common_log_date(time_t t);
+// Writes given time |t| from epoch in Common Log format into the
+// buffer pointed by |res|. The buffer must be at least 26 bytes
+// long. This function returns the one beyond the last position.
+char *common_log_date(char *res, time_t t);
+
+// Returns given millisecond |ms| from epoch in ISO 8601 format (e.g.,
+// 2014-11-15T12:58:24.741Z or 2014-11-15T12:58:24.741+09:00)
+std::string iso8601_date(int64_t ms);
+// Writes given time |t| from epoch in ISO 8601 format into the buffer
+// pointed by |res|. The buffer must be at least 29 bytes long. This
+// function returns the one beyond the last position.
+char *iso8601_date(char *res, int64_t ms);
+
+// Writes given time |t| from epoch in ISO 8601 basic format into the
+// buffer pointed by |res|. The buffer must be at least 24 bytes
+// long. This function returns the one beyond the last position.
+char *iso8601_basic_date(char *res, int64_t ms);
+
+time_t parse_http_date(const StringRef &s);
+
+// Parses time formatted as "MMM DD HH:MM:SS YYYY [GMT]" (e.g., Feb 3
+// 00:55:52 2015 GMT), which is specifically used by OpenSSL
+// ASN1_TIME_print().
+time_t parse_openssl_asn1_time_print(const StringRef &s);
+
+char upcase(char c);
+
+inline char lowcase(char c) {
+ constexpr static unsigned char tbl[] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
+ 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
+ 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
+ 60, 61, 62, 63, 64, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
+ 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y',
+ 'z', 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104,
+ 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
+ 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134,
+ 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149,
+ 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164,
+ 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179,
+ 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194,
+ 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209,
+ 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224,
+ 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
+ 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254,
+ 255,
+ };
+ return tbl[static_cast<unsigned char>(c)];
+}
+
+template <typename InputIterator1, typename InputIterator2>
+bool starts_with(InputIterator1 first1, InputIterator1 last1,
+ InputIterator2 first2, InputIterator2 last2) {
+ if (last1 - first1 < last2 - first2) {
+ return false;
+ }
+ return std::equal(first2, last2, first1);
+}
+
+template <typename S, typename T> bool starts_with(const S &a, const T &b) {
+ return starts_with(a.begin(), a.end(), b.begin(), b.end());
+}
+
+struct CaseCmp {
+ bool operator()(char lhs, char rhs) const {
+ return lowcase(lhs) == lowcase(rhs);
+ }
+};
+
+template <typename InputIterator1, typename InputIterator2>
+bool istarts_with(InputIterator1 first1, InputIterator1 last1,
+ InputIterator2 first2, InputIterator2 last2) {
+ if (last1 - first1 < last2 - first2) {
+ return false;
+ }
+ return std::equal(first2, last2, first1, CaseCmp());
+}
+
+template <typename S, typename T> bool istarts_with(const S &a, const T &b) {
+ return istarts_with(a.begin(), a.end(), b.begin(), b.end());
+}
+
+template <typename T, typename CharT, size_t N>
+bool istarts_with_l(const T &a, const CharT (&b)[N]) {
+ return istarts_with(a.begin(), a.end(), b, b + N - 1);
+}
+
+template <typename InputIterator1, typename InputIterator2>
+bool ends_with(InputIterator1 first1, InputIterator1 last1,
+ InputIterator2 first2, InputIterator2 last2) {
+ if (last1 - first1 < last2 - first2) {
+ return false;
+ }
+ return std::equal(first2, last2, last1 - (last2 - first2));
+}
+
+template <typename T, typename S> bool ends_with(const T &a, const S &b) {
+ return ends_with(a.begin(), a.end(), b.begin(), b.end());
+}
+
+template <typename T, typename CharT, size_t N>
+bool ends_with_l(const T &a, const CharT (&b)[N]) {
+ return ends_with(a.begin(), a.end(), b, b + N - 1);
+}
+
+template <typename InputIterator1, typename InputIterator2>
+bool iends_with(InputIterator1 first1, InputIterator1 last1,
+ InputIterator2 first2, InputIterator2 last2) {
+ if (last1 - first1 < last2 - first2) {
+ return false;
+ }
+ return std::equal(first2, last2, last1 - (last2 - first2), CaseCmp());
+}
+
+template <typename T, typename S> bool iends_with(const T &a, const S &b) {
+ return iends_with(a.begin(), a.end(), b.begin(), b.end());
+}
+
+template <typename T, typename CharT, size_t N>
+bool iends_with_l(const T &a, const CharT (&b)[N]) {
+ return iends_with(a.begin(), a.end(), b, b + N - 1);
+}
+
+template <typename InputIt1, typename InputIt2>
+bool strieq(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2) {
+ if (std::distance(first1, last1) != std::distance(first2, last2)) {
+ return false;
+ }
+
+ return std::equal(first1, last1, first2, CaseCmp());
+}
+
+template <typename T, typename S> bool strieq(const T &a, const S &b) {
+ return strieq(a.begin(), a.end(), b.begin(), b.end());
+}
+
+template <typename CharT, typename InputIt, size_t N>
+bool strieq_l(const CharT (&a)[N], InputIt b, size_t blen) {
+ return strieq(a, a + (N - 1), b, b + blen);
+}
+
+template <typename CharT, size_t N, typename T>
+bool strieq_l(const CharT (&a)[N], const T &b) {
+ return strieq(a, a + (N - 1), b.begin(), b.end());
+}
+
+template <typename InputIt1, typename InputIt2>
+bool streq(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2) {
+ if (std::distance(first1, last1) != std::distance(first2, last2)) {
+ return false;
+ }
+ return std::equal(first1, last1, first2);
+}
+
+template <typename T, typename S> bool streq(const T &a, const S &b) {
+ return streq(a.begin(), a.end(), b.begin(), b.end());
+}
+
+template <typename CharT, typename InputIt, size_t N>
+bool streq_l(const CharT (&a)[N], InputIt b, size_t blen) {
+ return streq(a, a + (N - 1), b, b + blen);
+}
+
+template <typename CharT, size_t N, typename T>
+bool streq_l(const CharT (&a)[N], const T &b) {
+ return streq(a, a + (N - 1), b.begin(), b.end());
+}
+
+// Returns true if |a| contains |b|. If both |a| and |b| are empty,
+// this function returns false.
+template <typename S, typename T> bool strifind(const S &a, const T &b) {
+ return std::search(a.begin(), a.end(), b.begin(), b.end(), CaseCmp()) !=
+ a.end();
+}
+
+template <typename InputIt> void inp_strlower(InputIt first, InputIt last) {
+ std::transform(first, last, first, lowcase);
+}
+
+// Lowercase |s| in place.
+inline void inp_strlower(std::string &s) {
+ inp_strlower(std::begin(s), std::end(s));
+}
+
+// Returns string representation of |n| with 2 fractional digits.
+std::string dtos(double n);
+
+template <typename T> std::string utos(T n) {
+ std::string res;
+ if (n == 0) {
+ res = "0";
+ return res;
+ }
+ size_t nlen = 0;
+ for (auto t = n; t; t /= 10, ++nlen)
+ ;
+ res.resize(nlen);
+ for (; n; n /= 10) {
+ res[--nlen] = (n % 10) + '0';
+ }
+ return res;
+}
+
+template <typename T, typename OutputIt> OutputIt utos(OutputIt dst, T n) {
+ if (n == 0) {
+ *dst++ = '0';
+ return dst;
+ }
+ size_t nlen = 0;
+ for (auto t = n; t; t /= 10, ++nlen)
+ ;
+ auto p = dst + nlen;
+ auto res = p;
+ for (; n; n /= 10) {
+ *--p = (n % 10) + '0';
+ }
+ return res;
+}
+
+template <typename T>
+StringRef make_string_ref_uint(BlockAllocator &balloc, T n) {
+ auto iov = make_byte_ref(balloc, NGHTTP2_MAX_UINT64_DIGITS + 1);
+ auto p = iov.base;
+ p = util::utos(p, n);
+ *p = '\0';
+ return StringRef{iov.base, p};
+}
+
+template <typename T> std::string utos_unit(T n) {
+ char u = 0;
+ if (n >= (1 << 30)) {
+ u = 'G';
+ n /= (1 << 30);
+ } else if (n >= (1 << 20)) {
+ u = 'M';
+ n /= (1 << 20);
+ } else if (n >= (1 << 10)) {
+ u = 'K';
+ n /= (1 << 10);
+ }
+ if (u == 0) {
+ return utos(n);
+ }
+ return utos(n) + u;
+}
+
+// Like utos_unit(), but 2 digits fraction part is followed.
+template <typename T> std::string utos_funit(T n) {
+ char u = 0;
+ int b = 0;
+ if (n >= (1 << 30)) {
+ u = 'G';
+ b = 30;
+ } else if (n >= (1 << 20)) {
+ u = 'M';
+ b = 20;
+ } else if (n >= (1 << 10)) {
+ u = 'K';
+ b = 10;
+ }
+ if (b == 0) {
+ return utos(n);
+ }
+ return dtos(static_cast<double>(n) / (1 << b)) + u;
+}
+
+template <typename T> std::string utox(T n) {
+ std::string res;
+ if (n == 0) {
+ res = "0";
+ return res;
+ }
+ int i = 0;
+ T t = n;
+ for (; t; t /= 16, ++i)
+ ;
+ res.resize(i);
+ --i;
+ for (; n; --i, n /= 16) {
+ res[i] = UPPER_XDIGITS[(n & 0x0f)];
+ }
+ return res;
+}
+
+void to_token68(std::string &base64str);
+
+StringRef to_base64(BlockAllocator &balloc, const StringRef &token68str);
+
+void show_candidates(const char *unkopt, const option *options);
+
+bool has_uri_field(const http_parser_url &u, http_parser_url_fields field);
+
+bool fieldeq(const char *uri1, const http_parser_url &u1, const char *uri2,
+ const http_parser_url &u2, http_parser_url_fields field);
+
+bool fieldeq(const char *uri, const http_parser_url &u,
+ http_parser_url_fields field, const char *t);
+
+bool fieldeq(const char *uri, const http_parser_url &u,
+ http_parser_url_fields field, const StringRef &t);
+
+StringRef get_uri_field(const char *uri, const http_parser_url &u,
+ http_parser_url_fields field);
+
+uint16_t get_default_port(const char *uri, const http_parser_url &u);
+
+bool porteq(const char *uri1, const http_parser_url &u1, const char *uri2,
+ const http_parser_url &u2);
+
+void write_uri_field(std::ostream &o, const char *uri, const http_parser_url &u,
+ http_parser_url_fields field);
+
+bool numeric_host(const char *hostname);
+
+bool numeric_host(const char *hostname, int family);
+
+// Returns numeric address string of |addr|. If getnameinfo() is
+// failed, "unknown" is returned.
+std::string numeric_name(const struct sockaddr *sa, socklen_t salen);
+
+// Returns string representation of numeric address and port of
+// |addr|. If address family is AF_UNIX, this return path to UNIX
+// domain socket. Otherwise, the format is like <HOST>:<PORT>. For
+// IPv6 address, address is enclosed by square brackets ([]).
+std::string to_numeric_addr(const Address *addr);
+
+std::string to_numeric_addr(const struct sockaddr *sa, socklen_t salen);
+
+// Sets |port| to |addr|.
+void set_port(Address &addr, uint16_t port);
+
+// Returns ASCII dump of |data| of length |len|. Only ASCII printable
+// characters are preserved. Other characters are replaced with ".".
+std::string ascii_dump(const uint8_t *data, size_t len);
+
+// Returns absolute path of executable path. If argc == 0 or |cwd| is
+// nullptr, this function returns nullptr. If argv[0] starts with
+// '/', this function returns argv[0]. Otherwise return cwd + "/" +
+// argv[0]. If non-null is returned, it is NULL-terminated string and
+// dynamically allocated by malloc. The caller is responsible to free
+// it.
+char *get_exec_path(int argc, char **const argv, const char *cwd);
+
+// Validates path so that it does not contain directory traversal
+// vector. Returns true if path is safe. The |path| must start with
+// "/" otherwise returns false. This function should be called after
+// percent-decode was performed.
+bool check_path(const std::string &path);
+
+// Returns the |tv| value as 64 bit integer using a microsecond as an
+// unit.
+int64_t to_time64(const timeval &tv);
+
+// Returns true if ALPN ID |proto| is supported HTTP/2 protocol
+// identifier.
+bool check_h2_is_selected(const StringRef &proto);
+
+// Selects h2 protocol ALPN ID if one of supported h2 versions are
+// present in |in| of length inlen. Returns true if h2 version is
+// selected.
+bool select_h2(const unsigned char **out, unsigned char *outlen,
+ const unsigned char *in, unsigned int inlen);
+
+// Selects protocol ALPN ID if one of identifiers contained in |protolist| is
+// present in |in| of length inlen. Returns true if identifier is
+// selected.
+bool select_protocol(const unsigned char **out, unsigned char *outlen,
+ const unsigned char *in, unsigned int inlen,
+ std::vector<std::string> proto_list);
+
+// Returns default ALPN protocol list, which only contains supported
+// HTTP/2 protocol identifier.
+std::vector<unsigned char> get_default_alpn();
+
+// 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 StringRef &s,
+ char delim = ',');
+
+// Parses delimited strings in |s| and returns Substrings in |s|
+// delimited by |delim|. The any white spaces around substring are
+// treated as a part of substring.
+std::vector<StringRef> split_str(const StringRef &s, char delim);
+
+// Behaves like split_str, but this variant splits at most |n| - 1
+// times and returns at most |n| sub-strings. If |n| is zero, it
+// falls back to split_str.
+std::vector<StringRef> split_str(const StringRef &s, char delim, size_t n);
+
+// Writes given time |tp| in Common Log format (e.g.,
+// 03/Jul/2014:00:19:38 +0900) in buffer pointed by |out|. The buffer
+// must be at least 27 bytes, including terminal NULL byte. Expected
+// type of |tp| is std::chrono::time_point. This function returns
+// StringRef wrapping the buffer pointed by |out|, and this string is
+// terminated by NULL.
+template <typename T> StringRef format_common_log(char *out, const T &tp) {
+ auto t =
+ std::chrono::duration_cast<std::chrono::seconds>(tp.time_since_epoch());
+ auto p = common_log_date(out, t.count());
+ *p = '\0';
+ return StringRef{out, p};
+}
+
+// Returns given time |tp| in ISO 8601 format (e.g.,
+// 2014-11-15T12:58:24.741Z or 2014-11-15T12:58:24.741+09:00).
+// Expected type of |tp| is std::chrono::time_point
+template <typename T> std::string format_iso8601(const T &tp) {
+ auto t = std::chrono::duration_cast<std::chrono::milliseconds>(
+ tp.time_since_epoch());
+ return iso8601_date(t.count());
+}
+
+// Writes given time |tp| in ISO 8601 format (e.g.,
+// 2014-11-15T12:58:24.741Z or 2014-11-15T12:58:24.741+09:00) in
+// buffer pointed by |out|. The buffer must be at least 30 bytes,
+// including terminal NULL byte. Expected type of |tp| is
+// std::chrono::time_point. This function returns StringRef wrapping
+// the buffer pointed by |out|, and this string is terminated by NULL.
+template <typename T> StringRef format_iso8601(char *out, const T &tp) {
+ auto t = std::chrono::duration_cast<std::chrono::milliseconds>(
+ tp.time_since_epoch());
+ auto p = iso8601_date(out, t.count());
+ *p = '\0';
+ return StringRef{out, p};
+}
+
+// Writes given time |tp| in ISO 8601 basic format (e.g.,
+// 20141115T125824.741Z or 20141115T125824.741+0900) in buffer pointed
+// by |out|. The buffer must be at least 25 bytes, including terminal
+// NULL byte. Expected type of |tp| is std::chrono::time_point. This
+// function returns StringRef wrapping the buffer pointed by |out|,
+// and this string is terminated by NULL.
+template <typename T> StringRef format_iso8601_basic(char *out, const T &tp) {
+ auto t = std::chrono::duration_cast<std::chrono::milliseconds>(
+ tp.time_since_epoch());
+ auto p = iso8601_basic_date(out, t.count());
+ *p = '\0';
+ return StringRef{out, p};
+}
+
+// Writes given time |tp| in HTTP Date format (e.g., Mon, 10 Oct 2016
+// 10:25:58 GMT) in buffer pointed by |out|. The buffer must be at
+// least 30 bytes, including terminal NULL byte. Expected type of
+// |tp| is std::chrono::time_point. This function returns StringRef
+// wrapping the buffer pointed by |out|, and this string is terminated
+// by NULL.
+template <typename T> StringRef format_http_date(char *out, const T &tp) {
+ auto t =
+ std::chrono::duration_cast<std::chrono::seconds>(tp.time_since_epoch());
+ auto p = http_date(out, t.count());
+ *p = '\0';
+ return StringRef{out, p};
+}
+
+// Return the system precision of the template parameter |Clock| as
+// a nanosecond value of type |Rep|
+template <typename Clock, typename Rep> Rep clock_precision() {
+ std::chrono::duration<Rep, std::nano> duration = typename Clock::duration(1);
+
+ return duration.count();
+}
+
+#ifdef HAVE_LIBEV
+template <typename Duration = std::chrono::steady_clock::duration>
+Duration duration_from(ev_tstamp d) {
+ return std::chrono::duration_cast<Duration>(std::chrono::duration<double>(d));
+}
+
+template <typename Duration> ev_tstamp ev_tstamp_from(const Duration &d) {
+ return std::chrono::duration<double>(d).count();
+}
+#endif // HAVE_LIBEV
+
+int make_socket_closeonexec(int fd);
+int make_socket_nonblocking(int fd);
+int make_socket_nodelay(int fd);
+
+int create_nonblock_socket(int family);
+int create_nonblock_udp_socket(int family);
+
+int bind_any_addr_udp(int fd, int family);
+
+bool check_socket_connected(int fd);
+
+// Returns the error code (errno) by inspecting SO_ERROR of given
+// |fd|. This function returns the error code if it succeeds, or -1.
+// Returning 0 means no error.
+int get_socket_error(int fd);
+
+// Returns true if |host| is IPv6 numeric address (e.g., ::1)
+bool ipv6_numeric_addr(const char *host);
+
+// Parses NULL terminated string |s| as unsigned integer and returns
+// the parsed integer. Additionally, if |s| ends with 'k', 'm', 'g'
+// and its upper case characters, multiply the integer by 1024, 1024 *
+// 1024 and 1024 * 1024 respectively. If there is an error, returns
+// -1.
+int64_t parse_uint_with_unit(const char *s);
+// The following overload does not require |s| is NULL terminated.
+int64_t parse_uint_with_unit(const uint8_t *s, size_t len);
+int64_t parse_uint_with_unit(const StringRef &s);
+
+// Parses NULL terminated string |s| as unsigned integer and returns
+// the parsed integer. If there is an error, returns -1.
+int64_t parse_uint(const char *s);
+// The following overload does not require |s| is NULL terminated.
+int64_t parse_uint(const uint8_t *s, size_t len);
+int64_t parse_uint(const std::string &s);
+int64_t parse_uint(const StringRef &s);
+
+// Parses NULL terminated string |s| as unsigned integer and returns
+// the parsed integer casted to double. If |s| ends with "s", the
+// parsed value's unit is a second. If |s| ends with "ms", the unit
+// is millisecond. Similarly, it also supports 'm' and 'h' for
+// minutes and hours respectively. If none of them are given, the
+// unit is second. This function returns
+// std::numeric_limits<double>::infinity() if error occurs.
+double parse_duration_with_unit(const char *s);
+// The following overload does not require |s| is NULL terminated.
+double parse_duration_with_unit(const uint8_t *s, size_t len);
+double parse_duration_with_unit(const StringRef &s);
+
+// Returns string representation of time duration |t|. If t has
+// fractional part (at least more than or equal to 1e-3), |t| is
+// multiplied by 1000 and the unit "ms" is appended. Otherwise, |t|
+// is left as is and "s" is appended.
+std::string duration_str(double t);
+
+// Returns string representation of time duration |t|. It appends
+// unit after the formatting. The available units are s, ms and us.
+// The unit which is equal to or less than |t| is used and 2
+// fractional digits follow.
+std::string format_duration(const std::chrono::microseconds &u);
+
+// Just like above, but this takes |t| as seconds.
+std::string format_duration(double t);
+
+// The maximum buffer size including terminal NULL to store the result
+// of make_hostport.
+constexpr size_t max_hostport = NI_MAXHOST + /* [] for IPv6 */ 2 + /* : */ 1 +
+ /* port */ 5 + /* terminal NULL */ 1;
+
+// Just like make_http_hostport(), but doesn't treat 80 and 443
+// specially.
+StringRef make_hostport(BlockAllocator &balloc, const StringRef &host,
+ uint16_t port);
+
+template <typename OutputIt>
+StringRef make_hostport(OutputIt first, const StringRef &host, uint16_t port) {
+ auto ipv6 = ipv6_numeric_addr(host.c_str());
+ auto serv = utos(port);
+ auto p = first;
+
+ if (ipv6) {
+ *p++ = '[';
+ }
+
+ p = std::copy(std::begin(host), std::end(host), p);
+
+ if (ipv6) {
+ *p++ = ']';
+ }
+
+ *p++ = ':';
+
+ p = std::copy(std::begin(serv), std::end(serv), p);
+
+ *p = '\0';
+
+ return StringRef{first, p};
+}
+
+// Creates "host:port" string using given |host| and |port|. If
+// |host| is numeric IPv6 address (e.g., ::1), it is enclosed by "["
+// and "]". If |port| is 80 or 443, port part is omitted.
+StringRef make_http_hostport(BlockAllocator &balloc, const StringRef &host,
+ uint16_t port);
+
+template <typename OutputIt>
+StringRef make_http_hostport(OutputIt first, const StringRef &host,
+ uint16_t port) {
+ if (port != 80 && port != 443) {
+ return make_hostport(first, host, port);
+ }
+
+ auto ipv6 = ipv6_numeric_addr(host.c_str());
+ auto p = first;
+
+ if (ipv6) {
+ *p++ = '[';
+ }
+
+ p = std::copy(std::begin(host), std::end(host), p);
+
+ if (ipv6) {
+ *p++ = ']';
+ }
+
+ *p = '\0';
+
+ return StringRef{first, p};
+}
+
+// Dumps |src| of length |len| in the format similar to `hexdump -C`.
+void hexdump(FILE *out, const uint8_t *src, size_t len);
+
+// Copies 2 byte unsigned integer |n| in host byte order to |buf| in
+// network byte order.
+void put_uint16be(uint8_t *buf, uint16_t n);
+
+// Copies 4 byte unsigned integer |n| in host byte order to |buf| in
+// network byte order.
+void put_uint32be(uint8_t *buf, uint32_t n);
+
+// Retrieves 2 byte unsigned integer stored in |data| in network byte
+// order and returns it in host byte order.
+uint16_t get_uint16(const uint8_t *data);
+
+// Retrieves 4 byte unsigned integer stored in |data| in network byte
+// order and returns it in host byte order.
+uint32_t get_uint32(const uint8_t *data);
+
+// Retrieves 8 byte unsigned integer stored in |data| in network byte
+// order and returns it in host byte order.
+uint64_t get_uint64(const uint8_t *data);
+
+// Reads mime types file (see /etc/mime.types), and stores extension
+// -> MIME type map in |res|. This function returns 0 if it succeeds,
+// or -1.
+int read_mime_types(std::map<std::string, std::string> &res,
+ const char *filename);
+
+// Fills random alpha and digit byte to the range [|first|, |last|).
+// Returns the one beyond the |last|.
+template <typename OutputIt, typename Generator>
+OutputIt random_alpha_digit(OutputIt first, OutputIt last, Generator &gen) {
+ // If we use uint8_t instead char, gcc 6.2.0 complains by shouting
+ // char-array initialized from wide string.
+ static constexpr char s[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+ std::uniform_int_distribution<> dis(0, 26 * 2 + 10 - 1);
+ for (; first != last; ++first) {
+ *first = s[dis(gen)];
+ }
+ return first;
+}
+
+// Fills random bytes to the range [|first|, |last|).
+template <typename OutputIt, typename Generator>
+void random_bytes(OutputIt first, OutputIt last, Generator &gen) {
+ std::uniform_int_distribution<uint8_t> dis;
+ std::generate(first, last, [&dis, &gen]() { return dis(gen); });
+}
+
+// Shuffles the range [|first|, |last|] by calling swap function |fun|
+// for each pair. |fun| takes 2 RandomIt iterators. If |fun| is
+// noop, no modification is made.
+template <typename RandomIt, typename Generator, typename SwapFun>
+void shuffle(RandomIt first, RandomIt last, Generator &&gen, SwapFun fun) {
+ auto len = std::distance(first, last);
+ if (len < 2) {
+ return;
+ }
+
+ for (unsigned int i = 0; i < static_cast<unsigned int>(len - 1); ++i) {
+ auto dis = std::uniform_int_distribution<unsigned int>(i, len - 1);
+ auto j = dis(gen);
+ if (i == j) {
+ continue;
+ }
+ fun(first + i, first + j);
+ }
+}
+
+template <typename OutputIterator, typename CharT, size_t N>
+OutputIterator copy_lit(OutputIterator it, CharT (&s)[N]) {
+ return std::copy_n(s, N - 1, it);
+}
+
+// Returns x**y
+double int_pow(double x, size_t y);
+
+uint32_t hash32(const StringRef &s);
+
+// Computes SHA-256 of |s|, and stores it in |buf|. This function
+// returns 0 if it succeeds, or -1.
+int sha256(uint8_t *buf, const StringRef &s);
+
+// Computes SHA-1 of |s|, and stores it in |buf|. This function
+// returns 0 if it succeeds, or -1.
+int sha1(uint8_t *buf, const StringRef &s);
+
+// Returns host from |hostport|. If host cannot be found in
+// |hostport|, returns empty string. The returned string might not be
+// NULL-terminated.
+StringRef extract_host(const StringRef &hostport);
+
+// split_hostport splits host and port in |hostport|. Unlike
+// extract_host, square brackets enclosing host name is stripped. If
+// port is not available, it returns empty string in the second
+// string. The returned string might not be NULL-terminated. On any
+// error, it returns a pair which has empty strings.
+std::pair<StringRef, StringRef> split_hostport(const StringRef &hostport);
+
+// Returns new std::mt19937 object.
+std::mt19937 make_mt19937();
+
+// daemonize calls daemon(3). If __APPLE__ is defined, it implements
+// daemon() using fork().
+int daemonize(int nochdir, int noclose);
+
+// Returns |s| from which trailing white spaces (SPC or HTAB) are
+// removed. If any white spaces are removed, new string is allocated
+// by |balloc| and returned. Otherwise, the copy of |s| is returned
+// without allocation.
+StringRef rstrip(BlockAllocator &balloc, const StringRef &s);
+
+#ifdef ENABLE_HTTP3
+int msghdr_get_local_addr(Address &dest, msghdr *msg, int family);
+
+uint8_t msghdr_get_ecn(msghdr *msg, int family);
+
+// msghdr_get_udp_gro returns UDP_GRO value from |msg|. If UDP_GRO is
+// not found, or UDP_GRO is not supported, this function returns 0.
+size_t msghdr_get_udp_gro(msghdr *msg);
+#endif // ENABLE_HTTP3
+
+} // namespace util
+
+} // namespace nghttp2
+
+#endif // UTIL_H
diff --git a/src/util_test.cc b/src/util_test.cc
new file mode 100644
index 0000000..0ac0f1d
--- /dev/null
+++ b/src/util_test.cc
@@ -0,0 +1,707 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "util_test.h"
+
+#include <cstring>
+#include <iostream>
+#include <random>
+
+#include <CUnit/CUnit.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "util.h"
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+void test_util_streq(void) {
+ CU_ASSERT(
+ util::streq(StringRef::from_lit("alpha"), StringRef::from_lit("alpha")));
+ CU_ASSERT(!util::streq(StringRef::from_lit("alpha"),
+ StringRef::from_lit("alphabravo")));
+ CU_ASSERT(!util::streq(StringRef::from_lit("alphabravo"),
+ StringRef::from_lit("alpha")));
+ CU_ASSERT(
+ !util::streq(StringRef::from_lit("alpha"), StringRef::from_lit("alphA")));
+ CU_ASSERT(!util::streq(StringRef{}, StringRef::from_lit("a")));
+ CU_ASSERT(util::streq(StringRef{}, StringRef{}));
+ CU_ASSERT(!util::streq(StringRef::from_lit("alpha"), StringRef{}));
+
+ CU_ASSERT(
+ !util::streq(StringRef::from_lit("alph"), StringRef::from_lit("alpha")));
+ CU_ASSERT(
+ !util::streq(StringRef::from_lit("alpha"), StringRef::from_lit("alph")));
+ CU_ASSERT(
+ !util::streq(StringRef::from_lit("alpha"), StringRef::from_lit("alphA")));
+
+ CU_ASSERT(util::streq_l("alpha", "alpha", 5));
+ CU_ASSERT(util::streq_l("alpha", "alphabravo", 5));
+ CU_ASSERT(!util::streq_l("alpha", "alphabravo", 6));
+ CU_ASSERT(!util::streq_l("alphabravo", "alpha", 5));
+ CU_ASSERT(!util::streq_l("alpha", "alphA", 5));
+ CU_ASSERT(!util::streq_l("", "a", 1));
+ CU_ASSERT(util::streq_l("", "", 0));
+ CU_ASSERT(!util::streq_l("alpha", "", 0));
+}
+
+void test_util_strieq(void) {
+ CU_ASSERT(util::strieq(std::string("alpha"), std::string("alpha")));
+ CU_ASSERT(util::strieq(std::string("alpha"), std::string("AlPhA")));
+ CU_ASSERT(util::strieq(std::string(), std::string()));
+ CU_ASSERT(!util::strieq(std::string("alpha"), std::string("AlPhA ")));
+ CU_ASSERT(!util::strieq(std::string(), std::string("AlPhA ")));
+
+ CU_ASSERT(
+ util::strieq(StringRef::from_lit("alpha"), StringRef::from_lit("alpha")));
+ CU_ASSERT(
+ util::strieq(StringRef::from_lit("alpha"), StringRef::from_lit("AlPhA")));
+ CU_ASSERT(util::strieq(StringRef{}, StringRef{}));
+ CU_ASSERT(!util::strieq(StringRef::from_lit("alpha"),
+ StringRef::from_lit("AlPhA ")));
+ CU_ASSERT(
+ !util::strieq(StringRef::from_lit(""), StringRef::from_lit("AlPhA ")));
+
+ CU_ASSERT(util::strieq_l("alpha", "alpha", 5));
+ CU_ASSERT(util::strieq_l("alpha", "AlPhA", 5));
+ CU_ASSERT(util::strieq_l("", static_cast<const char *>(nullptr), 0));
+ CU_ASSERT(!util::strieq_l("alpha", "AlPhA ", 6));
+ CU_ASSERT(!util::strieq_l("", "AlPhA ", 6));
+
+ CU_ASSERT(util::strieq_l("alpha", StringRef::from_lit("alpha")));
+ CU_ASSERT(util::strieq_l("alpha", StringRef::from_lit("AlPhA")));
+ CU_ASSERT(util::strieq_l("", StringRef{}));
+ CU_ASSERT(!util::strieq_l("alpha", StringRef::from_lit("AlPhA ")));
+ CU_ASSERT(!util::strieq_l("", StringRef::from_lit("AlPhA ")));
+}
+
+void test_util_inp_strlower(void) {
+ std::string a("alPha");
+ util::inp_strlower(a);
+ CU_ASSERT("alpha" == a);
+
+ a = "ALPHA123BRAVO";
+ util::inp_strlower(a);
+ CU_ASSERT("alpha123bravo" == a);
+
+ a = "";
+ util::inp_strlower(a);
+ CU_ASSERT("" == a);
+}
+
+void test_util_to_base64(void) {
+ BlockAllocator balloc(4096, 4096);
+
+ CU_ASSERT("AAA++B/=" ==
+ util::to_base64(balloc, StringRef::from_lit("AAA--B_")));
+ CU_ASSERT("AAA++B/B" ==
+ util::to_base64(balloc, StringRef::from_lit("AAA--B_B")));
+}
+
+void test_util_to_token68(void) {
+ std::string x = "AAA++B/=";
+ util::to_token68(x);
+ CU_ASSERT("AAA--B_" == x);
+
+ x = "AAA++B/B";
+ util::to_token68(x);
+ CU_ASSERT("AAA--B_B" == x);
+}
+
+void test_util_percent_encode_token(void) {
+ BlockAllocator balloc(4096, 4096);
+ CU_ASSERT("h2" ==
+ util::percent_encode_token(balloc, StringRef::from_lit("h2")));
+ CU_ASSERT("h3~" ==
+ util::percent_encode_token(balloc, StringRef::from_lit("h3~")));
+ CU_ASSERT("100%25" ==
+ util::percent_encode_token(balloc, StringRef::from_lit("100%")));
+ CU_ASSERT("http%202" ==
+ util::percent_encode_token(balloc, StringRef::from_lit("http 2")));
+}
+
+void test_util_percent_decode(void) {
+ {
+ std::string s = "%66%6F%6f%62%61%72";
+ CU_ASSERT("foobar" == util::percent_decode(std::begin(s), std::end(s)));
+ }
+ {
+ std::string s = "%66%6";
+ CU_ASSERT("f%6" == util::percent_decode(std::begin(s), std::end(s)));
+ }
+ {
+ std::string s = "%66%";
+ CU_ASSERT("f%" == util::percent_decode(std::begin(s), std::end(s)));
+ }
+ BlockAllocator balloc(1024, 1024);
+
+ CU_ASSERT("foobar" == util::percent_decode(
+ balloc, StringRef::from_lit("%66%6F%6f%62%61%72")));
+
+ CU_ASSERT("f%6" ==
+ util::percent_decode(balloc, StringRef::from_lit("%66%6")));
+
+ CU_ASSERT("f%" == util::percent_decode(balloc, StringRef::from_lit("%66%")));
+}
+
+void test_util_quote_string(void) {
+ BlockAllocator balloc(4096, 4096);
+ CU_ASSERT("alpha" ==
+ util::quote_string(balloc, StringRef::from_lit("alpha")));
+ CU_ASSERT("" == util::quote_string(balloc, StringRef::from_lit("")));
+ CU_ASSERT("\\\"alpha\\\"" ==
+ util::quote_string(balloc, StringRef::from_lit("\"alpha\"")));
+}
+
+void test_util_utox(void) {
+ CU_ASSERT("0" == util::utox(0));
+ CU_ASSERT("1" == util::utox(1));
+ CU_ASSERT("F" == util::utox(15));
+ CU_ASSERT("10" == util::utox(16));
+ CU_ASSERT("3B9ACA07" == util::utox(1000000007));
+ CU_ASSERT("100000000" == util::utox(1LL << 32));
+}
+
+void test_util_http_date(void) {
+ CU_ASSERT("Thu, 01 Jan 1970 00:00:00 GMT" == util::http_date(0));
+ CU_ASSERT("Wed, 29 Feb 2012 09:15:16 GMT" == util::http_date(1330506916));
+
+ std::array<char, 30> http_buf;
+
+ CU_ASSERT("Thu, 01 Jan 1970 00:00:00 GMT" ==
+ util::format_http_date(http_buf.data(),
+ std::chrono::system_clock::time_point()));
+ CU_ASSERT("Wed, 29 Feb 2012 09:15:16 GMT" ==
+ util::format_http_date(http_buf.data(),
+ std::chrono::system_clock::time_point(
+ std::chrono::seconds(1330506916))));
+}
+
+void test_util_select_h2(void) {
+ const unsigned char *out = nullptr;
+ unsigned char outlen = 0;
+
+ // Check single entry and select it.
+ const unsigned char t1[] = "\x2h2";
+ CU_ASSERT(util::select_h2(&out, &outlen, t1, sizeof(t1) - 1));
+ CU_ASSERT(
+ memcmp(NGHTTP2_PROTO_VERSION_ID, out, NGHTTP2_PROTO_VERSION_ID_LEN) == 0);
+ CU_ASSERT(NGHTTP2_PROTO_VERSION_ID_LEN == outlen);
+
+ out = nullptr;
+ outlen = 0;
+
+ // Check the case where id is correct but length is invalid and too
+ // long.
+ const unsigned char t2[] = "\x6h2-14";
+ CU_ASSERT(!util::select_h2(&out, &outlen, t2, sizeof(t2) - 1));
+
+ // Check the case where h2 is located after bogus ID.
+ const unsigned char t3[] = "\x2h3\x2h2";
+ CU_ASSERT(util::select_h2(&out, &outlen, t3, sizeof(t3) - 1));
+
+ CU_ASSERT(
+ memcmp(NGHTTP2_PROTO_VERSION_ID, out, NGHTTP2_PROTO_VERSION_ID_LEN) == 0);
+ CU_ASSERT(NGHTTP2_PROTO_VERSION_ID_LEN == outlen);
+
+ out = nullptr;
+ outlen = 0;
+
+ // Check the case that last entry's length is invalid and too long.
+ const unsigned char t4[] = "\x2h3\x6h2-14";
+ CU_ASSERT(!util::select_h2(&out, &outlen, t4, sizeof(t4) - 1));
+
+ // Check the case that all entries are not supported.
+ const unsigned char t5[] = "\x2h3\x2h4";
+ CU_ASSERT(!util::select_h2(&out, &outlen, t5, sizeof(t5) - 1));
+
+ // Check the case where 2 values are eligible, but last one is
+ // picked up because it has precedence over the other.
+ const unsigned char t6[] = "\x5h2-14\x5h2-16";
+ CU_ASSERT(util::select_h2(&out, &outlen, t6, sizeof(t6) - 1));
+ CU_ASSERT(util::streq(NGHTTP2_H2_16, StringRef{out, outlen}));
+}
+
+void test_util_ipv6_numeric_addr(void) {
+ CU_ASSERT(util::ipv6_numeric_addr("::1"));
+ CU_ASSERT(util::ipv6_numeric_addr("2001:0db8:85a3:0042:1000:8a2e:0370:7334"));
+ // IPv4
+ CU_ASSERT(!util::ipv6_numeric_addr("127.0.0.1"));
+ // not numeric address
+ CU_ASSERT(!util::ipv6_numeric_addr("localhost"));
+}
+
+void test_util_utos(void) {
+ uint8_t buf[32];
+
+ CU_ASSERT(("0" == StringRef{buf, util::utos(buf, 0)}));
+ CU_ASSERT(("123" == StringRef{buf, util::utos(buf, 123)}));
+ CU_ASSERT(("18446744073709551615" ==
+ StringRef{buf, util::utos(buf, 18446744073709551615ULL)}));
+}
+
+void test_util_make_string_ref_uint(void) {
+ BlockAllocator balloc(1024, 1024);
+
+ CU_ASSERT("0" == util::make_string_ref_uint(balloc, 0));
+ CU_ASSERT("123" == util::make_string_ref_uint(balloc, 123));
+ CU_ASSERT("18446744073709551615" ==
+ util::make_string_ref_uint(balloc, 18446744073709551615ULL));
+}
+
+void test_util_utos_unit(void) {
+ CU_ASSERT("0" == util::utos_unit(0));
+ CU_ASSERT("1023" == util::utos_unit(1023));
+ CU_ASSERT("1K" == util::utos_unit(1024));
+ CU_ASSERT("1K" == util::utos_unit(1025));
+ CU_ASSERT("1M" == util::utos_unit(1 << 20));
+ CU_ASSERT("1G" == util::utos_unit(1 << 30));
+ CU_ASSERT("1024G" == util::utos_unit(1LL << 40));
+}
+
+void test_util_utos_funit(void) {
+ CU_ASSERT("0" == util::utos_funit(0));
+ CU_ASSERT("1023" == util::utos_funit(1023));
+ CU_ASSERT("1.00K" == util::utos_funit(1024));
+ CU_ASSERT("1.00K" == util::utos_funit(1025));
+ CU_ASSERT("1.09K" == util::utos_funit(1119));
+ CU_ASSERT("1.27K" == util::utos_funit(1300));
+ CU_ASSERT("1.00M" == util::utos_funit(1 << 20));
+ CU_ASSERT("1.18M" == util::utos_funit(1234567));
+ CU_ASSERT("1.00G" == util::utos_funit(1 << 30));
+ CU_ASSERT("4492450797.23G" == util::utos_funit(4823732313248234343LL));
+ CU_ASSERT("1024.00G" == util::utos_funit(1LL << 40));
+}
+
+void test_util_parse_uint_with_unit(void) {
+ CU_ASSERT(0 == util::parse_uint_with_unit("0"));
+ CU_ASSERT(1023 == util::parse_uint_with_unit("1023"));
+ CU_ASSERT(1024 == util::parse_uint_with_unit("1k"));
+ CU_ASSERT(2048 == util::parse_uint_with_unit("2K"));
+ CU_ASSERT(1 << 20 == util::parse_uint_with_unit("1m"));
+ CU_ASSERT(1 << 21 == util::parse_uint_with_unit("2M"));
+ CU_ASSERT(1 << 30 == util::parse_uint_with_unit("1g"));
+ CU_ASSERT(1LL << 31 == util::parse_uint_with_unit("2G"));
+ CU_ASSERT(9223372036854775807LL ==
+ util::parse_uint_with_unit("9223372036854775807"));
+ // check overflow case
+ CU_ASSERT(-1 == util::parse_uint_with_unit("9223372036854775808"));
+ CU_ASSERT(-1 == util::parse_uint_with_unit("10000000000000000000"));
+ CU_ASSERT(-1 == util::parse_uint_with_unit("9223372036854775807G"));
+ // bad characters
+ CU_ASSERT(-1 == util::parse_uint_with_unit("1.1"));
+ CU_ASSERT(-1 == util::parse_uint_with_unit("1a"));
+ CU_ASSERT(-1 == util::parse_uint_with_unit("a1"));
+ CU_ASSERT(-1 == util::parse_uint_with_unit("1T"));
+ CU_ASSERT(-1 == util::parse_uint_with_unit(""));
+}
+
+void test_util_parse_uint(void) {
+ CU_ASSERT(0 == util::parse_uint("0"));
+ CU_ASSERT(1023 == util::parse_uint("1023"));
+ CU_ASSERT(-1 == util::parse_uint("1k"));
+ CU_ASSERT(9223372036854775807LL == util::parse_uint("9223372036854775807"));
+ // check overflow case
+ CU_ASSERT(-1 == util::parse_uint("9223372036854775808"));
+ CU_ASSERT(-1 == util::parse_uint("10000000000000000000"));
+ // bad characters
+ CU_ASSERT(-1 == util::parse_uint("1.1"));
+ CU_ASSERT(-1 == util::parse_uint("1a"));
+ CU_ASSERT(-1 == util::parse_uint("a1"));
+ CU_ASSERT(-1 == util::parse_uint("1T"));
+ CU_ASSERT(-1 == util::parse_uint(""));
+}
+
+void test_util_parse_duration_with_unit(void) {
+ CU_ASSERT(0. == util::parse_duration_with_unit("0"));
+ CU_ASSERT(123. == util::parse_duration_with_unit("123"));
+ CU_ASSERT(123. == util::parse_duration_with_unit("123s"));
+ CU_ASSERT(0.500 == util::parse_duration_with_unit("500ms"));
+ CU_ASSERT(123. == util::parse_duration_with_unit("123S"));
+ CU_ASSERT(0.500 == util::parse_duration_with_unit("500MS"));
+ CU_ASSERT(180 == util::parse_duration_with_unit("3m"));
+ CU_ASSERT(3600 * 5 == util::parse_duration_with_unit("5h"));
+
+ auto err = std::numeric_limits<double>::infinity();
+ // check overflow case
+ CU_ASSERT(err == util::parse_duration_with_unit("9223372036854775808"));
+ // bad characters
+ CU_ASSERT(err == util::parse_duration_with_unit("0u"));
+ CU_ASSERT(err == util::parse_duration_with_unit("0xs"));
+ CU_ASSERT(err == util::parse_duration_with_unit("0mt"));
+ CU_ASSERT(err == util::parse_duration_with_unit("0mss"));
+ CU_ASSERT(err == util::parse_duration_with_unit("s"));
+ CU_ASSERT(err == util::parse_duration_with_unit("ms"));
+}
+
+void test_util_duration_str(void) {
+ CU_ASSERT("0" == util::duration_str(0.));
+ CU_ASSERT("1s" == util::duration_str(1.));
+ CU_ASSERT("500ms" == util::duration_str(0.5));
+ CU_ASSERT("1500ms" == util::duration_str(1.5));
+ CU_ASSERT("2m" == util::duration_str(120.));
+ CU_ASSERT("121s" == util::duration_str(121.));
+ CU_ASSERT("1h" == util::duration_str(3600.));
+}
+
+void test_util_format_duration(void) {
+ CU_ASSERT("0us" == util::format_duration(std::chrono::microseconds(0)));
+ CU_ASSERT("999us" == util::format_duration(std::chrono::microseconds(999)));
+ CU_ASSERT("1.00ms" == util::format_duration(std::chrono::microseconds(1000)));
+ CU_ASSERT("1.09ms" == util::format_duration(std::chrono::microseconds(1090)));
+ CU_ASSERT("1.01ms" == util::format_duration(std::chrono::microseconds(1009)));
+ CU_ASSERT("999.99ms" ==
+ util::format_duration(std::chrono::microseconds(999990)));
+ CU_ASSERT("1.00s" ==
+ util::format_duration(std::chrono::microseconds(1000000)));
+ CU_ASSERT("1.05s" ==
+ util::format_duration(std::chrono::microseconds(1050000)));
+
+ CU_ASSERT("0us" == util::format_duration(0.));
+ CU_ASSERT("999us" == util::format_duration(0.000999));
+ CU_ASSERT("1.00ms" == util::format_duration(0.001));
+ CU_ASSERT("1.09ms" == util::format_duration(0.00109));
+ CU_ASSERT("1.01ms" == util::format_duration(0.001009));
+ CU_ASSERT("999.99ms" == util::format_duration(0.99999));
+ CU_ASSERT("1.00s" == util::format_duration(1.));
+ CU_ASSERT("1.05s" == util::format_duration(1.05));
+}
+
+void test_util_starts_with(void) {
+ CU_ASSERT(util::starts_with(StringRef::from_lit("foo"),
+ StringRef::from_lit("foo")));
+ CU_ASSERT(util::starts_with(StringRef::from_lit("fooo"),
+ StringRef::from_lit("foo")));
+ CU_ASSERT(util::starts_with(StringRef::from_lit("ofoo"), StringRef{}));
+ CU_ASSERT(!util::starts_with(StringRef::from_lit("ofoo"),
+ StringRef::from_lit("foo")));
+
+ CU_ASSERT(util::istarts_with(StringRef::from_lit("FOO"),
+ StringRef::from_lit("fOO")));
+ CU_ASSERT(util::istarts_with(StringRef::from_lit("ofoo"), StringRef{}));
+ CU_ASSERT(util::istarts_with(StringRef::from_lit("fOOo"),
+ StringRef::from_lit("Foo")));
+ CU_ASSERT(!util::istarts_with(StringRef::from_lit("ofoo"),
+ StringRef::from_lit("foo")));
+
+ CU_ASSERT(util::istarts_with_l(StringRef::from_lit("fOOo"), "Foo"));
+ CU_ASSERT(!util::istarts_with_l(StringRef::from_lit("ofoo"), "foo"));
+}
+
+void test_util_ends_with(void) {
+ CU_ASSERT(
+ util::ends_with(StringRef::from_lit("foo"), StringRef::from_lit("foo")));
+ CU_ASSERT(util::ends_with(StringRef::from_lit("foo"), StringRef{}));
+ CU_ASSERT(
+ util::ends_with(StringRef::from_lit("ofoo"), StringRef::from_lit("foo")));
+ CU_ASSERT(
+ !util::ends_with(StringRef::from_lit("ofoo"), StringRef::from_lit("fo")));
+
+ CU_ASSERT(
+ util::iends_with(StringRef::from_lit("fOo"), StringRef::from_lit("Foo")));
+ CU_ASSERT(util::iends_with(StringRef::from_lit("foo"), StringRef{}));
+ CU_ASSERT(util::iends_with(StringRef::from_lit("oFoo"),
+ StringRef::from_lit("fOO")));
+ CU_ASSERT(!util::iends_with(StringRef::from_lit("ofoo"),
+ StringRef::from_lit("fo")));
+
+ CU_ASSERT(util::iends_with_l(StringRef::from_lit("oFoo"), "fOO"));
+ CU_ASSERT(!util::iends_with_l(StringRef::from_lit("ofoo"), "fo"));
+}
+
+void test_util_parse_http_date(void) {
+ CU_ASSERT(1001939696 == util::parse_http_date(StringRef::from_lit(
+ "Mon, 1 Oct 2001 12:34:56 GMT")));
+}
+
+void test_util_localtime_date(void) {
+ auto tz = getenv("TZ");
+ if (tz) {
+ tz = strdup(tz);
+ }
+#ifdef __linux__
+ setenv("TZ", "NZST-12:00:00:00", 1);
+#else // !__linux__
+ setenv("TZ", ":Pacific/Auckland", 1);
+#endif // !__linux__
+ tzset();
+
+ CU_ASSERT_STRING_EQUAL("02/Oct/2001:00:34:56 +1200",
+ util::common_log_date(1001939696).c_str());
+ CU_ASSERT_STRING_EQUAL("2001-10-02T00:34:56.123+12:00",
+ util::iso8601_date(1001939696000LL + 123).c_str());
+
+ std::array<char, 27> common_buf;
+
+ CU_ASSERT("02/Oct/2001:00:34:56 +1200" ==
+ util::format_common_log(common_buf.data(),
+ std::chrono::system_clock::time_point(
+ std::chrono::seconds(1001939696))));
+
+ std::array<char, 30> iso8601_buf;
+
+ CU_ASSERT(
+ "2001-10-02T00:34:56.123+12:00" ==
+ util::format_iso8601(iso8601_buf.data(),
+ std::chrono::system_clock::time_point(
+ std::chrono::milliseconds(1001939696123LL))));
+
+ if (tz) {
+ setenv("TZ", tz, 1);
+ free(tz);
+ } else {
+ unsetenv("TZ");
+ }
+ tzset();
+}
+
+void test_util_get_uint64(void) {
+ {
+ auto v = std::array<unsigned char, 8>{
+ {0x01, 0x12, 0x34, 0x56, 0xff, 0x9a, 0xab, 0xbc}};
+
+ auto n = util::get_uint64(v.data());
+
+ CU_ASSERT(0x01123456ff9aabbcULL == n);
+ }
+ {
+ auto v = std::array<unsigned char, 8>{
+ {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}};
+
+ auto n = util::get_uint64(v.data());
+
+ CU_ASSERT(0xffffffffffffffffULL == n);
+ }
+}
+
+void test_util_parse_config_str_list(void) {
+ auto res = util::parse_config_str_list(StringRef::from_lit("a"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("a" == res[0]);
+
+ res = util::parse_config_str_list(StringRef::from_lit("a,"));
+ CU_ASSERT(2 == res.size());
+ CU_ASSERT("a" == res[0]);
+ CU_ASSERT("" == res[1]);
+
+ res = util::parse_config_str_list(StringRef::from_lit(":a::"), ':');
+ CU_ASSERT(4 == res.size());
+ CU_ASSERT("" == res[0]);
+ CU_ASSERT("a" == res[1]);
+ CU_ASSERT("" == res[2]);
+ CU_ASSERT("" == res[3]);
+
+ res = util::parse_config_str_list(StringRef{});
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("" == res[0]);
+
+ res = util::parse_config_str_list(StringRef::from_lit("alpha,bravo,charlie"));
+ CU_ASSERT(3 == res.size());
+ CU_ASSERT("alpha" == res[0]);
+ CU_ASSERT("bravo" == res[1]);
+ CU_ASSERT("charlie" == res[2]);
+}
+
+void test_util_make_http_hostport(void) {
+ BlockAllocator balloc(4096, 4096);
+
+ CU_ASSERT("localhost" == util::make_http_hostport(
+ balloc, StringRef::from_lit("localhost"), 80));
+ CU_ASSERT("[::1]" ==
+ util::make_http_hostport(balloc, StringRef::from_lit("::1"), 443));
+ CU_ASSERT(
+ "localhost:3000" ==
+ util::make_http_hostport(balloc, StringRef::from_lit("localhost"), 3000));
+}
+
+void test_util_make_hostport(void) {
+ std::array<char, util::max_hostport> hostport_buf;
+ CU_ASSERT("localhost:80" ==
+ util::make_hostport(std::begin(hostport_buf),
+ StringRef::from_lit("localhost"), 80));
+ CU_ASSERT("[::1]:443" == util::make_hostport(std::begin(hostport_buf),
+ StringRef::from_lit("::1"),
+ 443));
+
+ BlockAllocator balloc(4096, 4096);
+ CU_ASSERT("localhost:80" ==
+ util::make_hostport(balloc, StringRef::from_lit("localhost"), 80));
+ CU_ASSERT("[::1]:443" ==
+ util::make_hostport(balloc, StringRef::from_lit("::1"), 443));
+}
+
+void test_util_strifind(void) {
+ CU_ASSERT(util::strifind(StringRef::from_lit("gzip, deflate, bzip2"),
+ StringRef::from_lit("gzip")));
+
+ CU_ASSERT(util::strifind(StringRef::from_lit("gzip, deflate, bzip2"),
+ StringRef::from_lit("dEflate")));
+
+ CU_ASSERT(util::strifind(StringRef::from_lit("gzip, deflate, bzip2"),
+ StringRef::from_lit("BZIP2")));
+
+ CU_ASSERT(util::strifind(StringRef::from_lit("nghttp2"), StringRef{}));
+
+ // Be aware this fact
+ CU_ASSERT(!util::strifind(StringRef{}, StringRef{}));
+
+ CU_ASSERT(!util::strifind(StringRef::from_lit("nghttp2"),
+ StringRef::from_lit("http1")));
+}
+
+void test_util_random_alpha_digit(void) {
+ std::random_device rd;
+ std::mt19937 gen(rd());
+ std::array<uint8_t, 19> data;
+
+ auto p = util::random_alpha_digit(std::begin(data), std::end(data), gen);
+
+ CU_ASSERT(std::end(data) == p);
+
+ for (auto b : data) {
+ CU_ASSERT(('A' <= b && b <= 'Z') || ('a' <= b && b <= 'z') ||
+ ('0' <= b && b <= '9'));
+ }
+}
+
+void test_util_format_hex(void) {
+ BlockAllocator balloc(4096, 4096);
+
+ CU_ASSERT("0ff0" ==
+ util::format_hex(balloc, StringRef::from_lit("\x0f\xf0")));
+ CU_ASSERT("" == util::format_hex(balloc, StringRef::from_lit("")));
+}
+
+void test_util_is_hex_string(void) {
+ CU_ASSERT(util::is_hex_string(StringRef{}));
+ CU_ASSERT(util::is_hex_string(StringRef::from_lit("0123456789abcdef")));
+ CU_ASSERT(util::is_hex_string(StringRef::from_lit("0123456789ABCDEF")));
+ CU_ASSERT(!util::is_hex_string(StringRef::from_lit("000")));
+ CU_ASSERT(!util::is_hex_string(StringRef::from_lit("XX")));
+}
+
+void test_util_decode_hex(void) {
+ BlockAllocator balloc(4096, 4096);
+
+ CU_ASSERT("\x0f\xf0" ==
+ util::decode_hex(balloc, StringRef::from_lit("0ff0")));
+ CU_ASSERT("" == util::decode_hex(balloc, StringRef{}));
+}
+
+void test_util_extract_host(void) {
+ CU_ASSERT(StringRef::from_lit("foo") ==
+ util::extract_host(StringRef::from_lit("foo")));
+ CU_ASSERT(StringRef::from_lit("foo") ==
+ util::extract_host(StringRef::from_lit("foo:")));
+ CU_ASSERT(StringRef::from_lit("foo") ==
+ util::extract_host(StringRef::from_lit("foo:0")));
+ CU_ASSERT(StringRef::from_lit("[::1]") ==
+ util::extract_host(StringRef::from_lit("[::1]")));
+ CU_ASSERT(StringRef::from_lit("[::1]") ==
+ util::extract_host(StringRef::from_lit("[::1]:")));
+
+ CU_ASSERT(util::extract_host(StringRef::from_lit(":foo")).empty());
+ CU_ASSERT(util::extract_host(StringRef::from_lit("[::1")).empty());
+ CU_ASSERT(util::extract_host(StringRef::from_lit("[::1]0")).empty());
+ CU_ASSERT(util::extract_host(StringRef{}).empty());
+}
+
+void test_util_split_hostport(void) {
+ CU_ASSERT(std::make_pair(StringRef::from_lit("foo"), StringRef{}) ==
+ util::split_hostport(StringRef::from_lit("foo")));
+ CU_ASSERT(
+ std::make_pair(StringRef::from_lit("foo"), StringRef::from_lit("80")) ==
+ util::split_hostport(StringRef::from_lit("foo:80")));
+ CU_ASSERT(
+ std::make_pair(StringRef::from_lit("::1"), StringRef::from_lit("80")) ==
+ util::split_hostport(StringRef::from_lit("[::1]:80")));
+ CU_ASSERT(std::make_pair(StringRef::from_lit("::1"), StringRef{}) ==
+ util::split_hostport(StringRef::from_lit("[::1]")));
+
+ CU_ASSERT(std::make_pair(StringRef{}, StringRef{}) ==
+ util::split_hostport(StringRef{}));
+ CU_ASSERT(std::make_pair(StringRef{}, StringRef{}) ==
+ util::split_hostport(StringRef::from_lit("[::1]:")));
+ CU_ASSERT(std::make_pair(StringRef{}, StringRef{}) ==
+ util::split_hostport(StringRef::from_lit("foo:")));
+ CU_ASSERT(std::make_pair(StringRef{}, StringRef{}) ==
+ util::split_hostport(StringRef::from_lit("[::1:")));
+ CU_ASSERT(std::make_pair(StringRef{}, StringRef{}) ==
+ util::split_hostport(StringRef::from_lit("[::1]80")));
+}
+
+void test_util_split_str(void) {
+ CU_ASSERT(std::vector<StringRef>{StringRef::from_lit("")} ==
+ util::split_str(StringRef::from_lit(""), ','));
+ CU_ASSERT(std::vector<StringRef>{StringRef::from_lit("alpha")} ==
+ util::split_str(StringRef::from_lit("alpha"), ','));
+ CU_ASSERT((std::vector<StringRef>{StringRef::from_lit("alpha"),
+ StringRef::from_lit("")}) ==
+ util::split_str(StringRef::from_lit("alpha,"), ','));
+ CU_ASSERT((std::vector<StringRef>{StringRef::from_lit("alpha"),
+ StringRef::from_lit("bravo")}) ==
+ util::split_str(StringRef::from_lit("alpha,bravo"), ','));
+ CU_ASSERT((std::vector<StringRef>{StringRef::from_lit("alpha"),
+ StringRef::from_lit("bravo"),
+ StringRef::from_lit("charlie")}) ==
+ util::split_str(StringRef::from_lit("alpha,bravo,charlie"), ','));
+ CU_ASSERT(
+ (std::vector<StringRef>{StringRef::from_lit("alpha"),
+ StringRef::from_lit("bravo"),
+ StringRef::from_lit("charlie")}) ==
+ util::split_str(StringRef::from_lit("alpha,bravo,charlie"), ',', 0));
+ CU_ASSERT(std::vector<StringRef>{StringRef::from_lit("")} ==
+ util::split_str(StringRef::from_lit(""), ',', 1));
+ CU_ASSERT(std::vector<StringRef>{StringRef::from_lit("")} ==
+ util::split_str(StringRef::from_lit(""), ',', 2));
+ CU_ASSERT(
+ (std::vector<StringRef>{StringRef::from_lit("alpha"),
+ StringRef::from_lit("bravo,charlie")}) ==
+ util::split_str(StringRef::from_lit("alpha,bravo,charlie"), ',', 2));
+ CU_ASSERT(std::vector<StringRef>{StringRef::from_lit("alpha")} ==
+ util::split_str(StringRef::from_lit("alpha"), ',', 2));
+ CU_ASSERT((std::vector<StringRef>{StringRef::from_lit("alpha"),
+ StringRef::from_lit("")}) ==
+ util::split_str(StringRef::from_lit("alpha,"), ',', 2));
+ CU_ASSERT(std::vector<StringRef>{StringRef::from_lit("alpha")} ==
+ util::split_str(StringRef::from_lit("alpha"), ',', 0));
+ CU_ASSERT(
+ std::vector<StringRef>{StringRef::from_lit("alpha,bravo,charlie")} ==
+ util::split_str(StringRef::from_lit("alpha,bravo,charlie"), ',', 1));
+}
+
+void test_util_rstrip(void) {
+ BlockAllocator balloc(4096, 4096);
+
+ CU_ASSERT("alpha" == util::rstrip(balloc, StringRef::from_lit("alpha")));
+ CU_ASSERT("alpha" == util::rstrip(balloc, StringRef::from_lit("alpha ")));
+ CU_ASSERT("alpha" == util::rstrip(balloc, StringRef::from_lit("alpha \t")));
+ CU_ASSERT("" == util::rstrip(balloc, StringRef::from_lit("")));
+ CU_ASSERT("" == util::rstrip(balloc, StringRef::from_lit("\t\t\t ")));
+}
+
+} // namespace shrpx
diff --git a/src/util_test.h b/src/util_test.h
new file mode 100644
index 0000000..48925ab
--- /dev/null
+++ b/src/util_test.h
@@ -0,0 +1,75 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef UTIL_TEST_H
+#define UTIL_TEST_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+namespace shrpx {
+
+void test_util_streq(void);
+void test_util_strieq(void);
+void test_util_inp_strlower(void);
+void test_util_to_base64(void);
+void test_util_to_token68(void);
+void test_util_percent_encode_token(void);
+void test_util_percent_decode(void);
+void test_util_quote_string(void);
+void test_util_utox(void);
+void test_util_http_date(void);
+void test_util_select_h2(void);
+void test_util_ipv6_numeric_addr(void);
+void test_util_utos(void);
+void test_util_make_string_ref_uint(void);
+void test_util_utos_unit(void);
+void test_util_utos_funit(void);
+void test_util_parse_uint_with_unit(void);
+void test_util_parse_uint(void);
+void test_util_parse_duration_with_unit(void);
+void test_util_duration_str(void);
+void test_util_format_duration(void);
+void test_util_starts_with(void);
+void test_util_ends_with(void);
+void test_util_parse_http_date(void);
+void test_util_localtime_date(void);
+void test_util_get_uint64(void);
+void test_util_parse_config_str_list(void);
+void test_util_make_http_hostport(void);
+void test_util_make_hostport(void);
+void test_util_strifind(void);
+void test_util_random_alpha_digit(void);
+void test_util_format_hex(void);
+void test_util_is_hex_string(void);
+void test_util_decode_hex(void);
+void test_util_extract_host(void);
+void test_util_split_hostport(void);
+void test_util_split_str(void);
+void test_util_rstrip(void);
+
+} // namespace shrpx
+
+#endif // UTIL_TEST_H
diff --git a/src/xsi_strerror.c b/src/xsi_strerror.c
new file mode 100644
index 0000000..d008d4e
--- /dev/null
+++ b/src/xsi_strerror.c
@@ -0,0 +1,50 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "xsi_strerror.h"
+
+/* Make sure that we get XSI-compliant version of strerror_r */
+#ifdef _POSIX_C_SOURCE
+# undef _POSIX_C_SOURCE
+#endif /* _POSIX_C_SOURCE */
+
+#ifdef _GNU_SOURCE
+# undef _GNU_SOURCE
+#endif /* _GNU_SOURCE */
+
+#include <string.h>
+
+char *xsi_strerror(int errnum, char *buf, size_t buflen) {
+ int rv;
+
+ rv = strerror_r(errnum, buf, buflen);
+
+ if (rv != 0) {
+ if (buflen > 0) {
+ buf[0] = '\0';
+ }
+ }
+
+ return buf;
+}
diff --git a/src/xsi_strerror.h b/src/xsi_strerror.h
new file mode 100644
index 0000000..32cadc3
--- /dev/null
+++ b/src/xsi_strerror.h
@@ -0,0 +1,55 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef XSI_STRERROR_H
+#define XSI_STRERROR_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* Looks like error message is quite small, but we really don't know
+ how much longer they become. */
+#define STRERROR_BUFSIZE 256
+
+/*
+ * Returns description of error denoted by |errnum|. The description
+ * is written in |buf| of length |buflen| including terminal NULL. If
+ * there is an error, including the case that buffer space is not
+ * sufficient to include error message, and |buflen| > 0, empty string
+ * is written to |buf|. This function returns |buf|.
+ */
+char *xsi_strerror(int errnum, char *buf, size_t buflen);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* XSI_STRERROR_H */